// ———— Search Page ———— function MarkdownLite({ text }) { const lines = text.split("\n"); const out = []; let listBuf = []; const flush = () => { if (listBuf.length) { out.push(); listBuf = []; } }; const inline = (s, k) => { const parts = []; let rest = s; let idx = 0; const patterns = [ [/\*\*(.+?)\*\*/, (m) => {m[1]}], [/`([^`]+)`/, (m) => {m[1]}], ]; while (rest) { let earliest = -1, which = -1, match; patterns.forEach(([re], i) => { const m = rest.match(re); if (m && (earliest === -1 || m.index < earliest)) { earliest = m.index; which = i; match = m; } }); if (which === -1) { parts.push(rest); break; } if (earliest > 0) parts.push(rest.slice(0, earliest)); parts.push(patterns[which][1](match)); rest = rest.slice(earliest + match[0].length); } return parts; }; lines.forEach((ln, i) => { if (ln.startsWith("- ")) { listBuf.push(
  • {inline(ln.slice(2), i)}
  • ); } else if (ln.trim() === "") { flush(); } else { flush(); out.push(

    {inline(ln, i)}

    ); } }); flush(); return
    {out}
    ; } function MechBadge({ name }) { const cls = MECH_COLORS[name] || "neutral"; return {name}; } function SearchPage({ openDetail }) { const [q, setQ] = useState(""); const [loading, setLoading] = useState(false); const [results, setResults] = useState([]); const [insight, setInsight] = useState(""); const [error, setError] = useState(""); const [searched, setSearched] = useState(false); const doSearch = async (e) => { if (e) e.preventDefault(); if (!q.trim()) return; setLoading(true); setError(""); try { const data = await window.API.search(q.trim(), 20); setInsight(data.insight || ""); setResults(data.videos || []); setSearched(true); } catch (err) { setError(err.message); } finally { setLoading(false); } }; const vectorCount = results.filter(r => r.score_source === "vector").length; const aiCount = results.filter(r => r.score_source === "hermes_ref").length; const bothCount = results.filter(r => r.score_source === "both").length; const _openDetail = (r) => { const platMap = { youtube: PLATFORMS[0], tiktok: PLATFORMS[1], bilibili: PLATFORMS[2] }; const meta = r.metadata || {}; const hue = (r.id || "").split("").reduce((a, c) => a + c.charCodeAt(0), 200) % 360; const mechs = (() => { try { return JSON.parse(meta.viral_mechanisms || "[]"); } catch { return []; } })(); const labels = (() => { try { return JSON.parse(meta.structure_labels || "[]"); } catch { return []; } })(); openDetail({ id: r.id, title: meta.title || r.id, platform: platMap[meta.platform] || { id: "?", label: "?", cls: "neutral" }, type: meta.video_type || "", structure: meta.story_structure || "", hook_type: meta.hook_type || "", score: meta.score_total || 0, reproducibility: meta.reproducibility || "", publishedAt: meta.published_at || "", duration: meta.duration_sec || 0, url: meta.url || "", thumbHue: hue, mechanisms: mechs, emotionPath: (meta.emotion_curve || "").split(/[→\-]/).map(s => s.trim()).filter(Boolean), transferable: mechs, structure_labels_arr: labels, radar: { "综合评分": meta.score_total || 0 }, metricsHistory: [], }); }; return ( <>

    语义搜索

    结合 ChromaDB 向量检索与 Hermes AI 洞察,返回最贴近意图的爆款样本。

    在知识库中,描述你想找的视频

    自然语言 / 机制关键词 / 结构标签都可以 · 支持中英文混写
    setQ(e.target.value)} placeholder="描述你想找的视频类型,如「传承赋能 三段式 情感共鸣」" />
    {error && (
    {error}
    )} {(searched || loading) && ( <> {/* AI Insight */}
    H
    AI 知识库洞察
    Hermes LLM Wiki
    {loading ? (
    正在查询知识库...
    ) : insight ? ( ) : (
    Hermes 未配置或无洞察内容。向量搜索结果如下。
    )}
    {/* Results */} {!loading && ( <>
    {results.length} 条视频结果 · 按综合评分排序
    {vectorCount > 0 && 向量匹配 {vectorCount}} {aiCount > 0 && AI 引用 {aiCount}} {bothCount > 0 && 双重命中 {bothCount}}
    {results.length === 0 ? (
    知识库中暂无相关视频,请先采集一些视频
    ) : (
    {results.map((r, idx) => { const meta = r.metadata || {}; const mechs = (() => { try { return JSON.parse(meta.viral_mechanisms || "[]"); } catch { return []; } })(); const platMap = { youtube: PLATFORMS[0], tiktok: PLATFORMS[1], bilibili: PLATFORMS[2] }; const plat = platMap[meta.platform] || { id: "?", label: "?", cls: "neutral" }; const hue = (r.id || "").split("").reduce((a, c) => a + c.charCodeAt(0), 200) % 360; const scoreDisplay = Math.round((r.score || 0) * 100); return (
    _openDetail(r)}>
    {formatDuration(meta.duration_sec || 0)}
    {meta.title || r.id}
    {meta.story_structure || ""} · {formatDate(meta.published_at || "")}
    {mechs.slice(0, 4).map(m => )}
    {meta.score_total || "—"} {meta.score_total && /100}
    {r.score_source === "vector" && 向量匹配} {r.score_source === "hermes_ref" && AI 引用} {r.score_source === "both" && 双重命中}
    得分 {(r.score || 0).toFixed(3)}
    ); })}
    )} )} )} {!searched && !loading && (
    输入查询词后按搜索
    )} ); } window.SearchPage = SearchPage;