优化: AR-10 想法→灵感文案统一(13 文件)

用户可见文案 + 后端错误信息 + LLM 工具描述/系统提示词 + store toast + 注释统一改灵感:
i18n zh-CN(ideas/aiTool/projectDetail) + idea.rs 错误 + tool_registry/prompt LLM + stores/project toast + api/views 注释
en 版待定(用 Ideas/Idea)+ docs/crates 注释低优先本轮略;cargo check/vue-tsc 0 error
This commit is contained in:
2026-06-14 16:26:32 +08:00
parent d6cd2805be
commit 65c475b549
13 changed files with 50 additions and 50 deletions

View File

@@ -44,7 +44,7 @@ pub(crate) fn risk_from_str(s: &str) -> Option<RiskLevel> {
/// - delete_project/restore_project/purge_project → "删除/恢复/清除项目"+id /// - delete_project/restore_project/purge_project → "删除/恢复/清除项目"+id
/// - update_project → "修改项目"+field /// - update_project → "修改项目"+field
/// - bind_directory → "绑定目录"+path /// - bind_directory → "绑定目录"+path
/// - create_task → "创建任务"+titlecreate_project → "创建项目"+namecreate_idea → "捕获想法"+title /// - create_task → "创建任务"+titlecreate_project → "创建项目"+namecreate_idea → "捕获灵感"+title
/// - run_workflow → "运行工作流"+name /// - run_workflow → "运行工作流"+name
/// args 无可读字段或工具未特化时fallback 原 risk 模板(含风险等级提示)。 /// args 无可读字段或工具未特化时fallback 原 risk 模板(含风险等级提示)。
fn build_approval_reason(tool_name: &str, args: &serde_json::Value, risk_level: RiskLevel) -> String { fn build_approval_reason(tool_name: &str, args: &serde_json::Value, risk_level: RiskLevel) -> String {
@@ -80,7 +80,7 @@ fn build_approval_reason(tool_name: &str, args: &serde_json::Value, risk_level:
} }
"create_idea" => { "create_idea" => {
let title = s("title"); let title = s("title");
if !title.is_empty() { format!("捕获想法{}", title) } else { String::new() } if !title.is_empty() { format!("捕获灵感{}", title) } else { String::new() }
} }
"run_workflow" => { "run_workflow" => {
let name = s("name"); let name = s("name");

View File

@@ -49,11 +49,11 @@ fn system_prompt_parts(lang: &str) -> (&'static str, &'static str) {
"\n## Current Projects\n", "\n## Current Projects\n",
), ),
_ => ( _ => (
"你是 DevFlow 的 AI 助手。你帮助用户管理项目、任务、想法和工作流。\n\ "你是 DevFlow 的 AI 助手。你帮助用户管理项目、任务、灵感和工作流。\n\
必须使用简体中文回复,禁止使用繁体中文字符。\n\n\ 必须使用简体中文回复,禁止使用繁体中文字符。\n\n\
## 当前能力\n\ ## 当前能力\n\
你可以通过工具调用执行以下操作:\n\ 你可以通过工具调用执行以下操作:\n\
- 创建/查询项目、任务、想法\n\ - 创建/查询项目、任务、灵感\n\
- 运行工作流\n\ - 运行工作流\n\
- 读取文件内容、列出目录、创建/写入文件\n\n\ - 读取文件内容、列出目录、创建/写入文件\n\n\
## 行为准则\n\ ## 行为准则\n\

View File

@@ -109,7 +109,7 @@ pub fn build_ai_tool_registry(db: &Arc<Database>) -> AiToolRegistry {
})}, })},
); );
registry.register( registry.register(
"list_ideas", "列出所有想法", "list_ideas", "列出所有灵感",
df_ai::ai_tools::object_schema(vec![]), RiskLevel::Low, df_ai::ai_tools::object_schema(vec![]), RiskLevel::Low,
{ let db = db.clone(); Box::new(move |_args: serde_json::Value| { { let db = db.clone(); Box::new(move |_args: serde_json::Value| {
let db = db.clone(); let db = db.clone();
@@ -265,7 +265,7 @@ pub fn build_ai_tool_registry(db: &Arc<Database>) -> AiToolRegistry {
})}, })},
); );
registry.register( registry.register(
"create_idea", "捕获一个新想法", "create_idea", "捕获一个新灵感",
df_ai::ai_tools::object_schema(vec![("title", "string", true), ("description", "string", false), ("tags", "string", false), ("source", "string", false)]), df_ai::ai_tools::object_schema(vec![("title", "string", true), ("description", "string", false), ("tags", "string", false), ("source", "string", false)]),
RiskLevel::Medium, RiskLevel::Medium,
{ let db = db.clone(); Box::new(move |args: serde_json::Value| { { let db = db.clone(); Box::new(move |args: serde_json::Value| {

View File

@@ -1,4 +1,4 @@
//! 想法相关命令 //! 灵感相关命令
use serde::Deserialize; use serde::Deserialize;
use tauri::State; use tauri::State;
@@ -11,7 +11,7 @@ use crate::state::AppState;
use super::now_millis; use super::now_millis;
/// 创建想法入参 /// 创建灵感入参
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct CreateIdeaInput { pub struct CreateIdeaInput {
pub title: String, pub title: String,
@@ -28,7 +28,7 @@ fn default_priority() -> i32 {
1 1
} }
/// 列出想法,可选按 status 过滤(指定状态走 query 走白名单索引列,否则全量) /// 列出灵感,可选按 status 过滤(指定状态走 query 走白名单索引列,否则全量)
#[tauri::command] #[tauri::command]
pub async fn list_ideas( pub async fn list_ideas(
state: State<'_, AppState>, state: State<'_, AppState>,
@@ -40,7 +40,7 @@ pub async fn list_ideas(
} }
} }
/// 创建想法,返回完整记录 /// 创建灵感,返回完整记录
#[tauri::command] #[tauri::command]
pub async fn create_idea( pub async fn create_idea(
state: State<'_, AppState>, state: State<'_, AppState>,
@@ -70,7 +70,7 @@ pub async fn create_idea(
Ok(record) Ok(record)
} }
/// 更新想法单个字段(字段名走 df-storage 白名单校验) /// 更新灵感单个字段(字段名走 df-storage 白名单校验)
#[tauri::command] #[tauri::command]
pub async fn update_idea( pub async fn update_idea(
state: State<'_, AppState>, state: State<'_, AppState>,
@@ -85,13 +85,13 @@ pub async fn update_idea(
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())
} }
/// 删除想法 /// 删除灵感
#[tauri::command] #[tauri::command]
pub async fn delete_idea(state: State<'_, AppState>, id: String) -> Result<bool, String> { pub async fn delete_idea(state: State<'_, AppState>, id: String) -> Result<bool, String> {
state.ideas.delete(&id).await.map_err(|e| e.to_string()) state.ideas.delete(&id).await.map_err(|e| e.to_string())
} }
/// 将想法晋升为项目 — 复用 df-project 领域逻辑创建项目,回写想法 status=promoted/promoted_to /// 将灵感晋升为项目 — 复用 df-project 领域逻辑创建项目,回写灵感 status=promoted/promoted_to
#[tauri::command] #[tauri::command]
pub async fn promote_idea( pub async fn promote_idea(
state: State<'_, AppState>, state: State<'_, AppState>,
@@ -102,10 +102,10 @@ pub async fn promote_idea(
.get_by_id(&id) .get_by_id(&id)
.await .await
.map_err(|e| e.to_string())? .map_err(|e| e.to_string())?
.ok_or_else(|| format!("想法不存在: {id}"))?; .ok_or_else(|| format!("灵感不存在: {id}"))?;
if record.promoted_to.is_some() { if record.promoted_to.is_some() {
return Err(format!("想法已立项: {}", record.promoted_to.unwrap())); return Err(format!("灵感已立项: {}", record.promoted_to.unwrap()));
} }
// 复用 df-project 领域逻辑构造项目实体create_from_idea // 复用 df-project 领域逻辑构造项目实体create_from_idea
@@ -133,9 +133,9 @@ pub async fn promote_idea(
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
// 回写想法status=promoted + promoted_toupdate_full 单事务覆盖可变字段) // 回写灵感status=promoted + promoted_toupdate_full 单事务覆盖可变字段)
// 补偿删除:第二步失败时回滚第一步已建的 project,保证最终一致性(非原子,但防项目存留而 // 补偿删除:第二步失败时回滚第一步已建的 project,保证最终一致性(非原子,但防项目存留而
// 想法状态未变的数据不一致)。Repository 方法各自持锁不支持跨 repo 共享事务对象,故选补偿 // 灵感状态未变的数据不一致)。Repository 方法各自持锁不支持跨 repo 共享事务对象,故选补偿
// 删除而非真事务(改动最小,工程投入产出比最高)。 // 删除而非真事务(改动最小,工程投入产出比最高)。
let updated = IdeaRecord { let updated = IdeaRecord {
status: "promoted".to_string(), status: "promoted".to_string(),
@@ -145,11 +145,11 @@ pub async fn promote_idea(
}; };
if let Err(e) = state.ideas.update_full(&updated).await { if let Err(e) = state.ideas.update_full(&updated).await {
// 回写失败:补偿删除已建项目,避免悬空项目(idea.promoted_to 仍空,可重试立项) // 回写失败:补偿删除已建项目,避免悬空项目(idea.promoted_to 仍空,可重试立项)
tracing::error!("想法 {id} 回写失败,补偿删除已建项目 {project_id}: {e}"); tracing::error!("灵感 {id} 回写失败,补偿删除已建项目 {project_id}: {e}");
if let Err(del_err) = state.projects.purge_with_descendants(&project_id).await { if let Err(del_err) = state.projects.purge_with_descendants(&project_id).await {
tracing::error!("补偿删除项目 {project_id} 也失败(需人工清理): {del_err}"); tracing::error!("补偿删除项目 {project_id} 也失败(需人工清理): {del_err}");
} }
return Err(format!("想法立项回写失败(已回滚项目创建): {}", e)); return Err(format!("灵感立项回写失败(已回滚项目创建): {}", e));
} }
Ok(df_ideas::promotion::PromotionResult { Ok(df_ideas::promotion::PromotionResult {
@@ -161,22 +161,22 @@ pub async fn promote_idea(
} }
// ============================================================ // ============================================================
// 想法评估 — 多维评分 + 对抗式评估 // 灵感评估 — 多维评分 + 对抗式评估
// ============================================================ // ============================================================
/// 评估想法:多维评分 + 对抗式评估,结果写回 scores/score/ai_analysis状态置 pending_review返回更新后的记录 /// 评估灵感:多维评分 + 对抗式评估,结果写回 scores/score/ai_analysis状态置 pending_review返回更新后的记录
#[tauri::command] #[tauri::command]
pub async fn evaluate_idea( pub async fn evaluate_idea(
state: State<'_, AppState>, state: State<'_, AppState>,
id: String, id: String,
) -> Result<IdeaRecord, String> { ) -> Result<IdeaRecord, String> {
// 取出想法 // 取出灵感
let record = state let record = state
.ideas .ideas
.get_by_id(&id) .get_by_id(&id)
.await .await
.map_err(|e| e.to_string())? .map_err(|e| e.to_string())?
.ok_or_else(|| format!("想法不存在: {id}"))?; .ok_or_else(|| format!("灵感不存在: {id}"))?;
let idea = record_to_idea(&record); let idea = record_to_idea(&record);

View File

@@ -46,7 +46,7 @@ pub fn run() {
commands::task::create_task, commands::task::create_task,
commands::task::update_task, commands::task::update_task,
commands::task::delete_task, commands::task::delete_task,
// 想法 // 灵感
commands::idea::list_ideas, commands::idea::list_ideas,
commands::idea::create_idea, commands::idea::create_idea,
commands::idea::update_idea, commands::idea::update_idea,

View File

@@ -135,7 +135,7 @@ impl LlmConcurrency {
pub struct AppState { pub struct AppState {
/// 数据库句柄Arc 包装,便于在异步任务中重建 Repo /// 数据库句柄Arc 包装,便于在异步任务中重建 Repo
pub db: Arc<Database>, pub db: Arc<Database>,
/// 想法表 Repo /// 灵感表 Repo
pub ideas: IdeaRepo, pub ideas: IdeaRepo,
/// 项目表 Repo /// 项目表 Repo
pub projects: ProjectRepo, pub projects: ProjectRepo,

View File

@@ -2,7 +2,7 @@ import { invoke } from '@tauri-apps/api/core'
import type { IdeaRecord, CreateIdeaInput, PromotionResult } from './types' import type { IdeaRecord, CreateIdeaInput, PromotionResult } from './types'
export const ideaApi = { export const ideaApi = {
/** 列出想法,可选按 status 过滤draft/pending_review/promoted 等) */ /** 列出灵感,可选按 status 过滤draft/pending_review/promoted 等) */
list(status?: string): Promise<IdeaRecord[]> { list(status?: string): Promise<IdeaRecord[]> {
return invoke('list_ideas', { status: status ?? null }) return invoke('list_ideas', { status: status ?? null })
}, },
@@ -24,7 +24,7 @@ export const ideaApi = {
return invoke('evaluate_idea', { id }) return invoke('evaluate_idea', { id })
}, },
/** 将想法晋升为项目,返回晋升结果(含 project_id */ /** 将灵感晋升为项目,返回晋升结果(含 project_id */
promote(id: string): Promise<PromotionResult> { promote(id: string): Promise<PromotionResult> {
return invoke('promote_idea', { id }) return invoke('promote_idea', { id })
}, },

View File

@@ -1,7 +1,7 @@
//! TypeScript 类型定义 — 与 Rust Record 结构体严格对齐 //! TypeScript 类型定义 — 与 Rust Record 结构体严格对齐
// ============================================================ // ============================================================
// 想法 // 灵感
// ============================================================ // ============================================================
export interface IdeaRecord { export interface IdeaRecord {
@@ -68,7 +68,7 @@ export interface AiScanResult {
raw: string | null raw: string | null
} }
/** 想法晋升结果(后端 promote_idea 返回) */ /** 灵感晋升结果(后端 promote_idea 返回) */
export interface PromotionResult { export interface PromotionResult {
idea_id: string idea_id: string
project_id: string project_id: string

View File

@@ -21,7 +21,7 @@ export default {
// completed 结果摘要(header 折叠态可见) // completed 结果摘要(header 折叠态可见)
taskCount: '{n} 项任务', taskCount: '{n} 项任务',
projectCount: '{n} 个项目', projectCount: '{n} 个项目',
ideaCount: '{n} 条想法', ideaCount: '{n} 条灵感',
createdWithName: '已创建:{name}', createdWithName: '已创建:{name}',
created: '已创建', created: '已创建',
updatedField: '已更新 {field}', updatedField: '已更新 {field}',

View File

@@ -15,7 +15,7 @@ export default {
}, },
// 空状态 // 空状态
emptyState: '选择一个想法查看详情', emptyState: '选择一个灵感查看详情',
// 对抗式评估 // 对抗式评估
adversarialTitle: '⚖️ 对抗式评估', adversarialTitle: '⚖️ 对抗式评估',
@@ -39,7 +39,7 @@ export default {
'with resources': '📦 配置资源后行动', 'with resources': '📦 配置资源后行动',
'research more': '🔍 需要更多研究', 'research more': '🔍 需要更多研究',
monitor: '👁️ 持续监控', monitor: '👁️ 持续监控',
cancel: '❌ 取消想法', cancel: '❌ 取消灵感',
}, },
// 多维评分 // 多维评分
@@ -62,17 +62,17 @@ export default {
// 操作按钮 // 操作按钮
promoteToProject: '🚀 立项为项目', promoteToProject: '🚀 立项为项目',
deleteIdea: '🗑️ 删除想法', deleteIdea: '🗑️ 删除灵感',
// 捕捉模态框 // 捕捉模态框
captureTitle: '✨ 捕捉新想法', captureTitle: '✨ 捕捉新灵感',
fieldTitle: '标题', fieldTitle: '标题',
fieldDesc: '描述', fieldDesc: '描述',
titlePlaceholder: '一句话描述你的想法...', titlePlaceholder: '一句话描述你的灵感...',
descPlaceholder: '详细说明(可选)...', descPlaceholder: '详细说明(可选)...',
// 原生对话框文案confirm / alert // 原生对话框文案confirm / alert
confirmDelete: '确定删除想法「{title}」?此操作不可撤销。', confirmDelete: '确定删除灵感「{title}」?此操作不可撤销。',
promoteFailed: '立项失败', promoteFailed: '立项失败',
evalFailed: '评估失败', evalFailed: '评估失败',
}, },

View File

@@ -6,7 +6,7 @@ export default {
delete: '🗑 删除', delete: '🗑 删除',
newTask: '+ 新任务', newTask: '+ 新任务',
// 阶段 pipeline // 阶段 pipeline
stageIdea: '💡 想法', stageIdea: '💡 灵感',
stageRequirement: '📋 需求', stageRequirement: '📋 需求',
stageCoding: '💻 编码', stageCoding: '💻 编码',
stageTesting: '🧪 测试', stageTesting: '🧪 测试',
@@ -21,9 +21,9 @@ export default {
confirmCreate: '确认创建', confirmCreate: '确认创建',
// 项目信息面板 // 项目信息面板
infoTitle: '📋 项目信息', infoTitle: '📋 项目信息',
sourceIdea: '来源想法', sourceIdea: '来源灵感',
viewIdea: '查看想法详情', viewIdea: '查看灵感详情',
ideaDeleted: '原始想法已删除', ideaDeleted: '原始灵感已删除',
createdAt: '创建时间', createdAt: '创建时间',
updatedAt: '更新时间', updatedAt: '更新时间',
description: '描述', description: '描述',

View File

@@ -142,12 +142,12 @@ function createStore() {
} }
} }
// ── 想法 CRUD ── // ── 灵感 CRUD ──
async function loadIdeas() { async function loadIdeas() {
try { try {
state.ideas = await ideaApi.list() state.ideas = await ideaApi.list()
} catch (e: any) { } catch (e: any) {
state.error = e?.toString() ?? '加载想法失败' state.error = e?.toString() ?? '加载灵感失败'
} }
} }
@@ -157,7 +157,7 @@ function createStore() {
state.ideas.push(record) state.ideas.push(record)
return record return record
} catch (e: any) { } catch (e: any) {
state.error = e?.toString() ?? '创建想法失败' state.error = e?.toString() ?? '创建灵感失败'
return null return null
} }
} }
@@ -175,7 +175,7 @@ function createStore() {
await ideaApi.delete(id) await ideaApi.delete(id)
state.ideas = state.ideas.filter(i => i.id !== id) state.ideas = state.ideas.filter(i => i.id !== id)
} catch (e: any) { } catch (e: any) {
state.error = e?.toString() ?? '删除想法失败' state.error = e?.toString() ?? '删除灵感失败'
} }
} }
@@ -309,7 +309,7 @@ function createStore() {
type ProjectStore = ReturnType<typeof createStore> type ProjectStore = ReturnType<typeof createStore>
let _storeInstance: ProjectStore | null = null let _storeInstance: ProjectStore | null = null
/** 项目/任务/想法/工作流 全局状态(单例,多组件复用同一 reactive 包装) */ /** 项目/任务/灵感/工作流 全局状态(单例,多组件复用同一 reactive 包装) */
export function useProjectStore(): ProjectStore { export function useProjectStore(): ProjectStore {
if (_storeInstance) return _storeInstance if (_storeInstance) return _storeInstance
_storeInstance = createStore() _storeInstance = createStore()

View File

@@ -30,7 +30,7 @@
<!-- 两栏布局 --> <!-- 两栏布局 -->
<div class="ideas-layout"> <div class="ideas-layout">
<!-- 左侧想法列表 --> <!-- 左侧灵感列表 -->
<section class="idea-list-panel"> <section class="idea-list-panel">
<div class="idea-list"> <div class="idea-list">
<div <div
@@ -53,7 +53,7 @@
</div> </div>
</section> </section>
<!-- 右侧想法详情 --> <!-- 右侧灵感详情 -->
<section class="idea-detail-panel" v-if="currentIdea"> <section class="idea-detail-panel" v-if="currentIdea">
<div class="detail-header"> <div class="detail-header">
<h2 class="detail-title">{{ currentIdea.title }}</h2> <h2 class="detail-title">{{ currentIdea.title }}</h2>
@@ -179,7 +179,7 @@
</section> </section>
</div> </div>
<!-- 捕捉想法模态框 --> <!-- 捕捉灵感模态框 -->
<div class="modal-overlay" v-if="showCaptureModal" @click.self="showCaptureModal = false"> <div class="modal-overlay" v-if="showCaptureModal" @click.self="showCaptureModal = false">
<div class="modal-box"> <div class="modal-box">
<h3>{{ $t('ideas.captureTitle') }}</h3> <h3>{{ $t('ideas.captureTitle') }}</h3>
@@ -194,7 +194,7 @@
</div> </div>
</div> </div>
<!-- 确认弹层删除想法替代原生 window.confirm --> <!-- 确认弹层删除灵感替代原生 window.confirm -->
<ConfirmDialog :visible="confirmState.visible" :msg="confirmState.msg" @result="answerConfirm" /> <ConfirmDialog :visible="confirmState.visible" :msg="confirmState.msg" @result="answerConfirm" />
</div> </div>
</template> </template>
@@ -234,7 +234,7 @@ const activeFilter = ref<FilterKey>('all')
const selectedId = ref<string | null>(null) const selectedId = ref<string | null>(null)
const searchQuery = ref('') const searchQuery = ref('')
// ── 新建想法模态框 ── // ── 新建灵感模态框 ──
const showCaptureModal = ref(false) const showCaptureModal = ref(false)
const newIdeaTitle = ref('') const newIdeaTitle = ref('')
const newIdeaDesc = ref('') const newIdeaDesc = ref('')