@ssmeo/mcp-local-rag 0.18.6
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/LICENSE +21 -0
- package/README.de.md +441 -0
- package/README.es.md +441 -0
- package/README.fr.md +441 -0
- package/README.md +530 -0
- package/README.pt-BR.md +441 -0
- package/README.zh-CN.md +441 -0
- package/dist/bin/install-skills.d.ts +19 -0
- package/dist/bin/install-skills.d.ts.map +1 -0
- package/dist/bin/install-skills.js +218 -0
- package/dist/bin/install-skills.js.map +1 -0
- package/dist/chunker/index.d.ts +24 -0
- package/dist/chunker/index.d.ts.map +1 -0
- package/dist/chunker/index.js +2 -0
- package/dist/chunker/index.js.map +1 -0
- package/dist/chunker/semantic-chunker.d.ts +96 -0
- package/dist/chunker/semantic-chunker.d.ts.map +1 -0
- package/dist/chunker/semantic-chunker.js +362 -0
- package/dist/chunker/semantic-chunker.js.map +1 -0
- package/dist/chunker/sentence-splitter.d.ts +32 -0
- package/dist/chunker/sentence-splitter.d.ts.map +1 -0
- package/dist/chunker/sentence-splitter.js +231 -0
- package/dist/chunker/sentence-splitter.js.map +1 -0
- package/dist/chunker/token-containment.d.ts +39 -0
- package/dist/chunker/token-containment.d.ts.map +1 -0
- package/dist/chunker/token-containment.js +83 -0
- package/dist/chunker/token-containment.js.map +1 -0
- package/dist/cli/common.d.ts +34 -0
- package/dist/cli/common.d.ts.map +1 -0
- package/dist/cli/common.js +121 -0
- package/dist/cli/common.js.map +1 -0
- package/dist/cli/delete.d.ts +3 -0
- package/dist/cli/delete.d.ts.map +1 -0
- package/dist/cli/delete.js +169 -0
- package/dist/cli/delete.js.map +1 -0
- package/dist/cli/file-collection.d.ts +2 -0
- package/dist/cli/file-collection.d.ts.map +1 -0
- package/dist/cli/file-collection.js +53 -0
- package/dist/cli/file-collection.js.map +1 -0
- package/dist/cli/ingest.d.ts +69 -0
- package/dist/cli/ingest.d.ts.map +1 -0
- package/dist/cli/ingest.js +323 -0
- package/dist/cli/ingest.js.map +1 -0
- package/dist/cli/list.d.ts +27 -0
- package/dist/cli/list.d.ts.map +1 -0
- package/dist/cli/list.js +207 -0
- package/dist/cli/list.js.map +1 -0
- package/dist/cli/options.d.ts +89 -0
- package/dist/cli/options.d.ts.map +1 -0
- package/dist/cli/options.js +244 -0
- package/dist/cli/options.js.map +1 -0
- package/dist/cli/query.d.ts +17 -0
- package/dist/cli/query.d.ts.map +1 -0
- package/dist/cli/query.js +194 -0
- package/dist/cli/query.js.map +1 -0
- package/dist/cli/read-neighbors.d.ts +3 -0
- package/dist/cli/read-neighbors.d.ts.map +1 -0
- package/dist/cli/read-neighbors.js +192 -0
- package/dist/cli/read-neighbors.js.map +1 -0
- package/dist/cli/status.d.ts +6 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +78 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/sync.d.ts +6 -0
- package/dist/cli/sync.d.ts.map +1 -0
- package/dist/cli/sync.js +271 -0
- package/dist/cli/sync.js.map +1 -0
- package/dist/cli-main.d.ts +9 -0
- package/dist/cli-main.d.ts.map +1 -0
- package/dist/cli-main.js +60 -0
- package/dist/cli-main.js.map +1 -0
- package/dist/embedder/index.d.ts +143 -0
- package/dist/embedder/index.d.ts.map +1 -0
- package/dist/embedder/index.js +429 -0
- package/dist/embedder/index.js.map +1 -0
- package/dist/embedder/remote.d.ts +51 -0
- package/dist/embedder/remote.d.ts.map +1 -0
- package/dist/embedder/remote.js +182 -0
- package/dist/embedder/remote.js.map +1 -0
- package/dist/features/list.d.ts +37 -0
- package/dist/features/list.d.ts.map +1 -0
- package/dist/features/list.js +41 -0
- package/dist/features/list.js.map +1 -0
- package/dist/features/sync.d.ts +219 -0
- package/dist/features/sync.d.ts.map +1 -0
- package/dist/features/sync.js +461 -0
- package/dist/features/sync.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest/compute.d.ts +63 -0
- package/dist/ingest/compute.d.ts.map +1 -0
- package/dist/ingest/compute.js +161 -0
- package/dist/ingest/compute.js.map +1 -0
- package/dist/ingest/file.d.ts +40 -0
- package/dist/ingest/file.d.ts.map +1 -0
- package/dist/ingest/file.js +74 -0
- package/dist/ingest/file.js.map +1 -0
- package/dist/ingest/visual.d.ts +52 -0
- package/dist/ingest/visual.d.ts.map +1 -0
- package/dist/ingest/visual.js +294 -0
- package/dist/ingest/visual.js.map +1 -0
- package/dist/parser/docx-parser.d.ts +12 -0
- package/dist/parser/docx-parser.d.ts.map +1 -0
- package/dist/parser/docx-parser.js +375 -0
- package/dist/parser/docx-parser.js.map +1 -0
- package/dist/parser/html-parser.d.ts +16 -0
- package/dist/parser/html-parser.d.ts.map +1 -0
- package/dist/parser/html-parser.js +119 -0
- package/dist/parser/html-parser.js.map +1 -0
- package/dist/parser/index.d.ts +125 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +364 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/pdf-extract.d.ts +49 -0
- package/dist/parser/pdf-extract.d.ts.map +1 -0
- package/dist/parser/pdf-extract.js +114 -0
- package/dist/parser/pdf-extract.js.map +1 -0
- package/dist/parser/pdf-filter.d.ts +91 -0
- package/dist/parser/pdf-filter.d.ts.map +1 -0
- package/dist/parser/pdf-filter.js +522 -0
- package/dist/parser/pdf-filter.js.map +1 -0
- package/dist/parser/title-extractor.d.ts +28 -0
- package/dist/parser/title-extractor.d.ts.map +1 -0
- package/dist/parser/title-extractor.js +105 -0
- package/dist/parser/title-extractor.js.map +1 -0
- package/dist/pdf-visual/captioner.d.ts +11 -0
- package/dist/pdf-visual/captioner.d.ts.map +1 -0
- package/dist/pdf-visual/captioner.js +48 -0
- package/dist/pdf-visual/captioner.js.map +1 -0
- package/dist/pdf-visual/captioners/fast.d.ts +7 -0
- package/dist/pdf-visual/captioners/fast.d.ts.map +1 -0
- package/dist/pdf-visual/captioners/fast.js +108 -0
- package/dist/pdf-visual/captioners/fast.js.map +1 -0
- package/dist/pdf-visual/captioners/quality.d.ts +7 -0
- package/dist/pdf-visual/captioners/quality.d.ts.map +1 -0
- package/dist/pdf-visual/captioners/quality.js +101 -0
- package/dist/pdf-visual/captioners/quality.js.map +1 -0
- package/dist/pdf-visual/captioners/shared.d.ts +81 -0
- package/dist/pdf-visual/captioners/shared.d.ts.map +1 -0
- package/dist/pdf-visual/captioners/shared.js +156 -0
- package/dist/pdf-visual/captioners/shared.js.map +1 -0
- package/dist/pdf-visual/detector.d.ts +9 -0
- package/dist/pdf-visual/detector.d.ts.map +1 -0
- package/dist/pdf-visual/detector.js +269 -0
- package/dist/pdf-visual/detector.js.map +1 -0
- package/dist/pdf-visual/index.d.ts +13 -0
- package/dist/pdf-visual/index.d.ts.map +1 -0
- package/dist/pdf-visual/index.js +53 -0
- package/dist/pdf-visual/index.js.map +1 -0
- package/dist/pdf-visual/renderer.d.ts +9 -0
- package/dist/pdf-visual/renderer.d.ts.map +1 -0
- package/dist/pdf-visual/renderer.js +186 -0
- package/dist/pdf-visual/renderer.js.map +1 -0
- package/dist/pdf-visual/types.d.ts +53 -0
- package/dist/pdf-visual/types.d.ts.map +1 -0
- package/dist/pdf-visual/types.js +17 -0
- package/dist/pdf-visual/types.js.map +1 -0
- package/dist/server/error-utils.d.ts +63 -0
- package/dist/server/error-utils.d.ts.map +1 -0
- package/dist/server/error-utils.js +124 -0
- package/dist/server/error-utils.js.map +1 -0
- package/dist/server/index.d.ts +216 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +1068 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/list-scanner.d.ts +30 -0
- package/dist/server/list-scanner.d.ts.map +1 -0
- package/dist/server/list-scanner.js +50 -0
- package/dist/server/list-scanner.js.map +1 -0
- package/dist/server/tool-definitions.d.ts +8 -0
- package/dist/server/tool-definitions.d.ts.map +1 -0
- package/dist/server/tool-definitions.js +181 -0
- package/dist/server/tool-definitions.js.map +1 -0
- package/dist/server/tool-input.d.ts +37 -0
- package/dist/server/tool-input.d.ts.map +1 -0
- package/dist/server/tool-input.js +211 -0
- package/dist/server/tool-input.js.map +1 -0
- package/dist/server/types.d.ts +307 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/types.js +3 -0
- package/dist/server/types.js.map +1 -0
- package/dist/server-main.d.ts +40 -0
- package/dist/server-main.d.ts.map +1 -0
- package/dist/server-main.js +254 -0
- package/dist/server-main.js.map +1 -0
- package/dist/utils/base-dirs.d.ts +145 -0
- package/dist/utils/base-dirs.d.ts.map +1 -0
- package/dist/utils/base-dirs.js +344 -0
- package/dist/utils/base-dirs.js.map +1 -0
- package/dist/utils/errors.d.ts +32 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +61 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/limits.d.ts +26 -0
- package/dist/utils/limits.d.ts.map +1 -0
- package/dist/utils/limits.js +28 -0
- package/dist/utils/limits.js.map +1 -0
- package/dist/utils/list-sources.d.ts +38 -0
- package/dist/utils/list-sources.d.ts.map +1 -0
- package/dist/utils/list-sources.js +40 -0
- package/dist/utils/list-sources.js.map +1 -0
- package/dist/utils/raw-data-utils.d.ts +76 -0
- package/dist/utils/raw-data-utils.d.ts.map +1 -0
- package/dist/utils/raw-data-utils.js +202 -0
- package/dist/utils/raw-data-utils.js.map +1 -0
- package/dist/utils/scan.d.ts +114 -0
- package/dist/utils/scan.d.ts.map +1 -0
- package/dist/utils/scan.js +209 -0
- package/dist/utils/scan.js.map +1 -0
- package/dist/utils/scope-match.d.ts +38 -0
- package/dist/utils/scope-match.d.ts.map +1 -0
- package/dist/utils/scope-match.js +86 -0
- package/dist/utils/scope-match.js.map +1 -0
- package/dist/utils/sensitive-path.d.ts +9 -0
- package/dist/utils/sensitive-path.d.ts.map +1 -0
- package/dist/utils/sensitive-path.js +78 -0
- package/dist/utils/sensitive-path.js.map +1 -0
- package/dist/utils/sync-path-key.d.ts +14 -0
- package/dist/utils/sync-path-key.d.ts.map +1 -0
- package/dist/utils/sync-path-key.js +27 -0
- package/dist/utils/sync-path-key.js.map +1 -0
- package/dist/utils/type-guards.d.ts +18 -0
- package/dist/utils/type-guards.d.ts.map +1 -0
- package/dist/utils/type-guards.js +34 -0
- package/dist/utils/type-guards.js.map +1 -0
- package/dist/utils/visual-profile.d.ts +10 -0
- package/dist/utils/visual-profile.d.ts.map +1 -0
- package/dist/utils/visual-profile.js +15 -0
- package/dist/utils/visual-profile.js.map +1 -0
- package/dist/vectordb/index.d.ts +132 -0
- package/dist/vectordb/index.d.ts.map +1 -0
- package/dist/vectordb/index.js +617 -0
- package/dist/vectordb/index.js.map +1 -0
- package/dist/vectordb/search-filters.d.ts +15 -0
- package/dist/vectordb/search-filters.d.ts.map +1 -0
- package/dist/vectordb/search-filters.js +129 -0
- package/dist/vectordb/search-filters.js.map +1 -0
- package/dist/vectordb/types.d.ts +194 -0
- package/dist/vectordb/types.d.ts.map +1 -0
- package/dist/vectordb/types.js +237 -0
- package/dist/vectordb/types.js.map +1 -0
- package/package.json +108 -0
- package/skills/mcp-local-rag/SKILL.md +234 -0
- package/skills/mcp-local-rag/references/cli-reference.md +190 -0
- package/skills/mcp-local-rag/references/html-ingestion.md +78 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Shared chunk + embed computation for the ingest pipeline: the single
|
|
2
|
+
// `chunkText -> embedBatch` call site for any ingest path.
|
|
3
|
+
//
|
|
4
|
+
// Persistence (delete + insert + rollback + optimize) stays in each caller,
|
|
5
|
+
// because the rollback semantics differ between the MCP and CLI paths.
|
|
6
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
7
|
+
import { basename, extname } from 'node:path';
|
|
8
|
+
import { MAX_VISUAL_RENDITION_BYTES } from '../utils/limits.js';
|
|
9
|
+
/** Distance from `offset` to a chunk's source span; 0 when it falls inside. */
|
|
10
|
+
function distanceToSpan(offset, chunk) {
|
|
11
|
+
if (offset < chunk.sourceStart) {
|
|
12
|
+
return chunk.sourceStart - offset;
|
|
13
|
+
}
|
|
14
|
+
if (offset > chunk.sourceEnd) {
|
|
15
|
+
return offset - chunk.sourceEnd;
|
|
16
|
+
}
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
export function findNearestChunk(chunks, sourceOffset) {
|
|
20
|
+
let owner;
|
|
21
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
22
|
+
for (const chunk of chunks) {
|
|
23
|
+
const distance = distanceToSpan(sourceOffset, chunk);
|
|
24
|
+
if (!owner) {
|
|
25
|
+
owner = chunk;
|
|
26
|
+
bestDistance = distance;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const isPreceding = chunk.sourceEnd <= sourceOffset;
|
|
30
|
+
const ownerIsPreceding = owner.sourceEnd <= sourceOffset;
|
|
31
|
+
if (distance < bestDistance ||
|
|
32
|
+
(distance === bestDistance && isPreceding && !ownerIsPreceding) ||
|
|
33
|
+
(distance === bestDistance && isPreceding === ownerIsPreceding && chunk.index < owner.index)) {
|
|
34
|
+
owner = chunk;
|
|
35
|
+
bestDistance = distance;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return owner;
|
|
39
|
+
}
|
|
40
|
+
export function createVisualAttachment(imageIndex, rendition) {
|
|
41
|
+
if (!Number.isInteger(imageIndex) ||
|
|
42
|
+
imageIndex < 0 ||
|
|
43
|
+
rendition.bytes.byteLength === 0 ||
|
|
44
|
+
rendition.bytes.byteLength > MAX_VISUAL_RENDITION_BYTES) {
|
|
45
|
+
throw new Error('Invalid bounded image rendition');
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
imageIndex,
|
|
49
|
+
mimeType: rendition.mimeType,
|
|
50
|
+
data: Buffer.from(rendition.bytes).toString('base64'),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Compute semantic chunks and their embeddings for already-extracted text.
|
|
55
|
+
*
|
|
56
|
+
* Does not fail fast on zero chunks — the MCP handler throws, the CLI warns
|
|
57
|
+
* and returns 0. Chunker and embedder errors propagate verbatim.
|
|
58
|
+
*/
|
|
59
|
+
export async function buildChunksAndEmbeddings(text, chunker, embedder, atomicRanges) {
|
|
60
|
+
const chunks = await chunker.chunkText(text, embedder, atomicRanges);
|
|
61
|
+
// F5: Skip `embedBatch` entirely on zero chunks. `embedBatch` runs
|
|
62
|
+
// `ensureInitialized()` (which triggers the ~90MB MiniLM download on a
|
|
63
|
+
// cold cache) BEFORE checking for the empty-array short-circuit, so an
|
|
64
|
+
// empty file would otherwise pay the model-load cost for no work.
|
|
65
|
+
if (chunks.length === 0) {
|
|
66
|
+
return { chunks: [], embeddings: [] };
|
|
67
|
+
}
|
|
68
|
+
const embeddings = await embedder.embedBatch(chunks.map((chunk) => chunk.text));
|
|
69
|
+
return { chunks, embeddings };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Preserve the parser content/range mapping at one shared boundary. Display
|
|
73
|
+
* title handling stays in each dispatch root because it does not affect chunks.
|
|
74
|
+
*/
|
|
75
|
+
export async function buildChunksFromParseResult(result, chunker, embedder) {
|
|
76
|
+
const computed = await buildChunksAndEmbeddings(result.content, chunker, embedder, result.atomicRanges);
|
|
77
|
+
const visualAttachments = new Map();
|
|
78
|
+
if (!result.imageAnchors?.length || computed.chunks.length === 0) {
|
|
79
|
+
return { ...computed, visualAttachments, omittedImageCount: 0 };
|
|
80
|
+
}
|
|
81
|
+
const { renderImageRendition } = await import('../pdf-visual/renderer.js');
|
|
82
|
+
let omittedCount = 0;
|
|
83
|
+
for (const anchor of [...result.imageAnchors].sort((left, right) => left.imageIndex - right.imageIndex)) {
|
|
84
|
+
const owner = findNearestChunk(computed.chunks, anchor.offset);
|
|
85
|
+
if (!owner) {
|
|
86
|
+
throw new Error(`Image ${anchor.imageIndex} has no owning chunk`);
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const rendition = renderImageRendition(anchor.bytes, anchor.mimeType);
|
|
90
|
+
const attachment = createVisualAttachment(anchor.imageIndex, rendition);
|
|
91
|
+
const owned = visualAttachments.get(owner.index) ?? [];
|
|
92
|
+
owned.push(attachment);
|
|
93
|
+
visualAttachments.set(owner.index, owned);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
omittedCount += 1;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { ...computed, visualAttachments, omittedImageCount: omittedCount };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Content identity of a source file: SHA-256 of its raw BYTES, not of the
|
|
103
|
+
* parsed text, so any caller that can read the file reproduces it — which is
|
|
104
|
+
* what lets a later sync decide "unchanged" without re-parsing.
|
|
105
|
+
*/
|
|
106
|
+
export function computeContentHash(bytes) {
|
|
107
|
+
return createHash('sha256').update(bytes).digest('hex');
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Build persistable `VectorChunk`s from computed chunks + embeddings — the
|
|
111
|
+
* single chunk→row mapping for both the MCP handler and both CLI paths. One
|
|
112
|
+
* shared `timestamp` per call, a fresh `id`, and `fileName`/`fileType` derived
|
|
113
|
+
* with `node:path` so they hold on Windows.
|
|
114
|
+
*
|
|
115
|
+
* Throws when a chunk has no embedding; the two arrays must align 1:1.
|
|
116
|
+
*
|
|
117
|
+
* `contentHash` is `null` for a chunk set with no source file, and the key is
|
|
118
|
+
* then omitted rather than stored empty, so a hashless row is never mistaken
|
|
119
|
+
* for a real hash. Required, not optional, so a new call site cannot silently
|
|
120
|
+
* write hashless rows.
|
|
121
|
+
*
|
|
122
|
+
* `visualProfile` is the requested visual intent of this ingestion, copied to
|
|
123
|
+
* every row. It accepts `null`/`undefined` and then omits the optional property,
|
|
124
|
+
* so a caller holding a nullable value can pass it directly under
|
|
125
|
+
* `exactOptionalPropertyTypes` and a caller with no visual concept can omit it.
|
|
126
|
+
*/
|
|
127
|
+
export function buildVectorChunks(params) {
|
|
128
|
+
const { filePath, chunks, embeddings, fileSize, fileTitle, contentHash, visualAttachments = new Map(), visualProfile = null, } = params;
|
|
129
|
+
const timestamp = new Date().toISOString();
|
|
130
|
+
return chunks.map((chunk, index) => {
|
|
131
|
+
const embedding = embeddings[index];
|
|
132
|
+
if (!embedding) {
|
|
133
|
+
throw new Error(`Missing embedding for chunk ${index}`);
|
|
134
|
+
}
|
|
135
|
+
const attachments = [...(visualAttachments.get(chunk.index) ?? [])]
|
|
136
|
+
.sort((left, right) => left.imageIndex - right.imageIndex)
|
|
137
|
+
.map(({ imageIndex, mimeType, data }) => ({
|
|
138
|
+
imageIndex,
|
|
139
|
+
mimeType,
|
|
140
|
+
data,
|
|
141
|
+
}));
|
|
142
|
+
return {
|
|
143
|
+
id: randomUUID(),
|
|
144
|
+
filePath,
|
|
145
|
+
chunkIndex: chunk.index,
|
|
146
|
+
text: chunk.text,
|
|
147
|
+
vector: embedding,
|
|
148
|
+
metadata: {
|
|
149
|
+
fileName: basename(filePath),
|
|
150
|
+
fileSize,
|
|
151
|
+
fileType: extname(filePath).slice(1),
|
|
152
|
+
},
|
|
153
|
+
fileTitle,
|
|
154
|
+
...(contentHash === null ? {} : { contentHash }),
|
|
155
|
+
...(visualProfile === null || visualProfile === undefined ? {} : { visualProfile }),
|
|
156
|
+
visualAttachments: JSON.stringify(attachments),
|
|
157
|
+
timestamp,
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=compute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compute.js","sourceRoot":"","sources":["../../src/ingest/compute.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { SemanticChunker, TextChunk } from '../chunker/index.js';
|
|
2
|
+
import type { EmbedderInterface } from '../chunker/semantic-chunker.js';
|
|
3
|
+
import type { DocumentParser } from '../parser/index.js';
|
|
4
|
+
import type { QualityProfile } from '../utils/visual-profile.js';
|
|
5
|
+
import type { VectorChunk, VisualAttachment } from '../vectordb/index.js';
|
|
6
|
+
import { type CaptionerConfig } from './visual.js';
|
|
7
|
+
/** Collaborators one file ingest run needs, injected as a unit. */
|
|
8
|
+
export interface FileIngestCollaborators {
|
|
9
|
+
parser: DocumentParser;
|
|
10
|
+
chunker: SemanticChunker;
|
|
11
|
+
embedder: EmbedderInterface;
|
|
12
|
+
}
|
|
13
|
+
export interface PrepareFileForIngestOptions {
|
|
14
|
+
images: boolean;
|
|
15
|
+
captioner?: CaptionerConfig;
|
|
16
|
+
}
|
|
17
|
+
export interface PreparedFileIngest {
|
|
18
|
+
filePath: string;
|
|
19
|
+
chunks: TextChunk[];
|
|
20
|
+
embeddings: number[][];
|
|
21
|
+
textLength: number;
|
|
22
|
+
title: string | null;
|
|
23
|
+
contentHash: string;
|
|
24
|
+
/** Requested visual intent of this run: a profile for a captioned PDF, `null` otherwise. */
|
|
25
|
+
visualProfile: QualityProfile | null;
|
|
26
|
+
visualAttachments: Map<number, VisualAttachment[]>;
|
|
27
|
+
omittedImageCount: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function buildPreparedFileVectorChunks(prepared: PreparedFileIngest): VectorChunk[];
|
|
30
|
+
/**
|
|
31
|
+
* Prepare one validated source file for persistence without touching storage or
|
|
32
|
+
* process output. The source hash is read before parsing so a concurrent rewrite
|
|
33
|
+
* remains visible to the next sync instead of being paired with stale chunks.
|
|
34
|
+
*
|
|
35
|
+
* The visual profile is resolved from the request before any caption outcome is
|
|
36
|
+
* known, so it records what was asked for: a run with no qualifying region or a
|
|
37
|
+
* tolerated caption failure still carries the requested profile.
|
|
38
|
+
*/
|
|
39
|
+
export declare function prepareFileForIngest(filePath: string, collaborators: FileIngestCollaborators, options: PrepareFileForIngestOptions): Promise<PreparedFileIngest>;
|
|
40
|
+
//# sourceMappingURL=file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/ingest/file.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AACrE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAEzE,OAAO,EACL,KAAK,eAAe,EAGrB,MAAM,aAAa,CAAA;AAEpB,mEAAmE;AACnE,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,cAAc,CAAA;IACtB,OAAO,EAAE,eAAe,CAAA;IACxB,QAAQ,EAAE,iBAAiB,CAAA;CAC5B;AAED,MAAM,WAAW,2BAA2B;IAC1C,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,CAAC,EAAE,eAAe,CAAA;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,SAAS,EAAE,CAAA;IACnB,UAAU,EAAE,MAAM,EAAE,EAAE,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,4FAA4F;IAC5F,aAAa,EAAE,cAAc,GAAG,IAAI,CAAA;IACpC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAA;IAClD,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAED,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,kBAAkB,GAAG,WAAW,EAAE,CAWzF;AAQD;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,uBAAuB,EACtC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,kBAAkB,CAAC,CA6C7B"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { buildChunksFromParseResult, buildVectorChunks, computeContentHash } from './compute.js';
|
|
3
|
+
import { prepareVisualPdfChunks, } from './visual.js';
|
|
4
|
+
export function buildPreparedFileVectorChunks(prepared) {
|
|
5
|
+
return buildVectorChunks({
|
|
6
|
+
filePath: prepared.filePath,
|
|
7
|
+
chunks: prepared.chunks,
|
|
8
|
+
embeddings: prepared.embeddings,
|
|
9
|
+
fileSize: prepared.textLength,
|
|
10
|
+
fileTitle: prepared.title,
|
|
11
|
+
contentHash: prepared.contentHash,
|
|
12
|
+
visualProfile: prepared.visualProfile,
|
|
13
|
+
visualAttachments: prepared.visualAttachments,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
async function readPreParseContentHash(filePath, parser) {
|
|
17
|
+
await parser.validateFilePath(filePath);
|
|
18
|
+
parser.validateFileSize(filePath);
|
|
19
|
+
return computeContentHash(await readFile(filePath));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Prepare one validated source file for persistence without touching storage or
|
|
23
|
+
* process output. The source hash is read before parsing so a concurrent rewrite
|
|
24
|
+
* remains visible to the next sync instead of being paired with stale chunks.
|
|
25
|
+
*
|
|
26
|
+
* The visual profile is resolved from the request before any caption outcome is
|
|
27
|
+
* known, so it records what was asked for: a run with no qualifying region or a
|
|
28
|
+
* tolerated caption failure still carries the requested profile.
|
|
29
|
+
*/
|
|
30
|
+
export async function prepareFileForIngest(filePath, collaborators, options) {
|
|
31
|
+
const { parser, chunker, embedder } = collaborators;
|
|
32
|
+
const contentHash = await readPreParseContentHash(filePath, parser);
|
|
33
|
+
const isPdf = filePath.toLowerCase().endsWith('.pdf');
|
|
34
|
+
let text;
|
|
35
|
+
let title;
|
|
36
|
+
let chunks;
|
|
37
|
+
let embeddings;
|
|
38
|
+
let visualAttachments;
|
|
39
|
+
let omittedImageCount;
|
|
40
|
+
if (isPdf && (options.captioner !== undefined || options.images)) {
|
|
41
|
+
const visualOptions = {
|
|
42
|
+
images: options.images,
|
|
43
|
+
...(options.captioner === undefined ? {} : { captioner: options.captioner }),
|
|
44
|
+
};
|
|
45
|
+
const result = await prepareVisualPdfChunks(filePath, collaborators, visualOptions);
|
|
46
|
+
text = result.text;
|
|
47
|
+
title = result.title;
|
|
48
|
+
chunks = result.chunks;
|
|
49
|
+
embeddings = result.embeddings;
|
|
50
|
+
visualAttachments = result.visualAttachments;
|
|
51
|
+
omittedImageCount = result.omittedImageCount;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
const result = isPdf
|
|
55
|
+
? await parser.parsePdf(filePath, embedder)
|
|
56
|
+
: await parser.parseFile(filePath, { images: options.images });
|
|
57
|
+
text = result.content;
|
|
58
|
+
title = result.title || null;
|
|
59
|
+
({ chunks, embeddings, visualAttachments, omittedImageCount } =
|
|
60
|
+
await buildChunksFromParseResult(result, chunker, embedder));
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
filePath,
|
|
64
|
+
chunks,
|
|
65
|
+
embeddings,
|
|
66
|
+
textLength: text.length,
|
|
67
|
+
title,
|
|
68
|
+
contentHash,
|
|
69
|
+
visualProfile: isPdf ? (options.captioner?.profile ?? null) : null,
|
|
70
|
+
visualAttachments,
|
|
71
|
+
omittedImageCount,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.js","sourceRoot":"","sources":["../../src/ingest/file.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { AtomicTextRange, SemanticChunker, TextChunk } from '../chunker/index.js';
|
|
2
|
+
import type { EmbedderInterface } from '../chunker/semantic-chunker.js';
|
|
3
|
+
import type { DocumentParser } from '../parser/index.js';
|
|
4
|
+
import type { FilteredTextFragment } from '../parser/pdf-filter.js';
|
|
5
|
+
import type { ProcessedVisualRegion } from '../pdf-visual/types.js';
|
|
6
|
+
import type { QualityProfile } from '../utils/visual-profile.js';
|
|
7
|
+
import type { VisualAttachment } from '../vectordb/types.js';
|
|
8
|
+
export interface VisualPdfParser {
|
|
9
|
+
parsePdfPages: DocumentParser['parsePdfPages'];
|
|
10
|
+
}
|
|
11
|
+
export interface CaptionerConfig {
|
|
12
|
+
profile: QualityProfile;
|
|
13
|
+
cacheDir: string;
|
|
14
|
+
device?: string | undefined;
|
|
15
|
+
}
|
|
16
|
+
/** Collaborators one visual PDF ingest run needs, injected as a unit. */
|
|
17
|
+
export interface VisualIngestCollaborators {
|
|
18
|
+
parser: VisualPdfParser;
|
|
19
|
+
chunker: SemanticChunker;
|
|
20
|
+
embedder: EmbedderInterface;
|
|
21
|
+
}
|
|
22
|
+
export interface PrepareVisualPdfChunksOptions {
|
|
23
|
+
images: boolean;
|
|
24
|
+
captioner?: CaptionerConfig;
|
|
25
|
+
}
|
|
26
|
+
export interface PrepareVisualPdfChunksResult {
|
|
27
|
+
chunks: TextChunk[];
|
|
28
|
+
embeddings: number[][];
|
|
29
|
+
title: string | null;
|
|
30
|
+
text: string;
|
|
31
|
+
atomicRanges: AtomicTextRange[];
|
|
32
|
+
visualAttachments: Map<number, VisualAttachment[]>;
|
|
33
|
+
omittedImageCount: number;
|
|
34
|
+
}
|
|
35
|
+
export interface OrderedVisualPage {
|
|
36
|
+
pageNum: number;
|
|
37
|
+
text: string;
|
|
38
|
+
textFragments: readonly FilteredTextFragment[];
|
|
39
|
+
}
|
|
40
|
+
export interface OrderedVisualRegion extends ProcessedVisualRegion {
|
|
41
|
+
visualIndex: number;
|
|
42
|
+
anchorOffset: number;
|
|
43
|
+
captionRange?: AtomicTextRange;
|
|
44
|
+
}
|
|
45
|
+
export interface OrderedVisualDocument {
|
|
46
|
+
text: string;
|
|
47
|
+
atomicRanges: AtomicTextRange[];
|
|
48
|
+
regions: OrderedVisualRegion[];
|
|
49
|
+
}
|
|
50
|
+
export declare function buildOrderedVisualDocument(pages: readonly OrderedVisualPage[], processedRegions: readonly ProcessedVisualRegion[]): OrderedVisualDocument;
|
|
51
|
+
export declare function prepareVisualPdfChunks(filePath: string, collaborators: VisualIngestCollaborators, options: PrepareVisualPdfChunksOptions): Promise<PrepareVisualPdfChunksResult>;
|
|
52
|
+
//# sourceMappingURL=visual.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual.d.ts","sourceRoot":"","sources":["../../src/ingest/visual.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AACtF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAA;AACnE,OAAO,KAAK,EAAwB,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AACzF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAG5D,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,cAAc,CAAC,eAAe,CAAC,CAAA;CAC/C;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,cAAc,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC5B;AAED,yEAAyE;AACzE,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,eAAe,CAAA;IACvB,OAAO,EAAE,eAAe,CAAA;IACxB,QAAQ,EAAE,iBAAiB,CAAA;CAC5B;AAED,MAAM,WAAW,6BAA6B;IAC5C,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,CAAC,EAAE,eAAe,CAAA;CAC5B;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,SAAS,EAAE,CAAA;IACnB,UAAU,EAAE,MAAM,EAAE,EAAE,CAAA;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,eAAe,EAAE,CAAA;IAC/B,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAA;IAClD,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,EAAE,SAAS,oBAAoB,EAAE,CAAA;CAC/C;AAED,MAAM,WAAW,mBAAoB,SAAQ,qBAAqB;IAChE,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,eAAe,CAAA;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,eAAe,EAAE,CAAA;IAC/B,OAAO,EAAE,mBAAmB,EAAE,CAAA;CAC/B;AAiND,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,gBAAgB,EAAE,SAAS,qBAAqB,EAAE,GACjD,qBAAqB,CAmCvB;AAoHD,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,yBAAyB,EACxC,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,4BAA4B,CAAC,CAyCvC"}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { buildChunksAndEmbeddings, createVisualAttachment, findNearestChunk } from './compute.js';
|
|
2
|
+
function horizontalOverlap(left, right) {
|
|
3
|
+
return Math.min(left[2] ?? 0, right[2] ?? 0) >= Math.max(left[0] ?? 0, right[0] ?? 0);
|
|
4
|
+
}
|
|
5
|
+
function xCenter(bbox) {
|
|
6
|
+
return ((bbox[0] ?? 0) + (bbox[2] ?? 0)) / 2;
|
|
7
|
+
}
|
|
8
|
+
function yCenter(bbox) {
|
|
9
|
+
return ((bbox[1] ?? 0) + (bbox[3] ?? 0)) / 2;
|
|
10
|
+
}
|
|
11
|
+
function findInsertionIndex(fragments, region) {
|
|
12
|
+
if (fragments.length === 0) {
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
let candidates = fragments
|
|
16
|
+
.map((fragment, index) => ({ fragment, index }))
|
|
17
|
+
.filter(({ fragment }) => horizontalOverlap(fragment.bbox, region.bbox));
|
|
18
|
+
if (candidates.length === 0) {
|
|
19
|
+
const regionCenter = xCenter(region.bbox);
|
|
20
|
+
const minimumDistance = Math.min(...fragments.map((fragment) => Math.abs(xCenter(fragment.bbox) - regionCenter)));
|
|
21
|
+
candidates = fragments
|
|
22
|
+
.map((fragment, index) => ({ fragment, index }))
|
|
23
|
+
.filter(({ fragment }) => Math.abs(xCenter(fragment.bbox) - regionCenter) === minimumDistance);
|
|
24
|
+
}
|
|
25
|
+
const regionMidpoint = yCenter(region.bbox);
|
|
26
|
+
const preceding = candidates.filter(({ fragment }) => yCenter(fragment.bbox) <= regionMidpoint);
|
|
27
|
+
if (preceding.length > 0) {
|
|
28
|
+
return Math.max(...preceding.map(({ index }) => index + 1));
|
|
29
|
+
}
|
|
30
|
+
return Math.min(...candidates.map(({ index }) => index));
|
|
31
|
+
}
|
|
32
|
+
function fallbackFragments(page) {
|
|
33
|
+
if (page.textFragments.length > 0 || page.text.length === 0) {
|
|
34
|
+
return page.textFragments;
|
|
35
|
+
}
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
pageNum: page.pageNum,
|
|
39
|
+
blockOrdinal: 0,
|
|
40
|
+
lineOrdinal: 0,
|
|
41
|
+
fragmentOrdinal: 0,
|
|
42
|
+
bbox: [0, 0, 0, 0],
|
|
43
|
+
text: page.text,
|
|
44
|
+
pageTextStart: 0,
|
|
45
|
+
pageTextEnd: page.text.length,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
/** Regions on one page, ordered by insertion slot then detection order. */
|
|
50
|
+
function positionRegions(page, pageFragments, processedRegions) {
|
|
51
|
+
return processedRegions
|
|
52
|
+
.filter((region) => region.pageNum === page.pageNum)
|
|
53
|
+
.map((region) => ({ region, insertionIndex: findInsertionIndex(pageFragments, region) }))
|
|
54
|
+
.sort((left, right) => left.insertionIndex - right.insertionIndex ||
|
|
55
|
+
left.region.detectionIndex - right.region.detectionIndex);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Group regions by the character offset in the page text they are inserted at,
|
|
59
|
+
* so regions landing in the same gap are emitted together.
|
|
60
|
+
*/
|
|
61
|
+
function groupByInsertionOffset(page, pageFragments, positioned) {
|
|
62
|
+
const groups = new Map();
|
|
63
|
+
for (const item of positioned) {
|
|
64
|
+
const preceding = pageFragments[item.insertionIndex - 1];
|
|
65
|
+
const following = pageFragments[item.insertionIndex];
|
|
66
|
+
const offset = Math.max(0, Math.min(page.text.length, preceding?.pageTextEnd ?? following?.pageTextStart ?? 0));
|
|
67
|
+
const group = groups.get(offset) ?? [];
|
|
68
|
+
group.push(item.region);
|
|
69
|
+
groups.set(offset, group);
|
|
70
|
+
}
|
|
71
|
+
return groups;
|
|
72
|
+
}
|
|
73
|
+
/** Newlines needed so `adjacent` is separated by a blank line. */
|
|
74
|
+
function blankLinePadding(adjacent) {
|
|
75
|
+
const present = adjacent.match(/^\n*/)?.[0].length ?? 0;
|
|
76
|
+
return '\n'.repeat(Math.max(0, 2 - present));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Append one insertion group's captions to `pageText`, recording each region's
|
|
80
|
+
* caption range and anchor offset. Uncaptioned regions only get an anchor.
|
|
81
|
+
*/
|
|
82
|
+
function appendGroupCaptions(pageText, group, context) {
|
|
83
|
+
const { page, pageVisualIndices, ...into } = context;
|
|
84
|
+
let text = pageText;
|
|
85
|
+
let captionIndex = 0;
|
|
86
|
+
for (const region of group) {
|
|
87
|
+
if (region.caption === null) {
|
|
88
|
+
into.anchorOffsets.set(region, text.length);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (captionIndex > 0) {
|
|
92
|
+
text += '\n\n';
|
|
93
|
+
}
|
|
94
|
+
const start = text.length;
|
|
95
|
+
text += `[Visual content on page ${page.pageNum}, visual ${pageVisualIndices.get(region)}: ${region.caption}]`;
|
|
96
|
+
into.captionRanges.set(region, { start, end: text.length });
|
|
97
|
+
into.anchorOffsets.set(region, start);
|
|
98
|
+
captionIndex += 1;
|
|
99
|
+
}
|
|
100
|
+
return text;
|
|
101
|
+
}
|
|
102
|
+
function renderPageWithCaptions(page, insertionGroups, pageVisualIndices) {
|
|
103
|
+
let pageText = '';
|
|
104
|
+
let pageCursor = 0;
|
|
105
|
+
const captionRanges = new Map();
|
|
106
|
+
const anchorOffsets = new Map();
|
|
107
|
+
for (const [offset, group] of [...insertionGroups].sort((left, right) => left[0] - right[0])) {
|
|
108
|
+
pageText += page.text.slice(pageCursor, offset);
|
|
109
|
+
const hasCaption = group.some((region) => region.caption !== null);
|
|
110
|
+
if (hasCaption && pageText.length > 0) {
|
|
111
|
+
pageText += blankLinePadding(pageText.match(/\n*$/)?.[0] ?? '');
|
|
112
|
+
}
|
|
113
|
+
pageText = appendGroupCaptions(pageText, group, {
|
|
114
|
+
page,
|
|
115
|
+
pageVisualIndices,
|
|
116
|
+
captionRanges,
|
|
117
|
+
anchorOffsets,
|
|
118
|
+
});
|
|
119
|
+
if (hasCaption && offset < page.text.length) {
|
|
120
|
+
pageText += blankLinePadding(page.text.slice(offset));
|
|
121
|
+
}
|
|
122
|
+
pageCursor = offset;
|
|
123
|
+
}
|
|
124
|
+
pageText += page.text.slice(pageCursor);
|
|
125
|
+
return { pageText, captionRanges, anchorOffsets };
|
|
126
|
+
}
|
|
127
|
+
/** Rebase one page-relative region onto the whole-document text. */
|
|
128
|
+
function toOrderedRegion(region, rendered, pageStart, visualIndex) {
|
|
129
|
+
const relative = rendered.captionRanges.get(region);
|
|
130
|
+
const captionRange = relative
|
|
131
|
+
? { start: pageStart + relative.start, end: pageStart + relative.end }
|
|
132
|
+
: undefined;
|
|
133
|
+
const anchorOffset = rendered.anchorOffsets.get(region);
|
|
134
|
+
if (anchorOffset === undefined) {
|
|
135
|
+
throw new Error('Visual region has no anchor offset');
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
...region,
|
|
139
|
+
visualIndex,
|
|
140
|
+
anchorOffset: pageStart + anchorOffset,
|
|
141
|
+
...(captionRange ? { captionRange } : {}),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
export function buildOrderedVisualDocument(pages, processedRegions) {
|
|
145
|
+
let text = '';
|
|
146
|
+
let visualIndex = 0;
|
|
147
|
+
const atomicRanges = [];
|
|
148
|
+
const regions = [];
|
|
149
|
+
const consumedRegions = new Set();
|
|
150
|
+
for (const page of pages) {
|
|
151
|
+
const pageFragments = fallbackFragments(page);
|
|
152
|
+
const positioned = positionRegions(page, pageFragments, processedRegions);
|
|
153
|
+
const pageVisualIndices = new Map(positioned.map((item, index) => [item.region, visualIndex + index]));
|
|
154
|
+
const insertionGroups = groupByInsertionOffset(page, pageFragments, positioned);
|
|
155
|
+
const rendered = renderPageWithCaptions(page, insertionGroups, pageVisualIndices);
|
|
156
|
+
const pageSeparator = text.length > 0 && rendered.pageText.length > 0 ? '\n\n' : '';
|
|
157
|
+
const pageStart = text.length + pageSeparator.length;
|
|
158
|
+
text += pageSeparator + rendered.pageText;
|
|
159
|
+
for (const positionedRegion of positioned) {
|
|
160
|
+
const record = toOrderedRegion(positionedRegion.region, rendered, pageStart, visualIndex++);
|
|
161
|
+
if (record.captionRange) {
|
|
162
|
+
atomicRanges.push(record.captionRange);
|
|
163
|
+
}
|
|
164
|
+
regions.push(record);
|
|
165
|
+
consumedRegions.add(positionedRegion.region);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (consumedRegions.size !== processedRegions.length) {
|
|
169
|
+
throw new Error('A processed visual region has no matching PDF page');
|
|
170
|
+
}
|
|
171
|
+
return { text, atomicRanges, regions };
|
|
172
|
+
}
|
|
173
|
+
function assignVisualAttachments(document, chunks, attachments) {
|
|
174
|
+
const attachmentsByChunkIndex = new Map();
|
|
175
|
+
if (chunks.length === 0) {
|
|
176
|
+
return attachmentsByChunkIndex;
|
|
177
|
+
}
|
|
178
|
+
const regionByVisualIndex = new Map(document.regions.map((region) => [region.visualIndex, region]));
|
|
179
|
+
const seenVisualIndices = new Set();
|
|
180
|
+
for (const attachment of [...attachments].sort((left, right) => left.imageIndex - right.imageIndex)) {
|
|
181
|
+
if (seenVisualIndices.has(attachment.imageIndex)) {
|
|
182
|
+
throw new Error(`Duplicate visual attachment index: ${attachment.imageIndex}`);
|
|
183
|
+
}
|
|
184
|
+
seenVisualIndices.add(attachment.imageIndex);
|
|
185
|
+
const region = regionByVisualIndex.get(attachment.imageIndex);
|
|
186
|
+
if (!region) {
|
|
187
|
+
throw new Error(`Visual attachment has no ordered region: ${attachment.imageIndex}`);
|
|
188
|
+
}
|
|
189
|
+
const owner = findNearestChunk(chunks, region.anchorOffset);
|
|
190
|
+
if (!owner) {
|
|
191
|
+
throw new Error(`Visual ${attachment.imageIndex} has no owning chunk`);
|
|
192
|
+
}
|
|
193
|
+
const current = attachmentsByChunkIndex.get(owner.index) ?? [];
|
|
194
|
+
current.push(attachment);
|
|
195
|
+
attachmentsByChunkIndex.set(owner.index, current);
|
|
196
|
+
}
|
|
197
|
+
return attachmentsByChunkIndex;
|
|
198
|
+
}
|
|
199
|
+
async function processImageOnlyRegions(regions, doc) {
|
|
200
|
+
const renderer = await import('../pdf-visual/renderer.js');
|
|
201
|
+
const processed = [];
|
|
202
|
+
let omittedImageCount = 0;
|
|
203
|
+
for (const region of regions) {
|
|
204
|
+
try {
|
|
205
|
+
const image = await renderer.renderPdfRendition(doc, region.pageNum, region.bbox, region.evidence);
|
|
206
|
+
processed.push({ ...region, caption: null, rendition: image });
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
omittedImageCount += 1;
|
|
210
|
+
processed.push({ ...region, caption: null });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return { processed, omittedImageCount };
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Detect visual regions and turn them into processed ones. With a captioner
|
|
217
|
+
* configured the VLM barrel is loaded and every region is captioned; without
|
|
218
|
+
* one only the lighter detector is loaded and regions carry images alone.
|
|
219
|
+
*/
|
|
220
|
+
async function detectAndProcessRegions(pages, doc, captionerConfig, includeImages) {
|
|
221
|
+
const pageRefs = pages.map((page) => ({ pageNum: page.pageNum, stextJson: page.stextJson }));
|
|
222
|
+
if (captionerConfig === undefined) {
|
|
223
|
+
const detector = await import('../pdf-visual/detector.js');
|
|
224
|
+
return processImageOnlyRegions(detector.detectVisualRegions(pageRefs, doc), doc);
|
|
225
|
+
}
|
|
226
|
+
const pdfVisual = await import('../pdf-visual/index.js');
|
|
227
|
+
const regions = pdfVisual.detectVisualRegions(pageRefs, doc);
|
|
228
|
+
const captioner = pdfVisual.createCaptioner(captionerConfig);
|
|
229
|
+
try {
|
|
230
|
+
const processed = await pdfVisual.processVisualRegions(regions, doc, {
|
|
231
|
+
captioner,
|
|
232
|
+
includeImages,
|
|
233
|
+
});
|
|
234
|
+
return { processed, omittedImageCount: 0 };
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
await captioner.dispose();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/** Encode every rendered region; one that cannot be encoded is omitted, not fatal. */
|
|
241
|
+
function collectAttachments(regions) {
|
|
242
|
+
const attachments = [];
|
|
243
|
+
let omittedImageCount = 0;
|
|
244
|
+
for (const region of regions) {
|
|
245
|
+
if (!region.rendition) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
attachments.push(createVisualAttachment(region.visualIndex, region.rendition));
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
omittedImageCount += 1;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return { attachments, omittedImageCount };
|
|
256
|
+
}
|
|
257
|
+
export async function prepareVisualPdfChunks(filePath, collaborators, options) {
|
|
258
|
+
const { parser, chunker, embedder } = collaborators;
|
|
259
|
+
const captionerConfig = options.captioner;
|
|
260
|
+
const { doc, title, pages } = await parser.parsePdfPages(filePath, embedder);
|
|
261
|
+
try {
|
|
262
|
+
let omittedImageCount = 0;
|
|
263
|
+
const detected = await detectAndProcessRegions(pages, doc, captionerConfig, options.images);
|
|
264
|
+
const processed = detected.processed;
|
|
265
|
+
omittedImageCount += detected.omittedImageCount;
|
|
266
|
+
const ordered = buildOrderedVisualDocument(pages, processed);
|
|
267
|
+
const { chunks, embeddings } = await buildChunksAndEmbeddings(ordered.text, chunker, embedder, ordered.atomicRanges);
|
|
268
|
+
const collected = options.images
|
|
269
|
+
? collectAttachments(ordered.regions)
|
|
270
|
+
: { attachments: [], omittedImageCount: 0 };
|
|
271
|
+
const attachments = collected.attachments;
|
|
272
|
+
omittedImageCount += collected.omittedImageCount;
|
|
273
|
+
const visualAttachments = assignVisualAttachments(ordered, chunks, attachments);
|
|
274
|
+
return {
|
|
275
|
+
chunks,
|
|
276
|
+
embeddings,
|
|
277
|
+
title,
|
|
278
|
+
text: ordered.text,
|
|
279
|
+
atomicRanges: ordered.atomicRanges,
|
|
280
|
+
visualAttachments,
|
|
281
|
+
omittedImageCount,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
finally {
|
|
285
|
+
try {
|
|
286
|
+
doc.destroy();
|
|
287
|
+
}
|
|
288
|
+
catch (destroyError) {
|
|
289
|
+
const message = destroyError instanceof Error ? destroyError.message : String(destroyError);
|
|
290
|
+
console.warn(`prepareVisualPdfChunks: doc.destroy() failed: ${message}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=visual.js.map
|