feat: 添加知识库检索功能和改进模型路由错误处理

- 在ChatRequest中添加Citation事件类型,支持引用来源展示
- 实现聊天上下文准备服务,构建带源元数据的受限聊天上下文
- 添加ThreadedProcess类以支持Windows平台的子进程操作
- 改进检索引擎中的错误处理和向量搜索逻辑
- 实现严格的嵌入模型验证和索引重建机制
- 添加前端聊天界面的知识库检索开关
- 实现搜索历史记录功能和错误降级处理
- 更新模型路由设置提示信息以反映索引重建需求
This commit is contained in:
2026-09-04 13:02:08 +08:00
parent 8c644d0aae
commit 6eb97bf9ab
20 changed files with 448 additions and 26 deletions
+31 -6
View File
@@ -5,10 +5,19 @@ import * as searchService from '@/services/searchService'
import { ApiErrorClass } from '@/services/apiClient'
const VECTOR_ERROR_CODES = new Set([
'SEMANTIC_INDEX_UNAVAILABLE',
'VECTOR_UNAVAILABLE', 'EMBEDDING_UNAVAILABLE', 'INDEX_UNAVAILABLE',
'MODEL_NOT_FOUND', 'MODEL_CAPABILITY_MISMATCH', 'PROVIDER_UNAVAILABLE',
])
const HISTORY_KEY = 'notes-agent.search-history.v1'
function readHistory(): string[] {
try {
const value: unknown = JSON.parse(localStorage.getItem(HISTORY_KEY) ?? '[]')
return Array.isArray(value) ? [...new Set(value.filter((item): item is string => typeof item === 'string').map(item => item.trim()).filter(Boolean))].slice(0, 10) : []
} catch { return [] }
}
export const useSearchStore = defineStore('search', () => {
const query = ref('')
const mode = ref<'fts' | 'vector' | 'hybrid'>('hybrid')
@@ -16,11 +25,23 @@ export const useSearchStore = defineStore('search', () => {
const total = ref(0)
const isSearching = ref(false)
const selectedIndex = ref(0)
const recentQueries = ref<string[]>(['红黑树', '死锁', 'TCP三次握手'])
const recentQueries = ref<string[]>(readHistory())
const historyError = ref('')
let searchVersion = 0
function persistHistory() {
try { localStorage.setItem(HISTORY_KEY, JSON.stringify(recentQueries.value)); historyError.value = '' }
catch { historyError.value = '浏览器无法保存搜索记录,本次记录仅保留到页面关闭。' }
}
function clearHistory() { recentQueries.value = []; persistHistory() }
const error = ref<string | null>(null)
const vectorUnavailable = ref(false)
async function doSearch(request: SearchRequest) {
request = { ...request, query: request.query.trim() }
if (!request.query) return
const version = ++searchVersion
recentQueries.value = [request.query, ...recentQueries.value.filter(item => item !== request.query)].slice(0, 10)
persistHistory()
query.value = request.query
mode.value = request.mode || 'hybrid'
isSearching.value = true
@@ -29,20 +50,24 @@ export const useSearchStore = defineStore('search', () => {
try {
const resp = await searchService.search(request)
if (version !== searchVersion) return
results.value = resp.results
total.value = resp.total
selectedIndex.value = 0
} catch (reason) {
if (version !== searchVersion) return
const canFallback = mode.value !== 'fts' && reason instanceof ApiErrorClass && VECTOR_ERROR_CODES.has(reason.code)
if (canFallback) {
try {
const fallback = await searchService.search({ ...request, mode: 'fts' })
if (version !== searchVersion) return
results.value = fallback.results
total.value = fallback.total
mode.value = 'fts'
vectorUnavailable.value = true
selectedIndex.value = 0
} catch (fallbackError) {
if (version !== searchVersion) return
error.value = fallbackError instanceof Error ? fallbackError.message : '全文检索降级失败'
results.value = []
total.value = 0
@@ -53,16 +78,14 @@ export const useSearchStore = defineStore('search', () => {
total.value = 0
}
} finally {
isSearching.value = false
if (version === searchVersion) isSearching.value = false
}
if (request.query && !recentQueries.value.includes(request.query)) {
recentQueries.value.unshift(request.query)
if (recentQueries.value.length > 10) recentQueries.value.pop()
}
}
function clearResults() {
searchVersion++
isSearching.value = false
results.value = []
query.value = ''
total.value = 0
@@ -90,6 +113,8 @@ export const useSearchStore = defineStore('search', () => {
isSearching,
selectedIndex,
recentQueries,
historyError,
clearHistory,
error,
vectorUnavailable,
doSearch,