@tiens.nguyen/gonext-local-worker 1.0.108 → 1.0.110
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/gonext-local-worker.mjs +24 -138
- package/package.json +1 -1
package/gonext-local-worker.mjs
CHANGED
|
@@ -139,6 +139,10 @@ const OCR_TIMEOUT_MS = 180_000;
|
|
|
139
139
|
const OCR_DEBUG = String(process.env.GONEXT_OCR_DEBUG ?? "").trim() === "1";
|
|
140
140
|
const OCR_SKIP_CORRECTION = String(process.env.GONEXT_OCR_SKIP_CORRECTION ?? "").trim() === "1";
|
|
141
141
|
const OCR_CORRECT_TIMEOUT_MS = 120_000;
|
|
142
|
+
// OCR translation/correction is hardcoded to a managed Ollama service + model.
|
|
143
|
+
// (The user-configurable "OCR translation model" chooser was removed.)
|
|
144
|
+
const OCR_TRANSLATE_OLLAMA_URL = "https://ollama1.gomarsic.cc";
|
|
145
|
+
const OCR_TRANSLATE_OLLAMA_MODEL = "gemma4:26b";
|
|
142
146
|
|
|
143
147
|
async function workerFetch(path, init = {}) {
|
|
144
148
|
const url = `${apiBase}${path.startsWith("/") ? path : `/${path}`}`;
|
|
@@ -987,16 +991,6 @@ function normalizeOcrOutput(output) {
|
|
|
987
991
|
return lines.join("\n").trim();
|
|
988
992
|
}
|
|
989
993
|
|
|
990
|
-
function resolveOcrCorrectionModelPath(override) {
|
|
991
|
-
if (override) {
|
|
992
|
-
const expanded = override.replace(/^~(?=\/)/, homedir());
|
|
993
|
-
return expanded.startsWith("/") ? expanded : join(homedir(), "mlx-models", expanded);
|
|
994
|
-
}
|
|
995
|
-
const fromEnv = String(process.env.GONEXT_OCR_CORRECT_MODEL_PATH ?? "").trim();
|
|
996
|
-
if (fromEnv) return fromEnv.replace(/^~(?=\/)/, homedir());
|
|
997
|
-
return join(homedir(), "mlx-models", "translategemma-4b-it-4bit");
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
994
|
function normalizeCorrection(raw, originalText) {
|
|
1001
995
|
let text = String(raw ?? "").replace(/\r\n/g, "\n");
|
|
1002
996
|
// Strip common mlx_lm.generate noise lines
|
|
@@ -1020,38 +1014,6 @@ function normalizeCorrection(raw, originalText) {
|
|
|
1020
1014
|
return result;
|
|
1021
1015
|
}
|
|
1022
1016
|
|
|
1023
|
-
/**
|
|
1024
|
-
* Call a correction model that's already running as an OpenAI-compatible server
|
|
1025
|
-
* (e.g. mlx_lm.server --model translategemma-4b-it-4bit --port 8083).
|
|
1026
|
-
* baseUrl should be like http://127.0.0.1:8083/v1
|
|
1027
|
-
*/
|
|
1028
|
-
async function correctOcrTextViaServer(extractedText, baseUrl) {
|
|
1029
|
-
const systemPrompt =
|
|
1030
|
-
"You are a text correction assistant. Fix grammar and language errors in the " +
|
|
1031
|
-
"provided text while preserving the original meaning and language. " +
|
|
1032
|
-
"Return only the corrected text without any explanation.";
|
|
1033
|
-
const res = await fetch(`${baseUrl.replace(/\/+$/, "")}/chat/completions`, {
|
|
1034
|
-
method: "POST",
|
|
1035
|
-
headers: { "Content-Type": "application/json" },
|
|
1036
|
-
body: JSON.stringify({
|
|
1037
|
-
model: "default",
|
|
1038
|
-
messages: [
|
|
1039
|
-
{ role: "system", content: systemPrompt },
|
|
1040
|
-
{ role: "user", content: `Text:\n${extractedText}` },
|
|
1041
|
-
],
|
|
1042
|
-
max_tokens: 2048,
|
|
1043
|
-
temperature: 0,
|
|
1044
|
-
}),
|
|
1045
|
-
signal: AbortSignal.timeout(OCR_CORRECT_TIMEOUT_MS),
|
|
1046
|
-
});
|
|
1047
|
-
if (!res.ok) {
|
|
1048
|
-
throw new Error(`Correction server ${baseUrl} returned ${res.status}`);
|
|
1049
|
-
}
|
|
1050
|
-
const json = await res.json();
|
|
1051
|
-
const corrected = json?.choices?.[0]?.message?.content?.trim() || "";
|
|
1052
|
-
return normalizeCorrection(corrected, extractedText);
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
1017
|
/**
|
|
1056
1018
|
* Call an Ollama host's native chat endpoint (POST {base}/api/chat) for text
|
|
1057
1019
|
* correction/translation, e.g. base "https://ollama1.gomarsic.cc" model "qwen3:14b".
|
|
@@ -1088,108 +1050,35 @@ async function correctOcrTextViaOllama(extractedText, base, model) {
|
|
|
1088
1050
|
return normalizeCorrection(corrected, extractedText);
|
|
1089
1051
|
}
|
|
1090
1052
|
|
|
1091
|
-
async function correctOcrText(extractedText
|
|
1053
|
+
async function correctOcrText(extractedText) {
|
|
1092
1054
|
if (OCR_SKIP_CORRECTION) {
|
|
1093
1055
|
console.log("[gonext-worker] OCR correction skipped (GONEXT_OCR_SKIP_CORRECTION=1)");
|
|
1094
1056
|
return extractedText;
|
|
1095
1057
|
}
|
|
1096
1058
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
if (override.toLowerCase().startsWith("ollama:")) {
|
|
1103
|
-
const rest = override.slice("ollama:".length);
|
|
1104
|
-
const at = rest.indexOf("@@");
|
|
1105
|
-
const model = (at >= 0 ? rest.slice(0, at) : rest).trim();
|
|
1106
|
-
const base = (at >= 0 ? rest.slice(at + 2) : "").trim().replace(/\/+$/, "");
|
|
1107
|
-
if (!model || !base) {
|
|
1108
|
-
console.warn(
|
|
1109
|
-
`[gonext-worker] OCR correction: malformed ollama override "${override}" (using raw OCR text)`
|
|
1110
|
-
);
|
|
1111
|
-
return extractedText;
|
|
1112
|
-
}
|
|
1113
|
-
console.log(`[gonext-worker] OCR correction via Ollama ${base} model=${model}`);
|
|
1114
|
-
console.log(`[gonext-worker] OCR correction input: ${extractedText.slice(0, 300)}`);
|
|
1115
|
-
try {
|
|
1116
|
-
const corrected = await correctOcrTextViaOllama(extractedText, base, model);
|
|
1117
|
-
console.log(`[gonext-worker] OCR correction output: ${corrected.slice(0, 300)}`);
|
|
1118
|
-
console.log(
|
|
1119
|
-
`[gonext-worker] OCR correction done: ${extractedText.length} → ${corrected.length} chars`
|
|
1120
|
-
);
|
|
1121
|
-
return corrected;
|
|
1122
|
-
} catch (e) {
|
|
1123
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
1124
|
-
console.warn(
|
|
1125
|
-
`[gonext-worker] OCR correction Ollama failed (using raw OCR text): ${msg.slice(0, 200)}`
|
|
1126
|
-
);
|
|
1127
|
-
return extractedText;
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
// If the override looks like a URL, call it as a running mlx_lm.server endpoint.
|
|
1132
|
-
// e.g. "http://127.0.0.1:8083/v1" or "http://127.0.0.1:8083"
|
|
1133
|
-
if (override.startsWith("http://") || override.startsWith("https://")) {
|
|
1134
|
-
const baseUrl = /\/v1\/?$/i.test(override) ? override : `${override}/v1`;
|
|
1135
|
-
console.log(`[gonext-worker] OCR correction via server ${baseUrl}`);
|
|
1136
|
-
console.log(`[gonext-worker] OCR correction input: ${extractedText.slice(0, 300)}`);
|
|
1137
|
-
try {
|
|
1138
|
-
const corrected = await correctOcrTextViaServer(extractedText, baseUrl);
|
|
1139
|
-
console.log(`[gonext-worker] OCR correction output: ${corrected.slice(0, 300)}`);
|
|
1140
|
-
console.log(`[gonext-worker] OCR correction done: ${extractedText.length} → ${corrected.length} chars`);
|
|
1141
|
-
return corrected;
|
|
1142
|
-
} catch (e) {
|
|
1143
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
1144
|
-
console.warn(`[gonext-worker] OCR correction server failed (using raw OCR text): ${msg.slice(0, 200)}`);
|
|
1145
|
-
return extractedText;
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
// Otherwise invoke mlx_lm.generate CLI (model loads per job — no server needed).
|
|
1150
|
-
const modelPath = resolveOcrCorrectionModelPath(override);
|
|
1151
|
-
const prompt =
|
|
1152
|
-
"The following text was extracted by an OCR model and may contain grammar errors, " +
|
|
1153
|
-
"wrong language, or garbled characters. " +
|
|
1154
|
-
"Correct any errors while preserving the original meaning and language. " +
|
|
1155
|
-
"Return only the corrected text without any explanation.\n\n" +
|
|
1156
|
-
`Text:\n${extractedText}`;
|
|
1157
|
-
console.log(`[gonext-worker] OCR correction via CLI model=${modelPath}`);
|
|
1059
|
+
// OCR translation/correction is hardcoded to a managed Ollama service + model
|
|
1060
|
+
// (the user-configurable chooser was removed). Any error falls back to raw text.
|
|
1061
|
+
console.log(
|
|
1062
|
+
`[gonext-worker] OCR correction via Ollama ${OCR_TRANSLATE_OLLAMA_URL} model=${OCR_TRANSLATE_OLLAMA_MODEL}`
|
|
1063
|
+
);
|
|
1158
1064
|
console.log(`[gonext-worker] OCR correction input: ${extractedText.slice(0, 300)}`);
|
|
1159
|
-
// Write prompt to a temp Python script that calls mlx_lm programmatically,
|
|
1160
|
-
// avoiding CLI arg length/escaping limits and the missing --prompt-file flag.
|
|
1161
|
-
const scriptFile = join(tmpdir(), `gonext-ocr-correct-${Date.now()}.py`);
|
|
1162
|
-
const promptFile = join(tmpdir(), `gonext-ocr-correct-${Date.now()}.txt`);
|
|
1163
|
-
const pyScript = `import sys, mlx_lm
|
|
1164
|
-
model, tokenizer = mlx_lm.load(sys.argv[1])
|
|
1165
|
-
with open(sys.argv[2], encoding="utf-8") as f:
|
|
1166
|
-
prompt = f.read()
|
|
1167
|
-
result = mlx_lm.generate(model, tokenizer, prompt=prompt, max_tokens=2048, verbose=False)
|
|
1168
|
-
print(result)
|
|
1169
|
-
`;
|
|
1170
1065
|
try {
|
|
1171
|
-
await
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
[scriptFile, modelPath, promptFile],
|
|
1176
|
-
{ timeout: OCR_CORRECT_TIMEOUT_MS, maxBuffer: 10 * 1024 * 1024 }
|
|
1066
|
+
const corrected = await correctOcrTextViaOllama(
|
|
1067
|
+
extractedText,
|
|
1068
|
+
OCR_TRANSLATE_OLLAMA_URL,
|
|
1069
|
+
OCR_TRANSLATE_OLLAMA_MODEL
|
|
1177
1070
|
);
|
|
1178
|
-
if (stderr?.trim()) {
|
|
1179
|
-
console.log(`[gonext-worker] OCR correction CLI stderr: ${stderr.trim().slice(0, 300)}`);
|
|
1180
|
-
}
|
|
1181
|
-
const corrected = normalizeCorrection(stdout, extractedText);
|
|
1182
1071
|
console.log(`[gonext-worker] OCR correction output: ${corrected.slice(0, 300)}`);
|
|
1183
|
-
console.log(
|
|
1072
|
+
console.log(
|
|
1073
|
+
`[gonext-worker] OCR correction done: ${extractedText.length} → ${corrected.length} chars`
|
|
1074
|
+
);
|
|
1184
1075
|
return corrected;
|
|
1185
1076
|
} catch (e) {
|
|
1186
|
-
const stderr = e && typeof e === "object" && "stderr" in e ? String(e.stderr ?? "").trim().slice(0, 300) : "";
|
|
1187
1077
|
const msg = e instanceof Error ? e.message : String(e);
|
|
1188
|
-
console.warn(
|
|
1078
|
+
console.warn(
|
|
1079
|
+
`[gonext-worker] OCR correction Ollama failed (using raw OCR text): ${msg.slice(0, 200)}`
|
|
1080
|
+
);
|
|
1189
1081
|
return extractedText;
|
|
1190
|
-
} finally {
|
|
1191
|
-
await rm(scriptFile, { force: true }).catch(() => {});
|
|
1192
|
-
await rm(promptFile, { force: true }).catch(() => {});
|
|
1193
1082
|
}
|
|
1194
1083
|
}
|
|
1195
1084
|
|
|
@@ -1335,13 +1224,10 @@ async function runOcrJob(job) {
|
|
|
1335
1224
|
} finally {
|
|
1336
1225
|
await rm(tempDir, { recursive: true, force: true }).catch(() => {});
|
|
1337
1226
|
}
|
|
1338
|
-
// Post-process: correct grammar/language
|
|
1339
|
-
// (
|
|
1340
|
-
//
|
|
1341
|
-
|
|
1342
|
-
? payload.correctionModel.trim()
|
|
1343
|
-
: "";
|
|
1344
|
-
extractedText = await correctOcrText(extractedText, correctionModelOverride);
|
|
1227
|
+
// Post-process: correct grammar/language via the hardcoded managed Ollama
|
|
1228
|
+
// service (gemma4:26b). Falls back to raw OCR text on any error so the job
|
|
1229
|
+
// never fails here. (`payload.correctionModel` is no longer used.)
|
|
1230
|
+
extractedText = await correctOcrText(extractedText);
|
|
1345
1231
|
const totalTimeSeconds = (Date.now() - start) / 1000;
|
|
1346
1232
|
const doneRes = await workerFetch(`/api/worker/jobs/${jobId}`, {
|
|
1347
1233
|
method: "PATCH",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gonext-local-worker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.110",
|
|
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",
|