'use strict'; const $ = (id) => document.getElementById(id); let pollTimer = null; // ---------- 启动:轮询索引状态(服务器开机自动建索引) ---------- window.addEventListener('DOMContentLoaded', pollStatus); function pollStatus() { clearInterval(pollTimer); tick(); pollTimer = setInterval(tick, 800); } async function tick() { let s; try { s = await (await fetch('/api/status')).json(); } catch { $('status').textContent = '连接服务失败,确认服务器已启动。'; return; } // BM25 就绪即可搜(向量还在编码也能先看关键词路) if (s.bm25_ready) { $('q').disabled = false; $('search').disabled = false; } const bar = $('buildBar'); if (s.state === 'building') { bar.classList.remove('hidden'); if (s.phase === 'loadmodel') { $('status').textContent = `图片 ${s.n_images} 张 / 名称 ${s.total} 个 · 正在加载向量模型…`; setBar('加载本地向量模型 BGE-M3(首次需下载约 2.3G)…', 8); } else if (s.phase === 'embedding') { const pct = s.emb_total ? Math.round((s.emb_done / s.emb_total) * 100) : 0; const dev = s.device ? ` · ${s.device}` : ''; $('status').textContent = `图片 ${s.n_images} 张 / 名称 ${s.total} 个 · 向量编码中(BM25 已就绪,可先搜)`; setBar(`向量编码中${dev} ${s.emb_done}/${s.emb_total}`, pct); } else { $('status').textContent = 'BM25 分词建索引中…'; setBar('BM25 分词建索引中…', 4); } } else if (s.state === 'ready') { clearInterval(pollTimer); const dev = s.device ? ` (${s.device})` : ''; const note = s.failed ? `,${s.failed} 条失败` : ''; $('status').innerHTML = `✓ 就绪:${s.total} 个名称 / ${s.n_images} 张图${dev}${note} —— 直接搜索吧。`; bar.classList.add('hidden'); } else if (s.state === 'error') { clearInterval(pollTimer); $('status').textContent = '❌ ' + (s.message || '建索引失败'); bar.classList.add('hidden'); } } function setBar(t, pct) { $('buildText').textContent = t; $('barFill').style.width = (pct || 0) + '%'; } // ---------- 搜索 ---------- $('search').addEventListener('click', doSearch); $('q').addEventListener('keydown', (e) => { if (e.key === 'Enter') doSearch(); }); async function doSearch() { const q = $('q').value.trim(); if (!q) return; const params = new URLSearchParams({ q, topk: $('topk').value || 20, rrf_k: $('rrfk').value || 60, w_bm25: $('wbm').value || 1, w_vec: $('wvec').value || 1, }); let d; try { d = await (await fetch('/api/search?' + params)).json(); } catch (err) { alert('搜索失败:' + err.message); return; } renderCol('bm25', d.bm25); renderCol('vector', d.vector); renderCol('fusion', d.fusion); } function renderCol(kind, items) { $('cnt-' + kind).textContent = items.length ? `共 ${items.length}` : ''; const box = $('col-' + kind); box.innerHTML = ''; if (!items.length) { box.innerHTML = '