1
11
12import { chunkText } from "./chunking.js";
13import type { ChunkOptions, IndexDocInput, SearchDoc } from "./types.js";
14
15
16export function chunkDocId(parentDocId: string, chunkIndex: number): string {
17 return `${parentDocId}#${chunkIndex}`;
18}
19
20
24export function buildIndexDocs(input: IndexDocInput, options?: ChunkOptions): SearchDoc[] {
25 const base: SearchDoc = {
26 docId: input.docId,
27 type: input.type,
28 id: input.id,
29 workspaceId: input.workspaceId,
30 title: input.title,
31 body: input.body,
32 grants: input.grants,
33 archived: input.archived,
34 url: input.url,
35 createdAt: input.createdAt,
36 updatedAt: input.updatedAt,
37 };
38 if (input.teamId !== undefined) base.teamId = input.teamId;
39 if (input.chunkId !== undefined) base.chunkId = input.chunkId;
40 if (input.vectorModel !== undefined) base.vectorModel = input.vectorModel;
41 if (input.createdBy !== undefined) base.createdBy = input.createdBy;
42 if (input.createdByName !== undefined) base.createdByName = input.createdByName;
43 if (input.icon !== undefined) base.icon = input.icon;
44
45 const chunked = input.type === "page" || input.type === "block";
46 if (!chunked || input.body.length === 0) {
47
48 return [input.vector !== undefined ? { ...base, vector: input.vector } : base];
49 }
50
51 const opts: Required<ChunkOptions> = {
52 maxChars: options?.maxChars ?? 800,
53 overlapChars: options?.overlapChars ?? 120,
54 };
55 const chunks = chunkText(input.body, opts);
56 if (chunks.length <= 1) {
57
58 return [input.vector !== undefined ? { ...base, vector: input.vector } : base];
59 }
60 return chunks.map((chunk) => ({
61 ...base,
62 docId: chunkDocId(input.docId, chunk.chunkIndex),
63 body: chunk.text,
64 chunkIndex: chunk.chunkIndex,
65 parentDocId: input.docId,
66
67
68 ...(chunk.chunkIndex === 0 && input.vector !== undefined ? { vector: input.vector } : {}),
69 }));
70}
71
72
77export function dedupeToParent(docs: { docId: string; parentDocId?: string; score: number }[]): {
78 docId: string;
79 score: number;
80 matchedChunk?: string;
81}[] {
82 const byParent = new Map<string, { docId: string; score: number; matchedChunk?: string }>();
83 for (const doc of docs) {
84 const key = doc.parentDocId ?? doc.docId;
85 const existing = byParent.get(key);
86 if (!existing || doc.score > existing.score) {
87 const entry: { docId: string; score: number; matchedChunk?: string } = {
88 docId: key,
89 score: doc.score,
90 };
91 if (doc.parentDocId) entry.matchedChunk = doc.docId;
92 byParent.set(key, entry);
93 }
94 }
95 return [...byParent.values()].sort((a, b) => b.score - a.score);
96}
97