@tiens.nguyen/gonext-local-worker 1.0.42 → 1.0.45
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 +178 -42
- package/package.json +1 -1
package/gonext-local-worker.mjs
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
11
11
|
import { execFile as execFileCallback } from "node:child_process";
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
12
13
|
import { homedir, platform, tmpdir } from "node:os";
|
|
13
14
|
import { extname, join } from "node:path";
|
|
14
15
|
import { promisify } from "node:util";
|
|
@@ -19,11 +20,25 @@ import OpenAI from "openai";
|
|
|
19
20
|
const execFile = promisify(execFileCallback);
|
|
20
21
|
|
|
21
22
|
const ENV_FILE = join(homedir(), ".gonext", "worker.env");
|
|
22
|
-
|
|
23
|
+
const preloadedWorkerKey = String(process.env.GONEXT_WORKER_KEY ?? "").trim();
|
|
24
|
+
const envFileLoad = dotenv.config({ path: ENV_FILE });
|
|
23
25
|
dotenv.config();
|
|
24
26
|
|
|
25
27
|
const args = process.argv.slice(2);
|
|
26
28
|
|
|
29
|
+
function workerKeyFingerprint(secret) {
|
|
30
|
+
const v = String(secret ?? "").trim();
|
|
31
|
+
if (!v) return "";
|
|
32
|
+
return createHash("sha256").update(v, "utf8").digest("hex").slice(0, 12);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function maskWorkerKey(secret) {
|
|
36
|
+
const v = String(secret ?? "").trim();
|
|
37
|
+
if (!v) return "(empty)";
|
|
38
|
+
if (v.length <= 12) return `${v.slice(0, 3)}***`;
|
|
39
|
+
return `${v.slice(0, 8)}...${v.slice(-4)}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
function printHelp() {
|
|
28
43
|
console.log(`
|
|
29
44
|
gonext-local-worker
|
|
@@ -44,6 +59,7 @@ Env (optional):
|
|
|
44
59
|
GONEXT_SIMULATE_TEXT default body for simulate-chat when no args
|
|
45
60
|
GONEXT_MLX_LM_PYTHON Python executable for MLX LM native probe (default: python3)
|
|
46
61
|
GONEXT_OCR_MODEL_PATH Local GLM-OCR-bf16 directory (default: ~/mlx-models/GLM-OCR-bf16)
|
|
62
|
+
GONEXT_OCR_DEBUG=1 Print raw OCR stdout/stderr snippets for troubleshooting
|
|
47
63
|
`);
|
|
48
64
|
}
|
|
49
65
|
|
|
@@ -96,16 +112,28 @@ if (args[0] === "set") {
|
|
|
96
112
|
process.exit(0);
|
|
97
113
|
}
|
|
98
114
|
|
|
99
|
-
const apiBase = (process.env.GONEXT_API_BASE ?? "")
|
|
100
|
-
|
|
115
|
+
const apiBase = String(process.env.GONEXT_API_BASE ?? "")
|
|
116
|
+
.trim()
|
|
117
|
+
.replace(/\/+$/, "");
|
|
118
|
+
const workerKey = String(process.env.GONEXT_WORKER_KEY ?? "").trim();
|
|
119
|
+
const envFileWorkerKey = String(envFileLoad.parsed?.GONEXT_WORKER_KEY ?? "").trim();
|
|
120
|
+
const workerKeySource = (() => {
|
|
121
|
+
if (preloadedWorkerKey && workerKey === preloadedWorkerKey) {
|
|
122
|
+
return "process.env";
|
|
123
|
+
}
|
|
124
|
+
if (envFileWorkerKey && workerKey === envFileWorkerKey) {
|
|
125
|
+
return ENV_FILE;
|
|
126
|
+
}
|
|
127
|
+
return "unknown";
|
|
128
|
+
})();
|
|
101
129
|
const pollMs = 500;
|
|
102
130
|
const localHealthConcurrency = 4;
|
|
103
131
|
|
|
104
132
|
const CHUNK_PATH = "/api/worker/job-chunk";
|
|
105
|
-
const OCR_PROMPT =
|
|
106
|
-
"Extract all readable text from this image. Return plain text only and preserve line breaks.";
|
|
133
|
+
const OCR_PROMPT = "Text Recognition:";
|
|
107
134
|
const OCR_MAX_TOKENS = 2048;
|
|
108
135
|
const OCR_TIMEOUT_MS = 180_000;
|
|
136
|
+
const OCR_DEBUG = String(process.env.GONEXT_OCR_DEBUG ?? "").trim() === "1";
|
|
109
137
|
|
|
110
138
|
async function workerFetch(path, init = {}) {
|
|
111
139
|
const url = `${apiBase}${path.startsWith("/") ? path : `/${path}`}`;
|
|
@@ -125,13 +153,23 @@ async function ensureWorkerOk(res, context) {
|
|
|
125
153
|
);
|
|
126
154
|
}
|
|
127
155
|
|
|
128
|
-
|
|
129
|
-
if (!
|
|
156
|
+
function assertWorkerRuntimeConfig(mode) {
|
|
157
|
+
if (!workerKey) {
|
|
158
|
+
console.error(
|
|
159
|
+
`[gonext-worker] ${mode}: missing GONEXT_WORKER_KEY. Run: gonext-local-worker set <workerKey> --api-base <url>`
|
|
160
|
+
);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
if (!apiBase) {
|
|
130
164
|
console.error(
|
|
131
|
-
|
|
165
|
+
`[gonext-worker] ${mode}: missing GONEXT_API_BASE. Run: gonext-local-worker set <workerKey> --api-base <url>`
|
|
132
166
|
);
|
|
133
167
|
process.exit(1);
|
|
134
168
|
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (args[0] === "ws-ping-test") {
|
|
172
|
+
assertWorkerRuntimeConfig("ws-ping-test");
|
|
135
173
|
const url = `${apiBase}/api/worker/ws-ping-test`;
|
|
136
174
|
const res = await fetch(url, {
|
|
137
175
|
method: "POST",
|
|
@@ -151,17 +189,21 @@ if (args[0] === "ws-ping-test") {
|
|
|
151
189
|
}
|
|
152
190
|
process.exit(1);
|
|
153
191
|
}
|
|
154
|
-
|
|
192
|
+
try {
|
|
193
|
+
const parsed = JSON.parse(text);
|
|
194
|
+
console.log(
|
|
195
|
+
`[gonext-worker] ws-ping-test OK user=${parsed.userId ?? "unknown"} host=${
|
|
196
|
+
parsed.workerHostId ?? "legacy"
|
|
197
|
+
} keyfp=${parsed.workerKeyFingerprint ?? workerKeyFingerprint(workerKey)}`
|
|
198
|
+
);
|
|
199
|
+
} catch {
|
|
200
|
+
console.log("[gonext-worker] ws-ping-test OK:", text);
|
|
201
|
+
}
|
|
155
202
|
process.exit(0);
|
|
156
203
|
}
|
|
157
204
|
|
|
158
205
|
if (args[0] === "simulate-chat") {
|
|
159
|
-
|
|
160
|
-
console.error(
|
|
161
|
-
"simulate-chat needs GONEXT_API_BASE and GONEXT_WORKER_KEY (run: gonext-local-worker set <key> --api-base <url>)"
|
|
162
|
-
);
|
|
163
|
-
process.exit(1);
|
|
164
|
-
}
|
|
206
|
+
assertWorkerRuntimeConfig("simulate-chat");
|
|
165
207
|
const reply =
|
|
166
208
|
args.slice(1).join(" ").trim() ||
|
|
167
209
|
String(process.env.GONEXT_SIMULATE_TEXT ?? "").trim() ||
|
|
@@ -263,15 +305,17 @@ if (args[0] === "simulate-chat") {
|
|
|
263
305
|
process.exit(0);
|
|
264
306
|
}
|
|
265
307
|
|
|
266
|
-
|
|
267
|
-
console.error(
|
|
268
|
-
"Set GONEXT_API_BASE (HTTP API origin, no /api suffix) and GONEXT_WORKER_KEY."
|
|
269
|
-
);
|
|
270
|
-
process.exit(1);
|
|
271
|
-
}
|
|
308
|
+
assertWorkerRuntimeConfig("worker");
|
|
272
309
|
|
|
273
310
|
console.log(
|
|
274
|
-
`[gonext-worker] API base ${apiBase} — streaming chunks POST ${apiBase}${CHUNK_PATH}
|
|
311
|
+
`[gonext-worker] API base ${apiBase} — streaming chunks POST ${apiBase}${CHUNK_PATH} — keyfp=${workerKeyFingerprint(
|
|
312
|
+
workerKey
|
|
313
|
+
)}`
|
|
314
|
+
);
|
|
315
|
+
console.log(
|
|
316
|
+
`[gonext-worker] worker key ${maskWorkerKey(workerKey)} (keyfp=${workerKeyFingerprint(
|
|
317
|
+
workerKey
|
|
318
|
+
)}, source=${workerKeySource})`
|
|
275
319
|
);
|
|
276
320
|
|
|
277
321
|
function toOpenAIMessages(messages) {
|
|
@@ -522,10 +566,18 @@ function resolveImageExtension(mimeType, fileName) {
|
|
|
522
566
|
return ext || ".png";
|
|
523
567
|
}
|
|
524
568
|
|
|
525
|
-
function resolveOcrModelPath() {
|
|
526
|
-
const
|
|
527
|
-
if (
|
|
528
|
-
|
|
569
|
+
function resolveOcrModelPath(modelFromJob) {
|
|
570
|
+
const fromJob = String(modelFromJob ?? "").trim();
|
|
571
|
+
if (fromJob) {
|
|
572
|
+
const expanded = fromJob.replace(/^~(?=\/)/, homedir());
|
|
573
|
+
if (expanded.startsWith("/")) {
|
|
574
|
+
return expanded;
|
|
575
|
+
}
|
|
576
|
+
return join(homedir(), "mlx-models", expanded);
|
|
577
|
+
}
|
|
578
|
+
const fromEnv = String(process.env.GONEXT_OCR_MODEL_PATH ?? "").trim();
|
|
579
|
+
if (fromEnv) {
|
|
580
|
+
return fromEnv.replace(/^~(?=\/)/, homedir());
|
|
529
581
|
}
|
|
530
582
|
return join(homedir(), "mlx-models", "GLM-OCR-bf16");
|
|
531
583
|
}
|
|
@@ -581,7 +633,7 @@ async function runMlxVlmGenerate(modelPath, imagePath) {
|
|
|
581
633
|
String(OCR_MAX_TOKENS),
|
|
582
634
|
];
|
|
583
635
|
try {
|
|
584
|
-
|
|
636
|
+
const result = await execFile(
|
|
585
637
|
"python3",
|
|
586
638
|
["-m", "mlx_vlm.generate", ...sharedArgs],
|
|
587
639
|
{
|
|
@@ -589,6 +641,7 @@ async function runMlxVlmGenerate(modelPath, imagePath) {
|
|
|
589
641
|
maxBuffer: 10 * 1024 * 1024,
|
|
590
642
|
}
|
|
591
643
|
);
|
|
644
|
+
return { ...result, invocation: "python3 -m mlx_vlm.generate" };
|
|
592
645
|
} catch (primaryError) {
|
|
593
646
|
const stderr =
|
|
594
647
|
primaryError && typeof primaryError === "object" && "stderr" in primaryError
|
|
@@ -602,7 +655,7 @@ async function runMlxVlmGenerate(modelPath, imagePath) {
|
|
|
602
655
|
if (!missingLegacyModule) {
|
|
603
656
|
throw primaryError;
|
|
604
657
|
}
|
|
605
|
-
|
|
658
|
+
const result = await execFile(
|
|
606
659
|
"python3",
|
|
607
660
|
["-m", "mlx_vlm", "generate", ...sharedArgs],
|
|
608
661
|
{
|
|
@@ -610,9 +663,24 @@ async function runMlxVlmGenerate(modelPath, imagePath) {
|
|
|
610
663
|
maxBuffer: 10 * 1024 * 1024,
|
|
611
664
|
}
|
|
612
665
|
);
|
|
666
|
+
return { ...result, invocation: "python3 -m mlx_vlm generate" };
|
|
613
667
|
}
|
|
614
668
|
}
|
|
615
669
|
|
|
670
|
+
function compactPreview(text, max = 800) {
|
|
671
|
+
const normalized = String(text ?? "")
|
|
672
|
+
.replace(/\r\n/g, "\n")
|
|
673
|
+
.replace(/\s+/g, " ")
|
|
674
|
+
.trim();
|
|
675
|
+
if (!normalized) {
|
|
676
|
+
return "(empty)";
|
|
677
|
+
}
|
|
678
|
+
if (normalized.length <= max) {
|
|
679
|
+
return normalized;
|
|
680
|
+
}
|
|
681
|
+
return `${normalized.slice(0, max)} …[truncated ${normalized.length - max} chars]`;
|
|
682
|
+
}
|
|
683
|
+
|
|
616
684
|
async function runOcrJob(job) {
|
|
617
685
|
const { jobId, payload } = job;
|
|
618
686
|
const start = Date.now();
|
|
@@ -628,17 +696,41 @@ async function runOcrJob(job) {
|
|
|
628
696
|
const attachment = payload.attachment;
|
|
629
697
|
const mimeType = typeof attachment?.mimeType === "string" ? attachment.mimeType : "";
|
|
630
698
|
const data = typeof attachment?.data === "string" ? attachment.data : "";
|
|
699
|
+
const s3Object =
|
|
700
|
+
attachment && typeof attachment === "object" ? attachment.s3Object : undefined;
|
|
701
|
+
const s3GetUrl =
|
|
702
|
+
s3Object && typeof s3Object.getUrl === "string" ? s3Object.getUrl.trim() : "";
|
|
703
|
+
const sourceLabel = s3GetUrl
|
|
704
|
+
? `s3://${s3Object?.bucket ?? "unknown"}/${s3Object?.key ?? "unknown"}`
|
|
705
|
+
: "inline_base64";
|
|
706
|
+
const ocrModel =
|
|
707
|
+
typeof payload.model === "string" && payload.model.trim()
|
|
708
|
+
? payload.model.trim()
|
|
709
|
+
: "GLM-OCR-bf16";
|
|
631
710
|
const name = typeof attachment?.name === "string" ? attachment.name : "";
|
|
632
711
|
if (!mimeType.startsWith("image/")) {
|
|
633
712
|
throw new Error("OCR job attachment must be an image.");
|
|
634
713
|
}
|
|
635
|
-
if (!data) {
|
|
714
|
+
if (!data && !s3GetUrl) {
|
|
636
715
|
throw new Error("OCR job attachment is empty.");
|
|
637
716
|
}
|
|
638
|
-
|
|
717
|
+
let bytes;
|
|
718
|
+
if (s3GetUrl) {
|
|
719
|
+
const dlRes = await fetch(s3GetUrl, { method: "GET" });
|
|
720
|
+
if (!dlRes.ok) {
|
|
721
|
+
throw new Error(
|
|
722
|
+
`Failed to download OCR image from S3 (${dlRes.status}).`
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
const arr = await dlRes.arrayBuffer();
|
|
726
|
+
bytes = Buffer.from(arr);
|
|
727
|
+
} else {
|
|
728
|
+
bytes = Buffer.from(data, "base64");
|
|
729
|
+
}
|
|
639
730
|
if (!bytes.length) {
|
|
640
|
-
throw new Error("OCR job attachment data is not valid
|
|
731
|
+
throw new Error("OCR job attachment data is not valid.");
|
|
641
732
|
}
|
|
733
|
+
console.log(`[gonext-worker] OCR source jobId=${jobId} ${sourceLabel}`);
|
|
642
734
|
|
|
643
735
|
const tempDir = await mkdtemp(join(tmpdir(), "gonext-ocr-worker-"));
|
|
644
736
|
let extractedText = "";
|
|
@@ -648,16 +740,27 @@ async function runOcrJob(job) {
|
|
|
648
740
|
`input${resolveImageExtension(mimeType, name)}`
|
|
649
741
|
);
|
|
650
742
|
await writeFile(imagePath, bytes);
|
|
651
|
-
const modelPath = resolveOcrModelPath();
|
|
652
|
-
const { stdout } = await runMlxVlmGenerate(
|
|
743
|
+
const modelPath = resolveOcrModelPath(ocrModel);
|
|
744
|
+
const { stdout, stderr, invocation } = await runMlxVlmGenerate(
|
|
745
|
+
modelPath,
|
|
746
|
+
imagePath
|
|
747
|
+
);
|
|
748
|
+
if (OCR_DEBUG) {
|
|
749
|
+
console.log(
|
|
750
|
+
`[gonext-worker][ocr-debug] jobId=${jobId} invocation=${invocation} model=${modelPath} image=${imagePath} source=${
|
|
751
|
+
sourceLabel
|
|
752
|
+
} stdout=${compactPreview(stdout)} stderr=${compactPreview(stderr)}`
|
|
753
|
+
);
|
|
754
|
+
}
|
|
653
755
|
extractedText = normalizeOcrOutput(stdout);
|
|
756
|
+
if (!extractedText) {
|
|
757
|
+
throw new Error(
|
|
758
|
+
`OCR returned empty text for this image. invocation=${invocation} stdout=${compactPreview(stdout)} stderr=${compactPreview(stderr)}`
|
|
759
|
+
);
|
|
760
|
+
}
|
|
654
761
|
} finally {
|
|
655
762
|
await rm(tempDir, { recursive: true, force: true }).catch(() => {});
|
|
656
763
|
}
|
|
657
|
-
|
|
658
|
-
if (!extractedText) {
|
|
659
|
-
throw new Error("OCR returned empty text for this image.");
|
|
660
|
-
}
|
|
661
764
|
const totalTimeSeconds = (Date.now() - start) / 1000;
|
|
662
765
|
const doneRes = await workerFetch(`/api/worker/jobs/${jobId}`, {
|
|
663
766
|
method: "PATCH",
|
|
@@ -670,7 +773,7 @@ async function runOcrJob(job) {
|
|
|
670
773
|
});
|
|
671
774
|
await ensureWorkerOk(doneRes, `complete OCR jobId=${jobId}`);
|
|
672
775
|
console.log(
|
|
673
|
-
`[gonext-worker] completed OCR ${jobId} (${totalTimeSeconds.toFixed(1)}s)`
|
|
776
|
+
`[gonext-worker] completed OCR ${jobId} (${totalTimeSeconds.toFixed(1)}s) model=${ocrModel}`
|
|
674
777
|
);
|
|
675
778
|
} catch (e) {
|
|
676
779
|
const message = e instanceof Error ? e.message : String(e);
|
|
@@ -742,20 +845,30 @@ async function checkOllamaTags(base) {
|
|
|
742
845
|
}
|
|
743
846
|
}
|
|
744
847
|
|
|
745
|
-
async function checkOpenAiModels(base, apiKey) {
|
|
848
|
+
async function checkOpenAiModels(base, apiKey, source) {
|
|
746
849
|
const endpoint = `${base}/models`;
|
|
747
850
|
const headers = {};
|
|
748
851
|
if (apiKey?.trim()) {
|
|
749
852
|
headers.Authorization = `Bearer ${apiKey.trim()}`;
|
|
750
853
|
}
|
|
854
|
+
const hostSourceId =
|
|
855
|
+
typeof source?.workerHostId === "string" ? source.workerHostId.trim() : "";
|
|
856
|
+
const hostSourceLabel =
|
|
857
|
+
typeof source?.hostName === "string" ? source.hostName.trim() : "";
|
|
751
858
|
try {
|
|
752
859
|
const res = await fetch(endpoint, { method: "GET", headers });
|
|
753
860
|
if (!res.ok) return { online: false, endpoint, models: [] };
|
|
754
861
|
const j = await res.json();
|
|
862
|
+
const modelSuffix = hostSourceId ? `@@host:${hostSourceId}` : "";
|
|
863
|
+
const displaySuffix = hostSourceLabel ? ` (${hostSourceLabel})` : "";
|
|
755
864
|
const models = (j.data ?? [])
|
|
756
865
|
.map((d) => d.id)
|
|
757
866
|
.filter(Boolean)
|
|
758
|
-
.map((id) => ({
|
|
867
|
+
.map((id) => ({
|
|
868
|
+
id: hostSourceId ? `${id}@@${hostSourceId}` : id,
|
|
869
|
+
name: `${id}${displaySuffix}`,
|
|
870
|
+
value: `mlx:${id}${modelSuffix}`,
|
|
871
|
+
}));
|
|
759
872
|
return { online: true, endpoint, models };
|
|
760
873
|
} catch {
|
|
761
874
|
return { online: false, endpoint, models: [] };
|
|
@@ -848,13 +961,24 @@ async function runLocalHealthJob(job) {
|
|
|
848
961
|
}
|
|
849
962
|
}
|
|
850
963
|
const mlxRoot = normalizeOpenAiV1Root(payload?.mlxOpenAiBaseUrl);
|
|
964
|
+
const targetWorkerHostId =
|
|
965
|
+
typeof payload?.targetWorkerHostId === "string"
|
|
966
|
+
? payload.targetWorkerHostId.trim()
|
|
967
|
+
: "";
|
|
968
|
+
const targetWorkerHostName =
|
|
969
|
+
typeof payload?.targetWorkerHostName === "string"
|
|
970
|
+
? payload.targetWorkerHostName.trim()
|
|
971
|
+
: "";
|
|
851
972
|
let mlxHttp = null;
|
|
852
973
|
let mlxNative = null;
|
|
853
974
|
|
|
854
975
|
if (mlxRoot) {
|
|
855
976
|
const mlxStart = Date.now();
|
|
856
977
|
console.log(`[gonext-worker] local_health ${jobId} check mlx HTTP ${mlxRoot}`);
|
|
857
|
-
mlxHttp = await checkOpenAiModels(mlxRoot, payload?.mlxApiKey ?? ""
|
|
978
|
+
mlxHttp = await checkOpenAiModels(mlxRoot, payload?.mlxApiKey ?? "", {
|
|
979
|
+
workerHostId: targetWorkerHostId,
|
|
980
|
+
hostName: targetWorkerHostName || sourceLabelFromBase(mlxRoot),
|
|
981
|
+
});
|
|
858
982
|
console.log(
|
|
859
983
|
`[gonext-worker] local_health ${jobId} mlx HTTP online=${mlxHttp.online} models=${mlxHttp.models.length} took=${((Date.now() - mlxStart) / 1000).toFixed(2)}s`
|
|
860
984
|
);
|
|
@@ -881,6 +1005,16 @@ async function runLocalHealthJob(job) {
|
|
|
881
1005
|
if (mlxRoot || mlxNative?.available) {
|
|
882
1006
|
const httpOk = Boolean(mlxHttp?.online && (mlxHttp?.models?.length ?? 0) > 0);
|
|
883
1007
|
const nativeOk = mlxNative?.available === true;
|
|
1008
|
+
const hostName = targetWorkerHostName || sourceLabelFromBase(mlxRoot || "mlx");
|
|
1009
|
+
const sources = [
|
|
1010
|
+
{
|
|
1011
|
+
workerHostId: targetWorkerHostId || undefined,
|
|
1012
|
+
hostName,
|
|
1013
|
+
endpoint: mlxHttp?.endpoint,
|
|
1014
|
+
online: httpOk || nativeOk,
|
|
1015
|
+
modelCount: httpOk ? mlxHttp.models.length : nativeOk ? 1 : 0,
|
|
1016
|
+
},
|
|
1017
|
+
];
|
|
884
1018
|
mlx = {
|
|
885
1019
|
configured: httpOk || nativeOk,
|
|
886
1020
|
online: httpOk || nativeOk,
|
|
@@ -906,6 +1040,7 @@ async function runLocalHealthJob(job) {
|
|
|
906
1040
|
}
|
|
907
1041
|
: undefined,
|
|
908
1042
|
native: mlxNative ?? undefined,
|
|
1043
|
+
sources,
|
|
909
1044
|
};
|
|
910
1045
|
}
|
|
911
1046
|
const result = {
|
|
@@ -1014,7 +1149,8 @@ async function pollOnce() {
|
|
|
1014
1149
|
job.payload &&
|
|
1015
1150
|
typeof job.payload === "object" &&
|
|
1016
1151
|
typeof job.payload.ocrId === "string" &&
|
|
1017
|
-
typeof job.payload.attachment?.data === "string"
|
|
1152
|
+
(typeof job.payload.attachment?.data === "string" ||
|
|
1153
|
+
typeof job.payload.attachment?.s3Object?.getUrl === "string");
|
|
1018
1154
|
if (isOcrByType || isOcrByPayload) {
|
|
1019
1155
|
await runOcrJob(job);
|
|
1020
1156
|
return;
|
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.45",
|
|
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",
|