新增: 批次工作落地(推进链/评估闭环/事件总线/并发/加固) + 技术债清理 + 文档整理
后端: - 工作流推进链(D-03):advance_task/状态机/闸门走 df-nodes Node trait,conditions 条件引擎扩展 - 想法评估闭环:启发式评分+对抗评估,df-ideas/scoring + df-storage/idea_eval_repo + idea 前端打通 - 全局事件数据总线:df-ai/context+context_helpers+augmentation 跨模块解耦 - AI planner/plan_hint/intent:aichat B 路线并行多轮基础 - patch_file 加固(TD-03/04):读改写整体锁防 lost update,expected_hash 合约闭环 - 压缩超时兜底(F-15 卡死根治) - F-09 多会话并发:LlmConcurrency per-conv + streamingGuard 前端守护 + verify 脚本 - 知识注入 DRY/skills/audit 扩展 清理: - aichat 技术债(误报 allow/死导入/过时注释 30 项) - URGENT.md 删除(11 项加急全解决/迁 todo) - 文档整理(todo/待决策/待审查/ARCHITECTURE/INDEX + 总线/技术债审查新文档)
This commit is contained in:
@@ -8,7 +8,8 @@ use tauri::State;
|
||||
use df_ai::provider::LlmProvider;
|
||||
use df_types::types::{new_id, Priority};
|
||||
use df_ideas::capture::Idea;
|
||||
use df_storage::models::{IdeaRecord, ProjectRecord};
|
||||
use df_storage::crud::is_unique_constraint_err;
|
||||
use df_storage::models::{IdeaEvaluationRecord, IdeaRecord, ProjectRecord};
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
@@ -43,6 +44,16 @@ pub async fn list_ideas(
|
||||
}
|
||||
}
|
||||
|
||||
/// 列出指定灵感的评估历史(version DESC,最新版本在前)。
|
||||
/// IdeaEvaluationRecord 已 Serialize,直接返回前端供历史面板渲染。
|
||||
#[tauri::command]
|
||||
pub async fn list_idea_evaluations(
|
||||
state: State<'_, AppState>,
|
||||
idea_id: String,
|
||||
) -> Result<Vec<IdeaEvaluationRecord>, String> {
|
||||
state.idea_evaluations.list_by_idea(&idea_id).await.map_err(err_str)
|
||||
}
|
||||
|
||||
/// 创建灵感,返回完整记录
|
||||
#[tauri::command]
|
||||
pub async fn create_idea(
|
||||
@@ -62,6 +73,7 @@ pub async fn create_idea(
|
||||
promoted_to: None,
|
||||
ai_analysis: None,
|
||||
scores: None,
|
||||
related_ids: None,
|
||||
created_at: now.clone(),
|
||||
updated_at: now,
|
||||
};
|
||||
@@ -219,6 +231,7 @@ pub async fn evaluate_idea(
|
||||
"negative_strength": negative_strength,
|
||||
"net_sentiment": net_sentiment,
|
||||
"recommendation": recommendation,
|
||||
"evaluated_by": eval.evaluated_by,
|
||||
"final_score": final_score,
|
||||
"summary": analyst_summary,
|
||||
"action_items": action_items,
|
||||
@@ -239,10 +252,11 @@ pub async fn evaluate_idea(
|
||||
|
||||
let score_value = (scores.overall * 10.0).round() as i64;
|
||||
|
||||
// 构造完整记录后单次原子写回(update_full 保留 id 与 created_at)
|
||||
// 构造完整记录后单次原子写回(update_full 保留 id 与 created_at)。
|
||||
// ai_analysis/scores_json 按值 move 进主表记录后,下方历史快照仍需复用 → 此处 clone 保留绑定。
|
||||
let updated = IdeaRecord {
|
||||
scores: Some(scores_json),
|
||||
ai_analysis: Some(ai_analysis),
|
||||
scores: Some(scores_json.clone()),
|
||||
ai_analysis: Some(ai_analysis.clone()),
|
||||
score: Some(score_value as f64),
|
||||
status: "pending_review".to_string(),
|
||||
updated_at: now_millis(),
|
||||
@@ -254,6 +268,57 @@ pub async fn evaluate_idea(
|
||||
.await
|
||||
.map_err(err_str)?;
|
||||
|
||||
// 追加一条评估历史快照(idea_evaluations 审计表,version 单调递增)。
|
||||
// 主表 update_full 成功后再追加,保证主表先落;历史表为额外冗余列(evaluated_by
|
||||
// 独立冗余,ai_analysis JSON 内的 evaluated_by 字段保留不删)。
|
||||
//
|
||||
// version 并发重复兜底(V25 唯一约束 + 重试):version 此前由
|
||||
// `list_by_idea().first().version + 1` 算出,读-改-写非原子,并发评估同一灵感
|
||||
// 可能写出相同 version。V25 在 idea_evaluations(idea_id, version) 上加了唯一索引,
|
||||
// 此处捕获唯一约束冲突 → 重新查最新 version 重算并重试(上限 3 次防死循环)。
|
||||
// 单用户桌面应用并发概率极低,但唯一约束 + 重试是数据完整性兜底,值得做。
|
||||
let mut attempt = 0;
|
||||
let max_attempts = 3;
|
||||
loop {
|
||||
attempt += 1;
|
||||
let version = state
|
||||
.idea_evaluations
|
||||
.list_by_idea(&id)
|
||||
.await
|
||||
.map_err(err_str)?
|
||||
.first()
|
||||
.map(|r| r.version + 1)
|
||||
.unwrap_or(1);
|
||||
let eval_record = IdeaEvaluationRecord {
|
||||
id: new_id(),
|
||||
idea_id: id.clone(),
|
||||
version,
|
||||
ai_analysis: Some(ai_analysis.clone()),
|
||||
scores: Some(scores_json.clone()),
|
||||
score: Some(score_value as f64),
|
||||
evaluated_by: Some(evaluated_by_str(&eval.evaluated_by).to_string()),
|
||||
evaluated_at: now_millis(),
|
||||
};
|
||||
match state.idea_evaluations.insert(eval_record).await {
|
||||
Ok(_) => break,
|
||||
Err(e) => {
|
||||
// 唯一约束冲突(SQLite extended code 2067 / SQLITE_CONSTRAINT_UNIQUE)
|
||||
// → version 并发重复,命中且未达上限则重试(重新查 version);否则向上抛错。
|
||||
// 检测逻辑收口到 df_storage::crud::is_unique_constraint_err,集中维护、
|
||||
// 大小写不敏感,不再散落脆弱的英文文案 contains。
|
||||
if is_unique_constraint_err(&e) && attempt < max_attempts {
|
||||
tracing::warn!(
|
||||
"灵感 {id} 评估历史 version 唯一约束冲突,重试 {}/{}",
|
||||
attempt,
|
||||
max_attempts
|
||||
);
|
||||
continue;
|
||||
}
|
||||
return Err(e.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(updated)
|
||||
}
|
||||
|
||||
@@ -347,3 +412,15 @@ fn action_items_for(r: &df_ideas::adversarial::Recommendation) -> Vec<String> {
|
||||
Monitor => vec!["持续跟踪相关指标".into(), "定期评估进展".into(), "等待更好时机".into()],
|
||||
}
|
||||
}
|
||||
|
||||
/// EvaluatedBy 枚举 → 评估历史表 evaluated_by 列的字符串冗余值。
|
||||
/// (ai_analysis JSON 内的 evaluated_by 字段保留不删;此处为历史表独立冗余列,
|
||||
/// 便于不解析 JSON 即可直接按评估来源过滤/统计历史。)
|
||||
fn evaluated_by_str(e: &df_ideas::adversarial::EvaluatedBy) -> &'static str {
|
||||
use df_ideas::adversarial::EvaluatedBy::*;
|
||||
match e {
|
||||
Llm => "Llm",
|
||||
Heuristic => "Heuristic",
|
||||
HeuristicFallback => "HeuristicFallback",
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user