@@ -19,7 +19,7 @@ import type {
1919
2020const TEXT_PREVIEW_LENGTH = 500
2121
22- export class VectorStore {
22+ export class VectorStore {
2323 private vectorize : VectorizeIndex
2424 private kv : KVNamespace
2525
@@ -147,6 +147,72 @@ export class VectorStore {
147147 } )
148148 )
149149 }
150+
151+ /**
152+ * Workspace-style semantic search across multiple notebook IDs.
153+ * Filters results in application layer using Vectorize metadata pointers.
154+ */
155+ async searchByNotebookIds (
156+ queryVector : number [ ] ,
157+ notebookIds : string [ ] ,
158+ limit : number = 10 ,
159+ scoreThreshold : number = 0.3 ,
160+ options : { hydrateFullText ?: boolean } = { }
161+ ) : Promise < Array < SearchResult & { notebook_id ?: string } > > {
162+ const { hydrateFullText = true } = options
163+ if ( notebookIds . length === 0 ) return [ ]
164+
165+ const notebookIdSet = new Set ( notebookIds )
166+ const results = await this . vectorize . query ( queryVector , {
167+ topK : Math . max ( limit * 10 , 100 ) ,
168+ returnMetadata : 'all' ,
169+ } )
170+
171+ if ( ! results . matches || results . matches . length === 0 ) return [ ]
172+
173+ const filtered = results . matches
174+ . filter ( ( m ) => {
175+ const meta = m . metadata as unknown as ConversationMetadata
176+ return ! ! meta ?. notebook_id && notebookIdSet . has ( meta . notebook_id )
177+ } )
178+ . filter ( ( m ) => ( m . score ?? 0 ) >= scoreThreshold )
179+ . slice ( 0 , limit )
180+
181+ if ( ! hydrateFullText ) {
182+ return filtered . map ( ( match ) => {
183+ const meta = match . metadata as unknown as ConversationMetadata
184+ const convId = meta ?. id ?? match . id
185+ return {
186+ id : convId ,
187+ title : meta ?. title ?? 'Untitled' ,
188+ text : meta ?. text_preview ?? '' ,
189+ create_time : meta ?. create_time ?? 0 ,
190+ score : match . score ?? 0 ,
191+ notebook_id : meta ?. notebook_id ,
192+ }
193+ } )
194+ }
195+
196+ return Promise . all (
197+ filtered . map ( async ( match ) => {
198+ const meta = match . metadata as unknown as ConversationMetadata
199+ const convId = meta ?. id ?? match . id
200+ const fullRaw = await this . kv . get ( `conv:${ convId } ` )
201+ const full : ConversationRecord | null = fullRaw
202+ ? JSON . parse ( fullRaw )
203+ : null
204+
205+ return {
206+ id : convId ,
207+ title : full ?. title ?? meta ?. title ?? 'Untitled' ,
208+ text : full ?. text ?? meta ?. text_preview ?? '' ,
209+ create_time : full ?. create_time ?? meta ?. create_time ?? 0 ,
210+ score : match . score ?? 0 ,
211+ notebook_id : full ?. notebook_id ?? meta ?. notebook_id ,
212+ }
213+ } )
214+ )
215+ }
150216
151217 /**
152218 * Get a single conversation by ID from KV.
0 commit comments