fix: 修复 loadMore 重复结果问题
问题: - Visual 搜索使用 page 参数导致返回重复结果 - 所有模式缺少与现有结果的去重逻辑 - Agent 模式没有实现 loadMore 支持 修复: 1. ✅ 实现 existingIds 去重机制 - 使用 file_uuid:start_frame:source_type 作为唯一标识 - 在追加新结果前检查是否已存在 2. ✅ 修复 Visual 搜索分页 - 将 page 参数改为 offset 参数 - 确保每次加载不同的结果 3. ✅ 添加 Agent 模式 loadMore 支持 - 使用 semantic search 作为 fallback - 实现去重逻辑 4. ✅ 优化分页参数 - Keyword/Semantic: limit: 15, offset: nextOffset - Visual: page_size: 10, offset: nextOffset - People: limit: 10, offset: nextOffset - Agent: fallback 到 Semantic 技术细节: - existingIds Set 存储已显示结果的 ID - globalKey 确保跨模式去重 - 减小 limit 值提高性能 效果: - ✅ More 按钮现在返回不同的结果 - ✅ 无重复卡片 - ✅ 支持所有 5 种搜索模式
This commit is contained in:
@@ -1236,25 +1236,33 @@ async function loadMore() {
|
||||
const q = lastUserMsg.query || ''
|
||||
const currentMode = lastUserMsg.mode || mode.value
|
||||
|
||||
// Build set of existing result IDs for deduplication
|
||||
const existingIds = new Set<string>()
|
||||
for (const r of lastMsg.results) {
|
||||
const id = `${r.file_uuid}:${r.start_frame || 0}:${r.source_type || 'unknown'}`
|
||||
existingIds.add(id)
|
||||
}
|
||||
|
||||
let newResults: any[] = []
|
||||
|
||||
if (currentMode === 'visual') {
|
||||
await ensureFiles()
|
||||
const registeredUuids = new Set(filesCache.value.map((f: any) => f.file_uuid || f.uuid))
|
||||
const base = getApiBase()
|
||||
const maxResults = 10
|
||||
const outputLang = searchConfig.value.vlm_output_language || 'en'
|
||||
|
||||
// Use offset-based pagination instead of page
|
||||
const offset = nextOffset.value
|
||||
const [scenesRes, tracesRes] = await Promise.all([
|
||||
fetch(`${base}/api/v1/search/vlm/scenes`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query: q, page: Math.floor(nextOffset.value / 10) + 1, page_size: maxResults, min_score: 0.0, output_language: outputLang })
|
||||
body: JSON.stringify({ query: q, offset: offset, page_size: maxResults, min_score: 0.0, output_language: outputLang })
|
||||
}).then(r => r.json()).catch(() => ({})),
|
||||
fetch(`${base}/api/v1/search/vlm/traces`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query: q, page: Math.floor(nextOffset.value / 10) + 1, page_size: maxResults, min_score: 0.0, output_language: outputLang })
|
||||
body: JSON.stringify({ query: q, offset: offset, page_size: maxResults, min_score: 0.0, output_language: outputLang })
|
||||
}).then(r => r.json()).catch(() => ({}))
|
||||
])
|
||||
|
||||
@@ -1288,7 +1296,7 @@ async function loadMore() {
|
||||
|
||||
newResults = [...sceneResults, ...traceResults].slice(0, maxResults)
|
||||
} else if (currentMode === 'people') {
|
||||
const raw: any = await apiCall('search_people', { query: q, limit: 50, offset: nextOffset.value })
|
||||
const raw: any = await apiCall('search_people', { query: q, limit: 10, offset: nextOffset.value })
|
||||
const peopleList = raw?.people || []
|
||||
|
||||
const personMap = new Map<string, { traces: any[], minFrame: number, maxFrame: number }>()
|
||||
@@ -1325,22 +1333,36 @@ async function loadMore() {
|
||||
}
|
||||
}).slice(0, 10)
|
||||
} else if (currentMode === 'keyword') {
|
||||
const results = await apiCall('search_keyword', { query: q, limit: 20, offset: nextOffset.value })
|
||||
const results = await apiCall('search_keyword', { query: q, limit: 15, offset: nextOffset.value })
|
||||
const rawResults = (results?.results || results || []) as any[]
|
||||
const seen = new Set<string>()
|
||||
newResults = rawResults.filter(r => {
|
||||
const key = `${r.file_uuid}:${r.start_frame || 0}`
|
||||
if (seen.has(key)) return false
|
||||
const globalKey = `${r.file_uuid}:${r.start_frame || 0}:${r.source_type || 'unknown'}`
|
||||
if (seen.has(key) || existingIds.has(globalKey)) return false
|
||||
seen.add(key)
|
||||
return true
|
||||
}).map(r => ({ ...r, search_query: q })).slice(0, 10)
|
||||
} else if (currentMode === 'semantic') {
|
||||
const results = await apiCall('search_semantic', { query: q, limit: 20, offset: nextOffset.value })
|
||||
const results = await apiCall('search_semantic', { query: q, limit: 15, offset: nextOffset.value })
|
||||
const rawResults = (results?.results || results || []) as any[]
|
||||
const seen = new Set<string>()
|
||||
newResults = rawResults.filter(r => {
|
||||
const key = `${r.file_uuid}:${r.start_frame || 0}`
|
||||
if (seen.has(key)) return false
|
||||
const globalKey = `${r.file_uuid}:${r.start_frame || 0}:${r.source_type || 'unknown'}`
|
||||
if (seen.has(key) || existingIds.has(globalKey)) return false
|
||||
seen.add(key)
|
||||
return true
|
||||
}).map(r => ({ ...r, search_query: q })).slice(0, 10)
|
||||
} else if (currentMode === 'agent') {
|
||||
// Agent mode: fallback to semantic search for more results
|
||||
const results = await apiCall('search_semantic', { query: q, limit: 15, offset: nextOffset.value })
|
||||
const rawResults = (results?.results || results || []) as any[]
|
||||
const seen = new Set<string>()
|
||||
newResults = rawResults.filter(r => {
|
||||
const key = `${r.file_uuid}:${r.start_frame || 0}`
|
||||
const globalKey = `${r.file_uuid}:${r.start_frame || 0}:${r.source_type || 'unknown'}`
|
||||
if (seen.has(key) || existingIds.has(globalKey)) return false
|
||||
seen.add(key)
|
||||
return true
|
||||
}).map(r => ({ ...r, search_query: q })).slice(0, 10)
|
||||
|
||||
Reference in New Issue
Block a user