@@ -68,6 +68,53 @@ pub fn kb_index_stats(state: State<'_, AppState>) -> Result<KbIndexStats, String
6868/// kb embedding 模型相对目录(model_dir 下;与 speaker 下载器同约定)
6969const EMBEDDING_MODEL_REL : & str = "embedding/bge-small-zh-v1.5" ;
7070
71+ /// 下载 kb embedding 模型(bge-small-zh-v1.5 int8 ONNX + BERT 词表)。
72+ ///
73+ /// @ai-context: 模型文件不入库、不打包(约 25MB 按需下载——REQ-262 口径);
74+ /// 源:ONNX 量化模型走 Xenova(社区导出),vocab.txt 走 BAAI
75+ /// 官方仓库;下载写 .part 后原子改名(中断残留可重下覆盖)。
76+ /// 下载完成 ≠ 就绪——需 kb_embedding_load 换槽 + 「学习库」段
77+ /// 重建索引回填向量(命令返回值给前端引导文案)。
78+ #[ tauri:: command]
79+ pub async fn kb_embedding_download ( state : State < ' _ , AppState > ) -> Result < String , String > {
80+ let dir = state. model_dir . join ( EMBEDDING_MODEL_REL ) ;
81+ let files: [ ( & str , & str ) ; 2 ] = [
82+ (
83+ "model_quantized.onnx" ,
84+ "https://hf-mirror.com/Xenova/bge-small-zh-v1.5/resolve/main/onnx/model_quantized.onnx" ,
85+ ) ,
86+ (
87+ "vocab.txt" ,
88+ "https://hf-mirror.com/BAAI/bge-small-zh-v1.5/resolve/main/vocab.txt" ,
89+ ) ,
90+ ] ;
91+ tauri:: async_runtime:: spawn_blocking ( move || -> Result < String , String > {
92+ std:: fs:: create_dir_all ( & dir) . map_err ( |e| format ! ( "创建模型目录失败: {e}" ) ) ?;
93+ let mut downloaded = Vec :: new ( ) ;
94+ for ( name, url) in files {
95+ let part = dir. join ( format ! ( "{name}.part" ) ) ;
96+ let target = dir. join ( name) ;
97+ let resp = ureq:: get ( url)
98+ . timeout ( std:: time:: Duration :: from_secs ( 600 ) )
99+ . call ( )
100+ . map_err ( |e| format ! ( "下载 {name} 失败: {e}" ) ) ?;
101+ let mut reader = resp. into_reader ( ) ;
102+ let mut out = std:: fs:: File :: create ( & part) . map_err ( |e| format ! ( "写临时文件失败: {e}" ) ) ?;
103+ std:: io:: copy ( & mut reader, & mut out) . map_err ( |e| format ! ( "写 {name} 失败: {e}" ) ) ?;
104+ drop ( out) ;
105+ std:: fs:: rename ( & part, & target) . map_err ( |e| format ! ( "落位 {name} 失败: {e}" ) ) ?;
106+ downloaded. push ( name. to_string ( ) ) ;
107+ }
108+ Ok ( format ! (
109+ "已下载:{}({})——请在「学习库」段点击加载引擎并重建索引以回填向量" ,
110+ downloaded. join( "、" ) ,
111+ dir. to_string_lossy( )
112+ ) )
113+ } )
114+ . await
115+ . map_err ( |e| format ! ( "下载任务调度失败: {e}" ) ) ?
116+ }
117+
71118/// 引擎状态视图(设置页「学习库」段数据源——无模型如实显示 noop 与原因)。
72119#[ derive( Serialize ) ]
73120#[ serde( rename_all = "camelCase" ) ]
0 commit comments