From b1c8c7acf04f629d0a8e10eae0632110391ab5d4 Mon Sep 17 00:00:00 2001 From: Umut CAN <78921017+C1N-S4@users.noreply.github.com> Date: Sun, 9 Feb 2025 23:06:44 +0300 Subject: [PATCH] Progress Bar Improvements for Deep Research Progress Bar Improvements for Deep Research --- src/deep-research.ts | 65 +++++++++++++++++++++++++++++++---- src/output-manager.ts | 56 ++++++++++++++++++++++++++++++ src/progress-manager.ts | 76 +++++++++++++++++++++++++++++++++++++++++ src/run.ts | 27 +++++++++++---- 4 files changed, 211 insertions(+), 13 deletions(-) create mode 100644 src/output-manager.ts create mode 100644 src/progress-manager.ts diff --git a/src/deep-research.ts b/src/deep-research.ts index b5a0d0f..abfd6c6 100644 --- a/src/deep-research.ts +++ b/src/deep-research.ts @@ -6,6 +6,25 @@ import { z } from 'zod'; import { o3MiniModel, trimPrompt } from './ai/providers'; import { systemPrompt } from './prompt'; +import { OutputManager } from './output-manager'; + +// Initialize output manager for coordinated console/progress output +const output = new OutputManager(); + +// Replace console.log with output.log +function log(...args: any[]) { + output.log(...args); +} + +export type ResearchProgress = { + currentDepth: number; + totalDepth: number; + currentBreadth: number; + totalBreadth: number; + currentQuery?: string; + totalQueries: number; + completedQueries: number; +}; type ResearchResult = { learnings: string[]; @@ -59,7 +78,7 @@ async function generateSerpQueries({ .describe(`List of SERP queries, max of ${numQueries}`), }), }); - console.log( + log( `Created ${res.object.queries.length} queries`, res.object.queries, ); @@ -81,7 +100,7 @@ async function processSerpResult({ const contents = compact(result.data.map(item => item.markdown)).map( content => trimPrompt(content, 25_000), ); - console.log(`Ran ${query}, found ${contents.length} contents`); + log(`Ran ${query}, found ${contents.length} contents`); const res = await generateObject({ model: o3MiniModel, @@ -101,7 +120,7 @@ async function processSerpResult({ ), }), }); - console.log( + log( `Created ${res.object.learnings.length} learnings`, res.object.learnings, ); @@ -147,18 +166,39 @@ export async function deepResearch({ depth, learnings = [], visitedUrls = [], + onProgress, }: { query: string; breadth: number; depth: number; learnings?: string[]; visitedUrls?: string[]; + onProgress?: (progress: ResearchProgress) => void; }): Promise { + const progress: ResearchProgress = { + currentDepth: depth, + totalDepth: depth, + currentBreadth: breadth, + totalBreadth: breadth, + totalQueries: 0, + completedQueries: 0, + }; + + const reportProgress = (update: Partial) => { + Object.assign(progress, update); + onProgress?.(progress); + }; const serpQueries = await generateSerpQueries({ query, learnings, numQueries: breadth, }); + + reportProgress({ + totalQueries: serpQueries.length, + currentQuery: serpQueries[0]?.query + }); + const limit = pLimit(ConcurrencyLimit); const results = await Promise.all( @@ -185,10 +225,17 @@ export async function deepResearch({ const allUrls = [...visitedUrls, ...newUrls]; if (newDepth > 0) { - console.log( + log( `Researching deeper, breadth: ${newBreadth}, depth: ${newDepth}`, ); + reportProgress({ + currentDepth: newDepth, + currentBreadth: newBreadth, + completedQueries: progress.completedQueries + 1, + currentQuery: serpQuery.query, + }); + const nextQuery = ` Previous research goal: ${serpQuery.researchGoal} Follow-up research directions: ${newLearnings.followUpQuestions.map(q => `\n${q}`).join('')} @@ -200,8 +247,14 @@ export async function deepResearch({ depth: newDepth, learnings: allLearnings, visitedUrls: allUrls, + onProgress, }); } else { + reportProgress({ + currentDepth: 0, + completedQueries: progress.completedQueries + 1, + currentQuery: serpQuery.query, + }); return { learnings: allLearnings, visitedUrls: allUrls, @@ -209,12 +262,12 @@ export async function deepResearch({ } } catch (e: any) { if (e.message && e.message.includes('Timeout')) { - console.error( + log( `Timeout error running query: ${serpQuery.query}: `, e, ); } else { - console.error(`Error running query: ${serpQuery.query}: `, e); + log(`Error running query: ${serpQuery.query}: `, e); } return { learnings: [], diff --git a/src/output-manager.ts b/src/output-manager.ts new file mode 100644 index 0000000..ee7fe70 --- /dev/null +++ b/src/output-manager.ts @@ -0,0 +1,56 @@ +import { ResearchProgress } from './deep-research'; + +export class OutputManager { + private progressLines: number = 4; + private progressArea: string[] = []; + private initialized: boolean = false; + + constructor() { + // Initialize terminal + process.stdout.write('\n'.repeat(this.progressLines)); + this.initialized = true; + } + + log(...args: any[]) { + // Move cursor up to progress area + if (this.initialized) { + process.stdout.write(`\x1B[${this.progressLines}A`); + // Clear progress area + process.stdout.write('\x1B[0J'); + } + // Print log message + console.log(...args); + // Redraw progress area if initialized + if (this.initialized) { + this.drawProgress(); + } + } + + updateProgress(progress: ResearchProgress) { + this.progressArea = [ + `Depth: [${this.getProgressBar(progress.totalDepth - progress.currentDepth, progress.totalDepth)}] ${Math.round((progress.totalDepth - progress.currentDepth) / progress.totalDepth * 100)}%`, + `Breadth: [${this.getProgressBar(progress.totalBreadth - progress.currentBreadth, progress.totalBreadth)}] ${Math.round((progress.totalBreadth - progress.currentBreadth) / progress.totalBreadth * 100)}%`, + `Queries: [${this.getProgressBar(progress.completedQueries, progress.totalQueries)}] ${Math.round(progress.completedQueries / progress.totalQueries * 100)}%`, + progress.currentQuery ? `Current: ${progress.currentQuery}` : '' + ]; + this.drawProgress(); + } + + private getProgressBar(value: number, total: number): string { + const width = process.stdout.columns ? Math.min(30, process.stdout.columns - 20) : 30; + const filled = Math.round((width * value) / total); + return '█'.repeat(filled) + ' '.repeat(width - filled); + } + + private drawProgress() { + if (!this.initialized || this.progressArea.length === 0) return; + + // Move cursor to progress area + const terminalHeight = process.stdout.rows || 24; + process.stdout.write(`\x1B[${terminalHeight - this.progressLines};1H`); + // Draw progress bars + process.stdout.write(this.progressArea.join('\n')); + // Move cursor back to content area + process.stdout.write(`\x1B[${terminalHeight - this.progressLines - 1};1H`); + } +} diff --git a/src/progress-manager.ts b/src/progress-manager.ts new file mode 100644 index 0000000..22b09d4 --- /dev/null +++ b/src/progress-manager.ts @@ -0,0 +1,76 @@ +import { ResearchProgress } from './deep-research'; + +export class ProgressManager { + private lastProgress: ResearchProgress | undefined; + private progressLines: number = 4; // Fixed number of lines for progress display + private initialized: boolean = false; + + constructor() { + // Initialize terminal + process.stdout.write('\n'.repeat(this.progressLines)); + } + + private drawProgressBar( + label: string, + value: number, + total: number, + char: string = '=' + ): string { + const width = process.stdout.columns ? Math.min(30, process.stdout.columns - 20) : 30; + const percent = (value / total) * 100; + const filled = Math.round((width * percent) / 100); + const empty = width - filled; + + return `${label} [${char.repeat(filled)}${' '.repeat(empty)}] ${Math.round(percent)}%`; + } + + updateProgress(progress: ResearchProgress) { + // Store progress for potential redraw + this.lastProgress = progress; + + // Calculate position for progress bars (at bottom of terminal) + const terminalHeight = process.stdout.rows || 24; + const progressStart = terminalHeight - this.progressLines; + + // Move cursor to progress area + process.stdout.write(`\x1B[${progressStart};1H\x1B[0J`); + + // Draw progress bars horizontally + const lines = [ + this.drawProgressBar( + 'Depth: ', + progress.totalDepth - progress.currentDepth, + progress.totalDepth, + '█' + ), + this.drawProgressBar( + 'Breadth: ', + progress.totalBreadth - progress.currentBreadth, + progress.totalBreadth, + '█' + ), + this.drawProgressBar( + 'Queries: ', + progress.completedQueries, + progress.totalQueries, + '█' + ), + ]; + + // Add current query if available + if (progress.currentQuery) { + lines.push(`Current: ${progress.currentQuery}`); + } + + // Output progress bars at fixed position + process.stdout.write(lines.join('\n') + '\n'); + + // Move cursor back up for next output + process.stdout.write(`\x1B[${this.progressLines}A`); + } + + stop() { + // Move cursor past progress area + process.stdout.write(`\x1B[${this.progressLines}B\n`); + } +} diff --git a/src/run.ts b/src/run.ts index cfa05df..87c6073 100644 --- a/src/run.ts +++ b/src/run.ts @@ -3,6 +3,14 @@ import * as readline from 'readline'; import { deepResearch, writeFinalReport } from './deep-research'; import { generateFeedback } from './feedback'; +import { OutputManager } from './output-manager'; + +const output = new OutputManager(); + +// Helper function for consistent logging +function log(...args: any[]) { + output.log(...args); +} const rl = readline.createInterface({ input: process.stdin, @@ -37,14 +45,14 @@ async function run() { 10, ) || 2; - console.log(`Creating research plan...`); + log(`Creating research plan...`); // Generate follow-up questions const followUpQuestions = await generateFeedback({ query: initialQuery, }); - console.log( + log( '\nTo better understand your research needs, please answer these follow-up questions:', ); @@ -59,22 +67,27 @@ async function run() { const combinedQuery = ` Initial Query: ${initialQuery} Follow-up Questions and Answers: -${followUpQuestions.map((q, i) => `Q: ${q}\nA: ${answers[i]}`).join('\n')} +${followUpQuestions.map((q: string, i: number) => `Q: ${q}\nA: ${answers[i]}`).join('\n')} `; - console.log('\nResearching your topic...'); + log('\nResearching your topic...'); + log('\nStarting research with progress tracking...\n'); + const { learnings, visitedUrls } = await deepResearch({ query: combinedQuery, breadth, depth, + onProgress: (progress) => { + output.updateProgress(progress); + }, }); - console.log(`\n\nLearnings:\n\n${learnings.join('\n')}`); - console.log( + log(`\n\nLearnings:\n\n${learnings.join('\n')}`); + log( `\n\nVisited URLs (${visitedUrls.length}):\n\n${visitedUrls.join('\n')}`, ); - console.log('Writing final report...'); + log('Writing final report...'); const report = await writeFinalReport({ prompt: combinedQuery,