@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,105 @@
|
|
|
1
|
+
// Title Extractor - Per-format document title extraction
|
|
2
|
+
// Title is display-only metadata (NOT used for search scoring)
|
|
3
|
+
// ============================================
|
|
4
|
+
// Constants
|
|
5
|
+
// ============================================
|
|
6
|
+
/** Minimum font size (pt) for page-1 largest-font text to be treated as a title. */
|
|
7
|
+
const TITLE_MIN_FONT_SIZE = 14;
|
|
8
|
+
// ============================================
|
|
9
|
+
// Shared Helper
|
|
10
|
+
// ============================================
|
|
11
|
+
/** `2024-annual-report.pdf` -> `2024 annual report`. */
|
|
12
|
+
export function fileNameToTitle(fileName) {
|
|
13
|
+
// Strip extension (last dot and everything after)
|
|
14
|
+
const lastDotIndex = fileName.lastIndexOf('.');
|
|
15
|
+
const nameWithoutExt = lastDotIndex > 0 ? fileName.substring(0, lastDotIndex) : fileName;
|
|
16
|
+
// Replace hyphens and underscores with spaces
|
|
17
|
+
return nameWithoutExt.replace(/[-_]/g, ' ');
|
|
18
|
+
}
|
|
19
|
+
// ============================================
|
|
20
|
+
// Per-Format Extractors
|
|
21
|
+
// ============================================
|
|
22
|
+
/** Priority: YAML frontmatter title -> first `#` H1 -> file name. */
|
|
23
|
+
export function extractMarkdownTitle(text, fileName) {
|
|
24
|
+
// 1. Try YAML frontmatter
|
|
25
|
+
const frontmatterMatch = text.match(/^---\n[\s\S]*?title:\s*['"]?(.+?)['"]?\s*\n[\s\S]*?---/);
|
|
26
|
+
if (frontmatterMatch?.[1]) {
|
|
27
|
+
return { title: frontmatterMatch[1].trim(), source: 'metadata' };
|
|
28
|
+
}
|
|
29
|
+
// 2. Try first H1 heading
|
|
30
|
+
const h1Match = text.match(/^# (.+)$/m);
|
|
31
|
+
if (h1Match?.[1]) {
|
|
32
|
+
return { title: h1Match[1].trim(), source: 'content' };
|
|
33
|
+
}
|
|
34
|
+
// 3. Fall back to file name
|
|
35
|
+
return { title: fileNameToTitle(fileName), source: 'filename' };
|
|
36
|
+
}
|
|
37
|
+
/** Priority: a first line followed by a blank line -> file name. */
|
|
38
|
+
export function extractTxtTitle(text, fileName) {
|
|
39
|
+
// Try first line followed by empty line
|
|
40
|
+
if (text.length > 0) {
|
|
41
|
+
const lines = text.split('\n');
|
|
42
|
+
const firstLine = lines[0];
|
|
43
|
+
const secondLine = lines[1];
|
|
44
|
+
if (firstLine !== undefined &&
|
|
45
|
+
secondLine !== undefined &&
|
|
46
|
+
firstLine.trim().length > 0 &&
|
|
47
|
+
secondLine.trim().length === 0) {
|
|
48
|
+
return { title: firstLine.trim(), source: 'content' };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Fall back to file name
|
|
52
|
+
return { title: fileNameToTitle(fileName), source: 'filename' };
|
|
53
|
+
}
|
|
54
|
+
/** Priority: Readability title -> file name. */
|
|
55
|
+
export function extractHtmlTitle(readabilityTitle, fileName) {
|
|
56
|
+
if (readabilityTitle && readabilityTitle.trim().length > 0) {
|
|
57
|
+
return { title: readabilityTitle.trim(), source: 'content' };
|
|
58
|
+
}
|
|
59
|
+
// Fall back to file name
|
|
60
|
+
return { title: fileNameToTitle(fileName), source: 'filename' };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Priority: PDF metadata `/Title` -> page-1 chunk 0 -> file name.
|
|
64
|
+
*
|
|
65
|
+
* A metadata title that looks like a file path (contains `/` or `\`) is
|
|
66
|
+
* rejected, since some producers write the source path there.
|
|
67
|
+
*/
|
|
68
|
+
export function extractPdfTitle(metadataTitle, firstPageChunkText, fileName, firstPageFontHint) {
|
|
69
|
+
// 1. Try PDF metadata title (reject file paths and empty values)
|
|
70
|
+
if (metadataTitle && metadataTitle.trim().length > 0) {
|
|
71
|
+
const trimmed = metadataTitle.trim();
|
|
72
|
+
const looksLikeFilePath = trimmed.includes('/') || trimmed.includes('\\');
|
|
73
|
+
if (!looksLikeFilePath) {
|
|
74
|
+
return { title: trimmed, source: 'metadata' };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// 2. Try largest-font text from page 1 (font size > threshold indicates title)
|
|
78
|
+
if (firstPageFontHint &&
|
|
79
|
+
firstPageFontHint.fontSize > TITLE_MIN_FONT_SIZE &&
|
|
80
|
+
firstPageFontHint.text.trim().length > 0) {
|
|
81
|
+
return { title: firstPageFontHint.text.trim(), source: 'content' };
|
|
82
|
+
}
|
|
83
|
+
// 3. Try first chunk from page 1 semantic chunking
|
|
84
|
+
if (firstPageChunkText && firstPageChunkText.trim().length > 0) {
|
|
85
|
+
return { title: firstPageChunkText.trim(), source: 'content' };
|
|
86
|
+
}
|
|
87
|
+
// 4. Fall back to file name
|
|
88
|
+
return { title: fileNameToTitle(fileName), source: 'filename' };
|
|
89
|
+
}
|
|
90
|
+
/** Priority: DOCX core title -> first non-empty `<h1>` -> file name. */
|
|
91
|
+
export function extractDocxTitle(document, fileName, metadataTitle) {
|
|
92
|
+
const normalizedMetadataTitle = metadataTitle?.replace(/\s+/g, ' ').trim();
|
|
93
|
+
if (normalizedMetadataTitle) {
|
|
94
|
+
return { title: normalizedMetadataTitle, source: 'metadata' };
|
|
95
|
+
}
|
|
96
|
+
for (const heading of document.querySelectorAll('h1')) {
|
|
97
|
+
const title = (heading.textContent ?? '').replace(/\s+/g, ' ').trim();
|
|
98
|
+
if (title) {
|
|
99
|
+
return { title, source: 'content' };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Fall back to file name
|
|
103
|
+
return { title: fileNameToTitle(fileName), source: 'filename' };
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=title-extractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"title-extractor.js","sourceRoot":"","sources":["../../src/parser/title-extractor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Captioner, CaptionerConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create a captioner for the requested profile, setting `env.cacheDir`
|
|
4
|
+
* immediately so the global is right even if no embedder has initialized.
|
|
5
|
+
*
|
|
6
|
+
* `env.cacheDir` is a transformers.js process global, so constructing two
|
|
7
|
+
* captioners with DIFFERENT cacheDirs in parallel would let the last writer
|
|
8
|
+
* win. Safe for the current one-captioner-per-run usage.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createCaptioner(config: CaptionerConfig): Captioner;
|
|
11
|
+
//# sourceMappingURL=captioner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captioner.d.ts","sourceRoot":"","sources":["../../src/pdf-visual/captioner.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAE5D;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CA6BlE"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Captioner dispatcher for the visual ingest path. Each profile is a
|
|
2
|
+
// self-contained module under `./captioners/`, so prompt, chat template,
|
|
3
|
+
// processor signature, generation options and model class stay coherent per
|
|
4
|
+
// profile.
|
|
5
|
+
//
|
|
6
|
+
// `env.cacheDir` is set once here rather than in the per-profile modules, so
|
|
7
|
+
// the shared global is configured before either `from_pretrained` runs and the
|
|
8
|
+
// profiles stay free of the side effect.
|
|
9
|
+
import { env } from '@huggingface/transformers';
|
|
10
|
+
import { createFastCaptioner } from './captioners/fast.js';
|
|
11
|
+
import { createQualityCaptioner } from './captioners/quality.js';
|
|
12
|
+
/**
|
|
13
|
+
* Create a captioner for the requested profile, setting `env.cacheDir`
|
|
14
|
+
* immediately so the global is right even if no embedder has initialized.
|
|
15
|
+
*
|
|
16
|
+
* `env.cacheDir` is a transformers.js process global, so constructing two
|
|
17
|
+
* captioners with DIFFERENT cacheDirs in parallel would let the last writer
|
|
18
|
+
* win. Safe for the current one-captioner-per-run usage.
|
|
19
|
+
*/
|
|
20
|
+
export function createCaptioner(config) {
|
|
21
|
+
// Defensive ordering: set the global cacheDir at construction so the very
|
|
22
|
+
// first `from_pretrained` call sees the right value. Setting the same
|
|
23
|
+
// global twice with the same value is idempotent (shared with the
|
|
24
|
+
// embedder).
|
|
25
|
+
env.cacheDir = config.cacheDir;
|
|
26
|
+
if (process.env['HF_ENDPOINT']) {
|
|
27
|
+
env.remoteHost = process.env['HF_ENDPOINT'];
|
|
28
|
+
}
|
|
29
|
+
const resolvedDevice = config.device || 'cpu';
|
|
30
|
+
switch (config.profile) {
|
|
31
|
+
case 'fast':
|
|
32
|
+
return createFastCaptioner(resolvedDevice);
|
|
33
|
+
case 'quality':
|
|
34
|
+
// No silent fallback to `fast`. A load failure surfaces as a `VlmError`
|
|
35
|
+
// per page and the file still ingests text-only, so a misconfigured
|
|
36
|
+
// install degrades visibly instead of being masked by a quieter model.
|
|
37
|
+
return createQualityCaptioner(resolvedDevice);
|
|
38
|
+
default: {
|
|
39
|
+
// Exhaustiveness guard. `QualityProfile` is statically narrow at the
|
|
40
|
+
// call sites (CLI + MCP both validate before reaching here) so this
|
|
41
|
+
// branch is unreachable today; the throw is defensive for future
|
|
42
|
+
// ProfileType additions that forget to extend this switch.
|
|
43
|
+
const _exhaustive = config.profile;
|
|
44
|
+
throw new Error(`Unknown QualityProfile: ${String(_exhaustive)}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=captioner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captioner.js","sourceRoot":"","sources":["../../src/pdf-visual/captioner.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,yEAAyE;AACzE,4EAA4E;AAC5E,WAAW;AACX,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,yCAAyC;AAEzC,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAA;AAE/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAGhE;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,MAAuB;IACrD,0EAA0E;IAC1E,sEAAsE;IACtE,kEAAkE;IAClE,aAAa;IACb,GAAG,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAA;IAC9B,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC;QAC/B,GAAG,CAAC,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;IAC7C,CAAC;IAED,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,IAAI,KAAK,CAAA;IAE7C,QAAQ,MAAM,CAAC,OAAO,EAAE,CAAC;QACvB,KAAK,MAAM;YACT,OAAO,mBAAmB,CAAC,cAAc,CAAC,CAAA;QAC5C,KAAK,SAAS;YACZ,wEAAwE;YACxE,oEAAoE;YACpE,uEAAuE;YACvE,OAAO,sBAAsB,CAAC,cAAc,CAAC,CAAA;QAC/C,SAAS,CAAC;YACR,qEAAqE;YACrE,oEAAoE;YACpE,iEAAiE;YACjE,2DAA2D;YAC3D,MAAM,WAAW,GAAU,MAAM,CAAC,OAAO,CAAA;YACzC,MAAM,IAAI,KAAK,CAAC,2BAA2B,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;QACnE,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Captioner } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create a `fast` profile captioner. The dispatcher has already configured
|
|
4
|
+
* `env.cacheDir`; this profile only owns lazy model loading and inference.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createFastCaptioner(resolvedDevice: string): Captioner;
|
|
7
|
+
//# sourceMappingURL=fast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fast.d.ts","sourceRoot":"","sources":["../../../src/pdf-visual/captioners/fast.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAwB5C;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAiErE"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// `fast` visual-quality profile — SmolVLM-256M-Instruct / IDEFICS3.
|
|
2
|
+
//
|
|
3
|
+
// Shares the profile-agnostic load/decode mechanics with `quality` via
|
|
4
|
+
// `shared.ts` (`createModelLoader`, `decodePngToRawImage`, `VLM_DTYPE`,
|
|
5
|
+
// post-processing). The profile-specific parts — model class, prompt,
|
|
6
|
+
// processor call shape, and generation options — stay here so `fast` and
|
|
7
|
+
// `quality` can diverge on exactly the parts that genuinely differ.
|
|
8
|
+
//
|
|
9
|
+
// Implementation contract (matches HEAD captioner contract steps 2–8):
|
|
10
|
+
// 1. Lazy-load processor + model on first `caption()` call with the pinned
|
|
11
|
+
// `VLM_DTYPE` and the resolved device. `env.cacheDir` is set by the
|
|
12
|
+
// dispatcher (`captioner.ts`) before this profile is constructed.
|
|
13
|
+
// 2. Decode PNG bytes via `RawImage.fromBlob(new Blob([pngBytes], { type:
|
|
14
|
+
// 'image/png' }))`. SmolVLM-256M is NOT resized client-side — IDEFICS3's
|
|
15
|
+
// processor handles its own preprocessing.
|
|
16
|
+
// 3. Build chat-style input via `processor.apply_chat_template(messages,
|
|
17
|
+
// { add_generation_prompt: true })` with the IDEFICS3 conversation shape
|
|
18
|
+
// `[{role:'user', content:[{type:'image'},{type:'text',text:...}]}]`.
|
|
19
|
+
// Probe-verified.
|
|
20
|
+
// 4. Call `model.generate({ ...inputs, max_new_tokens: 128,
|
|
21
|
+
// repetition_penalty: 1.15, no_repeat_ngram_size: 3 })` — these
|
|
22
|
+
// generation options are tuned for SmolVLM-256M and MUST stay on the
|
|
23
|
+
// `fast` profile (they cause forced variant generation on Qwen2.5-VL,
|
|
24
|
+
// which is why the `quality` profile drops them).
|
|
25
|
+
// 5. Decode via `processor.batch_decode(newTokens, { skip_special_tokens: true })`
|
|
26
|
+
// where `newTokens = outputs.slice(null, [inputs.input_ids.dims[1], null])`.
|
|
27
|
+
// 6. Post-processing via `shared.postProcess` (control-char strip, trim,
|
|
28
|
+
// empty → `null`, length > 1000 → truncate + `…`).
|
|
29
|
+
// 7. On model load / image decode / generation failure throw `VlmError`
|
|
30
|
+
// with `pageNum` + `cause`.
|
|
31
|
+
import { AutoModelForImageTextToText, AutoProcessor } from '@huggingface/transformers';
|
|
32
|
+
import { VlmError } from '../types.js';
|
|
33
|
+
import { asVlmModel, asVlmProcessor, createModelLoader, decodePngToRawImage, postProcess, } from './shared.js';
|
|
34
|
+
const MODEL_NAME = 'HuggingFaceTB/SmolVLM-256M-Instruct';
|
|
35
|
+
/**
|
|
36
|
+
* Static prompt — tuned for "describe for search retrieval" not "describe for
|
|
37
|
+
* a blind reader". It asks for retrieval-relevant detail rather than a short
|
|
38
|
+
* summary, while keeping claims grounded in visible evidence.
|
|
39
|
+
*/
|
|
40
|
+
const PROMPT = 'Write search text for this PDF page image. Include visible section names, visual titles, ' +
|
|
41
|
+
'headings, labels, legends, axes, row or column names, UI text, metric names, identifiers, ' +
|
|
42
|
+
'proper nouns, and flow or diagram step names. Prefer exact readable words from the image. ' +
|
|
43
|
+
'Cover the main visual regions across the page. Use short searchable phrases separated by ' +
|
|
44
|
+
'commas or semicolons. Use only readable or visually evident details. Use each phrase once.';
|
|
45
|
+
/**
|
|
46
|
+
* Create a `fast` profile captioner. The dispatcher has already configured
|
|
47
|
+
* `env.cacheDir`; this profile only owns lazy model loading and inference.
|
|
48
|
+
*/
|
|
49
|
+
export function createFastCaptioner(resolvedDevice) {
|
|
50
|
+
// Both classes accept `{ dtype }` (probe-verified). They load in sequence
|
|
51
|
+
// because the second resolves the runtime class
|
|
52
|
+
// (`Idefics3ForConditionalGeneration` for the default model) via the
|
|
53
|
+
// architecture-agnostic `AutoModelForImageTextToText` entry point.
|
|
54
|
+
const loader = createModelLoader(MODEL_NAME, resolvedDevice, async ({ dtypeOpt, modelOpt }) => {
|
|
55
|
+
const processor = await AutoProcessor.from_pretrained(MODEL_NAME, dtypeOpt);
|
|
56
|
+
const model = await AutoModelForImageTextToText.from_pretrained(MODEL_NAME, modelOpt);
|
|
57
|
+
return { processor, model };
|
|
58
|
+
});
|
|
59
|
+
return {
|
|
60
|
+
dispose: loader.dispose,
|
|
61
|
+
async caption(pngBytes, pageNum) {
|
|
62
|
+
try {
|
|
63
|
+
const { processor, model } = await loader.ensureLoaded();
|
|
64
|
+
// Decode PNG → RawImage. SmolVLM-256M is NOT resized client-side —
|
|
65
|
+
// IDEFICS3's processor handles its own preprocessing.
|
|
66
|
+
const rawImage = await decodePngToRawImage(pngBytes);
|
|
67
|
+
// Build chat-style input. The IDEFICS3 conversation shape is
|
|
68
|
+
// probe-verified for `Idefics3Processor.apply_chat_template`.
|
|
69
|
+
const messages = [
|
|
70
|
+
{
|
|
71
|
+
role: 'user',
|
|
72
|
+
content: [{ type: 'image' }, { type: 'text', text: PROMPT }],
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
// IDEFICS3 takes an image array.
|
|
76
|
+
const proc = asVlmProcessor(processor);
|
|
77
|
+
const mdl = asVlmModel(model);
|
|
78
|
+
const chatPrompt = proc.apply_chat_template(messages, { add_generation_prompt: true });
|
|
79
|
+
const inputs = await proc(chatPrompt, [rawImage]);
|
|
80
|
+
const outputs = await mdl.generate({
|
|
81
|
+
...inputs,
|
|
82
|
+
max_new_tokens: 128,
|
|
83
|
+
repetition_penalty: 1.15,
|
|
84
|
+
no_repeat_ngram_size: 3,
|
|
85
|
+
});
|
|
86
|
+
// `outputs.slice(null, [inputLen, null])` strips the prompt tokens.
|
|
87
|
+
const inputLen = inputs.input_ids.dims[1];
|
|
88
|
+
if (typeof inputLen !== 'number') {
|
|
89
|
+
throw new VlmError('Captioner returned an input tensor without a token dimension', {
|
|
90
|
+
pageNum,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const newTokens = outputs.slice(null, [inputLen, null]);
|
|
94
|
+
const decoded = proc.batch_decode(newTokens, { skip_special_tokens: true });
|
|
95
|
+
const text = decoded[0] ?? '';
|
|
96
|
+
return postProcess(text);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
if (err instanceof VlmError) {
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
const cause = err instanceof Error ? err : new Error(String(err));
|
|
103
|
+
throw new VlmError(`Captioning failed for page ${pageNum}`, { cause, pageNum });
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=fast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fast.js","sourceRoot":"","sources":["../../../src/pdf-visual/captioners/fast.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Captioner } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create a `quality` profile captioner. The dispatcher has already configured
|
|
4
|
+
* `env.cacheDir`; this profile only owns lazy model loading and inference.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createQualityCaptioner(resolvedDevice: string): Captioner;
|
|
7
|
+
//# sourceMappingURL=quality.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quality.d.ts","sourceRoot":"","sources":["../../../src/pdf-visual/captioners/quality.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AA0C5C;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CA2DxE"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// `quality` visual-quality profile — Qwen3.5-2B.
|
|
2
|
+
//
|
|
3
|
+
// Non-obvious constraints:
|
|
4
|
+
// - The `-ONNX-OPT` export of this model needs an ORT contrib op
|
|
5
|
+
// (`com.microsoft:CausalConvWithState`) that onnxruntime-node does not
|
|
6
|
+
// register, so the plain `-ONNX` repo is the only loadable one here.
|
|
7
|
+
// - Generation options were picked by measurement over a figure fixture set:
|
|
8
|
+
// a stronger `repetition_penalty` (1.15, as `fast` uses) makes this family
|
|
9
|
+
// invent label variants, and `no_repeat_ngram_size: 3` costs recall,
|
|
10
|
+
// because a figure legitimately repeats short phrases.
|
|
11
|
+
// - Keywords precede the summary because `postProcess` truncates the tail,
|
|
12
|
+
// and the tail is where an exhausted run degenerates.
|
|
13
|
+
import { AutoProcessor, Qwen3_5ForConditionalGeneration } from '@huggingface/transformers';
|
|
14
|
+
import { VlmError } from '../types.js';
|
|
15
|
+
import { asVlmModel, asVlmProcessor, capLongEdge, createModelLoader, decodePngToRawImage, postProcess, } from './shared.js';
|
|
16
|
+
const MODEL_NAME = 'onnx-community/Qwen3.5-2B-ONNX';
|
|
17
|
+
/**
|
|
18
|
+
* Input long-edge cap (px). The image processor does not downscale on its own,
|
|
19
|
+
* so this is what bounds per-page CPU time. Measured: 768 loses small in-figure
|
|
20
|
+
* text, 1280 costs ~20% more time for no recall gain.
|
|
21
|
+
*/
|
|
22
|
+
const INPUT_LONG_EDGE = 1024;
|
|
23
|
+
const PROMPT = `This image is a figure region cropped from a PDF page. Write search text for it.
|
|
24
|
+
|
|
25
|
+
Output two lines, in this order:
|
|
26
|
+
|
|
27
|
+
Line 1 starts with "Keywords:" and then lists the phrases, separated by semicolons.
|
|
28
|
+
Line 2 starts with "Summary:" and then holds one sentence.
|
|
29
|
+
|
|
30
|
+
Keywords rules:
|
|
31
|
+
- Copy the text that is legible in the image: titles, axis names, legend entries, row and column names, panel labels, annotations, diagram node and step names, and numbers shown as labels.
|
|
32
|
+
- Use the exact wording from the image.
|
|
33
|
+
- Keep a number with the label it belongs to, printed as shown, including decimal points and units.
|
|
34
|
+
- Every part of the image that carries legible text contributes at least one phrase.
|
|
35
|
+
- List each phrase once.
|
|
36
|
+
- For partly legible text, keep the part you can read.
|
|
37
|
+
- For a table, keep its title, row names and column names; leave the cell-by-cell values out.
|
|
38
|
+
- Once the legible labels are listed, write the Summary line and stop.
|
|
39
|
+
|
|
40
|
+
Summary rule:
|
|
41
|
+
- One sentence naming the figure type and its subject.
|
|
42
|
+
|
|
43
|
+
Ground every phrase and the summary in what is visible. Where a region carries no legible text, describe it in the summary instead of supplying words for it.`;
|
|
44
|
+
/**
|
|
45
|
+
* Create a `quality` profile captioner. The dispatcher has already configured
|
|
46
|
+
* `env.cacheDir`; this profile only owns lazy model loading and inference.
|
|
47
|
+
*/
|
|
48
|
+
export function createQualityCaptioner(resolvedDevice) {
|
|
49
|
+
const loader = createModelLoader(MODEL_NAME, resolvedDevice, async ({ dtypeOpt, modelOpt }) => {
|
|
50
|
+
const processor = await AutoProcessor.from_pretrained(MODEL_NAME, dtypeOpt);
|
|
51
|
+
const model = await Qwen3_5ForConditionalGeneration.from_pretrained(MODEL_NAME, modelOpt);
|
|
52
|
+
return { processor, model };
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
dispose: loader.dispose,
|
|
56
|
+
async caption(pngBytes, pageNum) {
|
|
57
|
+
try {
|
|
58
|
+
const { processor, model } = await loader.ensureLoaded();
|
|
59
|
+
const rawImage = await capLongEdge(await decodePngToRawImage(pngBytes), INPUT_LONG_EDGE);
|
|
60
|
+
const messages = [
|
|
61
|
+
{
|
|
62
|
+
role: 'user',
|
|
63
|
+
content: [{ type: 'image' }, { type: 'text', text: PROMPT }],
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
// Qwen3.5 takes a single image, not an array.
|
|
67
|
+
const proc = asVlmProcessor(processor);
|
|
68
|
+
const mdl = asVlmModel(model);
|
|
69
|
+
const chatPrompt = proc.apply_chat_template(messages, { add_generation_prompt: true });
|
|
70
|
+
const inputs = await proc(chatPrompt, rawImage);
|
|
71
|
+
const outputs = await mdl.generate({
|
|
72
|
+
...inputs,
|
|
73
|
+
max_new_tokens: 160,
|
|
74
|
+
no_repeat_ngram_size: 6,
|
|
75
|
+
repetition_penalty: 1.05,
|
|
76
|
+
// The model ships `do_sample: true`, which would make captions for
|
|
77
|
+
// the same page differ between ingests.
|
|
78
|
+
do_sample: false,
|
|
79
|
+
});
|
|
80
|
+
const inputLen = inputs.input_ids.dims.at(-1);
|
|
81
|
+
if (typeof inputLen !== 'number') {
|
|
82
|
+
throw new VlmError('Captioner returned an input tensor without a token dimension', {
|
|
83
|
+
pageNum,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const newTokens = outputs.slice(null, [inputLen, null]);
|
|
87
|
+
const decoded = proc.batch_decode(newTokens, { skip_special_tokens: true });
|
|
88
|
+
const text = decoded[0] ?? '';
|
|
89
|
+
return postProcess(text);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
if (err instanceof VlmError) {
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
const cause = err instanceof Error ? err : new Error(String(err));
|
|
96
|
+
throw new VlmError(`Captioning failed for page ${pageNum}`, { cause, pageNum });
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=quality.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quality.js","sourceRoot":"","sources":["../../../src/pdf-visual/captioners/quality.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type DeviceType, RawImage } from '@huggingface/transformers';
|
|
2
|
+
/**
|
|
3
|
+
* Build the `from_pretrained` option objects (processor + model) with the
|
|
4
|
+
* pinned dtype and resolved device. `RAG_DEVICE` passes through with no
|
|
5
|
+
* allowlist (see `resolveDevice`) into a closed literal union.
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildModelLoadOptions(resolvedDevice: string): {
|
|
8
|
+
dtypeOpt: {
|
|
9
|
+
dtype: 'q4';
|
|
10
|
+
};
|
|
11
|
+
modelOpt: {
|
|
12
|
+
dtype: 'q4';
|
|
13
|
+
device: DeviceType;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* `apply_chat_template` is declared to return TOKENIZED output by default, not
|
|
18
|
+
* the string the profiles pass on.
|
|
19
|
+
*/
|
|
20
|
+
export type VlmProcessor = {
|
|
21
|
+
apply_chat_template: (messages: unknown, options: {
|
|
22
|
+
add_generation_prompt: boolean;
|
|
23
|
+
}) => string;
|
|
24
|
+
batch_decode: (tokens: unknown, options: {
|
|
25
|
+
skip_special_tokens: boolean;
|
|
26
|
+
}) => string[];
|
|
27
|
+
} & ((prompt: string, images: unknown) => Promise<{
|
|
28
|
+
input_ids: {
|
|
29
|
+
dims: unknown[];
|
|
30
|
+
};
|
|
31
|
+
}>);
|
|
32
|
+
/** Picks the tensor side of `generate`'s declared model-output-or-tensor union. */
|
|
33
|
+
export interface VlmModel {
|
|
34
|
+
generate: (inputs: unknown) => Promise<{
|
|
35
|
+
slice: (axis: null, range: [number, number | null]) => unknown;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Not a guard: member existence says nothing about return values. The captioner
|
|
40
|
+
* tests replace the library, so they prove these call sites are right GIVEN
|
|
41
|
+
* this contract, not that the library still honors it — only `fast` runs
|
|
42
|
+
* against the real one, in `visual-ingest-e2e.test.ts`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function asVlmProcessor(processor: unknown): VlmProcessor;
|
|
45
|
+
/** Present a loaded model as {@link VlmModel}. See {@link asVlmProcessor}. */
|
|
46
|
+
export declare function asVlmModel(model: unknown): VlmModel;
|
|
47
|
+
/** The processor + model pair produced by a profile's load callback. */
|
|
48
|
+
export interface LoadedModel {
|
|
49
|
+
processor: unknown;
|
|
50
|
+
model: unknown;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Lazy model loader shared by both profiles: owns the pending→ok/failed state
|
|
54
|
+
* machine and the identical load-failure wrapping. A prior failure re-throws
|
|
55
|
+
* the same wrapped error rather than retrying. The per-profile `load` callback
|
|
56
|
+
* owns the model-class choice.
|
|
57
|
+
*/
|
|
58
|
+
export declare function createModelLoader(modelName: string, resolvedDevice: string, load: (opts: ReturnType<typeof buildModelLoadOptions>) => Promise<LoadedModel>): {
|
|
59
|
+
ensureLoaded: () => Promise<LoadedModel>;
|
|
60
|
+
dispose: () => Promise<void>;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Decode PNG bytes to a `RawImage`. `BlobPart` omits `Uint8Array<ArrayBufferLike>`
|
|
64
|
+
* (SharedArrayBuffer subtyping), so the bytes are copied into a view that is
|
|
65
|
+
* definitely backed by a plain `ArrayBuffer`. Profiles needing a fixed input
|
|
66
|
+
* size resize the result.
|
|
67
|
+
*/
|
|
68
|
+
export declare function decodePngToRawImage(pngBytes: Uint8Array): Promise<RawImage>;
|
|
69
|
+
/**
|
|
70
|
+
* Downscale so the long edge is at most `longEdge`, preserving the aspect
|
|
71
|
+
* ratio, and widen a hairline crop until the processor accepts it. Alignment to
|
|
72
|
+
* the model's patch grid is left to the image processor. Returns the image
|
|
73
|
+
* untouched when it already satisfies both.
|
|
74
|
+
*/
|
|
75
|
+
export declare function capLongEdge(image: RawImage, longEdge: number): Promise<RawImage>;
|
|
76
|
+
/**
|
|
77
|
+
* Apply the post-generation processing rules. Returns the final caption or
|
|
78
|
+
* `null` when the result is empty after stripping.
|
|
79
|
+
*/
|
|
80
|
+
export declare function postProcess(decoded: string): string | null;
|
|
81
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/pdf-visual/captioners/shared.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,UAAU,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAarE;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG;IAC7D,QAAQ,EAAE;QAAE,KAAK,EAAE,IAAI,CAAA;KAAE,CAAA;IACzB,QAAQ,EAAE;QAAE,KAAK,EAAE,IAAI,CAAC;QAAC,MAAM,EAAE,UAAU,CAAA;KAAE,CAAA;CAC9C,CAQA;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,mBAAmB,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE;QAAE,qBAAqB,EAAE,OAAO,CAAA;KAAE,KAAK,MAAM,CAAA;IAC/F,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE;QAAE,mBAAmB,EAAE,OAAO,CAAA;KAAE,KAAK,MAAM,EAAE,CAAA;CACvF,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IAAE,SAAS,EAAE;QAAE,IAAI,EAAE,OAAO,EAAE,CAAA;KAAE,CAAA;CAAE,CAAC,CAAC,CAAA;AAEtF,mFAAmF;AACnF,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;QACrC,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,KAAK,OAAO,CAAA;KAC/D,CAAC,CAAA;CACH;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,OAAO,GAAG,YAAY,CAG/D;AAED,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAGnD;AAED,wEAAwE;AACxE,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,OAAO,CAAA;CACf;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,qBAAqB,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,GAC7E;IAAE,YAAY,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAuC5E;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAIjF;AAKD;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAYtF;AA4BD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS1D"}
|