@exulu/backend 1.69.3 → 2.0.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/dist/{catalog-TBSPSN2N.js → catalog-UGTDNMDM.js} +2 -1
- package/dist/{chunk-YCE44CMU.js → chunk-7CCMW3IW.js} +2 -0
- package/dist/chunk-IJ4HNHOT.js +6416 -0
- package/dist/{chunk-IDHS2BZO.js → chunk-T6JVFT7L.js} +2 -0
- package/dist/cli/start-whisper.cjs +1 -0
- package/dist/cli/start-whisper.js +2 -1
- package/dist/convert-exulu-tools-to-ai-sdk-tools-2PEDFZ2X.js +9 -0
- package/dist/index.cjs +9558 -9262
- package/dist/index.d.cts +46 -29
- package/dist/index.d.ts +46 -29
- package/dist/index.js +4989 -548
- package/ee/agentic-retrieval/pipeline/config.test.ts +81 -0
- package/ee/agentic-retrieval/pipeline/config.ts +189 -0
- package/ee/agentic-retrieval/pipeline/hyde.test.ts +55 -0
- package/ee/agentic-retrieval/pipeline/hyde.ts +133 -0
- package/ee/agentic-retrieval/pipeline/index.test.ts +140 -0
- package/ee/agentic-retrieval/pipeline/index.ts +638 -0
- package/ee/agentic-retrieval/pipeline/memory.test.ts +101 -0
- package/ee/agentic-retrieval/pipeline/memory.ts +566 -0
- package/ee/agentic-retrieval/pipeline/multi-query.test.ts +51 -0
- package/ee/agentic-retrieval/pipeline/multi-query.ts +158 -0
- package/ee/agentic-retrieval/pipeline/prefilter.test.ts +93 -0
- package/ee/agentic-retrieval/pipeline/prefilter.ts +389 -0
- package/ee/agentic-retrieval/pipeline/rerank.test.ts +128 -0
- package/ee/agentic-retrieval/pipeline/rerank.ts +178 -0
- package/ee/agentic-retrieval/pipeline/routing.test.ts +144 -0
- package/ee/agentic-retrieval/pipeline/routing.ts +343 -0
- package/ee/agentic-retrieval/pipeline/search.test.ts +149 -0
- package/ee/agentic-retrieval/pipeline/search.ts +180 -0
- package/ee/agentic-retrieval/pipeline/text-utils.test.ts +43 -0
- package/ee/agentic-retrieval/pipeline/text-utils.ts +85 -0
- package/ee/agentic-retrieval/pipeline/types.ts +59 -0
- package/ee/python/documents/processing/doc_processor.ts +1 -1
- package/ee/python/documents/processing/split_pdf.py +78 -24
- package/package.json +2 -1
- package/dist/chunk-WCP3WZM3.js +0 -10391
- package/dist/convert-exulu-tools-to-ai-sdk-tools-GQ3UIYP7.js +0 -6
- package/ee/agentic-retrieval/v3/agent-loop.ts +0 -288
- package/ee/agentic-retrieval/v3/classifier.ts +0 -92
- package/ee/agentic-retrieval/v3/context-sampler.ts +0 -79
- package/ee/agentic-retrieval/v3/dynamic-tools.ts +0 -115
- package/ee/agentic-retrieval/v3/index.ts +0 -471
- package/ee/agentic-retrieval/v3/session-tools-registry.ts +0 -20
- package/ee/agentic-retrieval/v3/strategies.ts +0 -171
- package/ee/agentic-retrieval/v3/tools.ts +0 -558
- package/ee/agentic-retrieval/v3/trajectory.ts +0 -309
- package/ee/agentic-retrieval/v3/types.ts +0 -59
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeFileName, deriveKeywordVariants, extractIdentifierTokens,
|
|
3
|
+
itemMatchesIdentifierToken, applyRewrites, stripSeparators,
|
|
4
|
+
} from "./text-utils";
|
|
5
|
+
|
|
6
|
+
describe("text-utils", () => {
|
|
7
|
+
it("normalizeFileName strips the bucket segment, separators, and extension", () => {
|
|
8
|
+
expect(normalizeFileName("/bucket/folder/hb_PAM-E4_2023.de.pdf")).toBe("folder hbpame42023 de pdf");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("normalizeFileName drops the first segment for paths without leading slash", () => {
|
|
12
|
+
expect(normalizeFileName("bucket/folder/hb_PAM-E4_2023.de.pdf")).toBe("folder hbpame42023 de pdf");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("normalizeFileName preserves bare filenames without dropping segments", () => {
|
|
16
|
+
expect(normalizeFileName("hb_FST-2XT_manual.pdf")).toBe("hbfst2xtmanual pdf");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("deriveKeywordVariants yields lowercased, separator- and digit-stripped forms ≥4 chars", () => {
|
|
20
|
+
expect(deriveKeywordVariants("FST-2XT").sort()).toEqual(["fst-2xt", "fst2xt"].sort());
|
|
21
|
+
expect(deriveKeywordVariants("MISCEL6")).toEqual(expect.arrayContaining(["miscel6", "miscel"]));
|
|
22
|
+
expect(deriveKeywordVariants("ab")).toEqual([]); // too short
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("extractIdentifierTokens keeps ≥4-char tokens containing a digit and a letter", () => {
|
|
26
|
+
expect(extractIdentifierTokens(["FST-2XT", "sperren", "S2", undefined])).toEqual(["fst2xt"]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("itemMatchesIdentifierToken matches separator-insensitively against the filename", () => {
|
|
30
|
+
expect(itemMatchesIdentifierToken("hb_FST-2XT_manual.pdf", ["fst2xt"])).toBe(true);
|
|
31
|
+
expect(itemMatchesIdentifierToken("hb_FST2_manual.pdf", ["fst2xt"])).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("applyRewrites returns one variant per matching rule, none when nothing matches", () => {
|
|
35
|
+
const rules = [{ find: "bypass", replace: "override" }, { find: "zzz", replace: "yyy" }];
|
|
36
|
+
expect(applyRewrites("how to bypass the door", rules)).toEqual(["how to override the door"]);
|
|
37
|
+
expect(applyRewrites("hello", rules)).toEqual([]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("stripSeparators lowers and removes separators", () => {
|
|
41
|
+
expect(stripSeparators("FST-2 XT.a")).toBe("fst2xta");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export const normalizeFileName = (fileName: string): string => {
|
|
2
|
+
// 1. Remove the first path segment (e.g., "/aufzugsperipherie")
|
|
3
|
+
const parts = fileName.split('/').filter(p => p); // filter out empty strings
|
|
4
|
+
let normalized = parts.length > 1 ? parts.slice(1).join('/') : (parts[0] || '');
|
|
5
|
+
|
|
6
|
+
// 2. Replace the remaining slashes with a space
|
|
7
|
+
normalized = normalized.replace(/\//g, ' ');
|
|
8
|
+
|
|
9
|
+
// 3. Convert to lowercase
|
|
10
|
+
normalized = normalized.toLowerCase();
|
|
11
|
+
|
|
12
|
+
// 4. PRE-CLEANUP: Insert a space before the file extension period.
|
|
13
|
+
// This looks for a dot followed by 3-4 letters/digits at the end of the string.
|
|
14
|
+
// The captured group ($1) puts the extension back with a space before it.
|
|
15
|
+
normalized = normalized.replace(/(\.[a-z0-9]{3,4})$/g, ' $1');
|
|
16
|
+
|
|
17
|
+
// 5. Final cleanup: Remove all characters that are NOT a lowercase letter, digit, space, or a period.
|
|
18
|
+
// We keep the period here temporarily to ensure the extension is preserved.
|
|
19
|
+
normalized = normalized.replace(/[^a-z0-9\. ]/g, '');
|
|
20
|
+
|
|
21
|
+
// 6. Final step: Replace any space/dot/space pattern with just a space
|
|
22
|
+
// and trim excess spaces. This handles the space inserted in step 4.
|
|
23
|
+
// The regex /\s*\.\s*/g finds any combination of space-dot-space and replaces it with a single space.
|
|
24
|
+
normalized = normalized.replace(/\s*\.\s*/g, ' ');
|
|
25
|
+
|
|
26
|
+
// 7. Clean up multiple spaces and leading/trailing spaces
|
|
27
|
+
normalized = normalized.replace(/ +/g, ' ').trim();
|
|
28
|
+
|
|
29
|
+
return normalized;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const normalizeText = (text: string): string => {
|
|
33
|
+
return text.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const deriveKeywordVariants = (keyword: string): string[] => {
|
|
37
|
+
const lower = keyword.trim().toLowerCase();
|
|
38
|
+
if (!lower) return [];
|
|
39
|
+
const variants = new Set<string>();
|
|
40
|
+
variants.add(lower);
|
|
41
|
+
variants.add(lower.replace(/[-_\.\s]/g, ''));
|
|
42
|
+
variants.add(lower.replace(/\d+$/, ''));
|
|
43
|
+
variants.add(lower.replace(/[-_\.\s]/g, '').replace(/\d+$/, ''));
|
|
44
|
+
return [...variants].filter(v => v.length >= 4);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function extractIdentifierTokens(parts: Array<string | undefined>): string[] {
|
|
48
|
+
const tokens = new Set<string>();
|
|
49
|
+
for (const part of parts) {
|
|
50
|
+
if (!part) continue;
|
|
51
|
+
// Split on whitespace so multi-word strings (the raw question) yield candidates.
|
|
52
|
+
for (const raw of part.split(/\s+/)) {
|
|
53
|
+
const norm = raw.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
54
|
+
if (norm.length >= 4 && /\d/.test(norm) && /[a-z]/.test(norm)) {
|
|
55
|
+
tokens.add(norm);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return [...tokens];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** True if the document filename contains any of the identifier tokens (separators ignored). */
|
|
63
|
+
export function itemMatchesIdentifierToken(itemName: string | undefined, tokens: string[]): boolean {
|
|
64
|
+
if (!itemName || tokens.length === 0) return false;
|
|
65
|
+
const normName = normalizeFileName(itemName).toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
66
|
+
return tokens.some(t => normName.includes(t));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const stripSeparators = (s: string): string => s.toLowerCase().replace(/[-_\.\s]/g, "");
|
|
70
|
+
|
|
71
|
+
/** Apply configured find→replace rules (case-insensitive, all occurrences).
|
|
72
|
+
* Returns one rewritten query per rule that changed the input; deduped. */
|
|
73
|
+
export function applyRewrites(
|
|
74
|
+
question: string,
|
|
75
|
+
rewrites: { find: string; replace: string }[],
|
|
76
|
+
): string[] {
|
|
77
|
+
const out = new Set<string>();
|
|
78
|
+
for (const rule of rewrites) {
|
|
79
|
+
if (!rule.find) continue;
|
|
80
|
+
const escaped = rule.find.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
81
|
+
const rewritten = question.replace(new RegExp(escaped, "gi"), rule.replace);
|
|
82
|
+
if (rewritten !== question) out.add(rewritten);
|
|
83
|
+
}
|
|
84
|
+
return [...out];
|
|
85
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { VectorSearchChunkResult } from "@SRC/graphql/resolvers/vector-search";
|
|
2
|
+
|
|
3
|
+
export type Chunk = VectorSearchChunkResult;
|
|
4
|
+
export type ChunkWithScore = Chunk & { rerank_score?: number; context?: { id: string; name: string } };
|
|
5
|
+
|
|
6
|
+
export type PhaseStep = {
|
|
7
|
+
text: string;
|
|
8
|
+
chunks?: ChunkWithScore[];
|
|
9
|
+
toolCalls?: Array<{ name: string; id: string; input: unknown }>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type RoutingPhaseResult = {
|
|
13
|
+
mainContexts: string[];
|
|
14
|
+
fallbackContexts: string[];
|
|
15
|
+
userPinnedItemIdsByContext: Map<string, Set<string>>;
|
|
16
|
+
userRequestedPage: number | null;
|
|
17
|
+
hasExplicitDocAndPage: boolean;
|
|
18
|
+
steps: PhaseStep[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type MemoryPhaseResult = {
|
|
22
|
+
memoryChunksForAnswer: ChunkWithScore[];
|
|
23
|
+
memoryOverride: { active: boolean; chunks: ChunkWithScore[]; reason: string };
|
|
24
|
+
memoryPinnedItemIds: Set<string>;
|
|
25
|
+
updatedQuestion: string;
|
|
26
|
+
updatedKeywords: string[];
|
|
27
|
+
updatedImportantKeyword: string;
|
|
28
|
+
steps: PhaseStep[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type SearchContextsResult = { chunks: Chunk[] };
|
|
32
|
+
|
|
33
|
+
export type RerankState = {
|
|
34
|
+
pinnedItemIds: Set<string>;
|
|
35
|
+
userPinnedItemIds: Set<string>;
|
|
36
|
+
userRequestedPage: number | null;
|
|
37
|
+
keywords: string[];
|
|
38
|
+
importantKeyword: string;
|
|
39
|
+
};
|
|
40
|
+
export type RerankResult = {
|
|
41
|
+
limited_results: ChunkWithScore[];
|
|
42
|
+
sorted_reranked_results: ChunkWithScore[];
|
|
43
|
+
rerank_score_max_genuine: number;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type RetrievalStep = {
|
|
47
|
+
stepNumber: number;
|
|
48
|
+
text: string;
|
|
49
|
+
toolCalls: Array<{ name: string; id: string; input: unknown }>;
|
|
50
|
+
chunks: ChunkWithScore[];
|
|
51
|
+
tokens: number;
|
|
52
|
+
};
|
|
53
|
+
export type AgenticRetrievalOutput = {
|
|
54
|
+
steps: RetrievalStep[];
|
|
55
|
+
reasoning: { text: string; tools: unknown[] }[];
|
|
56
|
+
chunks: ChunkWithScore[];
|
|
57
|
+
usage: unknown[];
|
|
58
|
+
totalTokens: number;
|
|
59
|
+
};
|
|
@@ -834,7 +834,7 @@ async function processPdf(
|
|
|
834
834
|
|
|
835
835
|
const splitResult = await executePythonScript({
|
|
836
836
|
scriptPath: 'ee/python/documents/processing/split_pdf.py',
|
|
837
|
-
args: [paths.source, chunksDir, '--chunk-size', String(maxPagesPerChunk)],
|
|
837
|
+
args: [paths.source, chunksDir, '--chunk-size', String(maxPagesPerChunk), '--max-size-mb', '25'],
|
|
838
838
|
timeout: 5 * 60 * 1000,
|
|
839
839
|
});
|
|
840
840
|
|
|
@@ -6,13 +6,13 @@ Outputs a JSON array to stdout, each element:
|
|
|
6
6
|
{ "path": "<absolute-path>", "start_page": <int>, "end_page": <int> }
|
|
7
7
|
|
|
8
8
|
start_page is 0-indexed, end_page is exclusive (Python-slice convention).
|
|
9
|
-
If the document fits within chunk_size, a single
|
|
10
|
-
original file is returned (no copy made).
|
|
9
|
+
If the document fits within chunk_size AND within max_size_bytes, a single
|
|
10
|
+
entry pointing to the original file is returned (no copy made).
|
|
11
11
|
|
|
12
12
|
Progress and diagnostics go to stderr so stdout stays clean JSON.
|
|
13
13
|
|
|
14
14
|
Usage:
|
|
15
|
-
split_pdf.py <input_pdf> <output_dir> [--chunk-size N]
|
|
15
|
+
split_pdf.py <input_pdf> <output_dir> [--chunk-size N] [--max-size-mb F]
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
18
|
import sys
|
|
@@ -23,7 +23,54 @@ import argparse
|
|
|
23
23
|
import fitz # PyMuPDF — installed as a docling transitive dependency
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def
|
|
26
|
+
def _write_chunk(
|
|
27
|
+
doc: fitz.Document,
|
|
28
|
+
output_dir: str,
|
|
29
|
+
chunk_start: int,
|
|
30
|
+
chunk_end: int,
|
|
31
|
+
max_size_bytes: int | None,
|
|
32
|
+
) -> list[dict]:
|
|
33
|
+
"""Write pages [chunk_start, chunk_end) to a file.
|
|
34
|
+
|
|
35
|
+
If the result exceeds max_size_bytes and contains more than one page,
|
|
36
|
+
delete it and recurse with the range bisected. Single-page chunks that
|
|
37
|
+
still exceed the limit are kept with a warning — they cannot be split
|
|
38
|
+
further without re-encoding.
|
|
39
|
+
"""
|
|
40
|
+
chunk_path = os.path.join(output_dir, f"chunk_{chunk_start}_{chunk_end - 1}.pdf")
|
|
41
|
+
|
|
42
|
+
sub = fitz.open()
|
|
43
|
+
sub.insert_pdf(doc, from_page=chunk_start, to_page=chunk_end - 1)
|
|
44
|
+
sub.save(chunk_path)
|
|
45
|
+
sub.close()
|
|
46
|
+
|
|
47
|
+
chunk_bytes = os.path.getsize(chunk_path)
|
|
48
|
+
n_pages = chunk_end - chunk_start
|
|
49
|
+
|
|
50
|
+
if max_size_bytes and chunk_bytes > max_size_bytes and n_pages > 1:
|
|
51
|
+
os.remove(chunk_path)
|
|
52
|
+
mid = chunk_start + n_pages // 2
|
|
53
|
+
return (
|
|
54
|
+
_write_chunk(doc, output_dir, chunk_start, mid, max_size_bytes)
|
|
55
|
+
+ _write_chunk(doc, output_dir, mid, chunk_end, max_size_bytes)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if max_size_bytes and chunk_bytes > max_size_bytes:
|
|
59
|
+
print(
|
|
60
|
+
f"[split_pdf] WARNING: single-page chunk {chunk_start} is {chunk_bytes:,} bytes — "
|
|
61
|
+
"exceeds size limit but cannot be split further",
|
|
62
|
+
file=sys.stderr,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return [{"path": os.path.abspath(chunk_path), "start_page": chunk_start, "end_page": chunk_end}]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def split_pdf(
|
|
69
|
+
input_path: str,
|
|
70
|
+
output_dir: str,
|
|
71
|
+
chunk_size: int,
|
|
72
|
+
max_size_bytes: int | None = None,
|
|
73
|
+
) -> list[dict]:
|
|
27
74
|
doc = fitz.open(input_path)
|
|
28
75
|
|
|
29
76
|
# Some PDFs are saved with an empty owner/user password by certain writers
|
|
@@ -39,9 +86,17 @@ def split_pdf(input_path: str, output_dir: str, chunk_size: int) -> list[dict]:
|
|
|
39
86
|
print("[split_pdf] Authenticated with empty password (phantom-password PDF)", file=sys.stderr)
|
|
40
87
|
|
|
41
88
|
total_pages = len(doc)
|
|
42
|
-
|
|
89
|
+
file_size = os.path.getsize(input_path)
|
|
90
|
+
print(
|
|
91
|
+
f"[split_pdf] Total pages: {total_pages}, chunk size: {chunk_size}, "
|
|
92
|
+
f"file size: {file_size:,} bytes"
|
|
93
|
+
+ (f", max chunk size: {max_size_bytes:,} bytes" if max_size_bytes else ""),
|
|
94
|
+
file=sys.stderr,
|
|
95
|
+
)
|
|
43
96
|
|
|
44
|
-
|
|
97
|
+
needs_split = total_pages > chunk_size or (max_size_bytes and file_size > max_size_bytes)
|
|
98
|
+
|
|
99
|
+
if not needs_split:
|
|
45
100
|
print("[split_pdf] No split needed — returning original path", file=sys.stderr)
|
|
46
101
|
doc.close()
|
|
47
102
|
return [{
|
|
@@ -55,23 +110,14 @@ def split_pdf(input_path: str, output_dir: str, chunk_size: int) -> list[dict]:
|
|
|
55
110
|
chunks = []
|
|
56
111
|
for start_page in range(0, total_pages, chunk_size):
|
|
57
112
|
end_page = min(start_page + chunk_size, total_pages)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
chunks.append({
|
|
67
|
-
"path": os.path.abspath(chunk_path),
|
|
68
|
-
"start_page": start_page,
|
|
69
|
-
"end_page": end_page,
|
|
70
|
-
})
|
|
71
|
-
print(
|
|
72
|
-
f"[split_pdf] Chunk {len(chunks)}: pages {start_page}–{end_page - 1} → {chunk_filename}",
|
|
73
|
-
file=sys.stderr,
|
|
74
|
-
)
|
|
113
|
+
sub_chunks = _write_chunk(doc, output_dir, start_page, end_page, max_size_bytes)
|
|
114
|
+
for c in sub_chunks:
|
|
115
|
+
print(
|
|
116
|
+
f"[split_pdf] Chunk {len(chunks) + 1}: pages {c['start_page']}–{c['end_page'] - 1} "
|
|
117
|
+
f"({os.path.getsize(c['path']):,} bytes) → {os.path.basename(c['path'])}",
|
|
118
|
+
file=sys.stderr,
|
|
119
|
+
)
|
|
120
|
+
chunks.extend(sub_chunks)
|
|
75
121
|
|
|
76
122
|
doc.close()
|
|
77
123
|
return chunks
|
|
@@ -87,10 +133,18 @@ if __name__ == "__main__":
|
|
|
87
133
|
default=25,
|
|
88
134
|
help="Maximum pages per chunk (default: 25)",
|
|
89
135
|
)
|
|
136
|
+
parser.add_argument(
|
|
137
|
+
"--max-size-mb",
|
|
138
|
+
type=float,
|
|
139
|
+
default=None,
|
|
140
|
+
help="Maximum chunk file size in MB — chunks exceeding this are bisected by page count (default: no limit)",
|
|
141
|
+
)
|
|
90
142
|
args = parser.parse_args()
|
|
91
143
|
|
|
144
|
+
max_size_bytes = int(args.max_size_mb * 1024 * 1024) if args.max_size_mb is not None else None
|
|
145
|
+
|
|
92
146
|
try:
|
|
93
|
-
chunks = split_pdf(args.input_pdf, args.output_dir, args.chunk_size)
|
|
147
|
+
chunks = split_pdf(args.input_pdf, args.output_dir, args.chunk_size, max_size_bytes)
|
|
94
148
|
print(json.dumps(chunks))
|
|
95
149
|
except Exception as e:
|
|
96
150
|
print(f"[split_pdf] ERROR: {e}", file=sys.stderr)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exulu/backend",
|
|
3
3
|
"author": "Qventu Bv.",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.0",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"private": false,
|
|
7
7
|
"publishConfig": {
|
|
@@ -132,6 +132,7 @@
|
|
|
132
132
|
"express": "^5.1.0",
|
|
133
133
|
"express-http-proxy": "^2.1.2",
|
|
134
134
|
"franc": "^6.2.0",
|
|
135
|
+
"fuse.js": "^7.4.2",
|
|
135
136
|
"graphql": "^16.11.0",
|
|
136
137
|
"graphql-tools": "^9.0.18",
|
|
137
138
|
"graphql-type-json": "^0.3.2",
|