@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.
|
@@ -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
|
|
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
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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() {
|