@customize-agent/knowledge 4.0.15 → 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.
@@ -57,6 +57,7 @@ export declare class ContentExtractor {
57
57
  /** pdfjs-dist + canvas 渲染(降级方案) */
58
58
  private tryRenderWithPdfJs;
59
59
  private scoreOcrText;
60
+ private cleanOcrText;
60
61
  /** 加载图片像素数据(依赖 sharp) */
61
62
  private loadImagePixels;
62
63
  private extractPdfText;
@@ -1065,7 +1065,9 @@ export class ContentExtractor {
1065
1065
  width: 0, height: 0, channels: 0,
1066
1066
  filePath: imgPath,
1067
1067
  });
1068
- const ocrText = ocrResult.text.trim();
1068
+ const ocrText = this.cleanOcrText(ocrResult.text);
1069
+ if (ocrResult.warnings?.length)
1070
+ warnings.push(...ocrResult.warnings.map(item => `OCR 警告: ${item}`));
1069
1071
  if (ocrText) {
1070
1072
  ocrPages.push(i + 1);
1071
1073
  ocrStrategies.push({ page: i + 1, strategy: renderer, score: this.scoreOcrText(ocrText) });
@@ -1079,6 +1081,8 @@ export class ContentExtractor {
1079
1081
  failedPages.push({ page: i + 1, reason: error instanceof Error ? error.message : String(error) });
1080
1082
  }
1081
1083
  }
1084
+ if (ocrProvider)
1085
+ await ocrProvider.dispose();
1082
1086
  // 清理临时文件
1083
1087
  try {
1084
1088
  fs.rmSync(tmpDir, { recursive: true, force: true });
@@ -1164,6 +1168,15 @@ export class ContentExtractor {
1164
1168
  const replacementCount = (text.match(/[�□]/gu) ?? []).length;
1165
1169
  return cjkCount * 8 + normalizedLength - latinCount * 0.8 - replacementCount * 10;
1166
1170
  }
1171
+ cleanOcrText(value) {
1172
+ return String(value ?? '')
1173
+ .replace(/[ \t]+/gu, ' ')
1174
+ .replace(/([\p{Script=Han}])\s+([\p{Script=Han}])/gu, '$1$2')
1175
+ .replace(/([\p{Script=Han}])\s+([,。;:!?、)】》])/gu, '$1$2')
1176
+ .replace(/([(【《])\s+([\p{Script=Han}])/gu, '$1$2')
1177
+ .replace(/\n{3,}/gu, '\n\n')
1178
+ .trim();
1179
+ }
1167
1180
  /** 加载图片像素数据(依赖 sharp) */
1168
1181
  async loadImagePixels(filePath) {
1169
1182
  const sharpMod = await resolveAndImport('sharp');
@@ -18,6 +18,7 @@ export interface OcrResult {
18
18
  text: string;
19
19
  confidence: number;
20
20
  regions: OcrRegion[];
21
+ warnings?: string[];
21
22
  }
22
23
  export interface OcrProvider {
23
24
  readonly id: string;
@@ -29,11 +30,15 @@ export interface OcrProvider {
29
30
  channels?: number;
30
31
  filePath?: string;
31
32
  }): Promise<OcrResult>;
33
+ getWarnings?(): string[];
32
34
  dispose(): Promise<void>;
33
35
  }
34
36
  export declare class TesseractJsProvider implements OcrProvider {
35
37
  readonly id = "tesseract.js";
36
38
  private _available;
39
+ private worker;
40
+ private workerPromise;
41
+ private warnings;
37
42
  get available(): boolean;
38
43
  recognize(input: {
39
44
  data: Uint8Array;
@@ -42,6 +47,9 @@ export declare class TesseractJsProvider implements OcrProvider {
42
47
  channels?: number;
43
48
  filePath?: string;
44
49
  }): Promise<OcrResult>;
50
+ getWarnings(): string[];
51
+ private getWorker;
52
+ private createReusableWorker;
45
53
  dispose(): Promise<void>;
46
54
  }
47
55
  export declare function createOcrProvider(): Promise<OcrProvider>;
@@ -23,6 +23,9 @@ function tessdataDir() {
23
23
  export class TesseractJsProvider {
24
24
  id = 'tesseract.js';
25
25
  _available = null;
26
+ worker = null;
27
+ workerPromise = null;
28
+ warnings = [];
26
29
  get available() {
27
30
  if (this._available !== null)
28
31
  return this._available;
@@ -39,8 +42,6 @@ export class TesseractJsProvider {
39
42
  return this._available;
40
43
  }
41
44
  async recognize(input) {
42
- const tessMod = await resolveAndImport('tesseract.js');
43
- const { createWorker } = tessMod;
44
45
  let pngPath;
45
46
  let tmpDir = null;
46
47
  // 如果传了 filePath,直接使用;否则 raw pixels → PNG
@@ -61,39 +62,66 @@ export class TesseractJsProvider {
61
62
  .png().toFile(pngPath);
62
63
  }
63
64
  try {
64
- // tesseract.js worker(数组形式 languages)
65
- const worker = await createWorker(['chi_sim', 'eng'], 1, {
66
- langPath: tessdataDir(),
67
- gzip: false,
68
- });
69
- try {
70
- const result = await worker.recognize(pngPath);
71
- const text = (result.data.text ?? '').trim();
72
- const lines = (result.data.lines ?? []);
73
- const regions = lines
74
- .filter((l) => l.text?.trim())
75
- .map((l) => ({
76
- text: l.text.trim(),
77
- confidence: l.confidence ?? 0,
78
- box: {
79
- x: l.bbox?.x0 ?? 0,
80
- y: l.bbox?.y0 ?? 0,
81
- width: (l.bbox?.x1 ?? 0) - (l.bbox?.x0 ?? 0),
82
- height: (l.bbox?.y1 ?? 0) - (l.bbox?.y0 ?? 0),
83
- },
84
- }));
85
- return { text, confidence: result.data.confidence ?? 0, regions };
86
- }
87
- finally {
88
- await worker.terminate();
89
- }
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() };
90
82
  }
91
83
  finally {
92
84
  if (tmpDir)
93
85
  fs.rmSync(tmpDir, { recursive: true, force: true });
94
86
  }
95
87
  }
96
- async dispose() { }
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
+ }
97
125
  }
98
126
  // ─── 工厂 ───────────────────────────────────────────────────────
99
127
  export async function createOcrProvider() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@customize-agent/knowledge",
3
- "version": "4.0.15",
3
+ "version": "4.0.16",
4
4
  "description": "Local knowledge base infrastructure for customize-agent",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",