Progress Bar Improvements for Deep Research

Progress Bar Improvements for Deep Research
This commit is contained in:
Umut CAN
2025-02-09 23:06:44 +03:00
parent 2ef450a43c
commit b1c8c7acf0
4 changed files with 211 additions and 13 deletions

View File

@@ -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<ResearchResult> {
const progress: ResearchProgress = {
currentDepth: depth,
totalDepth: depth,
currentBreadth: breadth,
totalBreadth: breadth,
totalQueries: 0,
completedQueries: 0,
};
const reportProgress = (update: Partial<ResearchProgress>) => {
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: [],

56
src/output-manager.ts Normal file
View File

@@ -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`);
}
}

76
src/progress-manager.ts Normal file
View File

@@ -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`);
}
}

View File

@@ -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,