重构: 前端 composable 拆分+详情页 Markdown 渲染+流式 memo+状态同步+bug 修复
- composables/useMarkdown 抽(AiChat 复用)+ai/* 流式核心+useAiSend catch 回滚 user msg(B-21)+useAiStream 超时文案(B-03)+useAiConversations 切换 token(FR-R1) - views/TaskDetail 新建(F-02)+描述 md+mdReady 响应式+字段同行布局(B-24/31) - views/ProjectDetail 多选审批 UI(F-01)+描述 md+mdReady+字段同行(B-25/31) - views/Ideas+Knowledge 描述 md+mdReady(B-25) - views/Tasks 项目切换联动(B-29)+task 详情跳转 - components/AiChat toast 失败提示(B-20)+流式块级 memo(ARC-08)+clean UI(AR-7)+stop 兜底(AR-5) - components/ToolCard 审批卡片可读化(AR-3) - stores useAiStore barrel(ARC-04)+del settings mock(ARC-01)+approve options(R-PD-5) - i18n aiTool/nav 中英对称(AR-3/9)+删 nav.decisions 死链(ARC-02) - router /tasks/:id(F-02)+global.css selection 可见(R-260615-01)+api task detail+App 删 decisions nav
This commit is contained in:
@@ -5,9 +5,8 @@
|
||||
//! - 写操作立即更新缓存(乐观),并按 key debounce ~300ms 合并连续快速写
|
||||
//! - 组合式 `useSetting` 让组件像用 ref 一样用 v-model 绑定某个 key
|
||||
//!
|
||||
//! 注意:此为「KV 偏好持久化」层,与已有的 `stores/settings.ts`(mock 的 AI 提供商/
|
||||
//! 连接/通用配置列表)是两件事 —— 那个文件承载静态展示数据,本文件承载运行时偏好,
|
||||
//! 故单独成文,互不干扰。
|
||||
//! 注意:此为「KV 偏好持久化」层,与 AI 提供商/连接配置(store 层)是两件事 ——
|
||||
//! 那边承载 provider 业务数据,本文件承载运行时偏好,故单独成文,互不干扰。
|
||||
|
||||
import { reactive, ref, watch, type Ref } from 'vue'
|
||||
import { settingsApi } from '../api/settings'
|
||||
|
||||
@@ -3,7 +3,6 @@ export { useProjectStore } from './project'
|
||||
export { useKnowledgeStore } from './knowledge'
|
||||
|
||||
|
||||
export { useSettingsStore } from './settings'
|
||||
export type { AIProvider, Connection, GeneralSettings } from './settings'
|
||||
|
||||
export { useAppSettingsStore } from './appSettings'
|
||||
|
||||
export { useAiStore } from './ai'
|
||||
|
||||
@@ -19,6 +19,8 @@ const state = reactive({
|
||||
title: string
|
||||
description: string
|
||||
options: string[]
|
||||
/** F-260615-01: single(缺省)/multiple */
|
||||
select_type?: 'single' | 'multiple'
|
||||
} | null,
|
||||
loading: false,
|
||||
error: null as string | null,
|
||||
@@ -244,16 +246,31 @@ function createStore() {
|
||||
state.liveEvents = []
|
||||
}
|
||||
|
||||
async function approveHumanApproval(decision: string, comment?: string) {
|
||||
/**
|
||||
* 发送审批响应。
|
||||
* - 单选(select_type=single 或缺省):传 decision 单值,decisions 留空;
|
||||
* - 多选(select_type=multiple):传 decisions 数组。
|
||||
* F-260615-01: 新增 decisions/select_type 透传,与后端 IPC 签名对齐。
|
||||
*/
|
||||
async function approveHumanApproval(
|
||||
decision: string,
|
||||
comment?: string,
|
||||
decisions?: string[],
|
||||
) {
|
||||
if (!state.pendingApproval) return
|
||||
|
||||
try {
|
||||
// 调用 IPC 命令发送审批响应
|
||||
// R-PD-5: 透传 options 闭环后端校验(options 非空时 decision 必须 ∈ options,否则 IPC 报错)。
|
||||
// options 来自已收到的 HumanApprovalRequest 事件 payload(IPC 无法访问节点 config)。
|
||||
await invoke('approve_human_approval', {
|
||||
execution_id: state.pendingApproval.execution_id,
|
||||
node_id: state.pendingApproval.node_id,
|
||||
decision,
|
||||
comment
|
||||
comment,
|
||||
options: state.pendingApproval.options ?? [],
|
||||
decisions: decisions ?? [],
|
||||
selectType: state.pendingApproval.select_type ?? 'single', // Tauri camelCase→snake_case 自动转 select_type
|
||||
})
|
||||
|
||||
// 清除待审批状态
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import { reactive, computed } from 'vue'
|
||||
|
||||
export interface AIProvider {
|
||||
id: number
|
||||
name: string
|
||||
apiKey: string
|
||||
baseUrl: string
|
||||
models: string[]
|
||||
isDefault: boolean
|
||||
}
|
||||
|
||||
export interface Connection {
|
||||
id: number
|
||||
name: string
|
||||
type: 'mysql' | 'ssh' | 'redis' | 'mongo'
|
||||
typeLabel: string
|
||||
icon: string
|
||||
host: string
|
||||
port: number
|
||||
user: string
|
||||
connected: boolean
|
||||
}
|
||||
|
||||
export interface GeneralSettings {
|
||||
theme: 'dark' | 'light' | 'system'
|
||||
language: string
|
||||
dataDir: string
|
||||
autoExecute: boolean
|
||||
logLevel: 'error' | 'warn' | 'info' | 'debug'
|
||||
}
|
||||
|
||||
const state = reactive({
|
||||
aiProviders: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Anthropic Claude',
|
||||
apiKey: 'sk-ant-••••••••••••••••7xKm',
|
||||
baseUrl: 'https://api.anthropic.com',
|
||||
models: ['claude-sonnet-4-20250514', 'claude-opus-4-20250514'],
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Zhipu GLM',
|
||||
apiKey: '••••••••••••••••a3f2',
|
||||
baseUrl: 'https://open.bigmodel.cn/api/paas',
|
||||
models: ['glm-4-plus', 'glm-4-flash'],
|
||||
isDefault: false,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'DeepSeek',
|
||||
apiKey: '••••••••••••••••9b8c',
|
||||
baseUrl: 'https://api.deepseek.com',
|
||||
models: ['deepseek-chat', 'deepseek-coder'],
|
||||
isDefault: false,
|
||||
},
|
||||
] as AIProvider[],
|
||||
|
||||
connections: [
|
||||
{ id: 1, name: 'flux_dev', type: 'mysql' as const, typeLabel: 'MySQL', icon: '🗄️', host: '39.99.243.191', port: 3306, user: 'root', connected: true },
|
||||
{ id: 2, name: 'flux_dev', type: 'ssh' as const, typeLabel: 'SSH', icon: '🔐', host: '39.99.243.191', port: 22, user: 'root', connected: true },
|
||||
{ id: 3, name: 'flux_redis', type: 'redis' as const, typeLabel: 'Redis', icon: '⚡', host: '127.0.0.1', port: 6379, user: '', connected: true },
|
||||
{ id: 4, name: 'suke_dev', type: 'mongo' as const, typeLabel: 'MongoDB', icon: '🍃', host: '127.0.0.1', port: 27017, user: 'admin', connected: false },
|
||||
{ id: 5, name: 'rds_read', type: 'mysql' as const, typeLabel: 'MySQL', icon: '🗄️', host: 'rm-8vbrd2wil14hclop3vm.mysql.zhangbei.rds.aliyuncs.com', port: 3306, user: 'u_read', connected: true },
|
||||
] as Connection[],
|
||||
|
||||
general: {
|
||||
theme: 'dark' as const,
|
||||
language: 'zh-CN',
|
||||
dataDir: 'E:/wk-lab/devflow/data',
|
||||
autoExecute: false,
|
||||
logLevel: 'info' as const,
|
||||
} as GeneralSettings,
|
||||
})
|
||||
|
||||
export function useSettingsStore() {
|
||||
const aiProviders = computed(() => state.aiProviders)
|
||||
const connections = computed(() => state.connections)
|
||||
const general = computed(() => state.general)
|
||||
|
||||
const defaultProvider = computed(() =>
|
||||
state.aiProviders.find(p => p.isDefault)
|
||||
)
|
||||
|
||||
const connectionStats = computed(() => ({
|
||||
total: state.connections.length,
|
||||
connected: state.connections.filter(c => c.connected).length,
|
||||
}))
|
||||
|
||||
return {
|
||||
aiProviders,
|
||||
connections,
|
||||
general,
|
||||
defaultProvider,
|
||||
connectionStats,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user