diff --git a/src/renderer/components/chat-area.ts b/src/renderer/components/chat-area.ts index d8d76af..3ae74ef 100644 --- a/src/renderer/components/chat-area.ts +++ b/src/renderer/components/chat-area.ts @@ -3,7 +3,7 @@ */ import { state, KEYS } from '../state/state.js'; -import { logError } from '../services/log-service.js'; +import { logError, logSession } from '../services/log-service.js'; import { marked } from '../utils/marked-config.js'; import { escapeHtml, formatTime } from '../utils/utils.js'; import type { ChatSession, ChatMessage, ToolCallRecord } from '../types.js'; @@ -420,6 +420,7 @@ export async function exportAsMarkdown(session: ChatSession): Promise { if (m.think) md += `> **思考:** ${m.think}\n\n`; }); await nativeSaveFile(`${session.title}.md`, md); + logSession('导出', `${session.title}.md`); } export async function exportAsHtml(session: ChatSession): Promise { @@ -435,6 +436,7 @@ code{background:rgba(0,0,0,0.2);padding:2px 6px;border-radius:4px;} { @@ -443,4 +445,5 @@ export async function exportAsTxt(session: ChatSession): Promise { txt += `[${m.role === 'user' ? '用户' : 'AI'}]\n${m.content || ''}\n\n`; }); await nativeSaveFile(`${session.title}.txt`, txt); + logSession('导出', `${session.title}.txt`); } diff --git a/src/renderer/components/header.ts b/src/renderer/components/header.ts index 5dec427..14b480d 100644 --- a/src/renderer/components/header.ts +++ b/src/renderer/components/header.ts @@ -78,6 +78,7 @@ export async function updateRunningModels(): Promise { const data = await api.psModels(); if (!data.models || data.models.length === 0) { container.innerHTML = '

无运行中的模型

'; + logDebug('运行中模型: 0'); return; } container.innerHTML = data.models.map(m => ` @@ -86,6 +87,7 @@ export async function updateRunningModels(): Promise { ${formatSize(m.size_vram || 0)} VRAM `).join(''); + logDebug(`运行中模型: ${data.models.length}`, data.models.map(m => m.name).join(', ')); } catch { container.innerHTML = '

无法获取运行信息

'; } diff --git a/src/renderer/components/history-modal.ts b/src/renderer/components/history-modal.ts index d50cc05..e946646 100644 --- a/src/renderer/components/history-modal.ts +++ b/src/renderer/components/history-modal.ts @@ -190,6 +190,7 @@ export function openHistoryModal(): void { (document.querySelector('#historySearchInput') as HTMLInputElement).value = ''; (document.querySelector('#historySearchClear') as HTMLElement).style.display = 'none'; loadHistory(); + logSession('打开历史记录'); historyModalEl.style.display = ''; } diff --git a/src/renderer/components/kb-modal.ts b/src/renderer/components/kb-modal.ts index f51f317..511de1c 100644 --- a/src/renderer/components/kb-modal.ts +++ b/src/renderer/components/kb-modal.ts @@ -98,6 +98,7 @@ function updateRagBadge(): void { async function openKBModal(): Promise { kbModalEl.style.display = ''; + logRAG('打开知识库管理'); await refreshCollections(); await populateEmbedModels(); } diff --git a/src/renderer/services/rag.ts b/src/renderer/services/rag.ts index 3743f9e..6745646 100644 --- a/src/renderer/services/rag.ts +++ b/src/renderer/services/rag.ts @@ -14,6 +14,7 @@ export async function initVectorStore(): Promise { if (vectorStore) return vectorStore; vectorStore = new VectorStore(); await vectorStore.init(); + logRAG('向量存储已初始化'); return vectorStore; } @@ -34,6 +35,7 @@ export async function embedText(text: string, model: string): Promise export async function embedBatch(texts: string[], model: string, onProgress?: (done: number, total: number) => void): Promise { const results: number[][] = []; const batchSize = 5; + logDebug(`嵌入批次`, `${texts.length} 个文本, 模型: ${model}`); for (let i = 0; i < texts.length; i += batchSize) { const batch = texts.slice(i, i + batchSize); const embeddings = await Promise.all(batch.map(t => embedText(t, model))); @@ -87,6 +89,7 @@ export async function removeDocumentFromKB(colId: string, docId: string): Promis const vs = await initVectorStore(); const allVectors = await vs.getVectorsByCollection(colId); const docVectors = allVectors.filter(v => v.docId === docId); + logRAG(`删除文档`, `${docId} (${docVectors.length} 个分块)`); await vs.deleteVectorsByDocument(colId, docId); @@ -119,6 +122,7 @@ export async function retrieveContext(query: string, colId: string, topK = 5): P const embedModel = col.embeddingModel; if (!embedModel) throw new Error('未配置嵌入模型'); + logRAG('开始检索', col.name); const queryEmbedding = await embedText(query, embedModel); const results = await vs.search(colId, queryEmbedding, topK); if (results.length === 0) return { context: '', results: [] }; diff --git a/src/renderer/services/vector-store.ts b/src/renderer/services/vector-store.ts index a416220..4a1a827 100644 --- a/src/renderer/services/vector-store.ts +++ b/src/renderer/services/vector-store.ts @@ -3,6 +3,7 @@ */ import type { VectorCollection, VectorItem, SearchResult } from '../types.js'; +import { logRAG, logDebug } from './log-service.js'; const MIN_IVF_SIZE = 200; const DEFAULT_K = 20; @@ -30,7 +31,7 @@ export class VectorStore { return new Promise((resolve, reject) => { const req = indexedDB.open(this.dbName, 2); req.onerror = () => reject(req.error); - req.onsuccess = () => { this.db = req.result; resolve(); }; + req.onsuccess = () => { this.db = req.result; logDebug('向量数据库已连接'); resolve(); }; req.onupgradeneeded = (e) => { const db = (e.target as IDBOpenDBRequest).result; if (!db.objectStoreNames.contains('vectors')) { @@ -60,7 +61,7 @@ export class VectorStore { }; return new Promise((resolve, reject) => { const req = this._tx('collections', 'readwrite').put(col); - req.onsuccess = () => resolve(col); + req.onsuccess = () => { logRAG(`创建集合: ${name}`, col.id); resolve(col); }; req.onerror = () => reject(req.error); }); } @@ -92,6 +93,7 @@ export class VectorStore { async deleteCollection(colId: string): Promise { const vectors = await this.getVectorsByCollection(colId); + logRAG(`删除集合`, `${colId} (${vectors.length} 向量)`); await new Promise((resolve, reject) => { const tx = this.db!.transaction(['vectors', 'collections', 'indexes'], 'readwrite'); const vStore = tx.objectStore('vectors'); @@ -107,6 +109,8 @@ export class VectorStore { async addVectors(items: VectorItem[]): Promise { if (items.length === 0) return; + const colId = items[0].collectionId; + logDebug(`写入向量`, `${colId}: ${items.length} 条`); await new Promise((resolve, reject) => { const tx = this.db!.transaction('vectors', 'readwrite'); const store = tx.objectStore('vectors'); @@ -142,6 +146,7 @@ export class VectorStore { async deleteVectorsByDocument(colId: string, docId: string): Promise { const vectors = await this.getVectorsByCollection(colId); const docVectors = vectors.filter(v => v.docId === docId); + logDebug(`删除文档向量`, `${docId}: ${docVectors.length} 条`); await new Promise((resolve, reject) => { const tx = this.db!.transaction('vectors', 'readwrite'); const store = tx.objectStore('vectors'); @@ -160,15 +165,15 @@ export class VectorStore { const vectors = await this.getVectorsByCollection(colId); if (vectors.length === 0) return []; + let results: SearchResult[]; if (vectors.length < MIN_IVF_SIZE) { - return this._bruteForceSearch(vectors, queryEmbedding, topK); + results = this._bruteForceSearch(vectors, queryEmbedding, topK); + } else { + const index = await this._getIndex(colId, vectors); + results = index ? this._ivfSearch(vectors, index, queryEmbedding, topK) : this._bruteForceSearch(vectors, queryEmbedding, topK); } - - const index = await this._getIndex(colId, vectors); - if (!index) { - return this._bruteForceSearch(vectors, queryEmbedding, topK); - } - return this._ivfSearch(vectors, index, queryEmbedding, topK); + logDebug(`向量搜索`, `${colId}: ${vectors.length} 向量中检索到 ${results.length} 个结果`); + return results; } private _bruteForceSearch(vectors: VectorItem[], queryEmbedding: number[], topK: number): SearchResult[] {