import { ipcMain, BrowserWindow } from 'electron' import { getConfig, getDbPath } from '../config' import { embedOne } from '../rag/embedder' import { search } from '../db/lancedb' import { streamGenerate } from '../rag/generator' export function registerQueryHandlers(getWin: () => BrowserWindow | null): void { ipcMain.handle('query', async (_event, payload: { question: string systemPrompt: string topK: number }) => { const config = getConfig() const dbPath = getDbPath() console.log(`\n${'='.repeat(60)}`) console.log('[query] 问题:', payload.question) console.log('[query] topK:', payload.topK) console.log('[query] 向量化方式:', config.embed.provider, '生成/嵌入服务:er_backend 代理(127.0.0.1:17700)') const send = (channel: string, data: unknown): void => { getWin()?.webContents.send(channel, data) } try { // 1. 问题向量化 send('query:stage', { stage: '正在向量化...' }) const queryVector = await embedOne(payload.question, config.embed) console.log('[query] 问题向量化完成,维度:', queryVector.length) // 2. 向量检索 send('query:stage', { stage: '正在检索...' }) const chunks = await search(dbPath, queryVector, payload.topK) console.log(`[query] 检索到 ${chunks.length} 个片段:`) chunks.forEach((c, i) => { console.log(` 片段${i + 1} 相关度=${c.score} 来源=${c.source}`) console.log(` 内容前100字:${c.content.slice(0, 100).replace(/\n/g, ' ')}`) }) send('query:context', { chunks }) send('query:stage', { stage: '正在生成...' }) if (chunks.length === 0) { send('query:delta', { delta: '我不知道。' }) send('query:done', {}) return } // 3. 流式生成 await streamGenerate( payload.question, chunks, payload.systemPrompt, { onDelta: (delta) => send('query:delta', { delta }), onDone: (stats) => send('query:done', { stats: stats ?? null }), onError: (message) => send('query:error', { message }) } ) } catch (e) { send('query:error', { message: String(e) }) } }) }