From cfc494ba5358faf138df1a2e4c247d12c9964892 Mon Sep 17 00:00:00 2001 From: mitchell Date: Wed, 15 Apr 2026 15:51:08 -0400 Subject: [PATCH] refactor: move render logic to it's own module; add build script --- index.ts | 71 +++++---------------------------------------- package.json | 3 +- src/render/index.ts | 67 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 65 deletions(-) create mode 100644 src/render/index.ts diff --git a/index.ts b/index.ts index b0957e2..bff4871 100644 --- a/index.ts +++ b/index.ts @@ -1,6 +1,8 @@ import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; import { streamText } from "ai"; +import { renderStream } from "./src/render"; + const provider = createOpenAICompatible({ name: "llama.cpp", apiKey: "local", @@ -13,75 +15,16 @@ const { fullStream, reasoningText, text } = streamText({ prompt: Bun.argv[2] ?? "Introduce yourself.", }); -const moveCursor = (x: number, y: number) => - new Promise((resolve) => process.stdout.moveCursor(x, y, resolve)); -const clearScreenDown = () => - new Promise((resolve) => process.stdout.clearScreenDown(resolve)); - -let lineCount = 0; -let charsSinceNL = 0; -const [numColumns, numRows] = process.stdout.getWindowSize(); - -for await (const part of fullStream) { - if (part.type === "reasoning-start") { - process.stdout.write("\u001b[2m\n"); - } else if (part.type === "reasoning-delta") { - process.stdout.write(part.text); - - charsSinceNL += part.text.length; - if (part.text.includes("\n")) { - lineCount += part.text.match(/\n/g)?.length ?? 1; - charsSinceNL = 0; - } else if (charsSinceNL >= numColumns) { - lineCount++; - charsSinceNL = charsSinceNL - numColumns; - } - - if (lineCount >= numRows / 2) { - await moveCursor(0, -lineCount); - await clearScreenDown(); - lineCount = 0; - charsSinceNL = 0; - } - } else if (part.type === "reasoning-end") { - process.stdout.write("\n\u001b[22m"); - await moveCursor(0, -lineCount - 2); - await clearScreenDown(); - lineCount = 0; - charsSinceNL = 0; - } else if (part.type === "text-start") { - process.stdout.write("\n"); - } else if (part.type === "text-delta") { - process.stdout.write(part.text); - - charsSinceNL += part.text.length; - if (part.text.includes("\n")) { - lineCount += part.text.match(/\n/g)?.length ?? 1; - charsSinceNL = 0; - } else if (charsSinceNL >= numColumns) { - lineCount++; - charsSinceNL = charsSinceNL - numColumns; - } - - if (lineCount >= numRows / 2) { - await moveCursor(0, -lineCount); - await clearScreenDown(); - lineCount = 0; - charsSinceNL = 0; - } - } else if (part.type === "text-end") { - await moveCursor(0, -lineCount - 1); - await clearScreenDown(); - } -} +renderStream(fullStream); const reasoning = await reasoningText; -const response = await text; - Bun.file("reasoning.md").write(reasoning ?? ""); + +const response = await text; Bun.file("response.md").write(response); +const [numColumns] = process.stdout.getWindowSize(); Bun.spawnSync({ - cmd: ["glow", "response.md"], + cmd: ["glow", "-w", `${numColumns}`, "response.md"], stdio: ["inherit", "inherit", "inherit"], }); diff --git a/package.json b/package.json index 4b9dc6b..68ebd60 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "type": "module", "private": true, "scripts": { - "check": "bunx --bun @biomejs/biome check --write" + "check": "bunx --bun @biomejs/biome check --write", + "build": "bun build --compile --outfile=out/aicl ./index.ts" }, "devDependencies": { "@biomejs/biome": "2.4.12", diff --git a/src/render/index.ts b/src/render/index.ts new file mode 100644 index 0000000..24cc67b --- /dev/null +++ b/src/render/index.ts @@ -0,0 +1,67 @@ +import type { AsyncIterableStream, TextStreamPart, ToolSet } from "ai"; + +const moveCursor = (x: number, y: number) => + new Promise((resolve) => process.stdout.moveCursor(x, y, resolve)); +const clearScreenDown = () => + new Promise((resolve) => process.stdout.clearScreenDown(resolve)); + +export async function renderStream( + stream: AsyncIterableStream>, +) { + let lineCount = 0; + let charsSinceNL = 0; + const [numColumns, numRows] = process.stdout.getWindowSize(); + + for await (const part of stream) { + if (part.type === "reasoning-start") { + process.stdout.write("\u001b[2m\n"); + } else if (part.type === "reasoning-delta") { + process.stdout.write(part.text); + + charsSinceNL += part.text.length; + if (part.text.includes("\n")) { + lineCount += part.text.match(/\n/g)?.length ?? 1; + charsSinceNL = 0; + } else if (charsSinceNL >= numColumns) { + lineCount++; + charsSinceNL = charsSinceNL - numColumns; + } + + if (lineCount >= numRows / 2) { + await moveCursor(0, -lineCount); + await clearScreenDown(); + lineCount = 0; + charsSinceNL = 0; + } + } else if (part.type === "reasoning-end") { + process.stdout.write("\n\u001b[22m"); + await moveCursor(0, -lineCount - 2); + await clearScreenDown(); + lineCount = 0; + charsSinceNL = 0; + } else if (part.type === "text-start") { + process.stdout.write("\n"); + } else if (part.type === "text-delta") { + process.stdout.write(part.text); + + charsSinceNL += part.text.length; + if (part.text.includes("\n")) { + lineCount += part.text.match(/\n/g)?.length ?? 1; + charsSinceNL = 0; + } else if (charsSinceNL >= numColumns) { + lineCount++; + charsSinceNL = charsSinceNL - numColumns; + } + + if (lineCount >= numRows / 2) { + await moveCursor(0, -lineCount); + await clearScreenDown(); + lineCount = 0; + charsSinceNL = 0; + } + } else if (part.type === "text-end") { + await moveCursor(0, -lineCount - 1); + await clearScreenDown(); + } + } +}