用户可见文案 + 后端错误信息 + 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
318 lines
9.7 KiB
TypeScript
318 lines
9.7 KiB
TypeScript
import { reactive, computed } from 'vue'
|
||
import { invoke } from '@tauri-apps/api/core'
|
||
import { projectApi, taskApi, ideaApi, workflowApi } from '../api'
|
||
import type { ProjectRecord, TaskRecord, IdeaRecord, WorkflowRecord, WorkflowEventPayload } from '../api/types'
|
||
|
||
// ── 全局响应式状态(单例) ──
|
||
|
||
const state = reactive({
|
||
projects: [] as ProjectRecord[],
|
||
deletedProjects: [] as ProjectRecord[],
|
||
tasks: [] as TaskRecord[],
|
||
ideas: [] as IdeaRecord[],
|
||
workflowExecutions: [] as WorkflowRecord[],
|
||
liveEvents: [] as WorkflowEventPayload[],
|
||
pendingApproval: null as {
|
||
execution_id: string
|
||
node_id: string
|
||
title: string
|
||
description: string
|
||
options: string[]
|
||
} | null,
|
||
loading: false,
|
||
error: null as string | null,
|
||
})
|
||
|
||
let _eventUnlisten: (() => void) | null = null
|
||
|
||
function createStore() {
|
||
// ── 项目 CRUD ──
|
||
async function loadProjects() {
|
||
state.loading = true
|
||
state.error = null
|
||
try {
|
||
state.projects = await projectApi.list()
|
||
} catch (e: any) {
|
||
state.error = e?.toString() ?? '加载项目失败'
|
||
} finally {
|
||
state.loading = false
|
||
}
|
||
}
|
||
|
||
// 清除错误状态(供 toast 显示后重置,允许连续同值错误再次触发 watch)
|
||
function clearError() { state.error = null }
|
||
|
||
async function createProject(name: string, description = '', ideaId?: string, path?: string, stack?: string) {
|
||
try {
|
||
const record = await projectApi.create({ name, description, idea_id: ideaId, path, stack })
|
||
state.projects.push(record)
|
||
return record
|
||
} catch (e: any) {
|
||
state.error = e?.toString() ?? '创建项目失败'
|
||
return null
|
||
}
|
||
}
|
||
|
||
async function updateProject(id: string, field: string, value: string) {
|
||
await projectApi.update(id, field, value)
|
||
const idx = state.projects.findIndex(p => p.id === id)
|
||
if (idx >= 0) {
|
||
(state.projects[idx] as any)[field] = value
|
||
}
|
||
}
|
||
|
||
/** 重定位项目目录(后端重探测 stack,返回最新记录并更新本地状态) */
|
||
async function relocateProjectPath(id: string, newPath: string) {
|
||
const record = await projectApi.relocatePath(id, newPath)
|
||
const idx = state.projects.findIndex(p => p.id === id)
|
||
if (idx >= 0) state.projects[idx] = record
|
||
return record
|
||
}
|
||
|
||
async function deleteProject(id: string) {
|
||
try {
|
||
await projectApi.delete(id) // 软删 → 回收站(可恢复)
|
||
state.projects = state.projects.filter(p => p.id !== id)
|
||
} catch (e: any) {
|
||
state.error = e?.toString() ?? '删除项目失败'
|
||
}
|
||
}
|
||
|
||
async function loadDeletedProjects() {
|
||
try {
|
||
state.deletedProjects = await projectApi.listDeleted()
|
||
} catch (e: any) {
|
||
state.error = e?.toString() ?? '加载回收站失败'
|
||
}
|
||
}
|
||
|
||
async function restoreProject(id: string) {
|
||
try {
|
||
await projectApi.restore(id)
|
||
state.deletedProjects = state.deletedProjects.filter(p => p.id !== id)
|
||
await loadProjects() // 恢复后刷新活跃列表
|
||
} catch (e: any) {
|
||
state.error = e?.toString() ?? '恢复项目失败'
|
||
}
|
||
}
|
||
|
||
async function purgeProject(id: string) {
|
||
try {
|
||
await projectApi.purge(id) // 彻底删(级联物理删,不可恢复)
|
||
state.deletedProjects = state.deletedProjects.filter(p => p.id !== id)
|
||
} catch (e: any) {
|
||
state.error = e?.toString() ?? '彻底删除失败'
|
||
}
|
||
}
|
||
|
||
// ── 任务 CRUD ──
|
||
async function loadTasks(projectId?: string) {
|
||
try {
|
||
state.tasks = await taskApi.list(projectId)
|
||
} catch (e: any) {
|
||
state.error = e?.toString() ?? '加载任务失败'
|
||
}
|
||
}
|
||
|
||
async function createTask(input: { project_id: string; title: string; description?: string; priority?: number; branch_name?: string; assignee?: string }) {
|
||
try {
|
||
const record = await taskApi.create(input)
|
||
state.tasks.push(record)
|
||
return record
|
||
} catch (e: any) {
|
||
state.error = e?.toString() ?? '创建任务失败'
|
||
return null
|
||
}
|
||
}
|
||
|
||
async function updateTask(id: string, field: string, value: string) {
|
||
await taskApi.update(id, field, value)
|
||
const idx = state.tasks.findIndex(t => t.id === id)
|
||
if (idx >= 0) {
|
||
(state.tasks[idx] as any)[field] = value
|
||
}
|
||
}
|
||
|
||
async function deleteTask(id: string) {
|
||
try {
|
||
await taskApi.delete(id)
|
||
state.tasks = state.tasks.filter(t => t.id !== id)
|
||
} catch (e: any) {
|
||
state.error = e?.toString() ?? '删除任务失败'
|
||
}
|
||
}
|
||
|
||
// ── 灵感 CRUD ──
|
||
async function loadIdeas() {
|
||
try {
|
||
state.ideas = await ideaApi.list()
|
||
} catch (e: any) {
|
||
state.error = e?.toString() ?? '加载灵感失败'
|
||
}
|
||
}
|
||
|
||
async function createIdea(input: { title: string; description?: string; priority?: number; tags?: string; source?: string }) {
|
||
try {
|
||
const record = await ideaApi.create(input)
|
||
state.ideas.push(record)
|
||
return record
|
||
} catch (e: any) {
|
||
state.error = e?.toString() ?? '创建灵感失败'
|
||
return null
|
||
}
|
||
}
|
||
|
||
async function updateIdea(id: string, field: string, value: string) {
|
||
await ideaApi.update(id, field, value)
|
||
const idx = state.ideas.findIndex(i => i.id === id)
|
||
if (idx >= 0) {
|
||
(state.ideas[idx] as any)[field] = value
|
||
}
|
||
}
|
||
|
||
async function deleteIdea(id: string) {
|
||
try {
|
||
await ideaApi.delete(id)
|
||
state.ideas = state.ideas.filter(i => i.id !== id)
|
||
} catch (e: any) {
|
||
state.error = e?.toString() ?? '删除灵感失败'
|
||
}
|
||
}
|
||
|
||
async function evaluateIdea(id: string) {
|
||
const record = await ideaApi.evaluate(id)
|
||
const idx = state.ideas.findIndex(i => i.id === id)
|
||
if (idx >= 0) state.ideas[idx] = record
|
||
return record
|
||
}
|
||
|
||
async function promoteIdea(id: string) {
|
||
const res = await ideaApi.promote(id)
|
||
await loadIdeas() // 后端已回写 status=promoted/promoted_to,刷新列表
|
||
return res
|
||
}
|
||
|
||
// ── 工作流 ──
|
||
async function runWorkflow(name: string, dag: unknown, config?: Record<string, unknown>) {
|
||
return await workflowApi.run(name, dag, config)
|
||
}
|
||
|
||
async function loadWorkflowExecutions() {
|
||
try {
|
||
state.workflowExecutions = await workflowApi.listExecutions()
|
||
} catch (e: any) {
|
||
state.error = e?.toString() ?? '加载工作流记录失败'
|
||
}
|
||
}
|
||
|
||
async function startEventListener() {
|
||
if (_eventUnlisten) return _eventUnlisten
|
||
_eventUnlisten = await workflowApi.onEvent((payload) => {
|
||
try {
|
||
state.liveEvents.push(payload)
|
||
// 处理人工审批请求
|
||
if (payload.event?.type === 'HumanApprovalRequest') {
|
||
state.pendingApproval = payload.event.data as typeof state.pendingApproval
|
||
}
|
||
} catch (e) {
|
||
console.error('处理工作流事件失败:', e, payload)
|
||
}
|
||
})
|
||
return _eventUnlisten
|
||
}
|
||
|
||
function clearLiveEvents() {
|
||
state.liveEvents = []
|
||
}
|
||
|
||
async function approveHumanApproval(decision: string, comment?: string) {
|
||
if (!state.pendingApproval) return
|
||
|
||
try {
|
||
// 调用 IPC 命令发送审批响应
|
||
await invoke('approve_human_approval', {
|
||
execution_id: state.pendingApproval.execution_id,
|
||
node_id: state.pendingApproval.node_id,
|
||
decision,
|
||
comment
|
||
})
|
||
|
||
// 清除待审批状态
|
||
state.pendingApproval = null
|
||
} catch (error) {
|
||
console.error('审批失败:', error)
|
||
}
|
||
}
|
||
|
||
/** 取消当前待审批节点(调 cancel_workflow_node IPC 置节点 Cancelled) */
|
||
async function cancelHumanApproval() {
|
||
if (!state.pendingApproval) return
|
||
|
||
try {
|
||
await invoke('cancel_workflow_node', {
|
||
execution_id: state.pendingApproval.execution_id,
|
||
node_id: state.pendingApproval.node_id,
|
||
})
|
||
// 清除待审批状态
|
||
state.pendingApproval = null
|
||
} catch (error) {
|
||
console.error('取消审批失败:', error)
|
||
}
|
||
}
|
||
|
||
function stopEventListener() {
|
||
if (_eventUnlisten) {
|
||
try {
|
||
_eventUnlisten()
|
||
} catch (e) {
|
||
console.error('停止事件监听失败:', e)
|
||
}
|
||
_eventUnlisten = null
|
||
}
|
||
}
|
||
|
||
// ── 统计 ──
|
||
const stats = computed(() => ({
|
||
ideas: state.ideas.length,
|
||
projects: state.projects.length,
|
||
activeTasks: state.tasks.filter(t => t.status === 'in_progress').length,
|
||
drafts: state.ideas.filter(i => i.status === 'draft').length,
|
||
}))
|
||
|
||
return reactive({
|
||
// 状态用 getter 实时读 state —— 直接 ideas: state.ideas 会快照引用,
|
||
// loadIdeas 等重新赋值 state.ideas 时返回对象的 ideas 属性不跟随,刷新后视图为空
|
||
get projects() { return state.projects },
|
||
get tasks() { return state.tasks },
|
||
get ideas() { return state.ideas },
|
||
get workflowExecutions() { return state.workflowExecutions },
|
||
get liveEvents() { return state.liveEvents },
|
||
get loading() { return state.loading },
|
||
get error() { return state.error },
|
||
clearError,
|
||
get deletedProjects() { return state.deletedProjects },
|
||
// project actions
|
||
loadProjects, createProject, updateProject, deleteProject, relocateProjectPath,
|
||
loadDeletedProjects, restoreProject, purgeProject,
|
||
// task actions
|
||
loadTasks, createTask, updateTask, deleteTask,
|
||
// idea actions
|
||
loadIdeas, createIdea, updateIdea, deleteIdea, evaluateIdea, promoteIdea,
|
||
// workflow actions
|
||
runWorkflow, loadWorkflowExecutions, startEventListener, stopEventListener, clearLiveEvents,
|
||
approveHumanApproval, cancelHumanApproval, pendingApproval: computed(() => state.pendingApproval),
|
||
// computed
|
||
stats,
|
||
})
|
||
}
|
||
|
||
type ProjectStore = ReturnType<typeof createStore>
|
||
let _storeInstance: ProjectStore | null = null
|
||
|
||
/** 项目/任务/灵感/工作流 全局状态(单例,多组件复用同一 reactive 包装) */
|
||
export function useProjectStore(): ProjectStore {
|
||
if (_storeInstance) return _storeInstance
|
||
_storeInstance = createStore()
|
||
return _storeInstance
|
||
}
|