@@ -15,7 +15,7 @@ use crate::engine::EnginePool;
1515use crate :: live_session:: LiveSessionManager ;
1616use crate :: model_downloader:: ModelDownloader ;
1717use crate :: streaming_asr:: StreamingAsrModels ;
18- use crate :: types:: { NewNote , Note , NoteDraft , OcrBlock , TranscriptSegment } ;
18+ use crate :: types:: { NewNote , Note } ;
1919use crate :: video_profile:: ProfileMemory ;
2020use crate :: windows:: { self , CaptureWindow } ;
2121
@@ -29,13 +29,11 @@ const KEYWORD_MAX_CHARS: usize = 100;
2929const TAG_MAX_CHARS : usize = 50 ;
3030/// 一键流水线最大图片数。
3131const MAX_IMAGES : usize = 20 ;
32- /// 拼接输入段/块数量上限(防恶意超大列表拖垮拼接)。
33- const MAX_INPUT_ITEMS : usize = 5000 ;
3432
35- /// 音频文件扩展名白名单(安全 L1 修复:transcribe_audio 不得接受任意非音频路径 ,
33+ /// 音频文件扩展名白名单(安全 L1 修复:音频路径不得是任意非音频文件 ,
3634/// 与 commands_import::VIDEO_EXTENSIONS 同口径)。
3735const AUDIO_EXTENSIONS : [ & str ; 6 ] = [ "mp3" , "wav" , "m4a" , "flac" , "ogg" , "aac" ] ;
38- /// 图片文件扩展名白名单(安全 L1 修复:recognize_image 同口径 )。
36+ /// 图片文件扩展名白名单(安全 L1 修复:与音频路径校验同口径 )。
3937const IMAGE_EXTENSIONS : [ & str ; 5 ] = [ "png" , "jpg" , "jpeg" , "webp" , "bmp" ] ;
4038
4139/// 扩展名白名单校验(安全 L1):返回小写扩展名,不在白名单内返回可诊断错误。
@@ -213,78 +211,6 @@ pub async fn list_windows() -> Result<Vec<CaptureWindow>, String> {
213211 . map_err ( |e| format ! ( "任务调度失败: {}" , e) )
214212}
215213
216- /// 本地 ASR:转写一个 WAV 文件(REQ-001)。
217- #[ tauri:: command]
218- pub async fn transcribe_audio ( state : State < ' _ , AppState > , path : String ) -> Result < TranscriptSegment , String > {
219- if path. trim ( ) . is_empty ( ) {
220- return Err ( "音频路径为空" . to_string ( ) ) ;
221- }
222- // 安全 L1 修复:扩展名白名单前置校验(拒绝非音频路径)
223- require_media_extension ( & path, & AUDIO_EXTENSIONS , "音频" ) ?;
224- let engines = state. engines . clone ( ) ;
225- // H2 修复:有界等待变体——引擎卡死时返回可诊断超时错误而非永久阻塞
226- tauri:: async_runtime:: spawn_blocking ( move || {
227- engines. transcribe_timeout ( & path, crate :: engine:: ASR_REQUEST_TIMEOUT )
228- } )
229- . await
230- . map_err ( |e| format ! ( "任务调度失败: {}" , e) ) ?
231- . map_err ( |e| e. to_string ( ) )
232- }
233-
234- /// 本地 OCR:识别一张图片(REQ-002)。
235- #[ tauri:: command]
236- pub async fn recognize_image ( state : State < ' _ , AppState > , path : String ) -> Result < Vec < OcrBlock > , String > {
237- if path. trim ( ) . is_empty ( ) {
238- return Err ( "图片路径为空" . to_string ( ) ) ;
239- }
240- // 安全 L1 修复:扩展名白名单前置校验(拒绝非图片路径)
241- require_media_extension ( & path, & IMAGE_EXTENSIONS , "图片" ) ?;
242- let engines = state. engines . clone ( ) ;
243- // H2 修复:有界等待变体(同 transcribe_audio)
244- tauri:: async_runtime:: spawn_blocking ( move || {
245- engines. recognize_timeout ( & path, crate :: engine:: OCR_REQUEST_TIMEOUT )
246- } )
247- . await
248- . map_err ( |e| format ! ( "任务调度失败: {}" , e) ) ?
249- . map_err ( |e| e. to_string ( ) )
250- }
251-
252- /// 本地拼接:转写段 + OCR 块 → 笔记初稿(REQ-003,纯本地无 LLM)。
253- #[ tauri:: command]
254- pub async fn build_draft (
255- title : String ,
256- segments : Vec < TranscriptSegment > ,
257- ocr_blocks : Vec < OcrBlock > ,
258- ) -> Result < NoteDraft , String > {
259- if segments. len ( ) > MAX_INPUT_ITEMS || ocr_blocks. len ( ) > MAX_INPUT_ITEMS {
260- return Err ( format ! ( "拼接输入数量超限(上限 {})" , MAX_INPUT_ITEMS ) ) ;
261- }
262- let title = normalize_title ( title, "未命名笔记" ) ;
263- tauri:: async_runtime:: spawn_blocking ( move || concat:: build_note_draft ( & title, & segments, & ocr_blocks) )
264- . await
265- . map_err ( |e| format ! ( "任务调度失败: {}" , e) )
266- }
267-
268- /// 课堂助手 → 笔记联动:把拼接初稿一键存为笔记(REQ-005)。
269- #[ tauri:: command]
270- pub async fn save_draft_as_note ( state : State < ' _ , AppState > , draft : NoteDraft ) -> Result < Note , String > {
271- let new = NewNote {
272- title : normalize_title ( draft. title , "未命名笔记" ) ,
273- content : truncate_chars ( draft. markdown , CONTENT_MAX_CHARS ) ,
274- source : "classroom" . to_string ( ) ,
275- session_id : None ,
276- rule_version : None ,
277- purify_stats : None ,
278- tags : None ,
279- properties : None ,
280- group_id : None ,
281- } ;
282- let note = state. db . create_note ( & new) . map_err ( |e| e. to_string ( ) ) ?;
283- // REQ-278:笔记域变更广播(其它视图即时刷新)
284- crate :: notify:: emit_changed ( & state. app , crate :: notify:: DataDomain :: Notes ) ;
285- Ok ( note)
286- }
287-
288214/// 一键流水线:音频转写 + 多图 OCR → 本地拼接 → 自动存为笔记。
289215///
290216/// @ai-context: 这是"课堂助手 → 笔记"的核心体验编排(高价值优化②):用户一次提交素材,
@@ -308,7 +234,7 @@ pub async fn process_to_note(
308234 return Err ( "音频路径为空" . to_string ( ) ) ;
309235 }
310236 // 安全 L1 修复(三维复审 #4):直送媒体路径同样套用扩展名白名单,
311- // 与 transcribe_audio/recognize_image 同口径 (此前本 command 是缺口)
237+ // 与音频/图片路径白名单同口径 (此前本 command 是缺口)
312238 if let Some ( p) = audio_path. as_deref ( ) {
313239 require_media_extension ( p, & AUDIO_EXTENSIONS , "音频" ) ?;
314240 }
0 commit comments