tokwise 0.1.2 → 0.1.3
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/dist/ask.js +49 -3
- package/dist/cli.js +25 -7
- package/dist/progress.js +69 -0
- package/package.json +1 -1
package/dist/ask.js
CHANGED
|
@@ -49,10 +49,56 @@ async function answerWithOllama(question, results, options) {
|
|
|
49
49
|
const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/generate`, {
|
|
50
50
|
method: "POST",
|
|
51
51
|
headers: { "content-type": "application/json" },
|
|
52
|
-
body: JSON.stringify({ model, prompt, stream:
|
|
52
|
+
body: JSON.stringify({ model, prompt, stream: true }),
|
|
53
53
|
});
|
|
54
54
|
if (!response.ok)
|
|
55
55
|
throw new Error(`Ollama ask failed: ${response.status} ${response.statusText}`);
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
if (!response.body)
|
|
57
|
+
throw new Error("Ollama ask failed: empty response body");
|
|
58
|
+
let full = "";
|
|
59
|
+
let firstToken = false;
|
|
60
|
+
for await (const chunk of iterateNdjson(response.body)) {
|
|
61
|
+
const piece = chunk.response ?? "";
|
|
62
|
+
if (!piece) {
|
|
63
|
+
if (chunk.done)
|
|
64
|
+
break;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (!firstToken) {
|
|
68
|
+
firstToken = true;
|
|
69
|
+
options.onFirstToken?.();
|
|
70
|
+
}
|
|
71
|
+
full += piece;
|
|
72
|
+
options.onToken?.(piece);
|
|
73
|
+
if (chunk.done)
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
return full.trim() || "Ollama returned an empty answer.";
|
|
77
|
+
}
|
|
78
|
+
async function* iterateNdjson(body) {
|
|
79
|
+
const reader = body.getReader();
|
|
80
|
+
const decoder = new TextDecoder();
|
|
81
|
+
let buffer = "";
|
|
82
|
+
try {
|
|
83
|
+
for (;;) {
|
|
84
|
+
const { done, value } = await reader.read();
|
|
85
|
+
if (done)
|
|
86
|
+
break;
|
|
87
|
+
buffer += decoder.decode(value, { stream: true });
|
|
88
|
+
const lines = buffer.split("\n");
|
|
89
|
+
buffer = lines.pop() ?? "";
|
|
90
|
+
for (const line of lines) {
|
|
91
|
+
const trimmed = line.trim();
|
|
92
|
+
if (!trimmed)
|
|
93
|
+
continue;
|
|
94
|
+
yield JSON.parse(trimmed);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const trailing = buffer.trim();
|
|
98
|
+
if (trailing)
|
|
99
|
+
yield JSON.parse(trailing);
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
reader.releaseLock();
|
|
103
|
+
}
|
|
58
104
|
}
|
package/dist/cli.js
CHANGED
|
@@ -22,7 +22,7 @@ import { createCommand, createLibraryPage, deleteLibraryPage, listCommands, sear
|
|
|
22
22
|
import { installSkill, skillContent, uninstallSkill } from "./skill.js";
|
|
23
23
|
import { barChart, box, c, kvList, setColorEnabled, table, truncate } from "./render.js";
|
|
24
24
|
import { formatReference } from "./reference.js";
|
|
25
|
-
import { createProgress } from "./progress.js";
|
|
25
|
+
import { createProgress, createSpinner } from "./progress.js";
|
|
26
26
|
const require = createRequire(import.meta.url);
|
|
27
27
|
function version() {
|
|
28
28
|
try {
|
|
@@ -482,12 +482,30 @@ export function buildCli() {
|
|
|
482
482
|
const prefs = await loadPreferences();
|
|
483
483
|
const { videos, index } = await requireIndex();
|
|
484
484
|
const results = searchWithIndex(videos, index, { query: question, limit: Number(options.limit) });
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
485
|
+
const engine = options.engine ?? prefs.askEngine ?? "extractive";
|
|
486
|
+
const spinner = engine === "ollama" ? createSpinner() : undefined;
|
|
487
|
+
spinner?.start();
|
|
488
|
+
let streamed = false;
|
|
489
|
+
let answer;
|
|
490
|
+
try {
|
|
491
|
+
answer = await answerQuestion(question, results, {
|
|
492
|
+
engine,
|
|
493
|
+
model: options.model ?? prefs.model,
|
|
494
|
+
ollamaBaseUrl: options.ollamaUrl ?? prefs.ollamaBaseUrl,
|
|
495
|
+
onFirstToken: () => spinner?.stop(),
|
|
496
|
+
onToken: (chunk) => {
|
|
497
|
+
streamed = true;
|
|
498
|
+
process.stdout.write(chunk);
|
|
499
|
+
},
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
finally {
|
|
503
|
+
spinner?.stop();
|
|
504
|
+
}
|
|
505
|
+
if (streamed)
|
|
506
|
+
process.stdout.write("\n");
|
|
507
|
+
else
|
|
508
|
+
console.log(answer);
|
|
491
509
|
if (options.save) {
|
|
492
510
|
const file = path.join(libraryDir(), "answers", `${Date.now()}-${slug(question)}.md`);
|
|
493
511
|
await fs.mkdir(path.dirname(file), { recursive: true });
|
package/dist/progress.js
CHANGED
|
@@ -54,3 +54,72 @@ export function createProgress(options) {
|
|
|
54
54
|
},
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
|
+
const DEFAULT_SPINNER_MESSAGES = [
|
|
58
|
+
"Thinking",
|
|
59
|
+
"Pondering",
|
|
60
|
+
"Rummaging through your bookmarks",
|
|
61
|
+
"Connecting the dots",
|
|
62
|
+
"Cogitating",
|
|
63
|
+
"Synthesizing",
|
|
64
|
+
"Consulting the archive",
|
|
65
|
+
"Musing",
|
|
66
|
+
"Distilling insights",
|
|
67
|
+
];
|
|
68
|
+
const MESSAGE_INTERVAL_MS = 2500;
|
|
69
|
+
export function createSpinner(options = {}) {
|
|
70
|
+
const messages = options.messages ?? DEFAULT_SPINNER_MESSAGES;
|
|
71
|
+
const messageIntervalMs = options.messageIntervalMs ?? MESSAGE_INTERVAL_MS;
|
|
72
|
+
const stream = process.stderr;
|
|
73
|
+
const tty = Boolean(stream.isTTY);
|
|
74
|
+
let running = false;
|
|
75
|
+
let frame = 0;
|
|
76
|
+
let messageIndex = 0;
|
|
77
|
+
let startedAt = 0;
|
|
78
|
+
let frameTimer;
|
|
79
|
+
let messageTimer;
|
|
80
|
+
function draw() {
|
|
81
|
+
const elapsedSec = Math.floor((Date.now() - startedAt) / 1000);
|
|
82
|
+
const spinner = c.accent(FRAMES[frame % FRAMES.length] ?? "");
|
|
83
|
+
const message = messages[messageIndex % messages.length] ?? "Thinking";
|
|
84
|
+
stream.write(`${CLEAR_LINE}${spinner} ${message}\u2026 ${c.muted(`(${elapsedSec}s)`)}`);
|
|
85
|
+
}
|
|
86
|
+
function clearTimers() {
|
|
87
|
+
if (frameTimer) {
|
|
88
|
+
clearInterval(frameTimer);
|
|
89
|
+
frameTimer = undefined;
|
|
90
|
+
}
|
|
91
|
+
if (messageTimer) {
|
|
92
|
+
clearInterval(messageTimer);
|
|
93
|
+
messageTimer = undefined;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
start() {
|
|
98
|
+
if (running || !tty)
|
|
99
|
+
return;
|
|
100
|
+
running = true;
|
|
101
|
+
startedAt = Date.now();
|
|
102
|
+
frame = 0;
|
|
103
|
+
messageIndex = 0;
|
|
104
|
+
draw();
|
|
105
|
+
frameTimer = setInterval(() => {
|
|
106
|
+
frame += 1;
|
|
107
|
+
draw();
|
|
108
|
+
}, FRAME_INTERVAL_MS);
|
|
109
|
+
frameTimer.unref();
|
|
110
|
+
messageTimer = setInterval(() => {
|
|
111
|
+
messageIndex += 1;
|
|
112
|
+
draw();
|
|
113
|
+
}, messageIntervalMs);
|
|
114
|
+
messageTimer.unref();
|
|
115
|
+
},
|
|
116
|
+
stop() {
|
|
117
|
+
if (!running)
|
|
118
|
+
return;
|
|
119
|
+
running = false;
|
|
120
|
+
clearTimers();
|
|
121
|
+
if (tty)
|
|
122
|
+
stream.write(CLEAR_LINE);
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
package/package.json
CHANGED