UNPKG

research-cli

Version:

AI-powered research assistant with web search capabilities and beautiful terminal UI

129 lines 5.45 kB
/** * Markdown renderer for TTY-friendly streaming output */ import { createCitationsPanel, createErrorPanel, createStatusPanel, createToolPanel, createUsageStatsPanel, } from "../ui/panels.js"; import { ProgressManager } from "../ui/progress-manager.js"; export class MarkdownRenderer { isFirstChunk = true; citations = []; progressManager; // private citationCounter = 1; // Unused for now, will be used in M2 constructor(options = {}) { this.progressManager = new ProgressManager(options); } async renderStream(eventStream) { try { for await (const event of eventStream) { await this.renderEvent(event); } // Render citations at the end if any if (this.citations.length > 0) { this.renderCitations(); } // Ensure we end with a newline for clean terminal output process.stdout.write("\n"); } catch (error) { // eslint-disable-next-line no-console console.error("\nError rendering stream:", error instanceof Error ? error.message : "Unknown error"); } } async renderEvent(event) { // Handle progress updates this.progressManager.handleProviderEvent(event); switch (event.type) { case "start": if (event.data && typeof event.data === "object" && "provider" in event.data && "model" in event.data) { const panel = createStatusPanel(String(event.data.provider), String(event.data.model)); // eslint-disable-next-line no-console console.error(`\n${panel}`); } break; case "tool_call": if (event.data && typeof event.data === "object" && "name" in event.data) { const panel = createToolPanel(String(event.data.name), "calling"); // eslint-disable-next-line no-console console.error(`${panel}\n`); } break; case "tool_result": if (event.data && typeof event.data === "object" && "name" in event.data && "results" in event.data) { const panel = createToolPanel(String(event.data.name), "completed"); // eslint-disable-next-line no-console console.error(`${panel}\n`); } break; case "chunk": if (event.data && typeof event.data === "object" && "text" in event.data && typeof event.data.text === "string") { // Stop progress bar when first chunk arrives if (this.isFirstChunk) { // Give a moment for progress bar to show completion await new Promise((resolve) => setTimeout(resolve, 100)); process.stdout.write("\n"); process.stdout.write(event.data.text); this.isFirstChunk = false; } else { process.stdout.write(event.data.text); } } break; case "final": if (event.data && typeof event.data === "object" && "citations" in event.data) { // Store citations for rendering at the end if (Array.isArray(event.data.citations)) { this.citations = event.data.citations.map((citation) => ({ url: String(citation.url || ""), title: String(citation.title || ""), date: citation.date ? String(citation.date) : undefined, })); } // Add final newline after content process.stdout.write("\n"); // Display usage stats if available if ("usage" in event.data && event.data.usage) { this.renderUsageStats(event.data.usage); } } break; case "error": if (event.data && typeof event.data === "object" && "message" in event.data) { const panel = createErrorPanel(String(event.data.message), "code" in event.data ? String(event.data.code) : undefined); // eslint-disable-next-line no-console console.error(`\n${panel}`); } break; default: // Ignore unknown event types break; } } renderUsageStats(usage) { const panel = createUsageStatsPanel(usage); // eslint-disable-next-line no-console console.error(`\n${panel}`); } renderCitations() { if (this.citations.length === 0) return; const panel = createCitationsPanel(this.citations); // eslint-disable-next-line no-console console.log(`\n${panel}`); } } //# sourceMappingURL=markdown.js.map