Skip to content

Commit cd9bcee

Browse files
committed
feat(kb): 设置页语义引擎治理——状态/下载/加载 UI(REQ-259/262)
1 parent 31f91e5 commit cd9bcee

1 file changed

Lines changed: 98 additions & 9 deletions

File tree

‎app/src/components/LearningLibraryPanel.tsx‎

Lines changed: 98 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,27 @@
22
* LearningLibraryPanel — 设置页「学习库」段(v0.19 REQ-258/REQ-260/REQ-262)。
33
*
44
* @ai-context: 检索与发现层的治理面:①引擎状态如实展示(FTS5 恒可用;
5-
* embedding v0.19.3 前恒"未接入"——诚实不装);②「学习库问答
6-
* 生成」开关 + 预算档位(双闸门之二,默认关——命中列表不受
7-
* 其约束);③索引统计/全量重建(脏源/失败角标可见 + 重建兜底;
8-
* 进度经 Channel 事件流——与聊天流同构,不做全局监听)。
5+
* embedding v0.19.5 起按引擎槽与回填态展示——诚实不装);②「学习
6+
* 库问答生成」开关 + 预算档位(双闸门之二,默认关——命中列表
7+
* 不受其约束);③索引统计/全量重建(脏源/失败角标可见 + 重建兜
8+
* 底;进度经 Channel 事件流——与聊天流同构);④v0.19.5(REQ-259/
9+
* REQ-262):本地语义引擎治理——状态/下载(hf-mirror 双源)/
10+
* 加载(换槽)/重建回填引导。
911
*/
1012
import { useCallback, useEffect, useState } from "react";
1113
import { Channel, invoke } from "@tauri-apps/api/core";
1214
import type { AiSettingsView } from "../types/ai";
1315
import type { KbIndexStats, KbReindexEvent } from "../types/kb";
1416

17+
/** kb_embedding_status/load 载荷(Rust EmbeddingStatusView camelCase 契约) */
18+
interface EmbeddingStatusView {
19+
kind: string;
20+
ready: boolean;
21+
dim: number | null;
22+
modelDir: string;
23+
detail: string;
24+
}
25+
1526
const TIERS = [
1627
{ id: "light", label: "轻量(~4K)" },
1728
{ id: "standard", label: "标准(~10K,推荐)" },
@@ -27,6 +38,17 @@ export default function LearningLibraryPanel({ active = true }: { active?: boole
2738
const [msg, setMsg] = useState<{ kind: "ok" | "err"; text: string } | null>(null);
2839
const [busy, setBusy] = useState(false);
2940
const [progress, setProgress] = useState<{ done: number; total: number } | null>(null);
41+
// REQ-259(v0.19.5):本地语义引擎状态(noop=未配置;onnx=就绪)
42+
const [emb, setEmb] = useState<EmbeddingStatusView | null>(null);
43+
const [engBusy, setEngBusy] = useState(false);
44+
45+
const loadEmb = useCallback(async () => {
46+
try {
47+
setEmb(await invoke<EmbeddingStatusView>("kb_embedding_status"));
48+
} catch (e) {
49+
setMsg({ kind: "err", text: `语义引擎状态读取失败: ${e}` });
50+
}
51+
}, []);
3052

3153
const loadStats = useCallback(async () => {
3254
try {
@@ -53,7 +75,8 @@ export default function LearningLibraryPanel({ active = true }: { active?: boole
5375
}
5476
})();
5577
void loadStats();
56-
}, [loadStats]);
78+
void loadEmb();
79+
}, [loadStats, loadEmb]);
5780

5881
// 轻量轮询(审查 L2 修复):设置页随应用常驻挂载(display:none 不卸载)——
5982
// 轮询只在页面可见时运行(对齐 SessionsPage active 门控先例;进入设置页即
@@ -142,22 +165,88 @@ export default function LearningLibraryPanel({ active = true }: { active?: boole
142165
stats != null &&
143166
(stats.dirtySources > 0 || (stats.errorCount > 0 || stats.indexVersion !== stats.currentIndexVersion));
144167

168+
/** REQ-259:加载本地语义引擎(换槽;成功后引导重建回填向量) */
169+
const loadEngine = async () => {
170+
if (engBusy) return;
171+
setEngBusy(true);
172+
setMsg(null);
173+
try {
174+
const v = await invoke<EmbeddingStatusView>("kb_embedding_load");
175+
setEmb(v);
176+
setMsg({ kind: "ok", text: v.detail });
177+
} catch (e) {
178+
setMsg({ kind: "err", text: `语义引擎加载失败: ${e}` });
179+
} finally {
180+
setEngBusy(false);
181+
}
182+
};
183+
184+
/** REQ-262:按需下载 bge 模型(model_quantized.onnx + vocab.txt) */
185+
const downloadEngine = async () => {
186+
if (engBusy) return;
187+
setEngBusy(true);
188+
setMsg(null);
189+
try {
190+
const text = await invoke<string>("kb_embedding_download");
191+
setMsg({ kind: "ok", text });
192+
} catch (e) {
193+
setMsg({ kind: "err", text: `模型下载失败: ${e}` });
194+
} finally {
195+
setEngBusy(false);
196+
}
197+
};
198+
145199
return (
146200
<div data-testid="learning-library-panel" style={{ padding: 8 }}>
147-
{/* 引擎状态(如实——FTS5 恒可用;embedding 未接入不伪装) */}
201+
{/* 引擎状态(如实——FTS5 恒可用;语义引擎按槽位/回填态展示) */}
148202
<div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 6, fontSize: 12 }}>
149203
<span style={{ color: "#047857", background: "#ecfdf5", border: "1px solid #a7f3d0", borderRadius: 10, padding: "1px 8px" }}>
150204
✓ 本地词法引擎(FTS5)就绪
151205
</span>
152206
{stats?.embeddingReady ? (
153-
<span style={{ color: "#047857", background: "#ecfdf5", borderRadius: 10, padding: "1px 8px" }}>✓ 语义检索已接入</span>
207+
<span style={{ color: "#047857", background: "#ecfdf5", borderRadius: 10, padding: "1px 8px" }}>✓ 语义检索已接入({stats.engine})</span>
154208
) : (
155-
<span style={{ color: "#6b7280", background: "#f3f4f6", borderRadius: 10, padding: "1px 8px" }} title="语义检索接入属 v0.19.4(embedding 选型 spike 后)——当前按词法精度工作,能力如实">
156-
○ 语义检索未接入(当前词法精度)
209+
<span style={{ color: "#6b7280", background: "#f3f4f6", borderRadius: 10, padding: "1px 8px" }} title="下载模型并加载后全量重建即回填向量">
210+
○ 语义检索未回填(当前词法精度)
157211
</span>
158212
)}
159213
</div>
160214

215+
{/* v0.19.5(REQ-259/262):本地语义引擎治理——状态 / 下载 / 加载 / 重建引导 */}
216+
<div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", marginBottom: 8, fontSize: 11.5 }}>
217+
<span style={{ fontWeight: 600, fontSize: 12, color: "#374151" }}>
218+
🧬 本地语义引擎(bge-small-zh)
219+
</span>
220+
{emb?.ready ? (
221+
<span data-testid="kb-emb-ready" style={{ color: "#047857" }}>
222+
✓ 就绪(dim={emb.dim})
223+
</span>
224+
) : (
225+
<span data-testid="kb-emb-noop" style={{ color: "#9ca3af" }} title={emb?.modelDir ?? "模型目录未探测"}>
226+
○ {emb?.detail ?? "状态读取中…"}
227+
</span>
228+
)}
229+
<span style={{ flex: 1 }} />
230+
<button
231+
data-testid="kb-emb-download"
232+
onClick={() => void downloadEngine()}
233+
disabled={engBusy || emb?.ready}
234+
title="按需下载 model_quantized.onnx 与 vocab.txt(约 25MB,hf-mirror)"
235+
style={{ fontSize: 12, cursor: emb?.ready || engBusy ? "default" : "pointer", padding: "3px 10px", borderRadius: 6, border: "1px solid #d1d5db", background: "#fff", color: emb?.ready ? "#9ca3af" : "#374151" }}
236+
>
237+
{engBusy ? "处理中…" : emb?.ready ? "已下载" : "⬇ 下载模型"}
238+
</button>
239+
<button
240+
data-testid="kb-emb-load"
241+
onClick={() => void loadEngine()}
242+
disabled={engBusy || emb?.ready}
243+
title="从模型目录加载引擎并换槽(加载成功后点「🔄 全量重建」回填向量)"
244+
style={{ fontSize: 12, cursor: emb?.ready || engBusy ? "default" : "pointer", padding: "3px 10px", borderRadius: 6, border: "1px solid #0d9488", background: emb?.ready ? "#f0fdfa" : "#fff", color: emb?.ready ? "#9ca3af" : "#0f766e" }}
245+
>
246+
{emb?.ready ? "已加载" : "▶ 加载引擎"}
247+
</button>
248+
</div>
249+
161250
{/* 生成开关 + 预算档位 */}
162251
<div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 4, flexWrap: "wrap" }}>
163252
<span style={{ fontWeight: 600, fontSize: 12, color: "#374151" }}>📚 学习库问答生成</span>

0 commit comments

Comments
 (0)