feat: add streaming markdown rendering with incremental output
This commit is contained in:
parent
27c6447ef2
commit
856a3160fe
4 changed files with 45 additions and 25 deletions
|
|
@ -18,6 +18,8 @@ export default class Renderer {
|
|||
private lineCount = 0;
|
||||
private charsSinceNL = 0;
|
||||
private memOk = true;
|
||||
private buffer = "";
|
||||
private unsafeIndex = 0;
|
||||
|
||||
constructor(
|
||||
private isPlain = false,
|
||||
|
|
@ -66,6 +68,33 @@ export default class Renderer {
|
|||
}
|
||||
}
|
||||
|
||||
private async bufferThenRender(text: string, pipe = process.stdout) {
|
||||
this.buffer += text;
|
||||
|
||||
const idx = this.buffer.lastIndexOf("\n#");
|
||||
|
||||
const blockDelims = this.buffer.match(/```/g)?.length;
|
||||
if (blockDelims && blockDelims % 2 !== 0) this.unsafeIndex = idx;
|
||||
|
||||
if (idx > this.unsafeIndex) {
|
||||
const str = this.buffer.substring(0, idx) ?? "";
|
||||
await this.render(str, pipe);
|
||||
this.buffer = this.buffer.substring(idx);
|
||||
this.unsafeIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private async render(str: string, pipe = process.stdout) {
|
||||
this.moveCursor(-this.lineCount, pipe);
|
||||
this.clearScreenDown(pipe);
|
||||
this.resetCounts();
|
||||
|
||||
const [columns] = pipe.getWindowSize();
|
||||
const rendered = await renderMarkdown(str, columns);
|
||||
pipe.write(rendered);
|
||||
pipe.write("\n");
|
||||
}
|
||||
|
||||
async renderStream(stream: AsyncIterableStream<TextStreamPart<ToolSet>>) {
|
||||
this.resetCounts();
|
||||
|
||||
|
|
@ -113,16 +142,19 @@ export default class Renderer {
|
|||
}
|
||||
case "text-delta": {
|
||||
this.write(part.text);
|
||||
if (!this.isPlain) this.checkContainment(part.text);
|
||||
if (!this.isPlain) {
|
||||
this.checkContainment(part.text);
|
||||
await this.bufferThenRender(part.text);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "text-end": {
|
||||
if (!this.isPlain) {
|
||||
this.moveCursor(-this.lineCount - 1);
|
||||
this.clearScreenDown(process.stderr);
|
||||
this.resetCounts();
|
||||
if (this.buffer) {
|
||||
await this.render(this.buffer);
|
||||
this.buffer = "";
|
||||
this.unsafeIndex = 0;
|
||||
} else {
|
||||
this.write("\n\n");
|
||||
this.write("\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -144,20 +176,4 @@ export default class Renderer {
|
|||
const template = `Context: ${context.toFixed(1)}k, Output: ${output.toFixed(1)}k, Time: ${time.toFixed(2)}s\n`;
|
||||
process.stderr.write(chalkStderr.dim(template));
|
||||
}
|
||||
|
||||
async renderFinal(text: PromiseLike<string>) {
|
||||
if (this.isPlain) return;
|
||||
|
||||
const [columns] = process.stdout.getWindowSize();
|
||||
|
||||
const start = Date.now();
|
||||
const rendered = await renderMarkdown(text, columns);
|
||||
const stop = Date.now();
|
||||
|
||||
this.write("\n");
|
||||
this.write(rendered);
|
||||
this.write("\n");
|
||||
|
||||
process.stderr.write(chalkStderr.dim(`Markdown: ${stop - start}ms, `));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ import kanagawaLotus from "@shikijs/themes/kanagawa-lotus";
|
|||
import kanagawaWave from "@shikijs/themes/kanagawa-wave";
|
||||
|
||||
export async function renderMarkdown(
|
||||
text: PromiseLike<string>,
|
||||
text: PromiseLike<string> | string,
|
||||
columns: number,
|
||||
) {
|
||||
return render(await text, {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue