@lzhzzzzwill/cofos 1.3.2 → 1.3.4
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 +1 -7
- package/bin/cofos.js +25 -294
- package/package.json +1 -4
- package/runtime/src/inference/student_adapter_inference.py +17 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ 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
|
-
|
|
9
|
+
COFOS uses a plain terminal interface, so normal terminal scrollback, mouse selection, copy, and `Ctrl+C` work as expected.
|
|
10
10
|
|
|
11
11
|
## Runtime Model
|
|
12
12
|
|
|
@@ -77,11 +77,6 @@ Ask once and exit, useful for scripts and smoke tests:
|
|
|
77
77
|
npx @lzhzzzzwill/cofos -p "What ROS does TT-T-COF generate under visible-light photocatalysis?"
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
Use the experimental fixed-input TUI:
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
npx @lzhzzzzwill/cofos --tui
|
|
84
|
-
```
|
|
85
80
|
|
|
86
81
|
## Startup Behavior
|
|
87
82
|
|
|
@@ -133,7 +128,6 @@ cofos \
|
|
|
133
128
|
| `--rebuild-pdf-index` | Force PDF parsing and BM25 rebuild |
|
|
134
129
|
| `--top-k` | Retrieval top-k |
|
|
135
130
|
| `-p`, `--prompt` | Ask one question and exit |
|
|
136
|
-
| `--tui` | Use the experimental fixed-input full-screen TUI |
|
|
137
131
|
| `--max-new-tokens` | Override generation length |
|
|
138
132
|
| `--no-rag` | Run without KG/BM25 retrieval |
|
|
139
133
|
|
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 { createInterface
|
|
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";
|
|
@@ -121,23 +121,23 @@ function slashMatches(line) {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
function slashCompletions(line) {
|
|
124
|
-
if (!line.startsWith("/")) return [[], line];
|
|
124
|
+
if (!line.startsWith("/") || line.trim() === "/") return [[], line];
|
|
125
125
|
const hits = slashMatches(line).map(({ cmd }) => cmd);
|
|
126
126
|
return [hits, line];
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
function slashHint(line = "/") {
|
|
130
|
-
const hits = slashMatches(line);
|
|
131
|
-
const rows =
|
|
130
|
+
const hits = line.trim() === "/" ? SLASH_COMMANDS : slashMatches(line);
|
|
131
|
+
const rows = hits
|
|
132
132
|
.map(({ cmd, desc }) => ` ${cmd.padEnd(10)} ${desc}`)
|
|
133
133
|
.join("\n");
|
|
134
134
|
return `\n Commands\n${rows}\n Press Tab to complete.\n`;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
function
|
|
137
|
+
function slashPrefixHint(line) {
|
|
138
138
|
const hits = slashMatches(line);
|
|
139
|
-
if (!hits.length) return
|
|
140
|
-
return hits.map(({ cmd, desc }) => ` ${
|
|
139
|
+
if (!hits.length) return `No command matches ${line}. Run /help for commands.`;
|
|
140
|
+
return hits.map(({ cmd, desc }) => ` ${cmd.padEnd(10)} ${desc}`).join("\n");
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
/* ─── helpers ─── */
|
|
@@ -158,207 +158,7 @@ function ensureDeps(py, log = console.log) {
|
|
|
158
158
|
if (r.status !== 0) { log("[cofos] Failed to install Python dependencies."); process.exit(1); }
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
async function createTui() {
|
|
164
|
-
const { default: blessed } = await import("blessed");
|
|
165
|
-
const screen = blessed.screen({
|
|
166
|
-
smartCSR: true,
|
|
167
|
-
fullUnicode: true,
|
|
168
|
-
title: "COFOS",
|
|
169
|
-
mouse: false,
|
|
170
|
-
});
|
|
171
|
-
screen.program.disableMouse();
|
|
172
|
-
|
|
173
|
-
const history = blessed.box({
|
|
174
|
-
top: 0,
|
|
175
|
-
left: 0,
|
|
176
|
-
width: "100%",
|
|
177
|
-
height: "100%-3",
|
|
178
|
-
scrollable: true,
|
|
179
|
-
alwaysScroll: true,
|
|
180
|
-
keys: true,
|
|
181
|
-
vi: true,
|
|
182
|
-
mouse: false,
|
|
183
|
-
tags: true,
|
|
184
|
-
scrollbar: { ch: " ", track: { bg: "black" }, style: { bg: "cyan" } },
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
const suggestions = blessed.box({
|
|
188
|
-
bottom: 3,
|
|
189
|
-
left: 2,
|
|
190
|
-
width: "70%",
|
|
191
|
-
height: 6,
|
|
192
|
-
hidden: true,
|
|
193
|
-
border: { type: "line" },
|
|
194
|
-
tags: true,
|
|
195
|
-
style: { border: { fg: "yellow" }, fg: "white", bg: "black" },
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
const input = blessed.textbox({
|
|
199
|
-
bottom: 0,
|
|
200
|
-
left: 0,
|
|
201
|
-
width: "100%",
|
|
202
|
-
height: 3,
|
|
203
|
-
inputOnFocus: true,
|
|
204
|
-
keys: true,
|
|
205
|
-
mouse: false,
|
|
206
|
-
border: { type: "line" },
|
|
207
|
-
label: " cofos ",
|
|
208
|
-
style: {
|
|
209
|
-
border: { fg: "cyan" },
|
|
210
|
-
focus: { border: { fg: "green" } },
|
|
211
|
-
},
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
screen.append(history);
|
|
215
|
-
screen.append(suggestions);
|
|
216
|
-
screen.append(input);
|
|
217
|
-
|
|
218
|
-
let buffer = "";
|
|
219
|
-
let onSubmit = () => {};
|
|
220
|
-
let onExit = () => {};
|
|
221
|
-
let submitting = false;
|
|
222
|
-
let suggestionMatches = [];
|
|
223
|
-
let selectedSuggestion = 0;
|
|
224
|
-
|
|
225
|
-
function renderBuffer(wasAtBottom = true) {
|
|
226
|
-
history.setContent(buffer);
|
|
227
|
-
if (wasAtBottom) history.setScrollPerc(100);
|
|
228
|
-
screen.render();
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function append(text = "") {
|
|
232
|
-
const wasAtBottom = history.getScrollPerc() >= 98;
|
|
233
|
-
const data = String(text).replace(/\x1b\[[0-9;]*[A-Za-z]/g, "");
|
|
234
|
-
for (const part of data.split("\r")) {
|
|
235
|
-
if (part === "") continue;
|
|
236
|
-
if (data.includes("\r")) {
|
|
237
|
-
const lastNewline = buffer.lastIndexOf("\n");
|
|
238
|
-
buffer = (lastNewline === -1 ? "" : buffer.slice(0, lastNewline + 1)) + part;
|
|
239
|
-
} else {
|
|
240
|
-
buffer += part;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
if (buffer.length > 250000) buffer = buffer.slice(-220000);
|
|
244
|
-
renderBuffer(wasAtBottom);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function hideSuggestions() {
|
|
248
|
-
suggestionMatches = [];
|
|
249
|
-
selectedSuggestion = 0;
|
|
250
|
-
if (!suggestions.hidden) {
|
|
251
|
-
suggestions.hide();
|
|
252
|
-
screen.render();
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function renderSuggestions() {
|
|
257
|
-
if (!suggestionMatches.length) {
|
|
258
|
-
hideSuggestions();
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
selectedSuggestion = Math.max(0, Math.min(selectedSuggestion, suggestionMatches.length - 1));
|
|
262
|
-
suggestions.setContent(suggestionMatches.map(({ cmd, desc }, i) => {
|
|
263
|
-
const marker = i === selectedSuggestion ? "{black-fg}{yellow-bg}›" : " ";
|
|
264
|
-
const close = i === selectedSuggestion ? "{/yellow-bg}{/black-fg}" : "";
|
|
265
|
-
return `${marker} {cyan-fg}${cmd}{/cyan-fg} ${desc}${close}`;
|
|
266
|
-
}).join("\n"));
|
|
267
|
-
suggestions.height = Math.min(8, suggestionMatches.length + 2);
|
|
268
|
-
suggestions.show();
|
|
269
|
-
screen.render();
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function updateSuggestions() {
|
|
273
|
-
const value = input.getValue() || "";
|
|
274
|
-
if (!value.startsWith("/")) {
|
|
275
|
-
hideSuggestions();
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
suggestionMatches = slashMatches(value);
|
|
279
|
-
selectedSuggestion = 0;
|
|
280
|
-
renderSuggestions();
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function selectedCommand() {
|
|
284
|
-
return suggestionMatches[selectedSuggestion]?.cmd || "";
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function submitInput() {
|
|
288
|
-
if (submitting) return;
|
|
289
|
-
submitting = true;
|
|
290
|
-
let value = input.getValue();
|
|
291
|
-
if (!suggestions.hidden && selectedCommand()) value = selectedCommand();
|
|
292
|
-
hideSuggestions();
|
|
293
|
-
input.clearValue();
|
|
294
|
-
input.focus();
|
|
295
|
-
screen.render();
|
|
296
|
-
onSubmit(String(value || ""));
|
|
297
|
-
setTimeout(() => { submitting = false; }, 0);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
input.key(["enter"], submitInput);
|
|
301
|
-
screen.key(["enter"], submitInput);
|
|
302
|
-
|
|
303
|
-
input.on("keypress", (_ch, key = {}) => {
|
|
304
|
-
if (["up", "down", "tab", "enter", "escape"].includes(key.name)) return;
|
|
305
|
-
setTimeout(updateSuggestions, 0);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
screen.key(["tab"], () => {
|
|
309
|
-
const value = input.getValue() || "";
|
|
310
|
-
if (!value.startsWith("/")) return;
|
|
311
|
-
if (!suggestionMatches.length) suggestionMatches = slashMatches(value);
|
|
312
|
-
if (!suggestionMatches.length) return;
|
|
313
|
-
input.setValue(selectedCommand());
|
|
314
|
-
hideSuggestions();
|
|
315
|
-
input.focus();
|
|
316
|
-
screen.render();
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
screen.key(["up"], () => {
|
|
320
|
-
if (suggestions.hidden || !suggestionMatches.length) return;
|
|
321
|
-
selectedSuggestion = (selectedSuggestion - 1 + suggestionMatches.length) % suggestionMatches.length;
|
|
322
|
-
renderSuggestions();
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
screen.key(["down"], () => {
|
|
326
|
-
if (suggestions.hidden || !suggestionMatches.length) return;
|
|
327
|
-
selectedSuggestion = (selectedSuggestion + 1) % suggestionMatches.length;
|
|
328
|
-
renderSuggestions();
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
screen.key(["escape"], hideSuggestions);
|
|
332
|
-
screen.key(["pageup"], () => { history.scroll(-Math.max(3, Math.floor(screen.height * 0.8))); screen.render(); });
|
|
333
|
-
screen.key(["pagedown"], () => { history.scroll(Math.max(3, Math.floor(screen.height * 0.8))); screen.render(); });
|
|
334
|
-
screen.key(["home"], () => { history.setScrollPerc(0); screen.render(); });
|
|
335
|
-
screen.key(["end"], () => { history.setScrollPerc(100); screen.render(); });
|
|
336
|
-
screen.key(["C-c"], () => onExit());
|
|
337
|
-
|
|
338
|
-
input.focus();
|
|
339
|
-
screen.render();
|
|
340
|
-
|
|
341
|
-
return {
|
|
342
|
-
supportsReplace: true,
|
|
343
|
-
screen,
|
|
344
|
-
input,
|
|
345
|
-
append,
|
|
346
|
-
print: (text = "") => append(String(text) + "\n"),
|
|
347
|
-
clear: () => { buffer = ""; renderBuffer(true); },
|
|
348
|
-
length: () => buffer.length,
|
|
349
|
-
replaceFrom: (index, text = "") => { buffer = buffer.slice(0, Math.max(0, index)) + String(text); renderBuffer(true); },
|
|
350
|
-
setSubmitHandler: (fn) => { onSubmit = fn; },
|
|
351
|
-
setExitHandler: (fn) => { onExit = fn; },
|
|
352
|
-
focus: () => { input.focus(); screen.render(); },
|
|
353
|
-
setPrompt: (label) => { input.setLabel(` ${label} `); screen.render(); },
|
|
354
|
-
destroy: () => {
|
|
355
|
-
try { screen.program.disableMouse(); } catch (_) {}
|
|
356
|
-
screen.destroy();
|
|
357
|
-
},
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function blessedToAnsi(text) {
|
|
161
|
+
function markupToAnsi(text) {
|
|
362
162
|
return String(text)
|
|
363
163
|
.replace(/\{bold\}/g, B)
|
|
364
164
|
.replace(/\{\/bold\}/g, R)
|
|
@@ -382,7 +182,6 @@ function createPlainUi() {
|
|
|
382
182
|
let onSubmit = () => {};
|
|
383
183
|
let onExit = () => {};
|
|
384
184
|
let prompt = "cofos";
|
|
385
|
-
let slashHintShown = false;
|
|
386
185
|
|
|
387
186
|
function setPrompt(label) {
|
|
388
187
|
prompt = label;
|
|
@@ -390,23 +189,7 @@ function createPlainUi() {
|
|
|
390
189
|
}
|
|
391
190
|
|
|
392
191
|
setPrompt(prompt);
|
|
393
|
-
if (process.stdin.isTTY) {
|
|
394
|
-
emitKeypressEvents(process.stdin, rl);
|
|
395
|
-
process.stdin.on("keypress", () => {
|
|
396
|
-
setTimeout(() => {
|
|
397
|
-
const line = rl.line || "";
|
|
398
|
-
if (line === "/" && !slashHintShown) {
|
|
399
|
-
slashHintShown = true;
|
|
400
|
-
process.stdout.write(`\n${slashInlineHint("/")}\n`);
|
|
401
|
-
rl.prompt(true);
|
|
402
|
-
} else if (!line.startsWith("/")) {
|
|
403
|
-
slashHintShown = false;
|
|
404
|
-
}
|
|
405
|
-
}, 0);
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
192
|
rl.on("line", (line) => {
|
|
409
|
-
slashHintShown = false;
|
|
410
193
|
onSubmit(line);
|
|
411
194
|
});
|
|
412
195
|
rl.on("SIGINT", () => onExit());
|
|
@@ -414,11 +197,12 @@ function createPlainUi() {
|
|
|
414
197
|
|
|
415
198
|
return {
|
|
416
199
|
supportsReplace: false,
|
|
417
|
-
|
|
418
|
-
|
|
200
|
+
echoUser: false,
|
|
201
|
+
append: (text = "") => process.stdout.write(markupToAnsi(text)),
|
|
202
|
+
print: (text = "") => console.log(markupToAnsi(text)),
|
|
419
203
|
clear: () => console.clear(),
|
|
420
204
|
length: () => 0,
|
|
421
|
-
replaceFrom: (_index, text = "") => process.stdout.write(
|
|
205
|
+
replaceFrom: (_index, text = "") => process.stdout.write(markupToAnsi(text)),
|
|
422
206
|
setSubmitHandler: (fn) => { onSubmit = fn; },
|
|
423
207
|
setExitHandler: (fn) => { onExit = fn; },
|
|
424
208
|
focus: () => rl.prompt(),
|
|
@@ -432,7 +216,7 @@ function createPlainUi() {
|
|
|
432
216
|
async function main() {
|
|
433
217
|
if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
434
218
|
console.log("\n COFOS CLI — 9B + RAG chat (Willlzh/COFOS)\n");
|
|
435
|
-
console.log(" Usage: cofos [
|
|
219
|
+
console.log(" Usage: cofos [-p QUESTION] [--model PATH] [--config PATH] [--pdf-dir DIR] [--cache-dir DIR] [--top-k N] [--no-rag]\n");
|
|
436
220
|
console.log(" Commands in chat:");
|
|
437
221
|
console.log(" /help Show this help");
|
|
438
222
|
console.log(" /exit Quit");
|
|
@@ -445,7 +229,6 @@ async function main() {
|
|
|
445
229
|
console.log(" --pdf-dir DIR Add PDFs from DIR as extra BM25 evidence");
|
|
446
230
|
console.log(" --cache-dir DIR Store model/data cache here (default: ./.cofos)");
|
|
447
231
|
console.log(" --max-new-tokens N Override generation length");
|
|
448
|
-
console.log(" --tui Use the experimental fixed-input TUI");
|
|
449
232
|
console.log(" -p, --prompt TEXT Ask once and exit");
|
|
450
233
|
console.log(" --rebuild-pdf-index Force reparsing PDFs and rebuilding BM25\n");
|
|
451
234
|
console.log(" Multi-line: end a line with \\ to continue on the next line.\n");
|
|
@@ -462,7 +245,6 @@ async function main() {
|
|
|
462
245
|
let maxNewTokens = null;
|
|
463
246
|
let cacheDir = process.env.COFOS_CACHE_DIR ? resolve(process.env.COFOS_CACHE_DIR) : resolve(process.cwd(), ".cofos");
|
|
464
247
|
let rebuildPdfIndex = false;
|
|
465
|
-
let useTui = false;
|
|
466
248
|
let singlePrompt = null;
|
|
467
249
|
const backendArgs = [chatScript];
|
|
468
250
|
|
|
@@ -494,8 +276,6 @@ async function main() {
|
|
|
494
276
|
cacheDir = resolve(value(++i, arg));
|
|
495
277
|
} else if (arg === "--rebuild-pdf-index") {
|
|
496
278
|
rebuildPdfIndex = true;
|
|
497
|
-
} else if (arg === "--tui") {
|
|
498
|
-
useTui = true;
|
|
499
279
|
} else if (arg === "-p" || arg === "--prompt") {
|
|
500
280
|
singlePrompt = value(++i, arg);
|
|
501
281
|
} else if (arg === "--no-rag") {
|
|
@@ -513,8 +293,8 @@ async function main() {
|
|
|
513
293
|
if (!py) die("Python not found. Install Python 3.9+ and re-run.");
|
|
514
294
|
if (!existsSync(chatScript)) die(`Missing script: ${chatScript}`);
|
|
515
295
|
|
|
516
|
-
const ui =
|
|
517
|
-
ui.print(
|
|
296
|
+
const ui = createPlainUi();
|
|
297
|
+
ui.print(render({ modelPath, cacheDir, pdfDir }));
|
|
518
298
|
ensureDeps(py, (msg) => ui.print(msg));
|
|
519
299
|
ui.print(" Loading COFOS 9B + RAG backend from Hugging Face … first run may take a while");
|
|
520
300
|
if (pdfDir) ui.print(" Local PDF RAG: enabled for this session");
|
|
@@ -584,7 +364,7 @@ async function main() {
|
|
|
584
364
|
if (!input) return false;
|
|
585
365
|
|
|
586
366
|
if (input === "/") {
|
|
587
|
-
ui.print(
|
|
367
|
+
ui.print("Commands: " + SLASH_COMMANDS.map(({ cmd }) => cmd).join(" ") + "\nType a prefix such as /r or /to, then press Tab for completion.");
|
|
588
368
|
return false;
|
|
589
369
|
}
|
|
590
370
|
if (/^\/(exit|quit)$/.test(input)) { shutdown(); return true; }
|
|
@@ -629,11 +409,11 @@ async function main() {
|
|
|
629
409
|
return false;
|
|
630
410
|
}
|
|
631
411
|
if (input.startsWith("/")) {
|
|
632
|
-
ui.print(`{red-fg}•{/red-fg} Unknown command: ${input}
|
|
412
|
+
ui.print(`{red-fg}•{/red-fg} Unknown command: ${input}.\n${slashPrefixHint(input)}`);
|
|
633
413
|
return false;
|
|
634
414
|
}
|
|
635
415
|
|
|
636
|
-
ui.print("{yellow-fg}●{/yellow-fg} " + input);
|
|
416
|
+
if (ui.echoUser) ui.print("{yellow-fg}●{/yellow-fg} " + input);
|
|
637
417
|
responseOpen = false;
|
|
638
418
|
assistantStart = -1;
|
|
639
419
|
currentAnswer = "";
|
|
@@ -674,7 +454,7 @@ async function main() {
|
|
|
674
454
|
}
|
|
675
455
|
|
|
676
456
|
const paragraphs = raw.split(/\n\s*\n/);
|
|
677
|
-
const reasonRe = /^(The user
|
|
457
|
+
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;
|
|
678
458
|
let reasonCount = 0;
|
|
679
459
|
while (reasonCount < paragraphs.length && reasonRe.test(paragraphs[reasonCount].trim())) reasonCount += 1;
|
|
680
460
|
if (reasonCount > 0) {
|
|
@@ -687,7 +467,7 @@ async function main() {
|
|
|
687
467
|
return "{green-fg}{bold}◆{/bold}{/green-fg} " + raw;
|
|
688
468
|
}
|
|
689
469
|
|
|
690
|
-
const reasonRe = /^(The user
|
|
470
|
+
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;
|
|
691
471
|
|
|
692
472
|
function redrawAssistant() {
|
|
693
473
|
if (assistantStart < 0 || !ui.supportsReplace) return;
|
|
@@ -696,65 +476,16 @@ async function main() {
|
|
|
696
476
|
|
|
697
477
|
function streamPlainAssistant(text, final = false) {
|
|
698
478
|
if (ui.supportsReplace) return;
|
|
699
|
-
plainAssistantPending += text;
|
|
700
479
|
if (plainAssistantMode === "undecided") {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
plainAssistantMode = "reason";
|
|
705
|
-
plainReasonBuffer = start.replace(/^<think>\s*/i, "");
|
|
706
|
-
plainReasonPrinted = 0;
|
|
707
|
-
plainAssistantPending = "";
|
|
708
|
-
ui.append(`${G}-${R} ${G}`);
|
|
709
|
-
} else if (final || start.length >= 12 || /\s/.test(start)) {
|
|
710
|
-
plainAssistantMode = "answer";
|
|
711
|
-
plainAssistantPending = start;
|
|
712
|
-
ui.append(`${GR}${B}◆${R} `);
|
|
713
|
-
} else {
|
|
714
|
-
return;
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
if (plainAssistantMode === "reason") {
|
|
719
|
-
plainReasonBuffer += plainAssistantPending;
|
|
720
|
-
plainAssistantPending = "";
|
|
721
|
-
const thinkEnd = plainReasonBuffer.indexOf("</think>");
|
|
722
|
-
const paraEnd = plainReasonBuffer.search(/\n\s*\n/);
|
|
723
|
-
const split = thinkEnd !== -1 ? thinkEnd : paraEnd;
|
|
724
|
-
if (split !== -1) {
|
|
725
|
-
const reason = plainReasonBuffer.slice(plainReasonPrinted, split).replace(/<\/think>/gi, "");
|
|
726
|
-
const restStart = thinkEnd !== -1 ? split + "</think>".length : split;
|
|
727
|
-
const rest = plainReasonBuffer.slice(restStart).trimStart();
|
|
728
|
-
if (reason) ui.append(reason);
|
|
729
|
-
ui.append(`${R}\n${GR}${B}◆${R} `);
|
|
730
|
-
plainAssistantMode = "answer";
|
|
731
|
-
plainAssistantPending = rest;
|
|
732
|
-
plainReasonBuffer = "";
|
|
733
|
-
plainReasonPrinted = 0;
|
|
734
|
-
} else {
|
|
735
|
-
const printableUntil = final ? plainReasonBuffer.length : Math.max(0, plainReasonBuffer.length - 2);
|
|
736
|
-
if (printableUntil > plainReasonPrinted) {
|
|
737
|
-
ui.append(plainReasonBuffer.slice(plainReasonPrinted, printableUntil));
|
|
738
|
-
plainReasonPrinted = printableUntil;
|
|
739
|
-
}
|
|
740
|
-
if (!final) return;
|
|
741
|
-
if (plainReasonPrinted < plainReasonBuffer.length) ui.append(plainReasonBuffer.slice(plainReasonPrinted));
|
|
742
|
-
plainReasonBuffer = "";
|
|
743
|
-
plainReasonPrinted = 0;
|
|
744
|
-
ui.append(R);
|
|
745
|
-
return;
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
if (plainAssistantMode === "answer") {
|
|
750
|
-
if (plainAssistantPending) {
|
|
751
|
-
ui.append(plainAssistantPending);
|
|
752
|
-
plainAssistantPending = "";
|
|
753
|
-
}
|
|
480
|
+
if (!text && !final) return;
|
|
481
|
+
plainAssistantMode = "answer";
|
|
482
|
+
ui.append(`${GR}${B}◆${R} `);
|
|
754
483
|
}
|
|
484
|
+
if (text) ui.append(text);
|
|
755
485
|
}
|
|
756
486
|
|
|
757
487
|
|
|
488
|
+
|
|
758
489
|
proc.stdout.on("data", (chunk) => {
|
|
759
490
|
buf += chunk;
|
|
760
491
|
if (!ready) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lzhzzzzwill/cofos",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.4",
|
|
4
4
|
"description": "COFOS 9B + RAG chat CLI with optional local PDF ingestion.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -38,8 +38,5 @@
|
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"engines": {
|
|
40
40
|
"node": ">=18"
|
|
41
|
-
},
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"blessed": "^0.1.81"
|
|
44
41
|
}
|
|
45
42
|
}
|
|
@@ -538,7 +538,23 @@ class StudentAdapterInference:
|
|
|
538
538
|
)
|
|
539
539
|
if not stripped.strip():
|
|
540
540
|
continue
|
|
541
|
-
|
|
541
|
+
stripped = stripped.lstrip()
|
|
542
|
+
if awaiting_initial_answer and re.match(
|
|
543
|
+
r"(?is)^(?:the user|okay[,,]?\s+the user|用户问|用户用|i need|i should|i'll|let me|since the user|we need)\b",
|
|
544
|
+
stripped,
|
|
545
|
+
):
|
|
546
|
+
paragraph_end = re.search(r"\n\s*\n", stripped)
|
|
547
|
+
if paragraph_end is None:
|
|
548
|
+
# Keep waiting for the end of the leading reasoning paragraph.
|
|
549
|
+
buffer = stripped[-4000:]
|
|
550
|
+
continue
|
|
551
|
+
buffer = stripped[paragraph_end.end():].lstrip()
|
|
552
|
+
awaiting_initial_answer = False
|
|
553
|
+
if not buffer.strip():
|
|
554
|
+
continue
|
|
555
|
+
else:
|
|
556
|
+
buffer = stripped
|
|
557
|
+
awaiting_initial_answer = False
|
|
542
558
|
started = True
|
|
543
559
|
|
|
544
560
|
if len(buffer) > len("<think>"):
|