优化: R-PD-10 commands err_str helper 统一错误格式化(87 处)
mod.rs 加 pub fn err_str<E: ToString>,commands/ 10 文件 87 处 .map_err(|e| e.to_string()) → .map_err(err_str),残留 0(10 处复杂表达式 e 用于 format!/anyhow 保留)。行为零变化,统一入口为未来加日志/分类留点。批5,cargo workspace 0
This commit is contained in:
@@ -10,7 +10,7 @@ use df_core::types::new_id;
|
||||
use df_storage::models::AiProviderRecord;
|
||||
|
||||
use crate::state::AppState;
|
||||
use crate::commands::now_millis;
|
||||
use crate::commands::{err_str, now_millis};
|
||||
|
||||
use super::agentic::{run_agentic_loop, try_continue_agent_loop};
|
||||
use super::audit::audit_finalize;
|
||||
@@ -230,7 +230,7 @@ pub async fn ai_chat_clear(state: State<'_, AppState>) -> Result<(), String> {
|
||||
.ai_conversations
|
||||
.clear_messages(&id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -311,7 +311,7 @@ fn mask_api_key(key: &str) -> String {
|
||||
/// 列出所有已配置的 AI 提供商(is_default 真相源为 DB,重启不丢)
|
||||
#[tauri::command]
|
||||
pub async fn ai_list_providers(state: State<'_, AppState>) -> Result<Vec<AiProviderRecord>, String> {
|
||||
let mut providers = state.ai_providers.list_all().await.map_err(|e| e.to_string())?;
|
||||
let mut providers = state.ai_providers.list_all().await.map_err(err_str)?;
|
||||
// IPC 不传明文 api_key(FR-S1):前端编辑用空 apiKey 表示不改,mask 后前端 realm 不持有明文。
|
||||
// 迁移后 DB api_key 空 → 从 keyring 取真实密钥再 mask(前端看到 mask 但不持有明文)
|
||||
for p in &mut providers {
|
||||
@@ -339,7 +339,7 @@ pub async fn ai_save_provider(
|
||||
// 编辑已有提供商时保留原 created_at,避免被覆盖
|
||||
let created_at = match &id {
|
||||
Some(pid) => state.ai_providers.get_by_id(pid).await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(err_str)?
|
||||
.map(|p| p.created_at)
|
||||
.unwrap_or_else(now_millis),
|
||||
None => now_millis(),
|
||||
@@ -347,11 +347,11 @@ pub async fn ai_save_provider(
|
||||
// is_default:编辑保留原值;新建时若全表尚无默认则设为默认(首个自动默认,避免无默认可用)
|
||||
let is_default = match &id {
|
||||
Some(pid) => state.ai_providers.get_by_id(pid).await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(err_str)?
|
||||
.map(|p| p.is_default)
|
||||
.unwrap_or(false),
|
||||
None => !state.ai_providers.list_all().await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(err_str)?
|
||||
.iter().any(|p| p.is_default),
|
||||
};
|
||||
// FR-S1:密钥存 OS keyring,DB api_key 列恒空(不入明文)。
|
||||
@@ -369,7 +369,7 @@ pub async fn ai_save_provider(
|
||||
// 兜底:发现未迁移态先即时迁移补密钥,迁移成功后再让下方清 DB 明文(收敛到迁移完成态);
|
||||
// 迁移失败则 Err 阻断保存且 INSERT OR REPLACE 不执行 → DB 明文保留,绝不劣化现状。
|
||||
let old = state.ai_providers.get_by_id(pid).await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
if let Some(old) = old {
|
||||
if !old.api_key.is_empty()
|
||||
&& super::secret::get_provider_secret(pid).is_none()
|
||||
@@ -409,7 +409,7 @@ pub async fn ai_save_provider(
|
||||
.ai_providers
|
||||
.insert(record)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
@@ -424,18 +424,18 @@ pub async fn ai_set_provider(
|
||||
.ai_providers
|
||||
.get_by_id(&provider_id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(err_str)?
|
||||
.ok_or_else(|| format!("提供商不存在: {}", provider_id))?;
|
||||
|
||||
// 互斥写 DB:目标 is_default=true,其余=false。仅写变化的记录。
|
||||
let providers = state.ai_providers.list_all().await.map_err(|e| e.to_string())?;
|
||||
let providers = state.ai_providers.list_all().await.map_err(err_str)?;
|
||||
for p in &providers {
|
||||
let should = p.id == provider_id;
|
||||
if p.is_default != should {
|
||||
let mut updated = p.clone();
|
||||
updated.is_default = should;
|
||||
updated.updated_at = now_millis();
|
||||
state.ai_providers.update_full(&updated).await.map_err(|e| e.to_string())?;
|
||||
state.ai_providers.update_full(&updated).await.map_err(err_str)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,7 +450,7 @@ pub async fn ai_delete_provider(
|
||||
state: State<'_, AppState>,
|
||||
provider_id: String,
|
||||
) -> Result<(), String> {
|
||||
state.ai_providers.delete(&provider_id).await.map_err(|e| e.to_string())?;
|
||||
state.ai_providers.delete(&provider_id).await.map_err(err_str)?;
|
||||
// CR-260615-01:DB 已删则清 keyring 残留密钥(失败仅 warn 不阻断——无 DB 消费方,
|
||||
// 残留 keyring 不可复活;同 id 复用也不会读到旧密钥,因 set 覆盖写)
|
||||
if let Err(e) = super::secret::delete_provider_secret(&provider_id) {
|
||||
@@ -518,7 +518,7 @@ pub async fn ai_conversation_list(
|
||||
) -> Result<Vec<serde_json::Value>, String> {
|
||||
let limit = limit.unwrap_or(50);
|
||||
let include_archived = include_archived.unwrap_or(false);
|
||||
let records = state.ai_conversations.list_all().await.map_err(|e| e.to_string())?;
|
||||
let records = state.ai_conversations.list_all().await.map_err(err_str)?;
|
||||
// list_all 已按 created_at DESC(最新在前);默认排除归档 + 截断 limit
|
||||
let summaries: Vec<serde_json::Value> = records.iter()
|
||||
.filter(|r| include_archived || !r.archived)
|
||||
@@ -551,7 +551,7 @@ pub async fn ai_conversation_switch(
|
||||
conversation_id: String,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
let record = state.ai_conversations.get_by_id(&conversation_id).await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(err_str)?
|
||||
.ok_or_else(|| format!("对话不存在: {}", conversation_id))?;
|
||||
|
||||
let messages: Vec<ChatMessage> = serde_json::from_str(&record.messages)
|
||||
@@ -591,7 +591,7 @@ pub async fn ai_conversation_delete(
|
||||
state: State<'_, AppState>,
|
||||
conversation_id: String,
|
||||
) -> Result<(), String> {
|
||||
state.ai_conversations.delete(&conversation_id).await.map_err(|e| e.to_string())?;
|
||||
state.ai_conversations.delete(&conversation_id).await.map_err(err_str)?;
|
||||
|
||||
let mut session = state.ai_session.lock().await;
|
||||
if session.active_conversation_id.as_deref() == Some(&conversation_id) {
|
||||
@@ -614,7 +614,7 @@ pub async fn ai_conversation_rename(
|
||||
return Err("标题不能为空".to_string());
|
||||
}
|
||||
state.ai_conversations.update_field(&conversation_id, "title", &title)
|
||||
.await.map_err(|e| e.to_string())?;
|
||||
.await.map_err(err_str)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -628,7 +628,7 @@ pub async fn ai_conversation_archive(
|
||||
state.ai_conversations
|
||||
.set_archived(&conversation_id, archived)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ use df_core::types::new_id;
|
||||
|
||||
use crate::state::{AppState, ExtractTrigger, LlmConcurrency};
|
||||
|
||||
use crate::commands::err_str;
|
||||
|
||||
use super::{AiSession};
|
||||
|
||||
/// 按配置构建 embedding provider + model。None = 配置缺失/provider 不存在。
|
||||
@@ -240,7 +242,7 @@ pub async fn trigger_extraction_now(state: &AppState) -> Result<bool, String> {
|
||||
session.active_conversation_id.clone()
|
||||
};
|
||||
let conv_id = conv_id.ok_or_else(|| "当前无活跃对话".to_string())?;
|
||||
let provider_config = super::prompt::get_active_provider(state).await.map_err(|e| e.to_string())?;
|
||||
let provider_config = super::prompt::get_active_provider(state).await.map_err(err_str)?;
|
||||
let db = state.db.clone();
|
||||
let llm_concurrency = state.llm_concurrency.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use df_storage::models::AiProviderRecord;
|
||||
|
||||
use crate::commands::err_str;
|
||||
use crate::state::AppState;
|
||||
|
||||
/// 获取当前活跃提供商配置
|
||||
@@ -12,7 +13,7 @@ pub(crate) async fn get_active_provider(state: &AppState) -> Result<AiProviderRe
|
||||
.ai_providers
|
||||
.get_by_id(pid)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(err_str)?
|
||||
.ok_or_else(|| format!("活跃提供商不存在: {}", pid))?;
|
||||
Ok(provider)
|
||||
} else {
|
||||
@@ -22,7 +23,7 @@ pub(crate) async fn get_active_provider(state: &AppState) -> Result<AiProviderRe
|
||||
.ai_providers
|
||||
.list_all()
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
let default = providers.iter().find(|p| p.is_default).cloned();
|
||||
default
|
||||
.or_else(|| providers.into_iter().next())
|
||||
|
||||
@@ -9,7 +9,7 @@ use df_storage::models::{IdeaRecord, ProjectRecord};
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::now_millis;
|
||||
use super::{err_str, now_millis};
|
||||
|
||||
/// 创建灵感入参
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -35,8 +35,8 @@ pub async fn list_ideas(
|
||||
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()),
|
||||
Some(s) => state.ideas.query("status", &s).await.map_err(err_str),
|
||||
None => state.ideas.list_all().await.map_err(err_str),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ pub async fn create_idea(
|
||||
.ideas
|
||||
.insert(record.clone())
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
Ok(record)
|
||||
}
|
||||
|
||||
@@ -82,13 +82,13 @@ pub async fn update_idea(
|
||||
.ideas
|
||||
.update_field(&id, &field, &value)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(err_str)
|
||||
}
|
||||
|
||||
/// 删除灵感
|
||||
#[tauri::command]
|
||||
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(err_str)
|
||||
}
|
||||
|
||||
/// 将灵感晋升为项目 — 复用 df-project 领域逻辑创建项目,回写灵感 status=promoted/promoted_to
|
||||
@@ -101,7 +101,7 @@ pub async fn promote_idea(
|
||||
.ideas
|
||||
.get_by_id(&id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(err_str)?
|
||||
.ok_or_else(|| format!("灵感不存在: {id}"))?;
|
||||
|
||||
if record.promoted_to.is_some() {
|
||||
@@ -131,7 +131,7 @@ pub async fn promote_idea(
|
||||
.projects
|
||||
.insert(project_record)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
|
||||
// 回写灵感:status=promoted + promoted_to(update_full 单事务覆盖可变字段)
|
||||
// 补偿删除:第二步失败时回滚第一步已建的 project,保证最终一致性(非原子,但防项目存留而
|
||||
@@ -175,7 +175,7 @@ pub async fn evaluate_idea(
|
||||
.ideas
|
||||
.get_by_id(&id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(err_str)?
|
||||
.ok_or_else(|| format!("灵感不存在: {id}"))?;
|
||||
|
||||
let idea = record_to_idea(&record);
|
||||
@@ -186,7 +186,7 @@ pub async fn evaluate_idea(
|
||||
// 对抗式评估
|
||||
let eval = df_ideas::adversarial::AdversarialEngine::evaluate(&idea)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
|
||||
// 组装前端扁平结构(与 Ideas.vue 的 AdversarialEval interface 对齐)
|
||||
let positive_strength = eval.positive.confidence;
|
||||
@@ -243,7 +243,7 @@ pub async fn evaluate_idea(
|
||||
.ideas
|
||||
.update_full(&updated)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
|
||||
Ok(updated)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use df_storage::models::{KnowledgeEventRecord, KnowledgeRecord};
|
||||
use crate::state::{AppState, KnowledgeConfig};
|
||||
|
||||
use super::knowledge_timeline::KnowledgeTimeline;
|
||||
use super::now_millis;
|
||||
use super::{err_str, now_millis};
|
||||
use serde::Serialize;
|
||||
|
||||
/// 创建知识入参
|
||||
@@ -76,10 +76,10 @@ pub async fn knowledge_list(
|
||||
) -> Result<Vec<KnowledgeRecord>, String> {
|
||||
match status {
|
||||
// 显式查 archived 时原样返回(含归档项)
|
||||
Some(s) if s == "archived" => state.knowledge.list_by_status("archived").await.map_err(|e| e.to_string()),
|
||||
Some(s) => state.knowledge.list_by_status(&s).await.map_err(|e| e.to_string()),
|
||||
Some(s) if s == "archived" => state.knowledge.list_by_status("archived").await.map_err(err_str),
|
||||
Some(s) => state.knowledge.list_by_status(&s).await.map_err(err_str),
|
||||
// 默认:列出非 archived 的全部(单查询 status != 'archived')
|
||||
None => state.knowledge.list_non_archived().await.map_err(|e| e.to_string()),
|
||||
None => state.knowledge.list_non_archived().await.map_err(err_str),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ pub async fn knowledge_get(
|
||||
.knowledge
|
||||
.get_by_id(&id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(err_str)?
|
||||
.ok_or_else(|| format!("知识不存在: {id}"))
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ pub async fn knowledge_search(
|
||||
.knowledge
|
||||
.search(&input.query, input.kind.as_deref(), limit)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(err_str)
|
||||
}
|
||||
|
||||
/// 创建知识(始终 candidate 状态) — MCP create tool
|
||||
@@ -138,7 +138,7 @@ pub async fn knowledge_create(
|
||||
.knowledge
|
||||
.insert(record.clone())
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
// 生命线:手动录入产生(fire-and-forget)
|
||||
KnowledgeTimeline::new(&state.db)
|
||||
.record_created(&record.id, "manual")
|
||||
@@ -161,7 +161,7 @@ pub async fn knowledge_update_status(
|
||||
.knowledge
|
||||
.get_by_id(&id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(err_str)?
|
||||
.ok_or_else(|| format!("知识不存在: {id}"))?;
|
||||
|
||||
let old_status = record.status.clone();
|
||||
@@ -179,7 +179,7 @@ pub async fn knowledge_update_status(
|
||||
.knowledge
|
||||
.update_full(&updated)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
|
||||
// 生命线:状态变更(fire-and-forget;to=published=审核通过,to=archived=归档)
|
||||
KnowledgeTimeline::new(&state.db)
|
||||
@@ -205,7 +205,7 @@ pub async fn knowledge_record_reuse(
|
||||
.knowledge
|
||||
.increment_reuse_count(&id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(err_str)
|
||||
}
|
||||
|
||||
/// 审核收件箱 — 列出 candidate(按 confidence 语义排序)
|
||||
@@ -217,7 +217,7 @@ pub async fn knowledge_list_candidates(
|
||||
.knowledge
|
||||
.list_by_status("candidate")
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(err_str)
|
||||
}
|
||||
|
||||
/// 归档(软删除) — UPDATE status='archived'
|
||||
@@ -300,13 +300,13 @@ pub async fn knowledge_get_detail(
|
||||
.knowledge
|
||||
.get_by_id(&id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(err_str)?
|
||||
.ok_or_else(|| format!("知识不存在: {id}"))?;
|
||||
let events = state
|
||||
.knowledge_events
|
||||
.list_by_knowledge(&id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
Ok(KnowledgeDetailPayload { knowledge, events })
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ pub async fn knowledge_update(
|
||||
.knowledge
|
||||
.get_by_id(&id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(err_str)?
|
||||
.ok_or_else(|| format!("知识不存在: {id}"))?;
|
||||
if let Some(v) = input.title {
|
||||
record.title = v;
|
||||
@@ -344,7 +344,7 @@ pub async fn knowledge_update(
|
||||
.knowledge
|
||||
.update_full(&record)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
Ok(record)
|
||||
}
|
||||
|
||||
@@ -363,13 +363,13 @@ pub async fn knowledge_events(
|
||||
.knowledge_events
|
||||
.list_by_knowledge_type(&knowledge_id, &et, limit)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(err_str)
|
||||
}
|
||||
None => state
|
||||
.knowledge_events
|
||||
.list_by_knowledge(&knowledge_id)
|
||||
.await
|
||||
.map_err(|e| e.to_string()),
|
||||
.map_err(err_str),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use df_storage::crud::KnowledgeEventsRepo;
|
||||
use df_storage::db::Database;
|
||||
use df_storage::models::KnowledgeEventRecord;
|
||||
|
||||
use super::now_millis;
|
||||
use super::{err_str, now_millis};
|
||||
|
||||
/// 事件类型常量(生命线节点;归档复用 status_changed 的 to=archived,不单列)
|
||||
pub const EVENT_CREATED: &str = "created";
|
||||
@@ -57,7 +57,7 @@ impl KnowledgeTimeline {
|
||||
KnowledgeEventsRepo::new(&self.db)
|
||||
.insert(record)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -17,3 +17,9 @@ pub mod workflow;
|
||||
pub(crate) fn now_millis() -> String {
|
||||
df_core::now_millis().to_string()
|
||||
}
|
||||
|
||||
/// .map_err 错误格式化统一入口 — 当前行为零变化(仅 e.to_string()),
|
||||
/// 集中收敛为后续加日志/分类/类型名等扩展留点,替代各处散落的 .map_err(|e| e.to_string())。
|
||||
pub fn err_str<E: std::string::ToString>(e: E) -> String {
|
||||
e.to_string()
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use df_storage::models::ProjectRecord;
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::now_millis;
|
||||
use super::{err_str, now_millis};
|
||||
|
||||
/// 创建项目入参
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -32,7 +32,7 @@ pub struct CreateProjectInput {
|
||||
/// 列出未删除项目(过滤回收站)
|
||||
#[tauri::command]
|
||||
pub async fn list_projects(state: State<'_, AppState>) -> Result<Vec<ProjectRecord>, String> {
|
||||
state.projects.list_active().await.map_err(|e| e.to_string())
|
||||
state.projects.list_active().await.map_err(err_str)
|
||||
}
|
||||
|
||||
/// 创建项目,返回完整记录
|
||||
@@ -59,9 +59,9 @@ pub async fn create_project(
|
||||
let root = std::path::PathBuf::from(p);
|
||||
let detected = tokio::task::spawn_blocking(move || detect_stack(&root))
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(|e| e.to_string())?;
|
||||
serde_json::to_string(&detected).map_err(|e| e.to_string())?
|
||||
.map_err(err_str)?
|
||||
.map_err(err_str)?;
|
||||
serde_json::to_string(&detected).map_err(err_str)?
|
||||
}
|
||||
};
|
||||
(Some(p.to_string()), Some(stack_json))
|
||||
@@ -85,7 +85,7 @@ pub async fn create_project(
|
||||
.projects
|
||||
.insert(record.clone())
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
Ok(record)
|
||||
}
|
||||
|
||||
@@ -153,14 +153,14 @@ pub async fn import_project(
|
||||
let stack_json = match want_stack.as_deref().map(str::trim).filter(|s| !s.is_empty()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => {
|
||||
let detected = detect_stack(&root).map_err(|e| e.to_string())?;
|
||||
serde_json::to_string(&detected).map_err(|e| e.to_string())?
|
||||
let detected = detect_stack(&root).map_err(err_str)?;
|
||||
serde_json::to_string(&detected).map_err(err_str)?
|
||||
}
|
||||
};
|
||||
Ok((name, description, stack_json))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())??;
|
||||
.map_err(err_str)??;
|
||||
|
||||
let now = now_millis();
|
||||
let record = ProjectRecord {
|
||||
@@ -178,7 +178,7 @@ pub async fn import_project(
|
||||
.projects
|
||||
.insert(record.clone())
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
Ok(record)
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ pub async fn get_project(
|
||||
.projects
|
||||
.get_by_id(&id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(err_str)
|
||||
}
|
||||
|
||||
/// 更新项目单个字段(字段名走 df-storage 白名单校验)
|
||||
@@ -207,13 +207,13 @@ pub async fn update_project(
|
||||
.projects
|
||||
.update_field(&id, &field, &value)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(err_str)
|
||||
}
|
||||
|
||||
/// 删除项目(软删 → 回收站,可恢复)
|
||||
#[tauri::command]
|
||||
pub async fn delete_project(state: State<'_, AppState>, id: String) -> Result<bool, String> {
|
||||
state.projects.soft_delete(&id).await.map_err(|e| e.to_string())
|
||||
state.projects.soft_delete(&id).await.map_err(err_str)
|
||||
}
|
||||
|
||||
/// 列出回收站项目(deleted_at IS NOT NULL)
|
||||
@@ -221,13 +221,13 @@ pub async fn delete_project(state: State<'_, AppState>, id: String) -> Result<bo
|
||||
pub async fn list_deleted_projects(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Vec<ProjectRecord>, String> {
|
||||
state.projects.list_deleted().await.map_err(|e| e.to_string())
|
||||
state.projects.list_deleted().await.map_err(err_str)
|
||||
}
|
||||
|
||||
/// 恢复项目(从回收站还原,清 deleted_at)
|
||||
#[tauri::command]
|
||||
pub async fn restore_project(state: State<'_, AppState>, id: String) -> Result<bool, String> {
|
||||
state.projects.restore(&id).await.map_err(|e| e.to_string())
|
||||
state.projects.restore(&id).await.map_err(err_str)
|
||||
}
|
||||
|
||||
/// 彻底删除项目(级联物理删 branches/releases/tasks,不可恢复)
|
||||
@@ -237,7 +237,7 @@ pub async fn purge_project(state: State<'_, AppState>, id: String) -> Result<boo
|
||||
.projects
|
||||
.purge_with_descendants(&id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(err_str)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
@@ -258,16 +258,16 @@ async fn find_binding_conflict(
|
||||
.projects
|
||||
.find_path_conflict(&norm, exclude_id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(err_str)
|
||||
}
|
||||
|
||||
/// 探测目录技术栈(前端选目录后实时预览)
|
||||
#[tauri::command]
|
||||
pub async fn scan_project_stack(path: String) -> Result<Vec<String>, String> {
|
||||
let root = std::path::PathBuf::from(&path);
|
||||
tokio::task::spawn_blocking(move || detect_stack(&root).map_err(|e| e.to_string()))
|
||||
tokio::task::spawn_blocking(move || detect_stack(&root).map_err(err_str))
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(err_str)?
|
||||
}
|
||||
|
||||
/// 检查目录是否已被其他项目绑定(防重复绑定)。返回占用项目(若有)。
|
||||
@@ -298,24 +298,24 @@ pub async fn relocate_project_path(
|
||||
let root = std::path::PathBuf::from(&new_path);
|
||||
let stack = tokio::task::spawn_blocking(move || detect_stack(&root))
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(|e| e.to_string())?;
|
||||
let stack_json = serde_json::to_string(&stack).map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?
|
||||
.map_err(err_str)?;
|
||||
let stack_json = serde_json::to_string(&stack).map_err(err_str)?;
|
||||
state
|
||||
.projects
|
||||
.update_field(&id, "path", &new_path)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
state
|
||||
.projects
|
||||
.update_field(&id, "stack", &stack_json)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
state
|
||||
.projects
|
||||
.get_by_id(&id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(err_str)?
|
||||
.ok_or_else(|| "项目不存在".to_string())
|
||||
}
|
||||
|
||||
@@ -364,11 +364,11 @@ pub async fn scan_project_with_ai(
|
||||
Ok::<_, anyhow::Error>((stack, sample))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?
|
||||
.map_err(err_str)?;
|
||||
|
||||
// 2. 取默认 provider(优先 is_default,否则首个)
|
||||
let providers = state.ai_providers.list_all().await.map_err(|e| e.to_string())?;
|
||||
let providers = state.ai_providers.list_all().await.map_err(err_str)?;
|
||||
let pc = providers
|
||||
.iter()
|
||||
.find(|p| p.is_default)
|
||||
|
||||
@@ -9,13 +9,15 @@ use tauri::State;
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::err_str;
|
||||
|
||||
/// 取单个 key 的值(JSON 字符串),不存在返回 None
|
||||
#[tauri::command]
|
||||
pub async fn settings_get(
|
||||
state: State<'_, AppState>,
|
||||
key: String,
|
||||
) -> Result<Option<String>, String> {
|
||||
state.settings.get(&key).await.map_err(|e| e.to_string())
|
||||
state.settings.get(&key).await.map_err(err_str)
|
||||
}
|
||||
|
||||
/// 写 key/value(`INSERT OR REPLACE`),刷新 updated_at
|
||||
@@ -25,7 +27,7 @@ pub async fn settings_set(
|
||||
key: String,
|
||||
value: String,
|
||||
) -> Result<bool, String> {
|
||||
state.settings.set(&key, &value).await.map_err(|e| e.to_string())
|
||||
state.settings.set(&key, &value).await.map_err(err_str)
|
||||
}
|
||||
|
||||
/// 取全部 key/value(前端启动时一次性拉回恢复偏好)
|
||||
@@ -33,7 +35,7 @@ pub async fn settings_set(
|
||||
pub async fn settings_get_all(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<HashMap<String, String>, String> {
|
||||
let rows = state.settings.get_all().await.map_err(|e| e.to_string())?;
|
||||
let rows = state.settings.get_all().await.map_err(err_str)?;
|
||||
Ok(rows.into_iter().collect())
|
||||
}
|
||||
|
||||
@@ -43,5 +45,5 @@ pub async fn settings_delete(
|
||||
state: State<'_, AppState>,
|
||||
key: String,
|
||||
) -> Result<bool, String> {
|
||||
state.settings.delete(&key).await.map_err(|e| e.to_string())
|
||||
state.settings.delete(&key).await.map_err(err_str)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use df_storage::models::TaskRecord;
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::now_millis;
|
||||
use super::{err_str, now_millis};
|
||||
|
||||
/// 创建任务入参
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -37,7 +37,7 @@ pub async fn list_tasks(
|
||||
Some(pid) => state.tasks.query("project_id", &pid).await,
|
||||
None => state.tasks.list_all().await,
|
||||
};
|
||||
result.map_err(|e| e.to_string())
|
||||
result.map_err(err_str)
|
||||
}
|
||||
|
||||
/// 按 id 查任务,找不到返回 Err(供前端详情页)
|
||||
@@ -50,7 +50,7 @@ pub async fn get_task_by_id(
|
||||
.tasks
|
||||
.get_by_id(&id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(err_str)?
|
||||
.ok_or_else(|| format!("任务 {} 不存在", id))
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ pub async fn create_task(
|
||||
.tasks
|
||||
.insert(record.clone())
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
Ok(record)
|
||||
}
|
||||
|
||||
@@ -104,11 +104,11 @@ pub async fn update_task(
|
||||
.tasks
|
||||
.update_field(&id, &field, &value)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(err_str)
|
||||
}
|
||||
|
||||
/// 删除任务
|
||||
#[tauri::command]
|
||||
pub async fn delete_task(state: State<'_, AppState>, id: String) -> Result<bool, String> {
|
||||
state.tasks.delete(&id).await.map_err(|e| e.to_string())
|
||||
state.tasks.delete(&id).await.map_err(err_str)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use df_workflow::executor::DagExecutor;
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::now_millis;
|
||||
use super::{err_str, now_millis};
|
||||
|
||||
/// 转发到前端的事件载荷
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
@@ -41,11 +41,11 @@ pub async fn run_workflow(
|
||||
config: serde_json::Value,
|
||||
) -> Result<String, String> {
|
||||
// 1. 先构建运行时 DAG,校验失败直接返回,不落库
|
||||
let runtime_dag = state.registry.build_dag(&dag).map_err(|e| e.to_string())?;
|
||||
let runtime_dag = state.registry.build_dag(&dag).map_err(err_str)?;
|
||||
|
||||
// 2. 写入执行记录(status=running)
|
||||
let execution_id = new_id();
|
||||
let dag_json = serde_json::to_string(&dag).map_err(|e| e.to_string())?;
|
||||
let dag_json = serde_json::to_string(&dag).map_err(err_str)?;
|
||||
let record = WorkflowRecord {
|
||||
id: execution_id.clone(),
|
||||
name,
|
||||
@@ -61,7 +61,7 @@ pub async fn run_workflow(
|
||||
.workflows
|
||||
.insert(record)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(err_str)?;
|
||||
|
||||
// 3. 订阅事件总线,把事件转发给前端(收到完成/失败事件后退出)
|
||||
let mut rx = state.event_bus.subscribe();
|
||||
@@ -173,7 +173,7 @@ pub async fn run_workflow(
|
||||
pub async fn list_workflow_executions(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Vec<WorkflowRecord>, String> {
|
||||
state.workflows.list_all().await.map_err(|e| e.to_string())
|
||||
state.workflows.list_all().await.map_err(err_str)
|
||||
}
|
||||
|
||||
/// 按 ID 查询工作流执行记录
|
||||
@@ -186,7 +186,7 @@ pub async fn get_workflow_execution(
|
||||
.workflows
|
||||
.get_by_id(&id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(err_str)
|
||||
}
|
||||
|
||||
/// 发送人工审批响应
|
||||
|
||||
Reference in New Issue
Block a user