refactor: move render logic to it's own module; add build script

This commit is contained in:
mitchell 2026-04-15 15:51:08 -04:00
parent ab648271c2
commit cfc494ba53
3 changed files with 76 additions and 65 deletions

View file

@ -1,6 +1,8 @@
import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { streamText } from "ai"; import { streamText } from "ai";
import { renderStream } from "./src/render";
const provider = createOpenAICompatible({ const provider = createOpenAICompatible({
name: "llama.cpp", name: "llama.cpp",
apiKey: "local", apiKey: "local",
@ -13,75 +15,16 @@ const { fullStream, reasoningText, text } = streamText({
prompt: Bun.argv[2] ?? "Introduce yourself.", prompt: Bun.argv[2] ?? "Introduce yourself.",
}); });
const moveCursor = (x: number, y: number) => renderStream(fullStream);
new Promise<void>((resolve) => process.stdout.moveCursor(x, y, resolve));
const clearScreenDown = () =>
new Promise<void>((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();
}
}
const reasoning = await reasoningText; const reasoning = await reasoningText;
const response = await text;
Bun.file("reasoning.md").write(reasoning ?? ""); Bun.file("reasoning.md").write(reasoning ?? "");
const response = await text;
Bun.file("response.md").write(response); Bun.file("response.md").write(response);
const [numColumns] = process.stdout.getWindowSize();
Bun.spawnSync({ Bun.spawnSync({
cmd: ["glow", "response.md"], cmd: ["glow", "-w", `${numColumns}`, "response.md"],
stdio: ["inherit", "inherit", "inherit"], stdio: ["inherit", "inherit", "inherit"],
}); });

View file

@ -4,7 +4,8 @@
"type": "module", "type": "module",
"private": true, "private": true,
"scripts": { "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": { "devDependencies": {
"@biomejs/biome": "2.4.12", "@biomejs/biome": "2.4.12",

67
src/render/index.ts Normal file
View file

@ -0,0 +1,67 @@
import type { AsyncIterableStream, TextStreamPart, ToolSet } from "ai";
const moveCursor = (x: number, y: number) =>
new Promise<void>((resolve) => process.stdout.moveCursor(x, y, resolve));
const clearScreenDown = () =>
new Promise<void>((resolve) => process.stdout.clearScreenDown(resolve));
export async function renderStream(
stream: AsyncIterableStream<TextStreamPart<ToolSet>>,
) {
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();
}
}
}