Skip to content

Commit 12eadb4

Browse files
committed
refactor(rust): 拆 ai_refine_task 步 2 — 终态骨架
1 parent a0c5e62 commit 12eadb4

3 files changed

Lines changed: 124 additions & 100 deletions

File tree

‎app/src-tauri/src/ai_refine_task.rs‎

Lines changed: 7 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
use base64::Engine;
1313
use tauri::Emitter;
1414

15-
use crate::ai_chat::{AiTurn, trajectory_to_json};
15+
use crate::ai_chat::AiTurn;
1616
use crate::ai_client::AiClient;
1717
use crate::ai_mock::AiMockAdapter;
1818
use crate::ai_note_refine::AiNoteRefineAdapter;
@@ -32,6 +32,12 @@ mod workers;
3232
/// 子模块再导出:既有 `crate::ai_refine_task::{…}` 导入路径保持不变。
3333
pub(crate) use self::workers::{refine_slices_concurrent, RefineCtx};
3434

35+
/// 任务终态骨架子模块(catch_unwind 归一 + 落库/审计/裁剪,锁序见 skeleton.rs 模块头)
36+
#[path = "ai_refine_task/skeleton.rs"]
37+
mod skeleton;
38+
/// 子模块再导出:既有 `crate::ai_refine_task::{…}` 导入路径保持不变。
39+
pub(crate) use self::skeleton::run_refine_task_skeleton;
40+
3541
/// 切片并发上限(REQ-145:并发 2-3——配额并发安全由 command 层启动前按
3642
/// 预估片数一次性消耗保证,此处 worker 数不超切片数)。v0.17.0:pub(crate)
3743
/// ——笔记级精修任务共用同一上限。
@@ -68,104 +74,6 @@ fn emit_refine_stream(st: &AppState, task_id: u64, frame: RefineStreamFrame) {
6874
let _ = st.app.emit("ai:refine-stream", RefineStreamPayload { task_id, frame });
6975
}
7076

71-
/// 精修任务审计记录(F1:REQ-140 轨迹可见化——summary 不含原文,隐私红线)。
72-
/// v0.17.0:summary_ctx 泛化("session=1"/"note=3"——会话级/笔记级共用)。
73-
fn push_refine_audit(st: &AppState, summary_ctx: &str, result: &str, model: Option<&str>) {
74-
let now = crate::db_sessions_rows::unix_seconds();
75-
if let Ok(mut g) = st.ai_guardrails.lock() {
76-
g.push_audit(crate::ai_guardrails::AiAuditEntry {
77-
at_unix: now,
78-
upload_summary: format!("refine {} model={}", summary_ctx, model.unwrap_or("?")),
79-
result: result.to_string(),
80-
});
81-
}
82-
}
83-
84-
/// 任务收尾公共骨架(会话级/笔记级共用——v0.17.0 REQ-246 提取)。
85-
///
86-
/// @ai-context: 彻底检测加固(2026-08-21):spawn_blocking 的 JoinHandle 未被
87-
/// await——闭包内 panic 会被 tokio 吞掉,任务状态永久停在
88-
/// Pending(前端永久显示"任务排队中",无失败可重试)。
89-
/// catch_unwind 把 panic 归一为 Failed 状态,状态流转永不失联。
90-
/// @ai-context: F2-B4:单片失败重试后仍失败 → 保留已成功片(partial_failed
91-
/// 语义:failed_slices > 0,前端显示"部分成功 x/y 片")。
92-
pub(crate) fn run_refine_task_skeleton(
93-
st: AppState,
94-
task_id: u64,
95-
target_summary: String,
96-
work: impl FnOnce() -> Result<(AiRefineResult, Vec<AiTurn>), AiTaskFailure>,
97-
) {
98-
let started = std::time::Instant::now();
99-
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(work)).unwrap_or_else(|_| {
100-
Err(AiTaskFailure::Other(
101-
"精修任务内部错误(panic)——请重试;若复现请反馈".to_string(),
102-
))
103-
});
104-
let elapsed_ms = started.elapsed().as_millis() as i64;
105-
match outcome {
106-
Ok((result, turns)) => {
107-
eprintln!(
108-
"[refine-task] task={} succeeded slices={} failed={} diff={}",
109-
task_id,
110-
result.slices,
111-
result.failed_slices,
112-
result.diff.len()
113-
);
114-
// v0.16.0(REQ-230):轨迹落库(提示词/回答全文——任务对话视图数据源)
115-
if let Some(json) = trajectory_to_json(&turns) {
116-
if let Err(e) = st.db.update_ai_task_trajectory(task_id, &json) {
117-
eprintln!("[refine-task] task={} 轨迹落库失败(不阻断): {}", task_id, e);
118-
}
119-
}
120-
{
121-
let mut tasks = st.ai_tasks.lock().unwrap_or_else(|e| e.into_inner());
122-
if let Some(entry) = tasks.get_mut(&task_id) {
123-
entry.result = serde_json::to_value(&result).ok();
124-
}
125-
}
126-
set_task(&st, task_id, AiTaskState::Succeeded);
127-
// REQ-247:终态帧(前端消息定格为最终摘要+双入口)
128-
emit_refine_stream(&st, task_id, RefineStreamFrame::Done {
129-
slices: result.slices,
130-
failed_slices: result.failed_slices,
131-
});
132-
// F1 修复(2026-08-21):精修调用上审计——REQ-140 轨迹可见化
133-
push_refine_audit(&st, &target_summary, "ok", Some(&result.model));
134-
// F2 任务中心:终态落库(写库失败不阻断——H2 设计)+ 保留策略
135-
// 裁剪(审查修复:trim 原只在启动时跑,运行期终态任务会累积——
136-
// 每次终态后清理超限旧终态,防表膨胀)
137-
let result_json = serde_json::to_string(&result).ok();
138-
let _ = st.db.finish_ai_task(
139-
task_id,
140-
"succeeded",
141-
result_json.as_deref(),
142-
None,
143-
elapsed_ms,
144-
);
145-
let _ = st.db.trim_ai_tasks();
146-
}
147-
Err(reason) => {
148-
// 打印具体 message——区分"未配置密钥"vs"密钥无效(401/403)"(真机排查)
149-
eprintln!(
150-
"[refine-task] task={} failed kind={} msg={}",
151-
task_id,
152-
reason.kind(),
153-
reason.message()
154-
);
155-
set_task(&st, task_id, AiTaskState::Failed { reason: reason.clone() });
156-
push_refine_audit(&st, &target_summary, "error", None);
157-
let _ = st.db.finish_ai_task(
158-
task_id,
159-
"failed",
160-
None,
161-
Some(&format!("{}: {}", reason.kind(), reason.message())),
162-
elapsed_ms,
163-
);
164-
let _ = st.db.trim_ai_tasks();
165-
}
166-
}
167-
}
168-
16977
/// 后台精修任务:规则草稿 → 切片 → 逐片精修(mock/云端)→ 合并 → diff。
17078
///
17179
/// @ai-context: v0.17.0(REQ-245):dims=策略解析结果(command 层 resolve 后
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//! AI 精修任务终态骨架(拆分自 ai_refine_task.rs;AGENTS.md §3 单文件 ≤300 行)。
2+
//!
3+
//! @ai-context: 会话级/笔记级精修任务共用的收尾骨架:catch_unwind 把 panic 归一为
4+
//! Failed(spawn_blocking 的 JoinHandle 未被 await——否则任务状态永久
5+
//! 停 Pending、前端永久"排队中");成功路径:轨迹落库 → 结果写注册表 →
6+
//! set_task → 终态帧 → 审计 → finish_ai_task + trim_ai_tasks。
7+
//! @ai-context: 锁序红线:`st.ai_tasks` 的写入用**显式块**(块尾释放守卫),set_task
8+
//! 在其后**重新加同一把 std::Mutex**——两者合并 = 自死锁(std Mutex 不可
9+
//! 重入),任务永久 Running。该序列不可分割。
10+
//! @ai-context: 审计写入 push_refine_audit 的守卫活到 `if let` 块尾(窗口 = push_audit
11+
//! 调用)——不得把 now/format! 挪进或挪出该块。
12+
13+
use crate::ai_chat::{AiTurn, trajectory_to_json};
14+
use crate::ai_task::{AiTaskFailure, AiTaskState};
15+
use crate::commands::AppState;
16+
use crate::commands_ai_refine::{set_task, AiRefineResult};
17+
18+
use super::{emit_refine_stream, RefineStreamFrame};
19+
20+
/// 精修任务审计记录(F1:REQ-140 轨迹可见化——summary 不含原文,隐私红线)。
21+
/// v0.17.0:summary_ctx 泛化("session=1"/"note=3"——会话级/笔记级共用)。
22+
fn push_refine_audit(st: &AppState, summary_ctx: &str, result: &str, model: Option<&str>) {
23+
let now = crate::db_sessions_rows::unix_seconds();
24+
if let Ok(mut g) = st.ai_guardrails.lock() {
25+
g.push_audit(crate::ai_guardrails::AiAuditEntry {
26+
at_unix: now,
27+
upload_summary: format!("refine {} model={}", summary_ctx, model.unwrap_or("?")),
28+
result: result.to_string(),
29+
});
30+
}
31+
}
32+
33+
/// 任务收尾公共骨架(会话级/笔记级共用——v0.17.0 REQ-246 提取)。
34+
///
35+
/// @ai-context: 彻底检测加固(2026-08-21):spawn_blocking 的 JoinHandle 未被
36+
/// await——闭包内 panic 会被 tokio 吞掉,任务状态永久停在
37+
/// Pending(前端永久显示"任务排队中",无失败可重试)。
38+
/// catch_unwind 把 panic 归一为 Failed 状态,状态流转永不失联。
39+
/// @ai-context: F2-B4:单片失败重试后仍失败 → 保留已成功片(partial_failed
40+
/// 语义:failed_slices > 0,前端显示"部分成功 x/y 片")。
41+
pub(crate) fn run_refine_task_skeleton(
42+
st: AppState,
43+
task_id: u64,
44+
target_summary: String,
45+
work: impl FnOnce() -> Result<(AiRefineResult, Vec<AiTurn>), AiTaskFailure>,
46+
) {
47+
let started = std::time::Instant::now();
48+
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(work)).unwrap_or_else(|_| {
49+
Err(AiTaskFailure::Other(
50+
"精修任务内部错误(panic)——请重试;若复现请反馈".to_string(),
51+
))
52+
});
53+
let elapsed_ms = started.elapsed().as_millis() as i64;
54+
match outcome {
55+
Ok((result, turns)) => {
56+
eprintln!(
57+
"[refine-task] task={} succeeded slices={} failed={} diff={}",
58+
task_id,
59+
result.slices,
60+
result.failed_slices,
61+
result.diff.len()
62+
);
63+
// v0.16.0(REQ-230):轨迹落库(提示词/回答全文——任务对话视图数据源)
64+
if let Some(json) = trajectory_to_json(&turns) {
65+
if let Err(e) = st.db.update_ai_task_trajectory(task_id, &json) {
66+
eprintln!("[refine-task] task={} 轨迹落库失败(不阻断): {}", task_id, e);
67+
}
68+
}
69+
{
70+
let mut tasks = st.ai_tasks.lock().unwrap_or_else(|e| e.into_inner());
71+
if let Some(entry) = tasks.get_mut(&task_id) {
72+
entry.result = serde_json::to_value(&result).ok();
73+
}
74+
}
75+
set_task(&st, task_id, AiTaskState::Succeeded);
76+
// REQ-247:终态帧(前端消息定格为最终摘要+双入口)
77+
emit_refine_stream(&st, task_id, RefineStreamFrame::Done {
78+
slices: result.slices,
79+
failed_slices: result.failed_slices,
80+
});
81+
// F1 修复(2026-08-21):精修调用上审计——REQ-140 轨迹可见化
82+
push_refine_audit(&st, &target_summary, "ok", Some(&result.model));
83+
// F2 任务中心:终态落库(写库失败不阻断——H2 设计)+ 保留策略
84+
// 裁剪(审查修复:trim 原只在启动时跑,运行期终态任务会累积——
85+
// 每次终态后清理超限旧终态,防表膨胀)
86+
let result_json = serde_json::to_string(&result).ok();
87+
let _ = st.db.finish_ai_task(
88+
task_id,
89+
"succeeded",
90+
result_json.as_deref(),
91+
None,
92+
elapsed_ms,
93+
);
94+
let _ = st.db.trim_ai_tasks();
95+
}
96+
Err(reason) => {
97+
// 打印具体 message——区分"未配置密钥"vs"密钥无效(401/403)"(真机排查)
98+
eprintln!(
99+
"[refine-task] task={} failed kind={} msg={}",
100+
task_id,
101+
reason.kind(),
102+
reason.message()
103+
);
104+
set_task(&st, task_id, AiTaskState::Failed { reason: reason.clone() });
105+
push_refine_audit(&st, &target_summary, "error", None);
106+
let _ = st.db.finish_ai_task(
107+
task_id,
108+
"failed",
109+
None,
110+
Some(&format!("{}: {}", reason.kind(), reason.message())),
111+
elapsed_ms,
112+
);
113+
let _ = st.db.trim_ai_tasks();
114+
}
115+
}
116+
}

‎docs/standards/line-limit-exemptions.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
| app/src/components/action-center/ActionCenterPanel.tsx | 537 | v0.20.5 行动中心独立页化:原 ActionCenterOverlay.tsx(509 行登记)更名迁移至 action-center/ 并去遮罩/关闭形态(refreshToken 切回重载)——编排内聚(TD-2026-09-06-G 预留目录兑现);2026-09-06 实测登记 | 若再增长:队列/历史/SOP 三区拆至 action-center/ 子组件 |
3333
| app/src-tauri/src/commands_knowledge_core.rs | 529 | v0.13.1(REQ-202~205):知识体系命令域(概念/模型/引用/审计——commands 9-18)内聚;源 commands_knowledge.rs(18 命令 + 校验)超限按规格 §四拆,本文件承接后半;commands 薄壳 + inner 纯函数 + @ai-context 注释内聚于命令域 | 若再增长:引用与审计拆至 commands_knowledge_links.rs |
3434
| app/src-tauri/src/live_frame_process.rs | 529 | v0.6.0 ADR-011 拆分产物:帧处理域(网格差异触发/两级判变/带外事件驱动/UI 面板抑制/字幕落库)内聚;process_frame 上下文参数 20+;H2 修复(OCR 热路径切超时变体)+ L2 修复(score 口径诚实化)行数微增 + v0.11.5 Task 2 新颖度变化区域接线再增 | 若再增长:handle_subtitle_frame 与 persist_voted_subtitle 拆至 live_subtitle_persist.rs |
35-
| app/src-tauri/src/note_filter.rs | 528 | **拆分进行中(批 0-C3 Task 8 S1/3)**:渲染层已拆至 note_filter_render.rs,本行行数每步由 `--write` 刷新 —— v0.6.0 M1(REQ-082/085):笔记过滤域(过滤链 + AI 判定应用 + 画面要点净化)内聚于单一管线(双出口一致性由构造保证);AI 部分已按登记计划拆至 note_filter_ai.rs | 拆分进行中:S2 净化纯函数族拆至 note_filter_purify.rs · S3 转写段过滤链拆至 note_filter_chain.rs(最终 ≤300 ⇒ 本条目整行移除) |
3635
| app/src-tauri/src/engine.rs | 526 | 引擎池句柄与同步 API(双 worker 编排 + ADR-009 设备状态 + M7 心跳/失败/缓存计数 + 有界等待变体);三维复审 #5 超时排空机制(drain_asr/ocr_backlog)与 #3 ASR_FILE_TIMEOUT 文件级超时常量接入后,worker 主循环与请求协议按登记计划拆至 engine_worker.rs(见文末"已拆分"注记)回归本值 | 若再增长:排空机制与同步 API 变体拆至 engine_request.rs |
3736
| app/src-tauri/src/asr_merge.rs | 524 | v0.5.0 ADR-012 F4-1 语义合并域 + v0.7.0 M2 REQ-119 混排空格(spacing_for/merge_segments_with_spacing);合并决策与切分共用标点常量 | 若再增长:split_sentences/split_timestamps 拆至 asr_merge_split.rs |
3837
| app/src/components/GroupSidebar.tsx | 519 | v0.20.12 批 7(REQ-316)拖拽归组/ⓘ 弹层移组清理留痕透传(509→519;登记值过期纠偏) | 若再增长:体系引用拉取与徽标聚合拆至 useGroupSystemLinks.ts hook |
@@ -66,6 +65,7 @@
6665
| app/src-tauri/src/db_note_groups_tests.rs | 426 | v0.11.0 组数据层测试域 + v0.14.1 删除/影响面用例(级联归零/悬空清理/迁移回归)——单模块 #[path] 挂载(video_profile_tests.rs 同款先例),组删除语义变更集中验证 | 若再增长:删除/影响面组拆至 db_note_groups_delete_tests.rs |
6766
| app/src-tauri/src/analysis.rs | 423 | v0.5.0 M2 起结构化分析编排域(章节/重点/术语/讲者 + 事件消费 + step_boundaries/practice_segments/player_actions 三字段 + 审查修复按类型判定);各机制输出聚合内聚于单一分析函数 | 若再增长:build_chapter_signals 事件版拆至 analysis_signals.rs |
6867
| app/src-tauri/src/artifact_templates_tests.rs | 423 | 产物模板测试域(五档案模板 + 代码块/步骤卡扩展 + 叙事变体 golden)单模块 #[path] 挂载 | 若再增长:代码块/步骤卡组拆至 artifact_code_tests.rs |
68+
| app/src-tauri/src/note_filter.rs | 422 | **拆分进行中(批 0-C3 Task 8 S1/3)**:渲染层已拆至 note_filter_render.rs,本行行数每步由 `--write` 刷新 —— v0.6.0 M1(REQ-082/085):笔记过滤域(过滤链 + AI 判定应用 + 画面要点净化)内聚于单一管线(双出口一致性由构造保证);AI 部分已按登记计划拆至 note_filter_ai.rs | 拆分进行中:S2 净化纯函数族拆至 note_filter_purify.rs · S3 转写段过滤链拆至 note_filter_chain.rs(最终 ≤300 ⇒ 本条目整行移除) |
6969
| app/src-tauri/src/app_setup.rs | 420 | 本模块承载 setup 的 AppState 初始化(数据目录/DB/引擎池/可校准(自动摘取,待细化) | 若再增长:按职责拆分 |
7070
| app/src-tauri/src/bin/asr_eval.rs | 418 | 目的——"无人工语料也能测 ASR"(2026-09-03 用户裁决①):(自动摘取,待细化) | 若再增长:按职责拆分 |
7171
| app/src-tauri/src/vocab.rs | 416 | 词表域(存储/纠错/候选提取/n-gram 分词)内聚;分词纯逻辑与存储同域便于单测 | 若再增长:collect_tokens/split_runs 拆至 vocab_tokens.rs |

0 commit comments

Comments
 (0)