feat: add --plain flag for unformatted terminal output
Rename StreamRenderer to Renderer and move stats rendering into the class. Support plain mode that skips ANSI formatting and glow preview when output is not a TTY.
This commit is contained in:
parent
e35e32f396
commit
9d599d98bf
2 changed files with 43 additions and 27 deletions
22
index.ts
22
index.ts
|
|
@ -2,9 +2,8 @@ import os from "node:os";
|
|||
import { parseArgs } from "node:util";
|
||||
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
||||
import { type ModelMessage, streamText } from "ai";
|
||||
import chalk from "chalk";
|
||||
|
||||
import StreamRenderer from "./src/render";
|
||||
import Renderer from "./src/render";
|
||||
|
||||
const { values: args, positionals } = parseArgs({
|
||||
args: Bun.argv,
|
||||
|
|
@ -15,6 +14,8 @@ const { values: args, positionals } = parseArgs({
|
|||
n: { type: "boolean" },
|
||||
save: { type: "boolean" },
|
||||
s: { type: "boolean" },
|
||||
plain: { type: "boolean" },
|
||||
p: { type: "boolean" },
|
||||
},
|
||||
strict: true,
|
||||
allowPositionals: true,
|
||||
|
|
@ -26,6 +27,7 @@ const configFile = Bun.file(`${home}/.aicl_config.json`);
|
|||
|
||||
async function main() {
|
||||
const newConvo = args.new ?? args.n;
|
||||
const plain = args.plain ?? args.p ?? !process.stdout.isTTY;
|
||||
let saveConfig = args.save ?? args.s;
|
||||
|
||||
let config: { model?: string };
|
||||
|
|
@ -47,6 +49,8 @@ async function main() {
|
|||
await configFile.write(JSON.stringify(config, null, " "));
|
||||
}
|
||||
|
||||
const renderer = new Renderer(plain);
|
||||
|
||||
const provider = createOpenAICompatible({
|
||||
name: "llama.cpp",
|
||||
apiKey: "local",
|
||||
|
|
@ -78,28 +82,22 @@ async function main() {
|
|||
});
|
||||
|
||||
const start = Date.now();
|
||||
await new StreamRenderer().renderStream(fullStream);
|
||||
await renderer.renderStream(fullStream);
|
||||
const stop = Date.now();
|
||||
|
||||
convo.messages.push({ role: "assistant", content: await text });
|
||||
await convoFile.write(JSON.stringify(convo));
|
||||
|
||||
if (!plain) {
|
||||
const [width] = process.stdout.getWindowSize();
|
||||
await Bun.spawn({
|
||||
cmd: ["glow", "-w", `${width}`, "-"],
|
||||
stdin: textStream,
|
||||
stdout: "inherit",
|
||||
}).exited;
|
||||
}
|
||||
|
||||
const { inputTokens, outputTokens } = await totalUsage;
|
||||
const context = (inputTokens ?? 0) / 1000;
|
||||
const output = (outputTokens ?? 0) / 1000;
|
||||
const time = (stop - start) / 1000;
|
||||
process.stderr.write(
|
||||
chalk.dim(
|
||||
`Context: ${context.toFixed(1)}k, Output: ${output.toFixed(1)}k, Time: ${time.toFixed(2)}s\n`,
|
||||
),
|
||||
);
|
||||
renderer.renderStats(await totalUsage, start, stop);
|
||||
}
|
||||
|
||||
await main();
|
||||
|
|
|
|||
|
|
@ -1,19 +1,24 @@
|
|||
import os from "node:os";
|
||||
import type { WriteStream } from "node:tty";
|
||||
import type { AsyncIterableStream, TextStreamPart, ToolSet } from "ai";
|
||||
import type {
|
||||
AsyncIterableStream,
|
||||
LanguageModelUsage,
|
||||
TextStreamPart,
|
||||
ToolSet,
|
||||
} from "ai";
|
||||
import styles from "ansi-styles";
|
||||
import { supportsColorStderr } from "chalk";
|
||||
import { chalkStderr, supportsColorStderr } from "chalk";
|
||||
|
||||
const home = os.homedir();
|
||||
const reasoningFile = Bun.file(`${home}/.aicl_reasoning.md`);
|
||||
const reasoningWriter = reasoningFile.writer();
|
||||
|
||||
export default class StreamRenderer {
|
||||
export default class Renderer {
|
||||
private lineCount = 0;
|
||||
private charsSinceNL = 0;
|
||||
private memOk = true;
|
||||
|
||||
constructor() {
|
||||
constructor(private isPlain = false) {
|
||||
process.stdout.addListener("drain", () => {
|
||||
this.memOk = true;
|
||||
});
|
||||
|
|
@ -50,7 +55,7 @@ export default class StreamRenderer {
|
|||
this.charsSinceNL = this.charsSinceNL - columns;
|
||||
}
|
||||
|
||||
if (this.lineCount >= Math.floor(rows * 0.75)) {
|
||||
if (this.lineCount >= rows - 4) {
|
||||
this.moveCursor(-this.lineCount, pipe);
|
||||
this.clearScreenDown(pipe);
|
||||
this.resetCounts();
|
||||
|
|
@ -96,13 +101,17 @@ export default class StreamRenderer {
|
|||
}
|
||||
case "text-delta": {
|
||||
this.write(part.text);
|
||||
this.checkContainment(part.text);
|
||||
if (!this.isPlain) this.checkContainment(part.text);
|
||||
break;
|
||||
}
|
||||
case "text-end": {
|
||||
if (!this.isPlain) {
|
||||
this.moveCursor(-this.lineCount - 1);
|
||||
this.clearScreenDown(process.stderr);
|
||||
this.resetCounts();
|
||||
} else {
|
||||
this.write("\n\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -110,4 +119,13 @@ export default class StreamRenderer {
|
|||
while (!this.memOk) await Bun.sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
renderStats(usage: LanguageModelUsage, start: number, stop: number) {
|
||||
const { inputTokens, outputTokens } = usage;
|
||||
const context = (inputTokens ?? 0) / 1000;
|
||||
const output = (outputTokens ?? 0) / 1000;
|
||||
const time = (stop - start) / 1000;
|
||||
const template = `Context: ${context.toFixed(1)}k, Output: ${output.toFixed(1)}k, Time: ${time.toFixed(2)}s\n`;
|
||||
process.stderr.write(chalkStderr.dim(template));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue