'use server' import { semanticSearchService, SearchResult } from '@/lib/ai/services/semantic-search.service' import { auth } from '@/auth' import { reserveUsageOrThrow, QuotaExceededError, QuotaServiceUnavailableError } from '@/lib/entitlements' export interface SemanticSearchResponse { results: SearchResult[] query: string totalResults: number } /** * Perform hybrid semantic + keyword search * Supports contextual search within notebook (IA5) */ export async function semanticSearch( query: string, options?: { limit?: number threshold?: number notebookId?: string // NEW: Filter by notebook for contextual search (IA5) } ): Promise { const session = await auth(); if (session?.user?.id) { try { await reserveUsageOrThrow(session.user.id, 'semantic_search'); } catch (err) { if (err instanceof QuotaExceededError) throw err; if (err instanceof QuotaServiceUnavailableError || process.env.NODE_ENV === 'production') { throw err instanceof QuotaServiceUnavailableError ? err : new QuotaServiceUnavailableError(); } console.error('[semantic-search] Quota check error (fail-open):', err); } } try { const results = await semanticSearchService.search(query, { limit: options?.limit || 20, threshold: options?.threshold || 0.3, notebookId: options?.notebookId // NEW: Pass notebook filter }) return { results, query, totalResults: results.length } } catch (error) { console.error('Error in semantic search action:', error) throw error } } /** * Index a note for semantic search (generate embedding) */ export async function indexNote(noteId: string): Promise { try { await semanticSearchService.indexNote(noteId) } catch (error) { console.error('Error indexing note:', error) throw error } } /** * Batch index notes (for initial setup) */ export async function batchIndexNotes(noteIds: string[]): Promise { try { await semanticSearchService.indexBatchNotes(noteIds) } catch (error) { console.error('Error batch indexing notes:', error) throw error } }