@lzhzzzzwill/cofos 1.2.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/bin/cofos.js +169 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@ COFOS CLI is a standalone Node.js command line wrapper around the COFOS Python r
|
|
|
6
6
|
npx @lzhzzzzwill/cofos
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
+
By default COFOS uses a plain terminal interface, so normal terminal scrollback, mouse selection, copy, and `Ctrl+C` work as expected. The fixed-input full-screen interface is still available as an experimental mode with `--tui`.
|
|
10
|
+
|
|
9
11
|
## Runtime Model
|
|
10
12
|
|
|
11
13
|
The standalone npm package includes a minimal Python runtime under `runtime/`:
|
|
@@ -69,6 +71,18 @@ Disable RAG for a session:
|
|
|
69
71
|
npx @lzhzzzzwill/cofos --no-rag
|
|
70
72
|
```
|
|
71
73
|
|
|
74
|
+
Ask once and exit, useful for scripts and smoke tests:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx @lzhzzzzwill/cofos -p "What ROS does TT-T-COF generate under visible-light photocatalysis?"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Use the experimental fixed-input TUI:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx @lzhzzzzwill/cofos --tui
|
|
84
|
+
```
|
|
85
|
+
|
|
72
86
|
## Startup Behavior
|
|
73
87
|
|
|
74
88
|
On startup, the CLI does the following:
|
|
@@ -118,6 +132,8 @@ cofos \
|
|
|
118
132
|
| `--cache-dir` | Folder for model, KG/BM25, and parsed PDF cache; defaults to `./.cofos` |
|
|
119
133
|
| `--rebuild-pdf-index` | Force PDF parsing and BM25 rebuild |
|
|
120
134
|
| `--top-k` | Retrieval top-k |
|
|
135
|
+
| `-p`, `--prompt` | Ask one question and exit |
|
|
136
|
+
| `--tui` | Use the experimental fixed-input full-screen TUI |
|
|
121
137
|
| `--max-new-tokens` | Override generation length |
|
|
122
138
|
| `--no-rag` | Run without KG/BM25 retrieval |
|
|
123
139
|
|
package/bin/cofos.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn, spawnSync } from "node:child_process";
|
|
3
|
-
import
|
|
3
|
+
import { createInterface } from "node:readline";
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import { dirname, join, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -106,7 +106,8 @@ function render({ modelPath, cacheDir, pdfDir }) {
|
|
|
106
106
|
const SLASH_COMMANDS = [
|
|
107
107
|
{ cmd: "/help", desc: "Show help" },
|
|
108
108
|
{ cmd: "/exit", desc: "Quit" },
|
|
109
|
-
{ cmd: "/clear", desc: "Clear conversation history" },
|
|
109
|
+
{ cmd: "/clear", desc: "Clear page and conversation history" },
|
|
110
|
+
{ cmd: "/cls", desc: "Clear page only" },
|
|
110
111
|
{ cmd: "/save", desc: "Save this session history" },
|
|
111
112
|
{ cmd: "/info", desc: "Show model and RAG info" },
|
|
112
113
|
{ cmd: "/topk 5", desc: "Set retrieval top-k" },
|
|
@@ -153,12 +154,15 @@ function ensureDeps(py, log = console.log) {
|
|
|
153
154
|
|
|
154
155
|
/* ─── TUI ─── */
|
|
155
156
|
|
|
156
|
-
function createTui() {
|
|
157
|
+
async function createTui() {
|
|
158
|
+
const { default: blessed } = await import("blessed");
|
|
157
159
|
const screen = blessed.screen({
|
|
158
160
|
smartCSR: true,
|
|
159
161
|
fullUnicode: true,
|
|
160
162
|
title: "COFOS",
|
|
163
|
+
mouse: false,
|
|
161
164
|
});
|
|
165
|
+
screen.program.disableMouse();
|
|
162
166
|
|
|
163
167
|
const history = blessed.box({
|
|
164
168
|
top: 0,
|
|
@@ -170,7 +174,7 @@ function createTui() {
|
|
|
170
174
|
keys: true,
|
|
171
175
|
vi: true,
|
|
172
176
|
mouse: false,
|
|
173
|
-
tags:
|
|
177
|
+
tags: true,
|
|
174
178
|
scrollbar: { ch: " ", track: { bg: "black" }, style: { bg: "cyan" } },
|
|
175
179
|
});
|
|
176
180
|
|
|
@@ -212,7 +216,14 @@ function createTui() {
|
|
|
212
216
|
let suggestionMatches = [];
|
|
213
217
|
let selectedSuggestion = 0;
|
|
214
218
|
|
|
219
|
+
function renderBuffer(wasAtBottom = true) {
|
|
220
|
+
history.setContent(buffer);
|
|
221
|
+
if (wasAtBottom) history.setScrollPerc(100);
|
|
222
|
+
screen.render();
|
|
223
|
+
}
|
|
224
|
+
|
|
215
225
|
function append(text = "") {
|
|
226
|
+
const wasAtBottom = history.getScrollPerc() >= 98;
|
|
216
227
|
const data = String(text).replace(/\x1b\[[0-9;]*[A-Za-z]/g, "");
|
|
217
228
|
for (const part of data.split("\r")) {
|
|
218
229
|
if (part === "") continue;
|
|
@@ -224,9 +235,7 @@ function createTui() {
|
|
|
224
235
|
}
|
|
225
236
|
}
|
|
226
237
|
if (buffer.length > 250000) buffer = buffer.slice(-220000);
|
|
227
|
-
|
|
228
|
-
history.setScrollPerc(100);
|
|
229
|
-
screen.render();
|
|
238
|
+
renderBuffer(wasAtBottom);
|
|
230
239
|
}
|
|
231
240
|
|
|
232
241
|
function hideSuggestions() {
|
|
@@ -314,30 +323,85 @@ function createTui() {
|
|
|
314
323
|
});
|
|
315
324
|
|
|
316
325
|
screen.key(["escape"], hideSuggestions);
|
|
326
|
+
screen.key(["pageup"], () => { history.scroll(-Math.max(3, Math.floor(screen.height * 0.8))); screen.render(); });
|
|
327
|
+
screen.key(["pagedown"], () => { history.scroll(Math.max(3, Math.floor(screen.height * 0.8))); screen.render(); });
|
|
328
|
+
screen.key(["home"], () => { history.setScrollPerc(0); screen.render(); });
|
|
329
|
+
screen.key(["end"], () => { history.setScrollPerc(100); screen.render(); });
|
|
317
330
|
screen.key(["C-c"], () => onExit());
|
|
318
331
|
|
|
319
332
|
input.focus();
|
|
320
333
|
screen.render();
|
|
321
334
|
|
|
322
335
|
return {
|
|
336
|
+
supportsReplace: true,
|
|
323
337
|
screen,
|
|
324
338
|
input,
|
|
325
339
|
append,
|
|
326
340
|
print: (text = "") => append(String(text) + "\n"),
|
|
341
|
+
clear: () => { buffer = ""; renderBuffer(true); },
|
|
342
|
+
length: () => buffer.length,
|
|
343
|
+
replaceFrom: (index, text = "") => { buffer = buffer.slice(0, Math.max(0, index)) + String(text); renderBuffer(true); },
|
|
327
344
|
setSubmitHandler: (fn) => { onSubmit = fn; },
|
|
328
345
|
setExitHandler: (fn) => { onExit = fn; },
|
|
329
346
|
focus: () => { input.focus(); screen.render(); },
|
|
330
347
|
setPrompt: (label) => { input.setLabel(` ${label} `); screen.render(); },
|
|
331
|
-
destroy: () =>
|
|
348
|
+
destroy: () => {
|
|
349
|
+
try { screen.program.disableMouse(); } catch (_) {}
|
|
350
|
+
screen.destroy();
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function stripBlessedTags(text) {
|
|
356
|
+
return String(text)
|
|
357
|
+
.replace(/\{\/?(?:bold|cyan-fg|yellow-fg|green-fg|gray-fg|red-fg|black-fg|yellow-bg)\}/g, "")
|
|
358
|
+
.replace(/[{}]/g, "");
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function createPlainUi() {
|
|
362
|
+
const rl = createInterface({
|
|
363
|
+
input: process.stdin,
|
|
364
|
+
output: process.stdout,
|
|
365
|
+
terminal: true,
|
|
366
|
+
completer: (line) => slashCompletions(line),
|
|
367
|
+
});
|
|
368
|
+
let onSubmit = () => {};
|
|
369
|
+
let onExit = () => {};
|
|
370
|
+
let prompt = "cofos";
|
|
371
|
+
|
|
372
|
+
function setPrompt(label) {
|
|
373
|
+
prompt = label;
|
|
374
|
+
rl.setPrompt(`${C}${prompt}${R} `);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
setPrompt(prompt);
|
|
378
|
+
rl.on("line", (line) => {
|
|
379
|
+
onSubmit(line);
|
|
380
|
+
});
|
|
381
|
+
rl.on("SIGINT", () => onExit());
|
|
382
|
+
rl.on("close", () => onExit());
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
supportsReplace: false,
|
|
386
|
+
append: (text = "") => process.stdout.write(stripBlessedTags(text)),
|
|
387
|
+
print: (text = "") => console.log(stripBlessedTags(text)),
|
|
388
|
+
clear: () => console.clear(),
|
|
389
|
+
length: () => 0,
|
|
390
|
+
replaceFrom: (_index, text = "") => process.stdout.write(stripBlessedTags(text)),
|
|
391
|
+
setSubmitHandler: (fn) => { onSubmit = fn; },
|
|
392
|
+
setExitHandler: (fn) => { onExit = fn; },
|
|
393
|
+
focus: () => rl.prompt(),
|
|
394
|
+
setPrompt,
|
|
395
|
+
destroy: () => { try { rl.close(); } catch (_) {} },
|
|
332
396
|
};
|
|
333
397
|
}
|
|
334
398
|
|
|
335
399
|
/* ─── main ─── */
|
|
336
400
|
|
|
337
|
-
function main() {
|
|
401
|
+
async function main() {
|
|
338
402
|
if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
339
403
|
console.log("\n COFOS CLI — 9B + RAG chat (Willlzh/COFOS)\n");
|
|
340
|
-
console.log(" Usage: cofos [--model PATH] [--config PATH] [--pdf-dir DIR] [--cache-dir DIR] [--top-k N] [--no-rag]\n");
|
|
404
|
+
console.log(" Usage: cofos [--tui] [-p QUESTION] [--model PATH] [--config PATH] [--pdf-dir DIR] [--cache-dir DIR] [--top-k N] [--no-rag]\n");
|
|
341
405
|
console.log(" Commands in chat:");
|
|
342
406
|
console.log(" /help Show this help");
|
|
343
407
|
console.log(" /exit Quit");
|
|
@@ -349,6 +413,9 @@ function main() {
|
|
|
349
413
|
console.log(" Startup options:");
|
|
350
414
|
console.log(" --pdf-dir DIR Add PDFs from DIR as extra BM25 evidence");
|
|
351
415
|
console.log(" --cache-dir DIR Store model/data cache here (default: ./.cofos)");
|
|
416
|
+
console.log(" --max-new-tokens N Override generation length");
|
|
417
|
+
console.log(" --tui Use the experimental fixed-input TUI");
|
|
418
|
+
console.log(" -p, --prompt TEXT Ask once and exit");
|
|
352
419
|
console.log(" --rebuild-pdf-index Force reparsing PDFs and rebuilding BM25\n");
|
|
353
420
|
console.log(" Multi-line: end a line with \\ to continue on the next line.\n");
|
|
354
421
|
process.exit(0);
|
|
@@ -361,8 +428,11 @@ function main() {
|
|
|
361
428
|
let configPath = DEFAULT_CONFIG;
|
|
362
429
|
let topK = "5";
|
|
363
430
|
let pdfDir = null;
|
|
431
|
+
let maxNewTokens = null;
|
|
364
432
|
let cacheDir = process.env.COFOS_CACHE_DIR ? resolve(process.env.COFOS_CACHE_DIR) : resolve(process.cwd(), ".cofos");
|
|
365
433
|
let rebuildPdfIndex = false;
|
|
434
|
+
let useTui = false;
|
|
435
|
+
let singlePrompt = null;
|
|
366
436
|
const backendArgs = [chatScript];
|
|
367
437
|
|
|
368
438
|
const die = (msg) => { console.error("[cofos] " + msg); process.exit(1); };
|
|
@@ -386,10 +456,17 @@ function main() {
|
|
|
386
456
|
} else if (arg === "--pdf-dir") {
|
|
387
457
|
pdfDir = resolve(value(++i, arg));
|
|
388
458
|
if (!existsSync(pdfDir)) die(`--pdf-dir does not exist: ${pdfDir}`);
|
|
459
|
+
} else if (arg === "--max-new-tokens") {
|
|
460
|
+
maxNewTokens = value(++i, arg);
|
|
461
|
+
if (!/^\d+$/.test(maxNewTokens) || Number(maxNewTokens) < 1) die(`--max-new-tokens must be a positive integer, got "${maxNewTokens}"`);
|
|
389
462
|
} else if (arg === "--cache-dir") {
|
|
390
463
|
cacheDir = resolve(value(++i, arg));
|
|
391
464
|
} else if (arg === "--rebuild-pdf-index") {
|
|
392
465
|
rebuildPdfIndex = true;
|
|
466
|
+
} else if (arg === "--tui") {
|
|
467
|
+
useTui = true;
|
|
468
|
+
} else if (arg === "-p" || arg === "--prompt") {
|
|
469
|
+
singlePrompt = value(++i, arg);
|
|
393
470
|
} else if (arg === "--no-rag") {
|
|
394
471
|
backendArgs.push("--no-rag");
|
|
395
472
|
} else {
|
|
@@ -398,14 +475,15 @@ function main() {
|
|
|
398
475
|
}
|
|
399
476
|
backendArgs.push("--merged-model-path", modelPath, "--config", configPath, "--top-k", topK);
|
|
400
477
|
if (pdfDir) backendArgs.push("--pdf-dir", pdfDir);
|
|
478
|
+
if (maxNewTokens) backendArgs.push("--max-new-tokens", maxNewTokens);
|
|
401
479
|
if (rebuildPdfIndex) backendArgs.push("--rebuild-pdf-index");
|
|
402
480
|
|
|
403
481
|
const py = findPy();
|
|
404
482
|
if (!py) die("Python not found. Install Python 3.9+ and re-run.");
|
|
405
483
|
if (!existsSync(chatScript)) die(`Missing script: ${chatScript}`);
|
|
406
484
|
|
|
407
|
-
const ui = createTui();
|
|
408
|
-
ui.print(plain(render({ modelPath, cacheDir, pdfDir })));
|
|
485
|
+
const ui = useTui ? await createTui() : createPlainUi();
|
|
486
|
+
ui.print(useTui ? plain(render({ modelPath, cacheDir, pdfDir })) : render({ modelPath, cacheDir, pdfDir }));
|
|
409
487
|
ensureDeps(py, (msg) => ui.print(msg));
|
|
410
488
|
ui.print(" Loading COFOS 9B + RAG backend from Hugging Face … first run may take a while");
|
|
411
489
|
if (pdfDir) ui.print(" Local PDF RAG: enabled for this session");
|
|
@@ -432,6 +510,7 @@ function main() {
|
|
|
432
510
|
proc.stderr.setEncoding("utf-8");
|
|
433
511
|
|
|
434
512
|
let buf = "", ready = false, generating = false, responseOpen = false, commandOpen = false, closed = false;
|
|
513
|
+
let currentAnswer = "", assistantStart = -1;
|
|
435
514
|
const inputQ = [];
|
|
436
515
|
let mlBuf = [];
|
|
437
516
|
|
|
@@ -441,6 +520,7 @@ function main() {
|
|
|
441
520
|
try { proc.stdin.end(); } catch (_) {}
|
|
442
521
|
try { proc.kill("SIGTERM"); } catch (_) {}
|
|
443
522
|
ui.destroy();
|
|
523
|
+
process.exit(0);
|
|
444
524
|
}
|
|
445
525
|
|
|
446
526
|
ui.setExitHandler(shutdown);
|
|
@@ -471,21 +551,30 @@ function main() {
|
|
|
471
551
|
input = input.trim();
|
|
472
552
|
if (!input) return false;
|
|
473
553
|
|
|
474
|
-
if (/^\/(exit|quit)$/.test(input)) {
|
|
554
|
+
if (/^\/(exit|quit)$/.test(input)) { shutdown(); return true; }
|
|
475
555
|
if (/^\/(help)$/.test(input)) {
|
|
476
556
|
ui.print(slashHint("/") + "\n Add local papers to RAG:\n cofos --pdf-dir ./new_pdfs\n cofos --pdf-dir ./new_pdfs --rebuild-pdf-index\n\n Multi-line: end a line with \\ to continue.");
|
|
477
557
|
return false;
|
|
478
558
|
}
|
|
559
|
+
if (/^\/(cls)$/.test(input)) {
|
|
560
|
+
ui.clear();
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
479
563
|
if (/^\/(clear)$/.test(input)) {
|
|
564
|
+
ui.clear();
|
|
480
565
|
mlBuf = [];
|
|
481
566
|
responseOpen = false;
|
|
567
|
+
assistantStart = -1;
|
|
568
|
+
currentAnswer = "";
|
|
482
569
|
commandOpen = true;
|
|
483
570
|
proc.stdin.write(input + "\n");
|
|
484
571
|
generating = true;
|
|
485
572
|
return true;
|
|
486
573
|
}
|
|
487
|
-
if (/^\/(topk)(?:\s+\d+)?$/.test(input) || /^\/rag\s+(?:on|off)$/.test(input)) {
|
|
574
|
+
if (/^\/(save)$/.test(input) || /^\/(topk)(?:\s+\d+)?$/.test(input) || /^\/rag\s+(?:on|off)$/.test(input)) {
|
|
488
575
|
responseOpen = false;
|
|
576
|
+
assistantStart = -1;
|
|
577
|
+
currentAnswer = "";
|
|
489
578
|
commandOpen = true;
|
|
490
579
|
proc.stdin.write(input + "\n");
|
|
491
580
|
generating = true;
|
|
@@ -495,9 +584,15 @@ function main() {
|
|
|
495
584
|
ui.print(`\n COFOS CLI — v${pkg.version}\n Model: ${modelPath} (Hugging Face)\n RAG: Willlzh/COFOS_data/runtime (KG + BM25) + optional --pdf-dir BM25 evidence\n Cache: ${cacheDir}\n HF: ${backendEnv.HF_HOME}\n Engine: StudentAdapterInference + transformers + PyTorch\n Hist: ~/.cofos_history.jsonl\n`);
|
|
496
585
|
return false;
|
|
497
586
|
}
|
|
587
|
+
if (input.startsWith("/")) {
|
|
588
|
+
ui.print(`{red-fg}•{/red-fg} Unknown command: ${input}. Press / then Tab for commands, or run /help.`);
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
498
591
|
|
|
499
|
-
ui.print(
|
|
592
|
+
ui.print("{yellow-fg}●{/yellow-fg} " + input);
|
|
500
593
|
responseOpen = false;
|
|
594
|
+
assistantStart = -1;
|
|
595
|
+
currentAnswer = "";
|
|
501
596
|
generating = true;
|
|
502
597
|
proc.stdin.write(input + "\n");
|
|
503
598
|
return true;
|
|
@@ -510,6 +605,42 @@ function main() {
|
|
|
510
605
|
|
|
511
606
|
proc.stderr.on("data", (chunk) => ui.append(chunk));
|
|
512
607
|
|
|
608
|
+
function escapeTags(text) {
|
|
609
|
+
return String(text).replace(/[{}]/g, "");
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function formatAssistant(answer) {
|
|
613
|
+
const raw = escapeTags(answer).trimStart();
|
|
614
|
+
if (!raw) return "";
|
|
615
|
+
if (raw.includes("</think>")) {
|
|
616
|
+
const [reason, ...answerParts] = raw.split("</think>");
|
|
617
|
+
const cleanReason = reason.replace(/^<think>\s*/i, "").trim();
|
|
618
|
+
const finalAnswer = answerParts.join("</think>").trimStart();
|
|
619
|
+
return finalAnswer
|
|
620
|
+
? "{gray-fg}- " + cleanReason + "{/gray-fg}\n{green-fg}{bold}◆{/bold}{/green-fg} " + finalAnswer
|
|
621
|
+
: "{gray-fg}- " + cleanReason + "{/gray-fg}";
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const paragraphs = raw.split(/\n\s*\n/);
|
|
625
|
+
const reasonRe = /^(The user|Okay[,,]|I need|We need|I should|I'll|Let me|Since the input|In COF nomenclature|Based on the provided information[,,]? I can|用户|让我)/i;
|
|
626
|
+
let reasonCount = 0;
|
|
627
|
+
while (reasonCount < paragraphs.length && reasonRe.test(paragraphs[reasonCount].trim())) reasonCount += 1;
|
|
628
|
+
if (reasonCount > 0) {
|
|
629
|
+
const reason = paragraphs.slice(0, reasonCount).join("\n\n").trim();
|
|
630
|
+
const finalAnswer = paragraphs.slice(reasonCount).join("\n\n").trimStart();
|
|
631
|
+
return finalAnswer
|
|
632
|
+
? "{gray-fg}- " + reason + "{/gray-fg}\n{green-fg}{bold}◆{/bold}{/green-fg} " + finalAnswer
|
|
633
|
+
: "{gray-fg}- " + reason + "{/gray-fg}";
|
|
634
|
+
}
|
|
635
|
+
return "{green-fg}{bold}◆{/bold}{/green-fg} " + raw;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function redrawAssistant() {
|
|
639
|
+
if (assistantStart < 0 || !ui.supportsReplace) return;
|
|
640
|
+
ui.replaceFrom(assistantStart, formatAssistant(currentAnswer));
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
|
|
513
644
|
proc.stdout.on("data", (chunk) => {
|
|
514
645
|
buf += chunk;
|
|
515
646
|
if (!ready) {
|
|
@@ -518,6 +649,8 @@ function main() {
|
|
|
518
649
|
ready = true;
|
|
519
650
|
buf = buf.slice(i + READY.length);
|
|
520
651
|
if (buf[0] === "\n") buf = buf.slice(1);
|
|
652
|
+
if (singlePrompt) inputQ.push(singlePrompt);
|
|
653
|
+
else ui.focus();
|
|
521
654
|
drain();
|
|
522
655
|
}
|
|
523
656
|
return;
|
|
@@ -528,12 +661,13 @@ function main() {
|
|
|
528
661
|
const di = buf.indexOf(DONE);
|
|
529
662
|
if (di === -1) return;
|
|
530
663
|
const pre = buf.slice(0, di).trim();
|
|
531
|
-
if (pre) ui.print(
|
|
664
|
+
if (pre) ui.print("{gray-fg}•{/gray-fg} " + pre);
|
|
532
665
|
buf = buf.slice(di + DONE.length);
|
|
533
666
|
if (buf[0] === "\n") buf = buf.slice(1);
|
|
534
667
|
generating = false;
|
|
535
668
|
commandOpen = false;
|
|
536
|
-
|
|
669
|
+
if (singlePrompt && inputQ.length === 0) shutdown();
|
|
670
|
+
else ui.focus();
|
|
537
671
|
drain();
|
|
538
672
|
return;
|
|
539
673
|
}
|
|
@@ -548,25 +682,29 @@ function main() {
|
|
|
548
682
|
buf = buf.slice(ri + RESPONSE.length);
|
|
549
683
|
if (buf[0] === "\n") buf = buf.slice(1);
|
|
550
684
|
responseOpen = true;
|
|
551
|
-
ui.
|
|
685
|
+
assistantStart = ui.supportsReplace ? ui.length() : 0;
|
|
686
|
+
currentAnswer = "";
|
|
552
687
|
}
|
|
553
688
|
|
|
554
689
|
const di = buf.indexOf(DONE);
|
|
555
690
|
if (di !== -1) {
|
|
556
691
|
const pre = buf.slice(0, di);
|
|
557
|
-
if (pre)
|
|
692
|
+
if (pre) { currentAnswer += pre; redrawAssistant(); }
|
|
558
693
|
buf = buf.slice(di + DONE.length);
|
|
559
694
|
if (buf[0] === "\n") buf = buf.slice(1);
|
|
695
|
+
if (ui.supportsReplace) redrawAssistant();
|
|
696
|
+
else ui.print(formatAssistant(currentAnswer));
|
|
560
697
|
generating = false;
|
|
561
698
|
responseOpen = false;
|
|
562
|
-
ui.append("\n");
|
|
563
|
-
|
|
699
|
+
if (ui.supportsReplace) ui.append("\n");
|
|
700
|
+
if (singlePrompt && inputQ.length === 0) shutdown();
|
|
701
|
+
else ui.focus();
|
|
564
702
|
drain();
|
|
565
703
|
} else {
|
|
566
704
|
const keep = DONE.length - 1;
|
|
567
705
|
if (buf.length > keep) {
|
|
568
706
|
const out = buf.slice(0, -keep);
|
|
569
|
-
if (out)
|
|
707
|
+
if (out) { currentAnswer += out; redrawAssistant(); }
|
|
570
708
|
buf = buf.slice(-keep);
|
|
571
709
|
}
|
|
572
710
|
}
|
|
@@ -574,12 +712,18 @@ function main() {
|
|
|
574
712
|
|
|
575
713
|
proc.on("error", (e) => { ui.print("[cofos] Python error: " + e.message); shutdown(); });
|
|
576
714
|
proc.on("exit", (c) => {
|
|
577
|
-
closed
|
|
578
|
-
|
|
579
|
-
ui.
|
|
715
|
+
if (closed) return;
|
|
716
|
+
ui.print(c && c !== 0 ? "[cofos] Python exited with code " + c : "[cofos] Python backend exited.");
|
|
717
|
+
ui.print("Press Ctrl+C to close.");
|
|
718
|
+
ui.focus();
|
|
580
719
|
process.exitCode = c ?? 0;
|
|
581
720
|
});
|
|
582
721
|
|
|
583
722
|
}
|
|
584
723
|
|
|
724
|
+
process.on("uncaughtException", (err) => {
|
|
725
|
+
try { process.stderr.write(`[cofos] Uncaught error: ${err?.stack || err}\n`); } catch (_) {}
|
|
726
|
+
process.exit(1);
|
|
727
|
+
});
|
|
728
|
+
|
|
585
729
|
main();
|