@lzhzzzzwill/cofos 1.1.13 → 1.1.15

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.
Files changed (2) hide show
  1. package/bin/cofos.js +46 -41
  2. package/package.json +1 -1
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, emitKeypressEvents } from "node:readline";
3
+ import { clearLine, createInterface, cursorTo } 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";
@@ -45,37 +45,43 @@ function row(left, right, lw, rw) { return "│ " + cell(left, lw) + " │ " + c
45
45
  function full(t, w) { return "│ " + cell(t, w - 2) + " │"; }
46
46
  function empty(w) { return full("", w); }
47
47
 
48
- function render() {
48
+ function logoLines(width) {
49
+ if (width >= ART_W) return ART.map((line) => C + B + line + R);
50
+ return [C + B + "COFOS" + R];
51
+ }
52
+
53
+ function render({ modelPath, cacheDir, pdfDir }) {
49
54
  console.clear();
50
55
  const w = fw();
51
56
  const minSplit = 96;
52
57
  const cwd = process.cwd();
53
- const rag = "RAG: KG + BM25 + optional PDFs";
54
- const leftTitle = C + B + "COFOS" + R;
58
+ const rag = pdfDir ? "RAG: KG + BM25 + local PDFs" : "RAG: KG + BM25";
55
59
  const left = [
56
- leftTitle,
57
- G + "9B scientific RAG CLI" + R,
60
+ ...logoLines(Math.floor((w - 5) * 0.48)),
58
61
  "",
59
- W + "Model" + R + " Willlzh/COFOS",
62
+ G + "Scientific RAG CLI" + R,
63
+ W + "Model" + R + " " + modelPath,
60
64
  W + "Params" + R + " 9B",
61
- W + "Path" + R + " " + cwd,
65
+ W + "Path" + R + " " + cwd,
66
+ W + "Cache" + R + " " + cacheDir,
62
67
  ];
63
68
  const right = [
64
69
  W + B + "What COFOS Does" + R,
65
- "Answers COF / photocatalysis / ROS questions with a local 9B model.",
70
+ "COFOS answers scientific questions about covalent organic frameworks,",
71
+ "photocatalysis, reactive oxygen species, and oxygen-reduction products.",
66
72
  rag,
67
73
  "",
68
74
  W + B + "Suggested Commands" + R,
69
- "/help show commands and PDF-RAG usage",
70
- "/topk 5 change retrieval depth",
71
- "/rag off answer without retrieval",
72
- "cofos --pdf-dir ./new_pdfs add local papers",
75
+ "/help show commands and PDF-RAG usage",
76
+ "/topk 5 change retrieval depth",
77
+ "/rag off answer without retrieval",
78
+ "cofos --pdf-dir ./new_pdfs add local papers",
73
79
  ];
74
80
 
75
81
  const lines = [top(w)];
76
82
  if (w >= minSplit) {
77
83
  const inner = w - 5;
78
- const lw = Math.max(28, Math.floor(inner * 0.34));
84
+ const lw = Math.max(46, Math.floor(inner * 0.48));
79
85
  const rw = inner - lw;
80
86
  const n = Math.max(left.length, right.length);
81
87
  for (let i = 0; i < n; i++) lines.push(row(left[i] || "", right[i] || "", lw, rw));
@@ -84,8 +90,6 @@ function render() {
84
90
  lines.push(sep(w));
85
91
  for (const item of right) lines.push(full(item, w));
86
92
  }
87
- lines.push(empty(w));
88
- lines.push(full(G + "Type / for commands · /exit · /topk 5 · /rag off · \\ for multiline" + R, w));
89
93
  lines.push(bot(w));
90
94
  console.log(lines.join("\n"));
91
95
  }
@@ -110,7 +114,7 @@ function slashMatches(line) {
110
114
  function slashCompletions(line) {
111
115
  if (!line.startsWith("/")) return [[], line];
112
116
  const hits = slashMatches(line).map(({ cmd }) => cmd);
113
- return [hits.length ? hits : SLASH_COMMANDS.map(({ cmd }) => cmd), line];
117
+ return [hits, line];
114
118
  }
115
119
 
116
120
  function slashHint(line = "/") {
@@ -205,7 +209,7 @@ function main() {
205
209
  if (pdfDir) backendArgs.push("--pdf-dir", pdfDir);
206
210
  if (rebuildPdfIndex) backendArgs.push("--rebuild-pdf-index");
207
211
 
208
- render();
212
+ render({ modelPath, cacheDir, pdfDir });
209
213
 
210
214
  const py = findPy();
211
215
  if (!py) die("Python not found. Install Python 3.9+ and re-run.");
@@ -250,32 +254,33 @@ function main() {
250
254
  prompt: mainPrompt,
251
255
  completer: slashCompletions,
252
256
  });
253
- let lastSlashHint = "";
254
257
  rl.on("line", (l) => {
255
- lastSlashHint = "";
256
258
  if (!ready || generating) inputQ.push(l);
257
259
  else if (!handle(l)) ask();
258
260
  });
259
- if (process.stdin.isTTY) {
260
- emitKeypressEvents(process.stdin, rl);
261
- process.stdin.on("keypress", () => {
262
- if (!ready || generating || closed) return;
263
- const line = rl.line || "";
264
- if (!line.startsWith("/")) {
265
- lastSlashHint = "";
266
- return;
267
- }
268
- const key = line.trim();
269
- if (key && key !== lastSlashHint) {
270
- lastSlashHint = key;
271
- rl.output.write(slashHint(line));
272
- rl.prompt(true);
273
- }
274
- });
261
+
262
+ function preserveInputWrite(stream, chunk) {
263
+ const data = String(chunk);
264
+ if (!rl.terminal || closed) {
265
+ stream.write(data);
266
+ return;
267
+ }
268
+ const activeInput = !generating;
269
+ if (activeInput) {
270
+ clearLine(rl.output, 0);
271
+ cursorTo(rl.output, 0);
272
+ }
273
+ stream.write(data);
274
+ if (activeInput) rl.prompt(true);
275
275
  }
276
276
 
277
- // Forward Python stderr (status / progress messages) to our stderr.
278
- proc.stderr.on("data", (chunk) => { process.stderr.write(chunk); });
277
+ // Let users type while the Python backend is still loading. Submitted lines
278
+ // are queued until READY, while status logs are written above and the input
279
+ // line is restored afterward.
280
+ ask();
281
+
282
+ // Forward Python stderr (status / progress messages) without destroying the input line.
283
+ proc.stderr.on("data", (chunk) => { preserveInputWrite(process.stderr, chunk); });
279
284
 
280
285
  proc.stdout.on("data", (chunk) => {
281
286
  buf += chunk;
@@ -310,19 +315,19 @@ function main() {
310
315
  const di = buf.indexOf(DONE);
311
316
  if (di !== -1) {
312
317
  const pre = buf.slice(0, di);
313
- if (pre) process.stdout.write(pre);
318
+ if (pre) preserveInputWrite(process.stdout, pre);
314
319
  buf = buf.slice(di + DONE.length);
315
320
  if (buf[0] === "\n") buf = buf.slice(1);
316
321
  generating = false;
317
322
  responseOpen = false;
318
- console.log("");
323
+ preserveInputWrite(process.stdout, "\n");
319
324
  drain();
320
325
  } else {
321
326
  // Stream buffered content, keeping enough tail to catch a partial DONE marker.
322
327
  const keep = DONE.length - 1;
323
328
  if (buf.length > keep) {
324
329
  const out = buf.slice(0, -keep);
325
- if (out) process.stdout.write(out);
330
+ if (out) preserveInputWrite(process.stdout, out);
326
331
  buf = buf.slice(-keep);
327
332
  }
328
333
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lzhzzzzwill/cofos",
3
- "version": "1.1.13",
3
+ "version": "1.1.15",
4
4
  "description": "COFOS 9B + RAG chat CLI with optional local PDF ingestion.",
5
5
  "type": "module",
6
6
  "bin": {