新增: 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:
2026-06-14 14:08:20 +08:00
parent 98393b4908
commit cf017f81e2
167 changed files with 19549 additions and 6886 deletions

View File

@@ -2,12 +2,12 @@
import { invoke } from '@tauri-apps/api/core'
import { listen, type UnlistenFn } from '@tauri-apps/api/event'
import type { AiChatEvent, AiConversationDetail, AiConversationSummary, AiProviderConfig } from './types'
import type { AiChatEvent, AiConversationDetail, AiConversationSummary, AiProviderConfig, SkillInfo } from './types'
export const aiApi = {
/** 发送消息(非阻塞,通过 ai-chat-event 流式返回) */
sendMessage(message: string, language?: string): Promise<string> {
return invoke('ai_chat_send', { message, language: language || 'zh-CN' })
/** 发送消息(非阻塞,通过 ai-chat-event 流式返回)skill 传技能名则注入其 SKILL.md */
sendMessage(message: string, language?: string, skill?: string): Promise<string> {
return invoke('ai_chat_send', { message, language: language || 'zh-CN', skill: skill || null })
},
/** 批准/拒绝工具调用 */
@@ -15,6 +15,11 @@ export const aiApi = {
return invoke('ai_approve', { toolCallId, approved })
},
/** 查询某对话积压的待审批工具(重启后恢复 toolCard pending_approval 态用) */
pendingToolCalls(convId: string): Promise<{ tool_call_id: string; conversation_id: string | null }[]> {
return invoke('ai_pending_tool_calls', { convId })
},
/** 清空对话 */
clearChat(): Promise<void> {
return invoke('ai_chat_clear')
@@ -54,6 +59,21 @@ export const aiApi = {
return invoke('ai_set_provider', { providerId })
},
/** 删除 AI 提供商 */
deleteProvider(providerId: string): Promise<void> {
return invoke('ai_delete_provider', { providerId })
},
/** 设置 LLM 调用并发上限(全局 / 单对话,运行时即时生效;软收敛不中断进行中调用) */
setConcurrencyConfig(globalLimit: number, perConvLimit: number): Promise<void> {
return invoke('ai_set_concurrency_config', { globalLimit, perConvLimit })
},
/** 列出本机 Claude 技能skills + commands + plugins */
listSkills(): Promise<SkillInfo[]> {
return invoke('ai_list_skills')
},
/** 监听 AI 聊天事件 */
onEvent(callback: (event: AiChatEvent) => void): Promise<UnlistenFn> {
return listen<AiChatEvent>('ai-chat-event', (e) => {
@@ -68,9 +88,12 @@ export const aiApi = {
return invoke('ai_conversation_create')
},
/** 列出所有对话(摘要) */
listConversations(): Promise<AiConversationSummary[]> {
return invoke('ai_conversation_list')
/** 列出对话(摘要limit 默认 50includeArchived 默认 false 隐藏归档 */
listConversations(limit?: number, includeArchived?: boolean): Promise<AiConversationSummary[]> {
return invoke('ai_conversation_list', {
limit: limit ?? null,
includeArchived: includeArchived ?? false,
})
},
/** 切换到指定对话 */
@@ -87,4 +110,9 @@ export const aiApi = {
renameConversation(conversationId: string, title: string): Promise<void> {
return invoke('ai_conversation_rename', { conversationId, title })
},
/** 归档/取消归档对话 */
archiveConversation(conversationId: string, archived: boolean): Promise<void> {
return invoke('ai_conversation_archive', { conversationId, archived })
},
}

View File

@@ -1,9 +1,10 @@
import { invoke } from '@tauri-apps/api/core'
import type { IdeaRecord, CreateIdeaInput } from './types'
import type { IdeaRecord, CreateIdeaInput, PromotionResult } from './types'
export const ideaApi = {
list(): Promise<IdeaRecord[]> {
return invoke('list_ideas')
/** 列出想法,可选按 status 过滤draft/pending_review/promoted 等) */
list(status?: string): Promise<IdeaRecord[]> {
return invoke('list_ideas', { status: status ?? null })
},
create(input: CreateIdeaInput): Promise<IdeaRecord> {
@@ -17,4 +18,14 @@ export const ideaApi = {
delete(id: string): Promise<boolean> {
return invoke('delete_idea', { id })
},
/** 多维评分 + 对抗式评估,结果写回并返回更新后的记录 */
evaluate(id: string): Promise<IdeaRecord> {
return invoke('evaluate_idea', { id })
},
/** 将想法晋升为项目,返回晋升结果(含 project_id */
promote(id: string): Promise<PromotionResult> {
return invoke('promote_idea', { id })
},
}

View File

@@ -4,3 +4,5 @@ export { taskApi } from './task'
export { ideaApi } from './idea'
export { workflowApi } from './workflow'
export { aiApi } from './ai'
export { knowledgeApi } from './knowledge'
export { settingsApi } from './settings'

82
src/api/knowledge.ts Normal file
View File

@@ -0,0 +1,82 @@
import { invoke } from '@tauri-apps/api/core'
import type {
KnowledgeRecord,
KnowledgeEventRecord,
KnowledgeDetailPayload,
CreateKnowledgeInput,
KnowledgeSearchInput,
UpdateKnowledgeInput,
KnowledgeConfig,
} from './types'
export const knowledgeApi = {
/** 列出知识(可按 status 筛选,默认排除 archived) */
list(status?: string | null): Promise<KnowledgeRecord[]> {
return invoke('knowledge_list', { status: status ?? null })
},
/** 单条查询 */
get(id: string): Promise<KnowledgeRecord> {
return invoke('knowledge_get', { id })
},
/** 检索知识(top-N,默认 3) */
search(input: KnowledgeSearchInput): Promise<KnowledgeRecord[]> {
return invoke('knowledge_search', { input })
},
/** 创建知识(始终 candidate 状态) */
create(input: CreateKnowledgeInput): Promise<KnowledgeRecord> {
return invoke('knowledge_create', { input })
},
/** 状态转换(含合法矩阵校验) */
updateStatus(id: string, status: string): Promise<boolean> {
return invoke('knowledge_update_status', { id, status })
},
/** 复用计数 +1 */
recordReuse(id: string): Promise<boolean> {
return invoke('knowledge_record_reuse', { id })
},
/** 审核收件箱(候选列表,按置信度排序) */
listCandidates(): Promise<KnowledgeRecord[]> {
return invoke('knowledge_list_candidates')
},
/** 归档(软删除) */
archive(id: string): Promise<boolean> {
return invoke('knowledge_archive', { id })
},
/** 读取知识库行为配置 */
getConfig(): Promise<KnowledgeConfig> {
return invoke('knowledge_get_config')
},
/** 保存知识库行为配置 */
saveConfig(config: KnowledgeConfig): Promise<boolean> {
return invoke('knowledge_save_config', { config })
},
/** 手动触发提炼 */
extractNow(): Promise<boolean> {
return invoke('knowledge_extract_now')
},
/** 知识详情(基本信息 + 生命线事件) */
getDetail(id: string): Promise<KnowledgeDetailPayload> {
return invoke('knowledge_get_detail', { id })
},
/** 编辑知识(部分更新) */
update(id: string, input: UpdateKnowledgeInput): Promise<KnowledgeRecord> {
return invoke('knowledge_update', { id, input })
},
/** 查询生命线事件(可按 event_type 过滤) */
events(knowledgeId: string, eventType?: string, limit?: number): Promise<KnowledgeEventRecord[]> {
return invoke('knowledge_events', { knowledgeId, eventType: eventType ?? null, limit: limit ?? null })
},
}

View File

@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core'
import type { ProjectRecord, CreateProjectInput } from './types'
import type { ProjectRecord, CreateProjectInput, AiScanResult } from './types'
export const projectApi = {
list(): Promise<ProjectRecord[]> {
@@ -21,4 +21,41 @@ export const projectApi = {
delete(id: string): Promise<boolean> {
return invoke('delete_project', { id })
},
listDeleted(): Promise<ProjectRecord[]> {
return invoke('list_deleted_projects')
},
restore(id: string): Promise<boolean> {
return invoke('restore_project', { id })
},
purge(id: string): Promise<boolean> {
return invoke('purge_project', { id })
},
/** 探测目录技术栈(选目录后实时预览) */
scanStack(path: string): Promise<string[]> {
return invoke('scan_project_stack', { path })
},
/** 检查目录是否已被其他项目绑定(防重复,excludeId 排除自身) */
checkBinding(path: string, excludeId?: string): Promise<ProjectRecord | null> {
return invoke('check_path_binding', { path, excludeId })
},
/** 重定位项目目录(校验+重探测 stack,返回最新记录) */
relocatePath(id: string, newPath: string): Promise<ProjectRecord> {
return invoke('relocate_project_path', { id, newPath })
},
/** 检查目录是否存在(详情页"目录是否还在") */
checkPathExists(path: string): Promise<boolean> {
return invoke('check_path_exists', { path })
},
/** AI 扫描目录,自动分析基础信息(description/stack/project_type) */
scanWithAi(path: string): Promise<AiScanResult> {
return invoke('scan_project_with_ai', { path })
},
}

28
src/api/settings.ts Normal file
View File

@@ -0,0 +1,28 @@
//! 应用设置 KV API — IPC invoke 封装(localStorage → SQLite 迁移目标)
//!
//! 后端 4 个 command 读写 `app_settings` 表,value 在 SQLite 里永远是 JSON 字符串。
//! 本层只做透传(不解析 JSON),JSON.parse / JSON.stringify 由 store 层负责。
import { invoke } from '@tauri-apps/api/core'
export const settingsApi = {
/** 取单个 key 的原始值(JSON 字符串),不存在返回 null */
get(key: string): Promise<string | null> {
return invoke<string | null>('settings_get', { key })
},
/** 写 key/value(`INSERT OR REPLACE`),成功返回 true */
set(key: string, value: string): Promise<boolean> {
return invoke<boolean>('settings_set', { key, value })
},
/** 取全部 key/value(启动时一次性拉回恢复偏好) */
getAll(): Promise<Record<string, string>> {
return invoke<Record<string, string>>('settings_get_all')
},
/** 删除单个 key,成功返回 true */
delete(key: string): Promise<boolean> {
return invoke<boolean>('settings_delete', { key })
},
}

View File

@@ -38,6 +38,10 @@ export interface ProjectRecord {
description: string
status: string // planning | in_progress | paused | completed | cancelled
idea_id: string | null
/** 绑定的本地代码目录绝对路径(null=未绑定) */
path: string | null
/** 技术栈 JSON 数组字符串(如 ["rust","vue"],null=未探测) */
stack: string | null
created_at: string
updated_at: string
}
@@ -46,6 +50,30 @@ export interface CreateProjectInput {
name: string
description?: string
idea_id?: string
/** 绑定的本地代码目录(可选) */
path?: string
/** 技术栈 JSON 数组字符串(可选,空则后端自动探测) */
stack?: string
}
/** AI 扫描项目结果(预览用,用户确认后填入) */
export interface AiScanResult {
/** 项目摘要(空=LLM 未得出,需手填) */
description: string
/** 技术栈(规则 LLM,去重小写) */
stack: string[]
/** 项目类型(web/api/cli/library/desktop/mobile/monorepo/other) */
project_type: string | null
/** LLM 原始返回(降级时含错误信息) */
raw: string | null
}
/** 想法晋升结果(后端 promote_idea 返回) */
export interface PromotionResult {
idea_id: string
project_id: string
promoted: boolean
reason: string
}
// ============================================================
@@ -141,7 +169,7 @@ export type AiChatEvent = ({
} | {
type: 'AiApprovalResult'; id: string; approved: boolean
} | {
type: 'AiCompleted'; total_tokens: number
type: 'AiCompleted'; total_tokens: number; prompt_tokens: number; completion_tokens: number
} | {
type: 'AiError'; error: string
} | {
@@ -157,6 +185,8 @@ export interface AiMessage {
content: string
isError?: boolean
toolCalls?: AiToolCallInfo[]
/** 生成该消息的 model(仅 assistant 消息,历史消息从 DB 读) */
model?: string
timestamp: number
}
@@ -167,6 +197,8 @@ export interface AiToolCallInfo {
args: unknown
status: 'running' | 'pending_approval' | 'completed' | 'rejected'
result?: unknown
/** 审批风险说明AiApprovalRequired 时填充) */
reason?: string
}
/** 对话列表摘要 */
@@ -175,6 +207,10 @@ export interface AiConversationSummary {
title: string | null
provider_id: string | null
model: string | null
models?: string[] | null
archived: boolean
prompt_tokens?: number | null
completion_tokens?: number | null
created_at: string
updated_at: string
}
@@ -202,3 +238,97 @@ export interface AiConversationDetail {
title: string | null
messages: string // JSON string of ChatMessage[]
}
/** 技能信息(本机 Claude skill供 `/` 联想 + 注入) */
export interface SkillInfo {
name: string
description: string
argument_hint?: string
/** skill | command | plugin */
source: string
/** SKILL.md 绝对路径(后端注入时读全文) */
path: string
}
// ============================================================
// 知识库
// ============================================================
/** 知识条目(与 Rust KnowledgeRecord 严格对齐) */
export interface KnowledgeRecord {
id: string
kind: string // review_rule | prompt_template | pitfall | architecture_pattern | diagnosis | deployment_note | workflow_optimization
title: string
content: string
tags: string | null // JSON 数组字符串
status: string // candidate | pending_review | published | archived
confidence: string | null // high | medium | low
reuse_count: number
verified: boolean
source_project: string | null
source_ref: string | null
/** AI 提炼判断依据("为何值得沉淀"),手动录入为 null */
reasoning: string | null
created_at: string
updated_at: string
}
/** 创建知识入参 */
export interface CreateKnowledgeInput {
kind: string
title: string
content: string
tags?: string
source_project?: string
source_ref?: string
confidence?: string
}
/** 检索知识入参 */
export interface KnowledgeSearchInput {
query: string
kind?: string
limit?: number
}
/** 知识生命线事件记录(产生/审核/引用/归档审计) */
export interface KnowledgeEventRecord {
id: string
knowledge_id: string
/** created | extracted | status_changed | referenced */
event_type: string
source_ref: string | null
/** JSON 字符串,内容因 event_type 而异(见后端 KnowledgeTimeline) */
context_json: string | null
timestamp: string
}
/** 知识详情聚合负载(基本信息 + 全部生命线事件) */
export interface KnowledgeDetailPayload {
knowledge: KnowledgeRecord
events: KnowledgeEventRecord[]
}
/** 编辑知识入参(部分更新,仅传需改字段) */
export interface UpdateKnowledgeInput {
title?: string
content?: string
tags?: string
confidence?: string
reasoning?: string
}
/** 知识库行为配置(提取 + 注入 + 向量检索) */
export interface KnowledgeConfig {
auto_extract: boolean
trigger_mode: 'on_complete' | 'on_idle' | 'manual_only'
min_messages: number
idle_timeout_ms: number
auto_inject: boolean
/** 语义检索(向量)开关,默认 false——关闭时纯 LIKE 零外部依赖 */
vector_enabled: boolean
/** embedding 用的 provider id(仅 openai_compat 类型) */
embedding_provider_id: string | null
/** embedding 模型名(如 embedding-3) */
embedding_model: string | null
}