okrapdf 0.12.1 → 0.14.0
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/README.md +29 -4
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +1 -1
- package/dist/{chunk-YVUL6ZLA.js → chunk-7BAEYVWG.js} +2 -2
- package/dist/{chunk-XOHPZW3V.js → chunk-ETARIBOV.js} +405 -39
- package/dist/chunk-ETARIBOV.js.map +1 -0
- package/dist/{chunk-QKII53VN.js → chunk-HPTXRSWK.js} +2 -2
- package/dist/chunk-HPTXRSWK.js.map +1 -0
- package/dist/chunk-MSZQPLMQ.js +497 -0
- package/dist/chunk-MSZQPLMQ.js.map +1 -0
- package/dist/{chunk-2HJPTW6S.js → chunk-YGIBZV5J.js} +200 -82
- package/dist/chunk-YGIBZV5J.js.map +1 -0
- package/dist/cli/bin.d.ts +39 -0
- package/dist/cli/bin.js +581 -42
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.js +2 -1
- package/dist/{client-p82YcAs3.d.ts → client-D4A0dQ4h.d.ts} +5 -1
- package/dist/index.d.ts +146 -4
- package/dist/index.js +19 -3
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +3 -3
- package/dist/react/index.js +2 -2
- package/dist/{types-DDm2eEL0.d.ts → types-SYOi8k1l.d.ts} +38 -4
- package/dist/url.d.ts +1 -1
- package/dist/url.js +1 -1
- package/package.json +4 -2
- package/dist/chunk-2HJPTW6S.js.map +0 -1
- package/dist/chunk-QKII53VN.js.map +0 -1
- package/dist/chunk-XOHPZW3V.js.map +0 -1
- /package/dist/{chunk-YVUL6ZLA.js.map → chunk-7BAEYVWG.js.map} +0 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OkraRuntimeError
|
|
3
|
+
} from "./chunk-NIZM2ETT.js";
|
|
4
|
+
|
|
5
|
+
// src/local/index.ts
|
|
6
|
+
import { basename, extname } from "path";
|
|
7
|
+
import { existsSync as existsSync3 } from "fs";
|
|
8
|
+
|
|
9
|
+
// src/local/store.ts
|
|
10
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
11
|
+
import { homedir } from "os";
|
|
12
|
+
import { dirname, join } from "path";
|
|
13
|
+
import { randomUUID } from "crypto";
|
|
14
|
+
var DEFAULT_LOCAL_DATA_DIR = join(homedir(), ".openclaw", "okra-docs");
|
|
15
|
+
function resolveLocalDataDir(dataDir) {
|
|
16
|
+
return dataDir || process.env.OKRA_LOCAL_DATA_DIR || DEFAULT_LOCAL_DATA_DIR;
|
|
17
|
+
}
|
|
18
|
+
function ensureLocalStore(dataDir) {
|
|
19
|
+
const resolved = resolveLocalDataDir(dataDir);
|
|
20
|
+
mkdirSync(join(resolved, "docs"), { recursive: true });
|
|
21
|
+
return resolved;
|
|
22
|
+
}
|
|
23
|
+
function createLocalDocumentId() {
|
|
24
|
+
return `doc-local-${randomUUID()}`;
|
|
25
|
+
}
|
|
26
|
+
function getDocumentDir(documentId, dataDir) {
|
|
27
|
+
return join(resolveLocalDataDir(dataDir), "docs", documentId);
|
|
28
|
+
}
|
|
29
|
+
function getDocumentMetaPath(documentId, dataDir) {
|
|
30
|
+
return join(getDocumentDir(documentId, dataDir), "meta.json");
|
|
31
|
+
}
|
|
32
|
+
function getDocumentPagesDir(documentId, dataDir) {
|
|
33
|
+
return join(getDocumentDir(documentId, dataDir), "pages");
|
|
34
|
+
}
|
|
35
|
+
function getDocumentPagePath(documentId, pageNumber, dataDir) {
|
|
36
|
+
return join(getDocumentPagesDir(documentId, dataDir), `page-${String(pageNumber).padStart(4, "0")}.txt`);
|
|
37
|
+
}
|
|
38
|
+
function initializeDocumentDir(documentId, dataDir) {
|
|
39
|
+
const dir = getDocumentDir(documentId, dataDir);
|
|
40
|
+
rmSync(dir, { force: true, recursive: true });
|
|
41
|
+
mkdirSync(join(dir, "pages"), { recursive: true });
|
|
42
|
+
return dir;
|
|
43
|
+
}
|
|
44
|
+
function copySourceIntoStore(documentId, sourcePath, dataDir) {
|
|
45
|
+
const target = join(getDocumentDir(documentId, dataDir), "source.pdf");
|
|
46
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
47
|
+
copyFileSync(sourcePath, target);
|
|
48
|
+
return target;
|
|
49
|
+
}
|
|
50
|
+
function writeDocumentRecord(record, dataDir) {
|
|
51
|
+
const metaPath = getDocumentMetaPath(record.documentId, dataDir);
|
|
52
|
+
mkdirSync(dirname(metaPath), { recursive: true });
|
|
53
|
+
writeFileSync(metaPath, JSON.stringify(record, null, 2), "utf8");
|
|
54
|
+
}
|
|
55
|
+
function readDocumentRecord(documentId, dataDir) {
|
|
56
|
+
const metaPath = getDocumentMetaPath(documentId, dataDir);
|
|
57
|
+
if (!existsSync(metaPath)) {
|
|
58
|
+
throw new OkraRuntimeError("DOCUMENT_NOT_FOUND", `Local document not found: ${documentId}`, 404);
|
|
59
|
+
}
|
|
60
|
+
return JSON.parse(readFileSync(metaPath, "utf8"));
|
|
61
|
+
}
|
|
62
|
+
function writeDocumentPageText(documentId, pageNumber, text, dataDir) {
|
|
63
|
+
const pagePath = getDocumentPagePath(documentId, pageNumber, dataDir);
|
|
64
|
+
mkdirSync(dirname(pagePath), { recursive: true });
|
|
65
|
+
writeFileSync(pagePath, text, "utf8");
|
|
66
|
+
return pagePath;
|
|
67
|
+
}
|
|
68
|
+
function readDocumentPageText(documentId, pageNumber, dataDir) {
|
|
69
|
+
const pagePath = getDocumentPagePath(documentId, pageNumber, dataDir);
|
|
70
|
+
if (!existsSync(pagePath)) {
|
|
71
|
+
throw new OkraRuntimeError(
|
|
72
|
+
"DOCUMENT_NOT_FOUND",
|
|
73
|
+
`Local page ${pageNumber} not found for document ${documentId}`,
|
|
74
|
+
404
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return readFileSync(pagePath, "utf8");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/local/extract.ts
|
|
81
|
+
import { existsSync as existsSync2, mkdtempSync, rmSync as rmSync2 } from "fs";
|
|
82
|
+
import { tmpdir } from "os";
|
|
83
|
+
import { join as join2 } from "path";
|
|
84
|
+
import { spawnSync } from "child_process";
|
|
85
|
+
function runCommand(command, args) {
|
|
86
|
+
const result = spawnSync(command, args, {
|
|
87
|
+
encoding: "utf8",
|
|
88
|
+
maxBuffer: 20 * 1024 * 1024
|
|
89
|
+
});
|
|
90
|
+
if (result.error) {
|
|
91
|
+
throw new OkraRuntimeError(
|
|
92
|
+
"INVALID_REQUEST",
|
|
93
|
+
`Failed to run ${command}: ${result.error.message}`,
|
|
94
|
+
500
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
if (result.status !== 0) {
|
|
98
|
+
const stderr = (result.stderr || "").trim();
|
|
99
|
+
throw new OkraRuntimeError(
|
|
100
|
+
"INVALID_REQUEST",
|
|
101
|
+
stderr ? `${command} failed: ${stderr}` : `${command} exited with code ${result.status}`,
|
|
102
|
+
500
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return result.stdout ?? "";
|
|
106
|
+
}
|
|
107
|
+
function detectTool(name) {
|
|
108
|
+
const result = spawnSync("/bin/zsh", ["-lc", `command -v ${name}`], { encoding: "utf8" });
|
|
109
|
+
const path = result.status === 0 ? (result.stdout || "").trim() : "";
|
|
110
|
+
return {
|
|
111
|
+
available: path.length > 0,
|
|
112
|
+
path: path || null
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function splitPages(raw, pageCount) {
|
|
116
|
+
const pages = raw.split("\f").map((value) => value.replace(/\r/g, "").trimEnd());
|
|
117
|
+
while (pages.length > 0 && pages[pages.length - 1]?.trim() === "") {
|
|
118
|
+
pages.pop();
|
|
119
|
+
}
|
|
120
|
+
if (pages.length < pageCount) {
|
|
121
|
+
return [...pages, ...new Array(pageCount - pages.length).fill("")];
|
|
122
|
+
}
|
|
123
|
+
return pages.slice(0, pageCount);
|
|
124
|
+
}
|
|
125
|
+
function normalizeWhitespace(text) {
|
|
126
|
+
return text.replace(/\r/g, "").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
127
|
+
}
|
|
128
|
+
function needsOcr(text) {
|
|
129
|
+
const normalized = normalizeWhitespace(text);
|
|
130
|
+
if (!normalized) return true;
|
|
131
|
+
const alphaNumeric = (normalized.match(/[A-Za-z0-9]/g) || []).length;
|
|
132
|
+
return normalized.length < 80 || alphaNumeric / normalized.length < 0.35;
|
|
133
|
+
}
|
|
134
|
+
function extractPageCount(sourcePath) {
|
|
135
|
+
const output = runCommand("pdfinfo", [sourcePath]);
|
|
136
|
+
const match = output.match(/^Pages:\s+(\d+)$/m);
|
|
137
|
+
if (!match) {
|
|
138
|
+
throw new OkraRuntimeError("INVALID_RESPONSE", `Unable to determine page count for ${sourcePath}`, 500);
|
|
139
|
+
}
|
|
140
|
+
return Number.parseInt(match[1], 10);
|
|
141
|
+
}
|
|
142
|
+
function ocrPage(sourcePath, pageNumber) {
|
|
143
|
+
const tempDir = mkdtempSync(join2(tmpdir(), "okra-local-ocr-"));
|
|
144
|
+
const prefix = join2(tempDir, `page-${pageNumber}`);
|
|
145
|
+
try {
|
|
146
|
+
runCommand("pdftoppm", ["-f", String(pageNumber), "-l", String(pageNumber), "-png", sourcePath, prefix]);
|
|
147
|
+
const imagePath = `${prefix}-${pageNumber}.png`;
|
|
148
|
+
if (!existsSync2(imagePath)) {
|
|
149
|
+
throw new OkraRuntimeError(
|
|
150
|
+
"INVALID_RESPONSE",
|
|
151
|
+
`pdftoppm did not produce an image for page ${pageNumber}`,
|
|
152
|
+
500
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return normalizeWhitespace(runCommand("tesseract", [imagePath, "stdout", "--psm", "6"]));
|
|
156
|
+
} finally {
|
|
157
|
+
rmSync2(tempDir, { recursive: true, force: true });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function localToolReport(dataDir) {
|
|
161
|
+
const tools = {
|
|
162
|
+
pdftotext: detectTool("pdftotext"),
|
|
163
|
+
pdfinfo: detectTool("pdfinfo"),
|
|
164
|
+
pdftoppm: detectTool("pdftoppm"),
|
|
165
|
+
tesseract: detectTool("tesseract")
|
|
166
|
+
};
|
|
167
|
+
return {
|
|
168
|
+
ok: tools.pdftotext.available && tools.pdfinfo.available,
|
|
169
|
+
dataDir,
|
|
170
|
+
tools
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function extractLocalDocument(sourcePath, dataDir) {
|
|
174
|
+
const report = localToolReport(dataDir);
|
|
175
|
+
if (!report.tools.pdftotext.available || !report.tools.pdfinfo.available) {
|
|
176
|
+
throw new OkraRuntimeError(
|
|
177
|
+
"INVALID_REQUEST",
|
|
178
|
+
"Local PDF extraction requires pdftotext and pdfinfo to be installed",
|
|
179
|
+
500,
|
|
180
|
+
report.tools
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
const pageCount = extractPageCount(sourcePath);
|
|
184
|
+
const raw = runCommand("pdftotext", ["-layout", "-enc", "UTF-8", sourcePath, "-"]);
|
|
185
|
+
const split = splitPages(raw, pageCount);
|
|
186
|
+
let charCount = 0;
|
|
187
|
+
let ocrUsed = false;
|
|
188
|
+
const pages = split.map((pageText, index) => {
|
|
189
|
+
const pageNumber = index + 1;
|
|
190
|
+
let text = normalizeWhitespace(pageText);
|
|
191
|
+
let ocrApplied = false;
|
|
192
|
+
if (needsOcr(text) && report.tools.pdftoppm.available && report.tools.tesseract.available) {
|
|
193
|
+
const ocrText = ocrPage(sourcePath, pageNumber);
|
|
194
|
+
if (ocrText.length > text.length) {
|
|
195
|
+
text = ocrText;
|
|
196
|
+
}
|
|
197
|
+
ocrApplied = ocrText.length > 0;
|
|
198
|
+
ocrUsed = ocrUsed || ocrApplied;
|
|
199
|
+
}
|
|
200
|
+
charCount += text.length;
|
|
201
|
+
return { pageNumber, text, ocrApplied };
|
|
202
|
+
});
|
|
203
|
+
return {
|
|
204
|
+
pageCount,
|
|
205
|
+
charCount,
|
|
206
|
+
ocrUsed,
|
|
207
|
+
pages,
|
|
208
|
+
tools: {
|
|
209
|
+
...report.tools,
|
|
210
|
+
tesseract: report.tools.tesseract,
|
|
211
|
+
pdftoppm: report.tools.pdftoppm,
|
|
212
|
+
pdftotext: report.tools.pdftotext,
|
|
213
|
+
pdfinfo: report.tools.pdfinfo
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/local/index.ts
|
|
219
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
220
|
+
"a",
|
|
221
|
+
"an",
|
|
222
|
+
"and",
|
|
223
|
+
"are",
|
|
224
|
+
"as",
|
|
225
|
+
"at",
|
|
226
|
+
"be",
|
|
227
|
+
"by",
|
|
228
|
+
"for",
|
|
229
|
+
"from",
|
|
230
|
+
"how",
|
|
231
|
+
"in",
|
|
232
|
+
"is",
|
|
233
|
+
"it",
|
|
234
|
+
"of",
|
|
235
|
+
"on",
|
|
236
|
+
"or",
|
|
237
|
+
"that",
|
|
238
|
+
"the",
|
|
239
|
+
"this",
|
|
240
|
+
"to",
|
|
241
|
+
"what",
|
|
242
|
+
"when",
|
|
243
|
+
"where",
|
|
244
|
+
"who",
|
|
245
|
+
"why",
|
|
246
|
+
"with"
|
|
247
|
+
]);
|
|
248
|
+
function sanitizeExcerpt(text, maxLength = 280) {
|
|
249
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
250
|
+
return normalized.length > maxLength ? `${normalized.slice(0, maxLength - 1).trimEnd()}\u2026` : normalized;
|
|
251
|
+
}
|
|
252
|
+
function sanitizeTablePreview(text, maxLength = 320) {
|
|
253
|
+
const normalized = text.replace(/\r/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
254
|
+
return normalized.length > maxLength ? `${normalized.slice(0, maxLength - 1).trimEnd()}\u2026` : normalized;
|
|
255
|
+
}
|
|
256
|
+
function tokenizeQuery(query) {
|
|
257
|
+
return query.toLowerCase().split(/[^a-z0-9]+/g).map((token) => token.trim()).filter((token) => token.length >= 2 && !STOP_WORDS.has(token));
|
|
258
|
+
}
|
|
259
|
+
function buildDocumentInfo(record) {
|
|
260
|
+
return {
|
|
261
|
+
documentId: record.documentId,
|
|
262
|
+
filename: record.filename,
|
|
263
|
+
status: record.status,
|
|
264
|
+
pageCount: record.pageCount,
|
|
265
|
+
charCount: record.charCount,
|
|
266
|
+
createdAt: record.createdAt,
|
|
267
|
+
updatedAt: record.updatedAt,
|
|
268
|
+
error: record.error,
|
|
269
|
+
extractor: record.extractor
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function ensureReady(documentId, dataDir) {
|
|
273
|
+
const record = readDocumentRecord(documentId, dataDir);
|
|
274
|
+
if (record.status === "failed") {
|
|
275
|
+
throw new OkraRuntimeError(
|
|
276
|
+
"INVALID_REQUEST",
|
|
277
|
+
record.error || `Local document ${documentId} failed to process`,
|
|
278
|
+
500
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
return record;
|
|
282
|
+
}
|
|
283
|
+
function snippetAround(text, tokens) {
|
|
284
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
285
|
+
if (!normalized) return "";
|
|
286
|
+
const lower = normalized.toLowerCase();
|
|
287
|
+
let index = -1;
|
|
288
|
+
for (const token of tokens) {
|
|
289
|
+
index = lower.indexOf(token);
|
|
290
|
+
if (index >= 0) break;
|
|
291
|
+
}
|
|
292
|
+
if (index < 0) {
|
|
293
|
+
return sanitizeExcerpt(normalized);
|
|
294
|
+
}
|
|
295
|
+
const start = Math.max(0, index - 110);
|
|
296
|
+
const end = Math.min(normalized.length, index + 170);
|
|
297
|
+
return sanitizeExcerpt(normalized.slice(start, end));
|
|
298
|
+
}
|
|
299
|
+
function summarizeText(record, dataDir) {
|
|
300
|
+
const citations = [];
|
|
301
|
+
const seenPages = /* @__PURE__ */ new Set();
|
|
302
|
+
const snippets = [];
|
|
303
|
+
for (const page of record.pages) {
|
|
304
|
+
const text = readDocumentPageText(record.documentId, page.pageNumber, dataDir).split(/\n{2,}/).map((chunk) => chunk.trim()).filter(Boolean);
|
|
305
|
+
for (const block of text) {
|
|
306
|
+
snippets.push(block);
|
|
307
|
+
if (!seenPages.has(page.pageNumber)) {
|
|
308
|
+
citations.push({
|
|
309
|
+
page: page.pageNumber,
|
|
310
|
+
snippet: sanitizeExcerpt(block, 200)
|
|
311
|
+
});
|
|
312
|
+
seenPages.add(page.pageNumber);
|
|
313
|
+
}
|
|
314
|
+
if (snippets.join(" ").length >= 420 || citations.length >= 3) {
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (snippets.join(" ").length >= 420 || citations.length >= 3) break;
|
|
319
|
+
}
|
|
320
|
+
const summary = snippets.length > 0 ? sanitizeExcerpt(snippets.join(" "), 520) : `No extractable text was found in ${record.filename}.`;
|
|
321
|
+
return { summary, citations };
|
|
322
|
+
}
|
|
323
|
+
function ingestLocalDocument(sourcePath, dataDir) {
|
|
324
|
+
if (!existsSync3(sourcePath)) {
|
|
325
|
+
throw new OkraRuntimeError("DOCUMENT_NOT_FOUND", `File not found: ${sourcePath}`, 404);
|
|
326
|
+
}
|
|
327
|
+
if (extname(sourcePath).toLowerCase() !== ".pdf") {
|
|
328
|
+
throw new OkraRuntimeError("INVALID_REQUEST", `Local ingest only supports PDFs: ${sourcePath}`, 400);
|
|
329
|
+
}
|
|
330
|
+
const resolvedDataDir = ensureLocalStore(dataDir);
|
|
331
|
+
const documentId = createLocalDocumentId();
|
|
332
|
+
initializeDocumentDir(documentId, resolvedDataDir);
|
|
333
|
+
const storedPath = copySourceIntoStore(documentId, sourcePath, resolvedDataDir);
|
|
334
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
335
|
+
const extracted = extractLocalDocument(storedPath, resolvedDataDir);
|
|
336
|
+
const pages = extracted.pages.map((page) => {
|
|
337
|
+
const textPath = writeDocumentPageText(documentId, page.pageNumber, page.text, resolvedDataDir);
|
|
338
|
+
return {
|
|
339
|
+
pageNumber: page.pageNumber,
|
|
340
|
+
textPath,
|
|
341
|
+
charCount: page.text.length,
|
|
342
|
+
excerpt: sanitizeExcerpt(page.text, 160),
|
|
343
|
+
ocrApplied: page.ocrApplied
|
|
344
|
+
};
|
|
345
|
+
});
|
|
346
|
+
const record = {
|
|
347
|
+
documentId,
|
|
348
|
+
filename: basename(sourcePath),
|
|
349
|
+
storedPath,
|
|
350
|
+
sourcePath,
|
|
351
|
+
status: extracted.charCount > 0 ? "ready" : "failed",
|
|
352
|
+
pageCount: extracted.pageCount,
|
|
353
|
+
charCount: extracted.charCount,
|
|
354
|
+
createdAt: now,
|
|
355
|
+
updatedAt: now,
|
|
356
|
+
error: extracted.charCount > 0 ? null : "No extractable text found in this PDF.",
|
|
357
|
+
extractor: {
|
|
358
|
+
...extracted.tools,
|
|
359
|
+
ocrUsed: extracted.ocrUsed
|
|
360
|
+
},
|
|
361
|
+
pages
|
|
362
|
+
};
|
|
363
|
+
writeDocumentRecord(record, resolvedDataDir);
|
|
364
|
+
return {
|
|
365
|
+
...buildDocumentInfo(record),
|
|
366
|
+
storedPath: record.storedPath
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function getLocalDocumentStatus(documentId, dataDir) {
|
|
370
|
+
const record = readDocumentRecord(documentId, dataDir);
|
|
371
|
+
return {
|
|
372
|
+
...buildDocumentInfo(record),
|
|
373
|
+
pagesWithText: record.pages.filter((page) => page.charCount > 0).length
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function summarizeLocalDocument(documentId, dataDir) {
|
|
377
|
+
const record = ensureReady(documentId, dataDir);
|
|
378
|
+
const { summary, citations } = summarizeText(record, dataDir);
|
|
379
|
+
return {
|
|
380
|
+
...buildDocumentInfo(record),
|
|
381
|
+
summary,
|
|
382
|
+
citations
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
function searchLocalDocument(documentId, query, dataDir) {
|
|
386
|
+
const record = ensureReady(documentId, dataDir);
|
|
387
|
+
const tokens = tokenizeQuery(query);
|
|
388
|
+
const matches = record.pages.map((page) => {
|
|
389
|
+
const text = readDocumentPageText(record.documentId, page.pageNumber, dataDir);
|
|
390
|
+
const lower = text.toLowerCase();
|
|
391
|
+
let score = 0;
|
|
392
|
+
for (const token of tokens) {
|
|
393
|
+
const occurrences = lower.split(token).length - 1;
|
|
394
|
+
score += occurrences * Math.max(1, token.length);
|
|
395
|
+
}
|
|
396
|
+
return {
|
|
397
|
+
page: page.pageNumber,
|
|
398
|
+
snippet: snippetAround(text, tokens),
|
|
399
|
+
score
|
|
400
|
+
};
|
|
401
|
+
}).filter((match) => match.score > 0 || tokens.length === 0).sort((lhs, rhs) => rhs.score - lhs.score).slice(0, 5);
|
|
402
|
+
return {
|
|
403
|
+
...buildDocumentInfo(record),
|
|
404
|
+
query,
|
|
405
|
+
matches
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
function readLocalPage(documentId, pageNumber, dataDir) {
|
|
409
|
+
const record = ensureReady(documentId, dataDir);
|
|
410
|
+
const page = record.pages.find((entry) => entry.pageNumber === pageNumber);
|
|
411
|
+
if (!page) {
|
|
412
|
+
throw new OkraRuntimeError(
|
|
413
|
+
"DOCUMENT_NOT_FOUND",
|
|
414
|
+
`Page ${pageNumber} not found for local document ${documentId}`,
|
|
415
|
+
404
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
...buildDocumentInfo(record),
|
|
420
|
+
page: page.pageNumber,
|
|
421
|
+
text: readDocumentPageText(documentId, pageNumber, dataDir),
|
|
422
|
+
ocrApplied: page.ocrApplied
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
function looksLikeTableRow(line) {
|
|
426
|
+
return /\S(?:.*(?: {2,}|\t).*)\S/.test(line.trim());
|
|
427
|
+
}
|
|
428
|
+
function detectTableCandidates(text, page, query) {
|
|
429
|
+
const lines = text.split("\n");
|
|
430
|
+
const tables = [];
|
|
431
|
+
let current = [];
|
|
432
|
+
let previousNonEmpty = "";
|
|
433
|
+
for (const line of lines) {
|
|
434
|
+
const trimmed = line.trim();
|
|
435
|
+
if (looksLikeTableRow(line)) {
|
|
436
|
+
current.push(line.trimEnd());
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (current.length >= 2) {
|
|
440
|
+
tables.push({ rows: current, heading: previousNonEmpty || void 0 });
|
|
441
|
+
}
|
|
442
|
+
current = [];
|
|
443
|
+
if (trimmed) {
|
|
444
|
+
previousNonEmpty = trimmed;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (current.length >= 2) {
|
|
448
|
+
tables.push({ rows: current, heading: previousNonEmpty || void 0 });
|
|
449
|
+
}
|
|
450
|
+
const loweredQuery = query?.toLowerCase().trim();
|
|
451
|
+
return tables.map((table) => {
|
|
452
|
+
const body = table.rows.join("\n");
|
|
453
|
+
const preview = sanitizeTablePreview(
|
|
454
|
+
table.heading ? `${table.heading}
|
|
455
|
+
${body}` : body,
|
|
456
|
+
320
|
|
457
|
+
);
|
|
458
|
+
const score = loweredQuery ? preview.toLowerCase().includes(loweredQuery) ? 10 : 0 : 1;
|
|
459
|
+
return {
|
|
460
|
+
page,
|
|
461
|
+
preview,
|
|
462
|
+
rowCount: table.rows.length,
|
|
463
|
+
score
|
|
464
|
+
};
|
|
465
|
+
}).filter((table) => table.score > 0);
|
|
466
|
+
}
|
|
467
|
+
function findLocalTables(documentId, query, dataDir) {
|
|
468
|
+
const record = ensureReady(documentId, dataDir);
|
|
469
|
+
const tables = record.pages.flatMap(
|
|
470
|
+
(page) => detectTableCandidates(
|
|
471
|
+
readDocumentPageText(record.documentId, page.pageNumber, dataDir),
|
|
472
|
+
page.pageNumber,
|
|
473
|
+
query
|
|
474
|
+
)
|
|
475
|
+
).sort((lhs, rhs) => (rhs.score ?? 0) - (lhs.score ?? 0)).slice(0, 8).map(({ page, preview, rowCount }) => ({ page, preview, rowCount }));
|
|
476
|
+
return {
|
|
477
|
+
...buildDocumentInfo(record),
|
|
478
|
+
query,
|
|
479
|
+
tables
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function doctorLocalHarness(dataDir) {
|
|
483
|
+
const resolvedDataDir = resolveLocalDataDir(dataDir);
|
|
484
|
+
ensureLocalStore(resolvedDataDir);
|
|
485
|
+
return localToolReport(resolvedDataDir);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export {
|
|
489
|
+
ingestLocalDocument,
|
|
490
|
+
getLocalDocumentStatus,
|
|
491
|
+
summarizeLocalDocument,
|
|
492
|
+
searchLocalDocument,
|
|
493
|
+
readLocalPage,
|
|
494
|
+
findLocalTables,
|
|
495
|
+
doctorLocalHarness
|
|
496
|
+
};
|
|
497
|
+
//# sourceMappingURL=chunk-MSZQPLMQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/local/index.ts","../src/local/store.ts","../src/local/extract.ts"],"sourcesContent":["import { basename, extname } from 'path';\nimport { existsSync } from 'fs';\nimport { OkraRuntimeError } from '../errors.js';\nimport {\n copySourceIntoStore,\n createLocalDocumentId,\n ensureLocalStore,\n initializeDocumentDir,\n readDocumentPageText,\n readDocumentRecord,\n resolveLocalDataDir,\n writeDocumentPageText,\n writeDocumentRecord,\n} from './store.js';\nimport { extractLocalDocument, localToolReport } from './extract.js';\nimport type {\n LocalCitation,\n LocalDoctorReport,\n LocalDocumentRecord,\n LocalTableCandidate,\n} from './types.js';\n\nconst STOP_WORDS = new Set([\n 'a',\n 'an',\n 'and',\n 'are',\n 'as',\n 'at',\n 'be',\n 'by',\n 'for',\n 'from',\n 'how',\n 'in',\n 'is',\n 'it',\n 'of',\n 'on',\n 'or',\n 'that',\n 'the',\n 'this',\n 'to',\n 'what',\n 'when',\n 'where',\n 'who',\n 'why',\n 'with',\n]);\n\nfunction sanitizeExcerpt(text: string, maxLength = 280): string {\n const normalized = text.replace(/\\s+/g, ' ').trim();\n return normalized.length > maxLength ? `${normalized.slice(0, maxLength - 1).trimEnd()}…` : normalized;\n}\n\nfunction sanitizeTablePreview(text: string, maxLength = 320): string {\n const normalized = text.replace(/\\r/g, '').replace(/\\n{3,}/g, '\\n\\n').trim();\n return normalized.length > maxLength ? `${normalized.slice(0, maxLength - 1).trimEnd()}…` : normalized;\n}\n\nfunction tokenizeQuery(query: string): string[] {\n return query\n .toLowerCase()\n .split(/[^a-z0-9]+/g)\n .map((token) => token.trim())\n .filter((token) => token.length >= 2 && !STOP_WORDS.has(token));\n}\n\nfunction buildDocumentInfo(record: LocalDocumentRecord) {\n return {\n documentId: record.documentId,\n filename: record.filename,\n status: record.status,\n pageCount: record.pageCount,\n charCount: record.charCount,\n createdAt: record.createdAt,\n updatedAt: record.updatedAt,\n error: record.error,\n extractor: record.extractor,\n };\n}\n\nfunction ensureReady(documentId: string, dataDir?: string): LocalDocumentRecord {\n const record = readDocumentRecord(documentId, dataDir);\n if (record.status === 'failed') {\n throw new OkraRuntimeError(\n 'INVALID_REQUEST',\n record.error || `Local document ${documentId} failed to process`,\n 500,\n );\n }\n return record;\n}\n\nfunction snippetAround(text: string, tokens: string[]): string {\n const normalized = text.replace(/\\s+/g, ' ').trim();\n if (!normalized) return '';\n const lower = normalized.toLowerCase();\n let index = -1;\n for (const token of tokens) {\n index = lower.indexOf(token);\n if (index >= 0) break;\n }\n if (index < 0) {\n return sanitizeExcerpt(normalized);\n }\n const start = Math.max(0, index - 110);\n const end = Math.min(normalized.length, index + 170);\n return sanitizeExcerpt(normalized.slice(start, end));\n}\n\nfunction summarizeText(record: LocalDocumentRecord, dataDir?: string): { summary: string; citations: LocalCitation[] } {\n const citations: LocalCitation[] = [];\n const seenPages = new Set<number>();\n const snippets: string[] = [];\n\n for (const page of record.pages) {\n const text = readDocumentPageText(record.documentId, page.pageNumber, dataDir)\n .split(/\\n{2,}/)\n .map((chunk) => chunk.trim())\n .filter(Boolean);\n\n for (const block of text) {\n snippets.push(block);\n if (!seenPages.has(page.pageNumber)) {\n citations.push({\n page: page.pageNumber,\n snippet: sanitizeExcerpt(block, 200),\n });\n seenPages.add(page.pageNumber);\n }\n if (snippets.join(' ').length >= 420 || citations.length >= 3) {\n break;\n }\n }\n if (snippets.join(' ').length >= 420 || citations.length >= 3) break;\n }\n\n const summary = snippets.length > 0\n ? sanitizeExcerpt(snippets.join(' '), 520)\n : `No extractable text was found in ${record.filename}.`;\n\n return { summary, citations };\n}\n\nexport function ingestLocalDocument(sourcePath: string, dataDir?: string) {\n if (!existsSync(sourcePath)) {\n throw new OkraRuntimeError('DOCUMENT_NOT_FOUND', `File not found: ${sourcePath}`, 404);\n }\n if (extname(sourcePath).toLowerCase() !== '.pdf') {\n throw new OkraRuntimeError('INVALID_REQUEST', `Local ingest only supports PDFs: ${sourcePath}`, 400);\n }\n\n const resolvedDataDir = ensureLocalStore(dataDir);\n const documentId = createLocalDocumentId();\n initializeDocumentDir(documentId, resolvedDataDir);\n const storedPath = copySourceIntoStore(documentId, sourcePath, resolvedDataDir);\n const now = new Date().toISOString();\n\n const extracted = extractLocalDocument(storedPath, resolvedDataDir);\n\n const pages = extracted.pages.map((page) => {\n const textPath = writeDocumentPageText(documentId, page.pageNumber, page.text, resolvedDataDir);\n return {\n pageNumber: page.pageNumber,\n textPath,\n charCount: page.text.length,\n excerpt: sanitizeExcerpt(page.text, 160),\n ocrApplied: page.ocrApplied,\n };\n });\n\n const record: LocalDocumentRecord = {\n documentId,\n filename: basename(sourcePath),\n storedPath,\n sourcePath,\n status: extracted.charCount > 0 ? 'ready' : 'failed',\n pageCount: extracted.pageCount,\n charCount: extracted.charCount,\n createdAt: now,\n updatedAt: now,\n error: extracted.charCount > 0 ? null : 'No extractable text found in this PDF.',\n extractor: {\n ...extracted.tools,\n ocrUsed: extracted.ocrUsed,\n },\n pages,\n };\n\n writeDocumentRecord(record, resolvedDataDir);\n return {\n ...buildDocumentInfo(record),\n storedPath: record.storedPath,\n };\n}\n\nexport function getLocalDocumentStatus(documentId: string, dataDir?: string) {\n const record = readDocumentRecord(documentId, dataDir);\n return {\n ...buildDocumentInfo(record),\n pagesWithText: record.pages.filter((page) => page.charCount > 0).length,\n };\n}\n\nexport function summarizeLocalDocument(documentId: string, dataDir?: string) {\n const record = ensureReady(documentId, dataDir);\n const { summary, citations } = summarizeText(record, dataDir);\n return {\n ...buildDocumentInfo(record),\n summary,\n citations,\n };\n}\n\nexport function searchLocalDocument(documentId: string, query: string, dataDir?: string) {\n const record = ensureReady(documentId, dataDir);\n const tokens = tokenizeQuery(query);\n const matches = record.pages\n .map((page) => {\n const text = readDocumentPageText(record.documentId, page.pageNumber, dataDir);\n const lower = text.toLowerCase();\n let score = 0;\n for (const token of tokens) {\n const occurrences = lower.split(token).length - 1;\n score += occurrences * Math.max(1, token.length);\n }\n return {\n page: page.pageNumber,\n snippet: snippetAround(text, tokens),\n score,\n };\n })\n .filter((match) => match.score > 0 || tokens.length === 0)\n .sort((lhs, rhs) => rhs.score - lhs.score)\n .slice(0, 5);\n\n return {\n ...buildDocumentInfo(record),\n query,\n matches,\n };\n}\n\nexport function readLocalPage(documentId: string, pageNumber: number, dataDir?: string) {\n const record = ensureReady(documentId, dataDir);\n const page = record.pages.find((entry) => entry.pageNumber === pageNumber);\n if (!page) {\n throw new OkraRuntimeError(\n 'DOCUMENT_NOT_FOUND',\n `Page ${pageNumber} not found for local document ${documentId}`,\n 404,\n );\n }\n\n return {\n ...buildDocumentInfo(record),\n page: page.pageNumber,\n text: readDocumentPageText(documentId, pageNumber, dataDir),\n ocrApplied: page.ocrApplied,\n };\n}\n\nfunction looksLikeTableRow(line: string): boolean {\n return /\\S(?:.*(?: {2,}|\\t).*)\\S/.test(line.trim());\n}\n\nfunction detectTableCandidates(text: string, page: number, query?: string): LocalTableCandidate[] {\n const lines = text.split('\\n');\n const tables: Array<{ rows: string[]; heading?: string }> = [];\n let current: string[] = [];\n let previousNonEmpty = '';\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (looksLikeTableRow(line)) {\n current.push(line.trimEnd());\n continue;\n }\n\n if (current.length >= 2) {\n tables.push({ rows: current, heading: previousNonEmpty || undefined });\n }\n current = [];\n if (trimmed) {\n previousNonEmpty = trimmed;\n }\n }\n\n if (current.length >= 2) {\n tables.push({ rows: current, heading: previousNonEmpty || undefined });\n }\n\n const loweredQuery = query?.toLowerCase().trim();\n\n return tables\n .map((table) => {\n const body = table.rows.join('\\n');\n const preview = sanitizeTablePreview(\n table.heading ? `${table.heading}\\n${body}` : body,\n 320,\n );\n const score = loweredQuery\n ? (preview.toLowerCase().includes(loweredQuery) ? 10 : 0)\n : 1;\n return {\n page,\n preview,\n rowCount: table.rows.length,\n score,\n };\n })\n .filter((table) => table.score > 0);\n}\n\nexport function findLocalTables(documentId: string, query?: string, dataDir?: string) {\n const record = ensureReady(documentId, dataDir);\n const tables = record.pages\n .flatMap((page) =>\n detectTableCandidates(\n readDocumentPageText(record.documentId, page.pageNumber, dataDir),\n page.pageNumber,\n query,\n ),\n )\n .sort((lhs, rhs) => (rhs.score ?? 0) - (lhs.score ?? 0))\n .slice(0, 8)\n .map(({ page, preview, rowCount }) => ({ page, preview, rowCount }));\n\n return {\n ...buildDocumentInfo(record),\n query,\n tables,\n };\n}\n\nexport function doctorLocalHarness(dataDir?: string): LocalDoctorReport {\n const resolvedDataDir = resolveLocalDataDir(dataDir);\n ensureLocalStore(resolvedDataDir);\n return localToolReport(resolvedDataDir);\n}\n","import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs';\nimport { homedir } from 'os';\nimport { dirname, join } from 'path';\nimport { randomUUID } from 'crypto';\nimport { OkraRuntimeError } from '../errors.js';\nimport type { LocalDocumentRecord } from './types.js';\n\nexport const DEFAULT_LOCAL_DATA_DIR = join(homedir(), '.openclaw', 'okra-docs');\n\nexport function resolveLocalDataDir(dataDir?: string): string {\n return dataDir || process.env.OKRA_LOCAL_DATA_DIR || DEFAULT_LOCAL_DATA_DIR;\n}\n\nexport function ensureLocalStore(dataDir?: string): string {\n const resolved = resolveLocalDataDir(dataDir);\n mkdirSync(join(resolved, 'docs'), { recursive: true });\n return resolved;\n}\n\nexport function createLocalDocumentId(): string {\n return `doc-local-${randomUUID()}`;\n}\n\nexport function getDocumentDir(documentId: string, dataDir?: string): string {\n return join(resolveLocalDataDir(dataDir), 'docs', documentId);\n}\n\nexport function getDocumentMetaPath(documentId: string, dataDir?: string): string {\n return join(getDocumentDir(documentId, dataDir), 'meta.json');\n}\n\nexport function getDocumentPagesDir(documentId: string, dataDir?: string): string {\n return join(getDocumentDir(documentId, dataDir), 'pages');\n}\n\nexport function getDocumentPagePath(\n documentId: string,\n pageNumber: number,\n dataDir?: string,\n): string {\n return join(getDocumentPagesDir(documentId, dataDir), `page-${String(pageNumber).padStart(4, '0')}.txt`);\n}\n\nexport function initializeDocumentDir(documentId: string, dataDir?: string): string {\n const dir = getDocumentDir(documentId, dataDir);\n rmSync(dir, { force: true, recursive: true });\n mkdirSync(join(dir, 'pages'), { recursive: true });\n return dir;\n}\n\nexport function copySourceIntoStore(\n documentId: string,\n sourcePath: string,\n dataDir?: string,\n): string {\n const target = join(getDocumentDir(documentId, dataDir), 'source.pdf');\n mkdirSync(dirname(target), { recursive: true });\n copyFileSync(sourcePath, target);\n return target;\n}\n\nexport function writeDocumentRecord(record: LocalDocumentRecord, dataDir?: string): void {\n const metaPath = getDocumentMetaPath(record.documentId, dataDir);\n mkdirSync(dirname(metaPath), { recursive: true });\n writeFileSync(metaPath, JSON.stringify(record, null, 2), 'utf8');\n}\n\nexport function readDocumentRecord(documentId: string, dataDir?: string): LocalDocumentRecord {\n const metaPath = getDocumentMetaPath(documentId, dataDir);\n if (!existsSync(metaPath)) {\n throw new OkraRuntimeError('DOCUMENT_NOT_FOUND', `Local document not found: ${documentId}`, 404);\n }\n return JSON.parse(readFileSync(metaPath, 'utf8')) as LocalDocumentRecord;\n}\n\nexport function writeDocumentPageText(\n documentId: string,\n pageNumber: number,\n text: string,\n dataDir?: string,\n): string {\n const pagePath = getDocumentPagePath(documentId, pageNumber, dataDir);\n mkdirSync(dirname(pagePath), { recursive: true });\n writeFileSync(pagePath, text, 'utf8');\n return pagePath;\n}\n\nexport function readDocumentPageText(\n documentId: string,\n pageNumber: number,\n dataDir?: string,\n): string {\n const pagePath = getDocumentPagePath(documentId, pageNumber, dataDir);\n if (!existsSync(pagePath)) {\n throw new OkraRuntimeError(\n 'DOCUMENT_NOT_FOUND',\n `Local page ${pageNumber} not found for document ${documentId}`,\n 404,\n );\n }\n return readFileSync(pagePath, 'utf8');\n}\n\n","import { existsSync, mkdtempSync, rmSync } from 'fs';\nimport { tmpdir } from 'os';\nimport { join } from 'path';\nimport { spawnSync } from 'child_process';\nimport { OkraRuntimeError } from '../errors.js';\nimport type { LocalDoctorReport, LocalToolAvailability } from './types.js';\n\nconst TOOL_NAMES = ['pdftotext', 'pdfinfo', 'pdftoppm', 'tesseract'] as const;\n\ntype ToolName = (typeof TOOL_NAMES)[number];\n\ninterface ExtractedPage {\n pageNumber: number;\n text: string;\n ocrApplied: boolean;\n}\n\nexport interface ExtractedDocument {\n pageCount: number;\n charCount: number;\n ocrUsed: boolean;\n pages: ExtractedPage[];\n tools: LocalDoctorReport['tools'];\n}\n\nfunction runCommand(command: string, args: string[]): string {\n const result = spawnSync(command, args, {\n encoding: 'utf8',\n maxBuffer: 20 * 1024 * 1024,\n });\n\n if (result.error) {\n throw new OkraRuntimeError(\n 'INVALID_REQUEST',\n `Failed to run ${command}: ${result.error.message}`,\n 500,\n );\n }\n\n if (result.status !== 0) {\n const stderr = (result.stderr || '').trim();\n throw new OkraRuntimeError(\n 'INVALID_REQUEST',\n stderr ? `${command} failed: ${stderr}` : `${command} exited with code ${result.status}`,\n 500,\n );\n }\n\n return result.stdout ?? '';\n}\n\nfunction detectTool(name: ToolName): LocalToolAvailability {\n const result = spawnSync('/bin/zsh', ['-lc', `command -v ${name}`], { encoding: 'utf8' });\n const path = result.status === 0 ? (result.stdout || '').trim() : '';\n return {\n available: path.length > 0,\n path: path || null,\n };\n}\n\nfunction splitPages(raw: string, pageCount: number): string[] {\n const pages = raw\n .split('\\f')\n .map((value) => value.replace(/\\r/g, '').trimEnd());\n\n while (pages.length > 0 && pages[pages.length - 1]?.trim() === '') {\n pages.pop();\n }\n\n if (pages.length < pageCount) {\n return [...pages, ...new Array(pageCount - pages.length).fill('')];\n }\n\n return pages.slice(0, pageCount);\n}\n\nfunction normalizeWhitespace(text: string): string {\n return text.replace(/\\r/g, '').replace(/[ \\t]+\\n/g, '\\n').replace(/\\n{3,}/g, '\\n\\n').trim();\n}\n\nfunction needsOcr(text: string): boolean {\n const normalized = normalizeWhitespace(text);\n if (!normalized) return true;\n const alphaNumeric = (normalized.match(/[A-Za-z0-9]/g) || []).length;\n return normalized.length < 80 || alphaNumeric / normalized.length < 0.35;\n}\n\nfunction extractPageCount(sourcePath: string): number {\n const output = runCommand('pdfinfo', [sourcePath]);\n const match = output.match(/^Pages:\\s+(\\d+)$/m);\n if (!match) {\n throw new OkraRuntimeError('INVALID_RESPONSE', `Unable to determine page count for ${sourcePath}`, 500);\n }\n return Number.parseInt(match[1]!, 10);\n}\n\nfunction ocrPage(sourcePath: string, pageNumber: number): string {\n const tempDir = mkdtempSync(join(tmpdir(), 'okra-local-ocr-'));\n const prefix = join(tempDir, `page-${pageNumber}`);\n try {\n runCommand('pdftoppm', ['-f', String(pageNumber), '-l', String(pageNumber), '-png', sourcePath, prefix]);\n const imagePath = `${prefix}-${pageNumber}.png`;\n if (!existsSync(imagePath)) {\n throw new OkraRuntimeError(\n 'INVALID_RESPONSE',\n `pdftoppm did not produce an image for page ${pageNumber}`,\n 500,\n );\n }\n return normalizeWhitespace(runCommand('tesseract', [imagePath, 'stdout', '--psm', '6']));\n } finally {\n rmSync(tempDir, { recursive: true, force: true });\n }\n}\n\nexport function localToolReport(dataDir: string): LocalDoctorReport {\n const tools = {\n pdftotext: detectTool('pdftotext'),\n pdfinfo: detectTool('pdfinfo'),\n pdftoppm: detectTool('pdftoppm'),\n tesseract: detectTool('tesseract'),\n };\n\n return {\n ok: tools.pdftotext.available && tools.pdfinfo.available,\n dataDir,\n tools,\n };\n}\n\nexport function extractLocalDocument(sourcePath: string, dataDir: string): ExtractedDocument {\n const report = localToolReport(dataDir);\n if (!report.tools.pdftotext.available || !report.tools.pdfinfo.available) {\n throw new OkraRuntimeError(\n 'INVALID_REQUEST',\n 'Local PDF extraction requires pdftotext and pdfinfo to be installed',\n 500,\n report.tools,\n );\n }\n\n const pageCount = extractPageCount(sourcePath);\n const raw = runCommand('pdftotext', ['-layout', '-enc', 'UTF-8', sourcePath, '-']);\n const split = splitPages(raw, pageCount);\n\n let charCount = 0;\n let ocrUsed = false;\n const pages = split.map((pageText, index) => {\n const pageNumber = index + 1;\n let text = normalizeWhitespace(pageText);\n let ocrApplied = false;\n\n if (needsOcr(text) && report.tools.pdftoppm.available && report.tools.tesseract.available) {\n const ocrText = ocrPage(sourcePath, pageNumber);\n if (ocrText.length > text.length) {\n text = ocrText;\n }\n ocrApplied = ocrText.length > 0;\n ocrUsed = ocrUsed || ocrApplied;\n }\n\n charCount += text.length;\n return { pageNumber, text, ocrApplied };\n });\n\n return {\n pageCount,\n charCount,\n ocrUsed,\n pages,\n tools: {\n ...report.tools,\n tesseract: report.tools.tesseract,\n pdftoppm: report.tools.pdftoppm,\n pdftotext: report.tools.pdftotext,\n pdfinfo: report.tools.pdfinfo,\n },\n };\n}\n\n"],"mappings":"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","names":["existsSync","existsSync","rmSync","join","join","existsSync","rmSync","existsSync"]}
|