@customize-agent/knowledge 4.0.14 → 4.0.16

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.
@@ -0,0 +1,134 @@
1
+ /**
2
+ * OCR 提供者 — tesseract.js 跨平台 WASM
3
+ *
4
+ * 使用 tesseract.js v7(WASM),bundled traineddata,
5
+ * 真正跨平台(Windows/macOS/Linux),无需系统依赖。
6
+ */
7
+ import * as fs from 'node:fs';
8
+ import * as os from 'node:os';
9
+ import * as path from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { resolveAndImport, resolvePackage } from './module-resolver.js';
12
+ // ─── 路径工具 ───────────────────────────────────────────────────
13
+ const knowledgeDir = path.dirname(fileURLToPath(import.meta.url));
14
+ function tessdataDir() {
15
+ if (process.env.TESSDATA_PREFIX)
16
+ return process.env.TESSDATA_PREFIX;
17
+ const pkg = path.resolve(knowledgeDir, '..', '..', 'models', 'tessdata');
18
+ if (fs.existsSync(pkg))
19
+ return pkg;
20
+ return pkg;
21
+ }
22
+ // ─── Tesseract.js Provider(主 OCR 引擎,跨平台) ──────────────
23
+ export class TesseractJsProvider {
24
+ id = 'tesseract.js';
25
+ _available = null;
26
+ worker = null;
27
+ workerPromise = null;
28
+ warnings = [];
29
+ get available() {
30
+ if (this._available !== null)
31
+ return this._available;
32
+ // 检查 traineddata 和 tesseract.js 是否可用
33
+ const td = tessdataDir();
34
+ const hasChiSim = fs.existsSync(path.join(td, 'chi_sim.traineddata'));
35
+ try {
36
+ resolvePackage('tesseract.js');
37
+ this._available = hasChiSim;
38
+ }
39
+ catch {
40
+ this._available = false;
41
+ }
42
+ return this._available;
43
+ }
44
+ async recognize(input) {
45
+ let pngPath;
46
+ let tmpDir = null;
47
+ // 如果传了 filePath,直接使用;否则 raw pixels → PNG
48
+ if (input.filePath && fs.existsSync(input.filePath)) {
49
+ pngPath = input.filePath;
50
+ }
51
+ else {
52
+ const sharpMod = await resolveAndImport('sharp');
53
+ const sharpFn = sharpMod.default ?? sharpMod;
54
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ocr-'));
55
+ pngPath = path.join(tmpDir, 'input.png');
56
+ const channels = input.channels ?? 3;
57
+ await sharpFn(Buffer.from(input.data), {
58
+ raw: { width: input.width, height: input.height, channels },
59
+ })
60
+ .removeAlpha().normalize().linear(3.0, -150)
61
+ .withMetadata({ density: 288 })
62
+ .png().toFile(pngPath);
63
+ }
64
+ try {
65
+ const worker = await this.getWorker();
66
+ const result = await worker.recognize(pngPath);
67
+ const text = (result.data.text ?? '').trim();
68
+ const lines = (result.data.lines ?? []);
69
+ const regions = lines
70
+ .filter((l) => l.text?.trim())
71
+ .map((l) => ({
72
+ text: l.text.trim(),
73
+ confidence: l.confidence ?? 0,
74
+ box: {
75
+ x: l.bbox?.x0 ?? 0,
76
+ y: l.bbox?.y0 ?? 0,
77
+ width: (l.bbox?.x1 ?? 0) - (l.bbox?.x0 ?? 0),
78
+ height: (l.bbox?.y1 ?? 0) - (l.bbox?.y0 ?? 0),
79
+ },
80
+ }));
81
+ return { text, confidence: result.data.confidence ?? 0, regions, warnings: this.getWarnings() };
82
+ }
83
+ finally {
84
+ if (tmpDir)
85
+ fs.rmSync(tmpDir, { recursive: true, force: true });
86
+ }
87
+ }
88
+ getWarnings() {
89
+ return [...new Set(this.warnings)].slice(-20);
90
+ }
91
+ async getWorker() {
92
+ if (this.worker)
93
+ return this.worker;
94
+ if (!this.workerPromise)
95
+ this.workerPromise = this.createReusableWorker();
96
+ this.worker = await this.workerPromise;
97
+ return this.worker;
98
+ }
99
+ async createReusableWorker() {
100
+ const tessMod = await resolveAndImport('tesseract.js');
101
+ const { createWorker, OEM, setLogging } = tessMod;
102
+ if (typeof setLogging === 'function')
103
+ setLogging(false);
104
+ const worker = await createWorker('chi_sim', OEM?.LSTM_ONLY ?? 1, {
105
+ langPath: tessdataDir(),
106
+ gzip: false,
107
+ logger: () => undefined,
108
+ errorHandler: (error) => {
109
+ const message = error instanceof Error ? error.message : String(error);
110
+ if (message.trim())
111
+ this.warnings.push(message.trim());
112
+ },
113
+ });
114
+ if (typeof worker.setParameters === 'function') {
115
+ await worker.setParameters({ preserve_interword_spaces: '0', user_defined_dpi: '300' });
116
+ }
117
+ return worker;
118
+ }
119
+ async dispose() {
120
+ if (this.worker)
121
+ await this.worker.terminate();
122
+ this.worker = null;
123
+ this.workerPromise = null;
124
+ }
125
+ }
126
+ // ─── 工厂 ───────────────────────────────────────────────────────
127
+ export async function createOcrProvider() {
128
+ const tess = new TesseractJsProvider();
129
+ if (tess.available)
130
+ return tess;
131
+ const td = tessdataDir();
132
+ throw new Error(`OCR 不可用。请将 chi_sim.traineddata 和 eng.traineddata 放置到 ${td},` +
133
+ '并确保 tesseract.js 已安装。');
134
+ }