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 q = lastUserMsg.query || ''
|
||||||
const currentMode = lastUserMsg.mode || mode.value
|
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[] = []
|
let newResults: any[] = []
|
||||||
|
|
||||||
if (currentMode === 'visual') {
|
if (currentMode === 'visual') {
|
||||||
await ensureFiles()
|
await ensureFiles()
|
||||||
const registeredUuids = new Set(filesCache.value.map((f: any) => f.file_uuid || f.uuid))
|
|
||||||
const base = getApiBase()
|
const base = getApiBase()
|
||||||
const maxResults = 10
|
const maxResults = 10
|
||||||
const outputLang = searchConfig.value.vlm_output_language || 'en'
|
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([
|
const [scenesRes, tracesRes] = await Promise.all([
|
||||||
fetch(`${base}/api/v1/search/vlm/scenes`, {
|
fetch(`${base}/api/v1/search/vlm/scenes`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
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(() => ({})),
|
}).then(r => r.json()).catch(() => ({})),
|
||||||
fetch(`${base}/api/v1/search/vlm/traces`, {
|
fetch(`${base}/api/v1/search/vlm/traces`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
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(() => ({}))
|
}).then(r => r.json()).catch(() => ({}))
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -1288,7 +1296,7 @@ async function loadMore() {
|
|||||||
|
|
||||||
newResults = [...sceneResults, ...traceResults].slice(0, maxResults)
|
newResults = [...sceneResults, ...traceResults].slice(0, maxResults)
|
||||||
} else if (currentMode === 'people') {
|
} 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 peopleList = raw?.people || []
|
||||||
|
|
||||||
const personMap = new Map<string, { traces: any[], minFrame: number, maxFrame: number }>()
|
const personMap = new Map<string, { traces: any[], minFrame: number, maxFrame: number }>()
|
||||||
@@ -1325,22 +1333,36 @@ async function loadMore() {
|
|||||||
}
|
}
|
||||||
}).slice(0, 10)
|
}).slice(0, 10)
|
||||||
} else if (currentMode === 'keyword') {
|
} 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 rawResults = (results?.results || results || []) as any[]
|
||||||
const seen = new Set<string>()
|
const seen = new Set<string>()
|
||||||
newResults = rawResults.filter(r => {
|
newResults = rawResults.filter(r => {
|
||||||
const key = `${r.file_uuid}:${r.start_frame || 0}`
|
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)
|
seen.add(key)
|
||||||
return true
|
return true
|
||||||
}).map(r => ({ ...r, search_query: q })).slice(0, 10)
|
}).map(r => ({ ...r, search_query: q })).slice(0, 10)
|
||||||
} else if (currentMode === 'semantic') {
|
} 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 rawResults = (results?.results || results || []) as any[]
|
||||||
const seen = new Set<string>()
|
const seen = new Set<string>()
|
||||||
newResults = rawResults.filter(r => {
|
newResults = rawResults.filter(r => {
|
||||||
const key = `${r.file_uuid}:${r.start_frame || 0}`
|
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)
|
seen.add(key)
|
||||||
return true
|
return true
|
||||||
}).map(r => ({ ...r, search_query: q })).slice(0, 10)
|
}).map(r => ({ ...r, search_query: q })).slice(0, 10)
|
||||||
|
|||||||
Reference in New Issue
Block a user