fix: simplify bilingual VLM search - use single API call

- Core API now returns both vlm_description (EN) and vlm_description_localized (JA)
- Removed duplicate API calls (was fetching EN + JA separately)
- Single API call with output_language param, uses both fields from response
- Load More also updated to use results array and correct field names
- Format: 【JA】localized text...【English】english text...
This commit is contained in:
2026-10-23 04:59:55 +08:00
parent 76d9afd43b
commit fa8e4a9191

View File

@@ -1087,79 +1087,22 @@ async function sendMessage() {
let traces: any[] = []
try {
if (bilingual) {
// Bilingual mode: fetch both English and preferred language
const [scenesEn, scenesLang, tracesEn, tracesLang] = await Promise.all([
fetch(`${base}/api/v1/search/vlm/scenes`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: q, page_size: maxResults, min_score: 0.0, output_language: 'en' })
}).then(r => r.json()).catch(() => ({})),
fetch(`${base}/api/v1/search/vlm/scenes`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: q, 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_size: maxResults, min_score: 0.0, output_language: 'en' })
}).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_size: maxResults, min_score: 0.0, output_language: outputLang })
}).then(r => r.json()).catch(() => ({}))
])
// Merge bilingual results
const scenesEnList = (scenesEn?.results || scenesEn || []) as any[]
const scenesLangList = (scenesLang?.results || scenesLang || []) as any[]
const tracesEnList = (tracesEn?.results || tracesEn || []) as any[]
const tracesLangList = (tracesLang?.results || tracesLang || []) as any[]
// Match results by file_uuid + scene_index
scenes = scenesEnList.map((sEn: any) => {
const sceneIndex = sEn.index ?? sEn.scene_index ?? 0
const sLang = scenesLangList.find((s: any) =>
s.file_uuid === sEn.file_uuid && (s.index ?? s.scene_index ?? 0) === sceneIndex
)
return {
...sEn,
vlm_description_en: sEn.vlm_description || '',
vlm_description_localized: sLang?.vlm_description || '',
vlm_description: sEn.vlm_description || ''
}
})
traces = tracesEnList.map((tEn: any) => {
const traceId = tEn.trace_id
const tLang = tracesLangList.find((t: any) => t.file_uuid === tEn.file_uuid && t.trace_id === traceId)
return {
...tEn,
vlm_description_en: tEn.vlm_description || '',
vlm_description_localized: tLang?.vlm_description || '',
vlm_description: tEn.vlm_description || ''
}
})
} else {
// English only
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_size: maxResults, min_score: 0.0, output_language: 'en' })
}).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_size: maxResults, min_score: 0.0, output_language: 'en' })
}).then(r => r.json()).catch(() => ({}))
])
scenes = (scenesRes?.results || scenesRes || []) as any[]
traces = (tracesRes?.results || tracesRes || []) as any[]
}
// Core API returns both vlm_description (EN) and vlm_description_localized (JA) in single response
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_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_size: maxResults, min_score: 0.0, output_language: outputLang })
}).then(r => r.json()).catch(() => ({}))
])
scenes = (scenesRes?.results || scenesRes || []) as any[]
traces = (tracesRes?.results || tracesRes || []) as any[]
} catch (e) {
console.error('Visual search API error:', e)
}
@@ -1182,18 +1125,14 @@ async function sendMessage() {
let vlmDesc = s.vlm_description || ''
let textContent = vlmDesc
if (bilingual && s.vlm_description_en && s.vlm_description_localized) {
if (bilingual && s.vlm_description && s.vlm_description_localized) {
// Show localized language first, then English
textContent = `【${outputLang.toUpperCase()}】\n${s.vlm_description_localized}\n\n【English】\n${s.vlm_description_en}`
textContent = `【${outputLang.toUpperCase()}】\n${s.vlm_description_localized}\n\n【English】\n${s.vlm_description}`
vlmDesc = s.vlm_description_localized
} else if (bilingual && s.vlm_description_localized) {
// Only localized language available
textContent = `【${outputLang.toUpperCase()}】\n${s.vlm_description_localized}`
vlmDesc = s.vlm_description_localized
} else if (bilingual && s.vlm_description_en) {
// Only English available
textContent = `【English】\n${s.vlm_description_en}`
vlmDesc = s.vlm_description_en
}
return {
@@ -1209,7 +1148,7 @@ async function sendMessage() {
similarity: s.score ?? null,
thumbnail_url: `${base}/api/v1/file/${s.file_uuid}/scenes/scene_${String(sceneIndex).padStart(3, '0')}.jpg`,
bilingual,
vlm_description_en: s.vlm_description_en,
vlm_description_en: s.vlm_description,
vlm_description_localized: s.vlm_description_localized
}
})
@@ -1225,18 +1164,14 @@ async function sendMessage() {
let vlmDesc = t.vlm_description || ''
let textContent = vlmDesc
if (bilingual && t.vlm_description_en && t.vlm_description_localized) {
if (bilingual && t.vlm_description && t.vlm_description_localized) {
// Show localized language first, then English
textContent = `【${outputLang.toUpperCase()}】\n${t.vlm_description_localized}\n\n【English】\n${t.vlm_description_en}`
textContent = `【${outputLang.toUpperCase()}】\n${t.vlm_description_localized}\n\n【English】\n${t.vlm_description}`
vlmDesc = t.vlm_description_localized
} else if (bilingual && t.vlm_description_localized) {
// Only localized language available
textContent = `【${outputLang.toUpperCase()}】\n${t.vlm_description_localized}`
vlmDesc = t.vlm_description_localized
} else if (bilingual && t.vlm_description_en) {
// Only English available
textContent = `【English】\n${t.vlm_description_en}`
vlmDesc = t.vlm_description_en
}
return {
@@ -1251,7 +1186,7 @@ async function sendMessage() {
text_content: textContent,
similarity: t.score ?? null,
bilingual,
vlm_description_en: t.vlm_description_en,
vlm_description_en: t.vlm_description,
vlm_description_localized: t.vlm_description_localized
}
})
@@ -1350,19 +1285,16 @@ async function loadMore() {
}).then(r => r.json()).catch(() => ({}))
])
const sceneResults = (scenesRes.scenes || []).map((s: any) => {
const sceneResults = (scenesRes.results || []).map((s: any) => {
let vlmDesc = s.vlm_description || ''
let textContent = vlmDesc
if (bilingual && s.vlm_description_en && s.vlm_description_localized) {
textContent = `【${outputLang.toUpperCase()}】\n${s.vlm_description_localized}\n\n【English】\n${s.vlm_description_en}`
if (bilingual && s.vlm_description && s.vlm_description_localized) {
textContent = `【${outputLang.toUpperCase()}】\n${s.vlm_description_localized}\n\n【English】\n${s.vlm_description}`
vlmDesc = s.vlm_description_localized
} else if (bilingual && s.vlm_description_localized) {
textContent = `【${outputLang.toUpperCase()}】\n${s.vlm_description_localized}`
vlmDesc = s.vlm_description_localized
} else if (bilingual && s.vlm_description_en) {
textContent = `【English】\n${s.vlm_description_en}`
vlmDesc = s.vlm_description_en
}
return {
@@ -1379,25 +1311,22 @@ async function loadMore() {
thumbnail_url: s.thumbnail_url,
vlm_tags: s.vlm_tags || [],
bilingual,
vlm_description_en: s.vlm_description_en,
vlm_description_en: s.vlm_description,
vlm_description_localized: s.vlm_description_localized,
search_query: q
}
})
const traceResults = (tracesRes.traces || []).map((t: any) => {
const traceResults = (tracesRes.results || []).map((t: any) => {
let vlmDesc = t.vlm_description || ''
let textContent = vlmDesc
if (bilingual && t.vlm_description_en && t.vlm_description_localized) {
textContent = `【${outputLang.toUpperCase()}】\n${t.vlm_description_localized}\n\n【English】\n${t.vlm_description_en}`
if (bilingual && t.vlm_description && t.vlm_description_localized) {
textContent = `【${outputLang.toUpperCase()}】\n${t.vlm_description_localized}\n\n【English】\n${t.vlm_description}`
vlmDesc = t.vlm_description_localized
} else if (bilingual && t.vlm_description_localized) {
textContent = `【${outputLang.toUpperCase()}】\n${t.vlm_description_localized}`
vlmDesc = t.vlm_description_localized
} else if (bilingual && t.vlm_description_en) {
textContent = `【English】\n${t.vlm_description_en}`
vlmDesc = t.vlm_description_en
}
return {
@@ -1411,7 +1340,7 @@ async function loadMore() {
source_type: 'trace',
trace_id: t.trace_id,
bilingual,
vlm_description_en: t.vlm_description_en,
vlm_description_en: t.vlm_description,
vlm_description_localized: t.vlm_description_localized,
search_query: q
}