mirror of
https://github.com/dzhng/deep-research.git
synced 2026-03-22 16:07:17 +08:00
Progress Bar Improvements for Deep Research
Progress Bar Improvements for Deep Research
This commit is contained in:
@@ -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
56
src/output-manager.ts
Normal 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
76
src/progress-manager.ts
Normal 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`);
|
||||
}
|
||||
}
|
||||
27
src/run.ts
27
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,
|
||||
|
||||
Reference in New Issue
Block a user