lazy load ocr dependency
This commit is contained in:
+15
-2
@@ -4,7 +4,6 @@ const fs = require('node:fs');
|
|||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const { execFileSync } = require('node:child_process');
|
const { execFileSync } = require('node:child_process');
|
||||||
|
|
||||||
const { createWorker } = require('tesseract.js');
|
|
||||||
const {
|
const {
|
||||||
buildCaptureTitle,
|
buildCaptureTitle,
|
||||||
normalizeOllamaHost,
|
normalizeOllamaHost,
|
||||||
@@ -33,6 +32,16 @@ function clamp(v, min, max) {
|
|||||||
return Math.min(max, Math.max(min, v));
|
return Math.min(max, Math.max(min, v));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let createWorkerImpl = null;
|
||||||
|
function loadCreateWorker() {
|
||||||
|
if (createWorkerImpl) return createWorkerImpl;
|
||||||
|
// OCR is optional at startup; lazy-load it so the app can still boot when
|
||||||
|
// the dependency has not been installed yet.
|
||||||
|
// eslint-disable-next-line global-require
|
||||||
|
({ createWorker: createWorkerImpl } = require('tesseract.js'));
|
||||||
|
return createWorkerImpl;
|
||||||
|
}
|
||||||
|
|
||||||
class TextIntelService {
|
class TextIntelService {
|
||||||
constructor({
|
constructor({
|
||||||
store,
|
store,
|
||||||
@@ -81,8 +90,9 @@ class TextIntelService {
|
|||||||
async getWorker() {
|
async getWorker() {
|
||||||
if (this.workerPromise) return this.workerPromise;
|
if (this.workerPromise) return this.workerPromise;
|
||||||
this.workerPromise = (async () => {
|
this.workerPromise = (async () => {
|
||||||
|
const workerFactory = loadCreateWorker();
|
||||||
const langPath = this.ensureLangData();
|
const langPath = this.ensureLangData();
|
||||||
const worker = await createWorker('eng', 1, {
|
const worker = await workerFactory('eng', 1, {
|
||||||
langPath,
|
langPath,
|
||||||
});
|
});
|
||||||
await worker.setParameters({
|
await worker.setParameters({
|
||||||
@@ -91,6 +101,9 @@ class TextIntelService {
|
|||||||
this.worker = worker;
|
this.worker = worker;
|
||||||
return worker;
|
return worker;
|
||||||
})();
|
})();
|
||||||
|
this.workerPromise.catch(() => {
|
||||||
|
this.workerPromise = null;
|
||||||
|
});
|
||||||
return this.workerPromise;
|
return this.workerPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,29 @@ test('ocr crop rectangles clamp to the image bounds', (t) => {
|
|||||||
assert.deepEqual(bottomRight, { x: 580, y: 280, width: 420, height: 220 });
|
assert.deepEqual(bottomRight, { x: 580, y: 280, width: 420, height: 220 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('ocr failures fall back to empty text instead of crashing', async (t) => {
|
||||||
|
const root = makeTmpDir('text-intel-ocr-fallback');
|
||||||
|
t.after(() => rmrf(root));
|
||||||
|
const service = new TextIntelService({
|
||||||
|
store: { settingsDir: root },
|
||||||
|
settings: makeSettings(),
|
||||||
|
getWindow: () => null,
|
||||||
|
dataDir: root,
|
||||||
|
fetchImpl: global.fetch,
|
||||||
|
});
|
||||||
|
service.getWorker = async () => {
|
||||||
|
throw new Error('tesseract missing');
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await service.ocrAroundClick({
|
||||||
|
image: {},
|
||||||
|
size: { width: 100, height: 100 },
|
||||||
|
display: { bounds: { x: 0, y: 0, width: 100, height: 100 } },
|
||||||
|
}, { x: 50, y: 50 });
|
||||||
|
|
||||||
|
assert.deepEqual(result, { text: '', confidence: null });
|
||||||
|
});
|
||||||
|
|
||||||
test('ai response normalization and application keeps fields structured', () => {
|
test('ai response normalization and application keeps fields structured', () => {
|
||||||
const patch = normalizeAiPatch(JSON.stringify({
|
const patch = normalizeAiPatch(JSON.stringify({
|
||||||
title: 'Open settings',
|
title: 'Open settings',
|
||||||
|
|||||||
Reference in New Issue
Block a user