新增: Phase2 阶段收尾(Sprint 1-20)
重构:删 5 零引用 crate(df-evolve/plugin/stages/task/traceability)+ 清死模块、ai.rs 拆 11 子 module、ai.ts 拆 6 composable、i18n 拆目录 功能:知识库全栈(df-project/scan + CRUD + 时间线 + 前端)、Settings 拆分、appSettings KV 迁移、模型池、LLM 并发 Semaphore 修复:审批持久化根治、ConditionEngine 默认拒绝、NodeRegistry unimplemented 清除、promote 补偿删除、工具结果截断 50KB、路径校验防 symlink 逃逸 文档:B-03 人工审批设计、决策记录三分档、规格契约自检、经验记录、todo 看板、PROGRESS 更新 详见 PROGRESS.md。src-tauri/儿童每日打卡应用/ 与本项目无关,已排除。
This commit is contained in:
@@ -3,8 +3,9 @@
|
||||
use serde::Deserialize;
|
||||
use tauri::State;
|
||||
|
||||
use df_core::types::new_id;
|
||||
use df_storage::models::IdeaRecord;
|
||||
use df_core::types::{new_id, Priority};
|
||||
use df_ideas::capture::Idea;
|
||||
use df_storage::models::{IdeaRecord, ProjectRecord};
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
@@ -27,10 +28,16 @@ fn default_priority() -> i32 {
|
||||
1
|
||||
}
|
||||
|
||||
/// 列出全部想法
|
||||
/// 列出想法,可选按 status 过滤(指定状态走 query 走白名单索引列,否则全量)
|
||||
#[tauri::command]
|
||||
pub async fn list_ideas(state: State<'_, AppState>) -> Result<Vec<IdeaRecord>, String> {
|
||||
state.ideas.list_all().await.map_err(|e| e.to_string())
|
||||
pub async fn list_ideas(
|
||||
state: State<'_, AppState>,
|
||||
status: Option<String>,
|
||||
) -> Result<Vec<IdeaRecord>, String> {
|
||||
match status {
|
||||
Some(s) => state.ideas.query("status", &s).await.map_err(|e| e.to_string()),
|
||||
None => state.ideas.list_all().await.map_err(|e| e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 创建想法,返回完整记录
|
||||
@@ -83,3 +90,216 @@ pub async fn update_idea(
|
||||
pub async fn delete_idea(state: State<'_, AppState>, id: String) -> Result<bool, String> {
|
||||
state.ideas.delete(&id).await.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// 将想法晋升为项目 — 复用 df-project 领域逻辑创建项目,回写想法 status=promoted/promoted_to
|
||||
#[tauri::command]
|
||||
pub async fn promote_idea(
|
||||
state: State<'_, AppState>,
|
||||
id: String,
|
||||
) -> Result<df_ideas::promotion::PromotionResult, String> {
|
||||
let record = state
|
||||
.ideas
|
||||
.get_by_id(&id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.ok_or_else(|| format!("想法不存在: {id}"))?;
|
||||
|
||||
if record.promoted_to.is_some() {
|
||||
return Err(format!("想法已立项: {}", record.promoted_to.unwrap()));
|
||||
}
|
||||
|
||||
// 复用 df-project 领域逻辑构造项目实体(create_from_idea)
|
||||
let project = df_project::manager::ProjectManager::create_from_idea(
|
||||
record.title.clone(),
|
||||
record.description.clone(),
|
||||
id.clone(),
|
||||
);
|
||||
let project_id = project.id.clone();
|
||||
let now = now_millis();
|
||||
let project_record = ProjectRecord {
|
||||
id: project_id.clone(),
|
||||
name: project.name,
|
||||
description: project.description,
|
||||
status: "planning".to_string(),
|
||||
idea_id: Some(id.clone()),
|
||||
path: None,
|
||||
stack: None,
|
||||
created_at: now.clone(),
|
||||
updated_at: now.clone(),
|
||||
};
|
||||
state
|
||||
.projects
|
||||
.insert(project_record)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// 回写想法:status=promoted + promoted_to(update_full 单事务覆盖可变字段)
|
||||
// 补偿删除:第二步失败时回滚第一步已建的 project,保证最终一致性(非原子,但防项目存留而
|
||||
// 想法状态未变的数据不一致)。Repository 方法各自持锁不支持跨 repo 共享事务对象,故选补偿
|
||||
// 删除而非真事务(改动最小,工程投入产出比最高)。
|
||||
let updated = IdeaRecord {
|
||||
status: "promoted".to_string(),
|
||||
promoted_to: Some(project_id.clone()),
|
||||
updated_at: now,
|
||||
..record
|
||||
};
|
||||
if let Err(e) = state.ideas.update_full(&updated).await {
|
||||
// 回写失败:补偿删除已建项目,避免悬空项目(idea.promoted_to 仍空,可重试立项)
|
||||
tracing::error!("想法 {id} 回写失败,补偿删除已建项目 {project_id}: {e}");
|
||||
if let Err(del_err) = state.projects.delete(&project_id).await {
|
||||
tracing::error!("补偿删除项目 {project_id} 也失败(需人工清理): {del_err}");
|
||||
}
|
||||
return Err(format!("想法立项回写失败(已回滚项目创建): {}", e));
|
||||
}
|
||||
|
||||
Ok(df_ideas::promotion::PromotionResult {
|
||||
idea_id: id,
|
||||
project_id: project_id,
|
||||
promoted: true,
|
||||
reason: "手动立项".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 想法评估 — 多维评分 + 对抗式评估
|
||||
// ============================================================
|
||||
|
||||
/// 评估想法:多维评分 + 对抗式评估,结果写回 scores/score/ai_analysis,状态置 pending_review,返回更新后的记录
|
||||
#[tauri::command]
|
||||
pub async fn evaluate_idea(
|
||||
state: State<'_, AppState>,
|
||||
id: String,
|
||||
) -> Result<IdeaRecord, String> {
|
||||
// 取出想法
|
||||
let record = state
|
||||
.ideas
|
||||
.get_by_id(&id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.ok_or_else(|| format!("想法不存在: {id}"))?;
|
||||
|
||||
let idea = record_to_idea(&record);
|
||||
|
||||
// 多维评分(0-10,IPC 层 *10 缩放为 0-100)
|
||||
let scores = df_ideas::scoring::ScoringEngine::compute_default(&idea);
|
||||
|
||||
// 对抗式评估
|
||||
let eval = df_ideas::adversarial::AdversarialEngine::evaluate(&idea)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// 组装前端扁平结构(与 Ideas.vue 的 AdversarialEval interface 对齐)
|
||||
let positive_strength = eval.positive.confidence;
|
||||
let negative_strength = eval.negative.confidence;
|
||||
let net_sentiment = positive_strength - negative_strength;
|
||||
let recommendation = recommendation_str(&eval.recommendation).to_string();
|
||||
let final_score = eval.final_score;
|
||||
let analyst_summary = eval.analyst.summary.clone();
|
||||
let action_items = action_items_for(&eval.recommendation);
|
||||
let positive = serde_json::json!({
|
||||
"thesis": eval.positive.thesis,
|
||||
"evidence": eval.positive.evidence,
|
||||
});
|
||||
let negative = serde_json::json!({
|
||||
"thesis": eval.negative.thesis,
|
||||
"evidence": eval.negative.evidence,
|
||||
});
|
||||
|
||||
let ai_analysis = serde_json::json!({
|
||||
"positive_strength": positive_strength,
|
||||
"negative_strength": negative_strength,
|
||||
"net_sentiment": net_sentiment,
|
||||
"recommendation": recommendation,
|
||||
"final_score": final_score,
|
||||
"summary": analyst_summary,
|
||||
"action_items": action_items,
|
||||
"positive": positive,
|
||||
"negative": negative,
|
||||
"analyst": { "summary": analyst_summary },
|
||||
})
|
||||
.to_string();
|
||||
|
||||
// scores JSON:中文维度 key + 0-100 值(前端雷达图直接当百分比用)
|
||||
let scores_json = serde_json::json!({
|
||||
"可行性": (scores.feasibility * 10.0).round() as i64,
|
||||
"影响力": (scores.impact * 10.0).round() as i64,
|
||||
"紧急度": (scores.urgency * 10.0).round() as i64,
|
||||
"综合": (scores.overall * 10.0).round() as i64,
|
||||
})
|
||||
.to_string();
|
||||
|
||||
let score_value = (scores.overall * 10.0).round() as i64;
|
||||
|
||||
// 构造完整记录后单次原子写回(update_full 保留 id 与 created_at)
|
||||
let updated = IdeaRecord {
|
||||
scores: Some(scores_json),
|
||||
ai_analysis: Some(ai_analysis),
|
||||
score: Some(score_value as f64),
|
||||
status: "pending_review".to_string(),
|
||||
updated_at: now_millis(),
|
||||
..record
|
||||
};
|
||||
state
|
||||
.ideas
|
||||
.update_full(&updated)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(updated)
|
||||
}
|
||||
|
||||
/// IdeaRecord → df_ideas::Idea(评估用,status/time 不影响评分)
|
||||
fn record_to_idea(record: &IdeaRecord) -> Idea {
|
||||
let tags: Vec<String> = record
|
||||
.tags
|
||||
.as_deref()
|
||||
.and_then(|t| serde_json::from_str(t).ok())
|
||||
.unwrap_or_default();
|
||||
Idea {
|
||||
id: record.id.clone(),
|
||||
title: record.title.clone(),
|
||||
description: record.description.clone(),
|
||||
status: df_core::types::IdeaStatus::Draft,
|
||||
priority: priority_from_i32(record.priority),
|
||||
scores: None,
|
||||
tags,
|
||||
source: record.source.clone(),
|
||||
related_ids: Vec::new(),
|
||||
created_at: chrono::Utc::now(),
|
||||
updated_at: chrono::Utc::now(),
|
||||
}
|
||||
}
|
||||
|
||||
/// i32 优先级 → Priority 枚举(与 df-core 枚举值一致:Low=0/Medium=1/High=2/Critical=3)
|
||||
fn priority_from_i32(p: i32) -> Priority {
|
||||
match p {
|
||||
0 => Priority::Low,
|
||||
2 => Priority::High,
|
||||
x if x >= 3 => Priority::Critical,
|
||||
_ => Priority::Medium,
|
||||
}
|
||||
}
|
||||
|
||||
/// Recommendation → 前端 assessmentLabel 期望的全小写空格分隔(匹配 map key)
|
||||
fn recommendation_str(r: &df_ideas::adversarial::Recommendation) -> &'static str {
|
||||
use df_ideas::adversarial::Recommendation::*;
|
||||
match r {
|
||||
ImmediateAction => "immediate action",
|
||||
Soon => "soon",
|
||||
WithResources => "with resources",
|
||||
ResearchMore => "research more",
|
||||
Monitor => "monitor",
|
||||
}
|
||||
}
|
||||
|
||||
/// 行动建议 — 按推荐等级返回
|
||||
fn action_items_for(r: &df_ideas::adversarial::Recommendation) -> Vec<String> {
|
||||
use df_ideas::adversarial::Recommendation::*;
|
||||
match r {
|
||||
ImmediateAction => vec!["立即组建项目团队".into(), "制定详细执行计划".into(), "分配必要资源".into()],
|
||||
Soon => vec!["下周启动项目".into(), "准备资源需求".into(), "制定时间表".into()],
|
||||
WithResources => vec!["确认资源预算".into(), "评估 ROI".into(), "制定风险预案".into()],
|
||||
ResearchMore => vec!["进行市场调研".into(), "收集用户反馈".into(), "验证技术可行性".into()],
|
||||
Monitor => vec!["持续跟踪相关指标".into(), "定期评估进展".into(), "等待更好时机".into()],
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user