@tiens.nguyen/gonext-local-worker 1.0.216 → 1.0.219

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.
@@ -1229,11 +1229,44 @@ function normalizeCorrection(raw, originalText) {
1229
1229
  * correction/translation, e.g. base "https://ollama1.gomarsic.cc" model "qwen3:14b".
1230
1230
  * `think:false` suppresses reasoning models' <think> preamble so we get clean text.
1231
1231
  */
1232
+ const OCR_CORRECT_SYSTEM_PROMPT =
1233
+ "You are a text correction assistant. Fix grammar and language errors in the " +
1234
+ "provided text while preserving the original meaning and language. " +
1235
+ "Return only the corrected text without any explanation.";
1236
+
1237
+ /**
1238
+ * Call an OpenAI-compatible chat endpoint (POST {base}/v1/chat/completions) for text
1239
+ * correction — e.g. an MLX `mlx_lm.server` (the agent model server) at
1240
+ * http://127.0.0.1:8080/v1 serving qwen3:14b. Strips any <think> preamble.
1241
+ */
1242
+ async function correctOcrTextViaOpenAI(extractedText, base, model) {
1243
+ const b = base.replace(/\/+$/, "");
1244
+ const url = /\/v1$/.test(b) ? `${b}/chat/completions` : `${b}/v1/chat/completions`;
1245
+ const res = await fetch(url, {
1246
+ method: "POST",
1247
+ headers: { "Content-Type": "application/json" },
1248
+ body: JSON.stringify({
1249
+ model,
1250
+ stream: false,
1251
+ temperature: 0,
1252
+ messages: [
1253
+ { role: "system", content: OCR_CORRECT_SYSTEM_PROMPT },
1254
+ { role: "user", content: `Text:\n${extractedText}` },
1255
+ ],
1256
+ }),
1257
+ signal: AbortSignal.timeout(OCR_CORRECT_TIMEOUT_MS),
1258
+ });
1259
+ if (!res.ok) {
1260
+ throw new Error(`MLX ${b} returned ${res.status}`);
1261
+ }
1262
+ const json = await res.json();
1263
+ let corrected = json?.choices?.[0]?.message?.content?.trim() || "";
1264
+ corrected = corrected.replace(/<think>[\s\S]*?<\/think>/gi, "").trim();
1265
+ return normalizeCorrection(corrected, extractedText);
1266
+ }
1267
+
1232
1268
  async function correctOcrTextViaOllama(extractedText, base, model) {
1233
- const systemPrompt =
1234
- "You are a text correction assistant. Fix grammar and language errors in the " +
1235
- "provided text while preserving the original meaning and language. " +
1236
- "Return only the corrected text without any explanation.";
1269
+ const systemPrompt = OCR_CORRECT_SYSTEM_PROMPT;
1237
1270
  const res = await fetch(`${base.replace(/\/+$/, "")}/api/chat`, {
1238
1271
  method: "POST",
1239
1272
  headers: { "Content-Type": "application/json" },
@@ -1260,24 +1293,27 @@ async function correctOcrTextViaOllama(extractedText, base, model) {
1260
1293
  return normalizeCorrection(corrected, extractedText);
1261
1294
  }
1262
1295
 
1263
- async function correctOcrText(extractedText) {
1296
+ async function correctOcrText(extractedText, correction) {
1264
1297
  if (OCR_SKIP_CORRECTION) {
1265
1298
  console.log("[gonext-worker] OCR correction skipped (GONEXT_OCR_SKIP_CORRECTION=1)");
1266
1299
  return extractedText;
1267
1300
  }
1268
1301
 
1269
- // OCR translation/correction is hardcoded to a managed Ollama service + model
1270
- // (the user-configurable chooser was removed). Any error falls back to raw text.
1302
+ // The API resolves the backend from the user's settings and bakes {backend,url,model}
1303
+ // into the job payload DEFAULT is "mlx" (the agent MLX model server, OpenAI /v1).
1304
+ // Absent (older jobs / no settings) → fall back to the legacy managed-Ollama default.
1305
+ const backend = correction?.backend === "mlx" ? "mlx" : "ollama";
1306
+ const url = (correction?.url || "").trim() || OCR_TRANSLATE_OLLAMA_URL;
1307
+ const model = (correction?.model || "").trim() || OCR_TRANSLATE_OLLAMA_MODEL;
1271
1308
  console.log(
1272
- `[gonext-worker] OCR correction via Ollama ${OCR_TRANSLATE_OLLAMA_URL} model=${OCR_TRANSLATE_OLLAMA_MODEL}`
1309
+ `[gonext-worker] OCR correction via ${backend} ${url} model=${model}`
1273
1310
  );
1274
1311
  console.log(`[gonext-worker] OCR correction input: ${extractedText.slice(0, 300)}`);
1275
1312
  try {
1276
- const corrected = await correctOcrTextViaOllama(
1277
- extractedText,
1278
- OCR_TRANSLATE_OLLAMA_URL,
1279
- OCR_TRANSLATE_OLLAMA_MODEL
1280
- );
1313
+ const corrected =
1314
+ backend === "mlx"
1315
+ ? await correctOcrTextViaOpenAI(extractedText, url, model)
1316
+ : await correctOcrTextViaOllama(extractedText, url, model);
1281
1317
  console.log(`[gonext-worker] OCR correction output: ${corrected.slice(0, 300)}`);
1282
1318
  console.log(
1283
1319
  `[gonext-worker] OCR correction done: ${extractedText.length} → ${corrected.length} chars`
@@ -1286,7 +1322,7 @@ async function correctOcrText(extractedText) {
1286
1322
  } catch (e) {
1287
1323
  const msg = e instanceof Error ? e.message : String(e);
1288
1324
  console.warn(
1289
- `[gonext-worker] OCR correction Ollama failed (using raw OCR text): ${msg.slice(0, 200)}`
1325
+ `[gonext-worker] OCR correction (${backend}) failed (using raw OCR text): ${msg.slice(0, 200)}`
1290
1326
  );
1291
1327
  return extractedText;
1292
1328
  }
@@ -1434,10 +1470,10 @@ async function runOcrJob(job) {
1434
1470
  } finally {
1435
1471
  await rm(tempDir, { recursive: true, force: true }).catch(() => {});
1436
1472
  }
1437
- // Post-process: correct grammar/language via the hardcoded managed Ollama
1438
- // service (qwen3:14b). Falls back to raw OCR text on any error so the job
1439
- // never fails here. (`payload.correctionModel` is no longer used.)
1440
- extractedText = await correctOcrText(extractedText);
1473
+ // Post-process: correct grammar/language via the backend the API resolved from the
1474
+ // user's settings (payload.correction = {backend,url,model}; default MLX = the agent
1475
+ // model server). Falls back to raw OCR text on any error so the job never fails here.
1476
+ extractedText = await correctOcrText(extractedText, payload.correction);
1441
1477
  const totalTimeSeconds = (Date.now() - start) / 1000;
1442
1478
  const doneRes = await workerFetch(`/api/worker/jobs/${jobId}`, {
1443
1479
  method: "PATCH",
package/gonext-repl.mjs CHANGED
@@ -965,9 +965,19 @@ async function runAgentTurn(history) {
965
965
  jobRunning = job.jobStatus === "running";
966
966
  const text = typeof job.resultText === "string" ? job.resultText : "";
967
967
  if (job.jobStatus === "completed") {
968
- // Drop the transient status line and any partial reasoning tail. If this was a
969
- // plain reply, its text is ALREADY fully visible (streamed live above) the
970
- // caller must not print it again; `shown` tells it so.
968
+ // The job can flip to "completed" between polls with authoritative resultText that
969
+ // the live stream hasn't caught up to yet (a debounced last chunk, or the final
970
+ // PATCH landing before its chunk flush was polled). resultText is the source of
971
+ // truth, so flush any not-yet-shown suffix BEFORE returning — otherwise a plain
972
+ // reply, whose live stream is its ONLY output, renders truncated mid-word. Gated
973
+ // to plainReplyFlow: the agent path reprints its answer from resultText via the
974
+ // caller (shown=false), so a live flush there is unnecessary (and would compound
975
+ // the separate agent double-print). This runs for the completed poll only; the
976
+ // running-branch consume below handles every earlier poll.
977
+ if (plainReplyFlow && text.length > shownChars) {
978
+ consume(text.slice(shownChars));
979
+ shownChars = text.length;
980
+ }
971
981
  clearStatus();
972
982
  return { text: answerFrom(text), shown: answerShownLive };
973
983
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gonext-local-worker",
3
- "version": "1.0.216",
3
+ "version": "1.0.219",
4
4
  "description": "Polls GoNext cloud API for async local LLM jobs and runs them against Ollama/OpenAI-compatible servers on this Mac",
5
5
  "type": "module",
6
6
  "license": "MIT",