新增: Conditions 启用 + 工作流 DAG 展示 + 决策记录
- 开启 conditions-eval feature flag(df-workflow 默认启用) - 新增 WorkflowDagDisplay.vue 展示工作流节点/边/条件 - TaskDetail 工作流推进后自动获取并显示 DAG 结构 - 记录 2026-06-27 讨论决策到待决策.md - 记录 Inbox 设计想法到 todo.md
This commit is contained in:
@@ -11,7 +11,7 @@ edition = "2021"
|
||||
# 开启时:节点收集 inputs 前,对带 condition 的入边以 source output 为 context 求值;
|
||||
# 求值 false 则该前驱 input 不收集,且若该节点所有入边(含 condition)均被过滤,
|
||||
# 则跳过该节点执行(保持 Pending),实现条件路由。
|
||||
default = []
|
||||
default = ["conditions-eval"]
|
||||
conditions-eval = []
|
||||
|
||||
[dependencies]
|
||||
|
||||
12
docs/todo.md
12
docs/todo.md
@@ -15,7 +15,13 @@
|
||||
- [ ] **L2 Enrichment 可见**: @[项目] 发送前展开 enrichment 摘要
|
||||
- [ ] **L3 完整上下文**: 可展开面板查看 system_prompt / augmentations
|
||||
|
||||
---
|
||||
### 💡 2026-06-27 统一 Inbox 设计(讨论产出)
|
||||
|
||||
> 思路:所有需要用户注意的事项(审批/知识待审/灵感确认/其他通知)集中到一个统一入口,
|
||||
> 不阻塞对话。
|
||||
>
|
||||
> 审批留在对话内(需要即时上下文),知识/灵感/通知放 Inbox。
|
||||
> 后续做详细设计方案。
|
||||
|
||||
## 🎯 统一规划(2026-06-26 全景)
|
||||
|
||||
@@ -65,10 +71,10 @@ graph TD
|
||||
| **G1** 目标钉扎 | 🔨 代码✅`8ce18cb` 待实测 | G1/G2/G4 落地 | — |
|
||||
| **父①** 小bug攒批 | ✅ 完成 | ①.2 白名单✅(settings.rs) / ①.3 priority✅(idea.rs) / ①.1 BUG层1❌过时(F-260619-03 方案①取代,层2待决策) / ①.4 雷达图→归父⑤ | — |
|
||||
| **父②** 知识图谱Phase1 | ✅ Phase1完成 | ②.1 V29迁移✅ / ②.2 TaskRecord+TaskLinkRepo✅ / ②.3 IPC(create_task扩展+task_link CRUD+move_queue+get_tree)✅ / ②.4 父聚合✅(set_status_for_aggregation绕status收口) / ②.5 AI工具6✅(基线38) | G1(弱) |
|
||||
| **父③** AI对话体验 | 🔨 ③.1✅ / ③.2待办 | ③.1 B-260619-04 ToolCard✅(跨轮收起已有+标题增强) / ③.2 REFACTOR-260619-04 审批状态机拆分 | — |
|
||||
| **父③** AI对话体验 | 🔨 ③.1✅ / ③.2待办 | ③.1 B-260619-04 ToolCard✅(跨轮收起已有+标题增强) / ③.2 REFACTOR-260619-04 审批状态机拆分 / 对话透明化 L1🟡待定 | — |
|
||||
| **父④** F-09 per-conv | ✅ 前端per-conv | ④.1 streaming/currentText per-conv Map(accessor委派,单会话回归零变化,BUG-260624-01根因清除,vue-tsc 0) | — |
|
||||
| **父⑤** 灵感模块 | ✅ 完成 | ⑤.1 软删除✅ / ①.4 雷达图✅ / ⑤.2 #05✅/#06拆const✅/#09/#10表单(逗号tags)✅ / #07 DEC-02保留purge(不改) / 附:priority_from_i32跨层映射修复(对齐前端0=critical) | #07→②.1 |
|
||||
| **父⑥** Phase2-5 | 🔨 Phase4 注入进行中 | ⑥.1事件流✅(V30) / ⑥.2基础设施✅(V31) / ⑥.3注入✅(resolver 增强+extra 渲染) / ⑥.4前端📋
|
||||
| **父⑥** Phase2-5 | 🔨 Phase4 注入进行中 | ⑥.1事件流✅(V30) / ⑥.2基础设施✅(V31) / ⑥.3注入✅(resolver 增强+extra 渲染) / ⑥.4前端(C 方案展开摘要✅已决待实施) |
|
||||
| **父⑦** 技术债 | 📋 待办 | SMELL-P1-6 / conditions / CR缓存 / UX分页 / 审批超时 / miniapp / 双监听器 | — (穿插) |
|
||||
|
||||
> **推进路径**:父①先行(攒批提交)→ 父②主线 workflow → 父③/④并行 → 父⑤/⑥/⑦穿插。每父任务一个 workflow 批,子任务相关文件批量读改减少交互。**状态更新约定**:子项完成→父状态 🔨;全子完成→父 ✅;每批提交后同步此表。
|
||||
|
||||
49
docs/待决策.md
49
docs/待决策.md
@@ -244,4 +244,51 @@
|
||||
|
||||
- [2026-06.md](./07-项目管理/待决策归档/2026-06.md) — 2026-06 已决策/已实施/已排期/已解决历史(③类产品取舍 12 项 / ④类设计方向 6 项 / 重投入排期 8 项 / ④类续 5 项 / C类已解决 2 项)
|
||||
|
||||
> 新月份拍板项累积时,新建 `YYYY-MM.md` 承载。
|
||||
### 2026-06-27 讨论已决
|
||||
|
||||
#### DEC-260627-01 F-09 B 路线(多会话真并发)
|
||||
- **决策**:✅ 需要实施
|
||||
- **记录**:用户确认需要推进多会话并发架构
|
||||
- **状态**:✅ 已决
|
||||
|
||||
#### DEC-260627-02 Conditions 条件引擎
|
||||
- **决策**:✅ 需要接入使用(开启 feature flag + 可视化)
|
||||
- **记录**:用户确认需要启用条件引擎并接入工作流执行器
|
||||
- **状态**:✅ 已决
|
||||
|
||||
#### DEC-260627-03 @项目展开摘要(⑥.4 Phase4 前端)
|
||||
- **决策**:✅ C 方案展开摘要
|
||||
- **记录**:@[项目] 发送前可展开查看 enrichment 内容
|
||||
- **状态**:✅ 已决
|
||||
|
||||
#### DEC-260627-04 历史消息渲染
|
||||
- **决策**:✅ C 暂缓
|
||||
- **记录**:长对话渲染卡顿暂不处理
|
||||
- **状态**:✅ 已决
|
||||
|
||||
### 2026-06-27 讨论待定
|
||||
|
||||
#### 对话透明化 L1(目标钉扎可见)
|
||||
- **背景**:G1 目标钉扎已落地,但用户完全看不见自己设了哪些目标
|
||||
- **选项**:
|
||||
- A: 不做
|
||||
- B: 对话顶部显示目标列表
|
||||
- C: 显示 + 可删除过时目标 + 持久化
|
||||
- **决策**:✅ C 方案。对话顶部 🎯 显示目标列表(✅/🔄/⏳ 状态),可清除/编辑,持久化到 ai_conversations 表
|
||||
- **配套**:模型选择器折叠缩小腾空间
|
||||
- **状态**:✅ 已决
|
||||
|
||||
#### 灵感来源采集
|
||||
- **背景**:灵感捕捉时没有自动记录来源
|
||||
- **选项**:
|
||||
- A: 对话自动采集
|
||||
- B: 手动录入增强
|
||||
- C: 暂缓
|
||||
- **决策**:✅ C 暂缓。与知识库抽取共用一次 AI 调用,但灵感需用户确认后才创建(非自动写入)。需前端通知机制,当前不做
|
||||
- **状态**:✅ 已决 — 暂缓
|
||||
|
||||
---
|
||||
|
||||
## 已决归档
|
||||
|
||||
按月归档(随时间增长追加月份文件,防主文件膨胀):
|
||||
|
||||
131
src/components/workflow/WorkflowDagDisplay.vue
Normal file
131
src/components/workflow/WorkflowDagDisplay.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div v-if="dag" class="wf-dag">
|
||||
<h4 class="wf-dag-title">{{ $t('taskDetail.workflowDagTitle') || '工作流结构' }}</h4>
|
||||
|
||||
<!-- 节点列表 -->
|
||||
<div class="wf-dag-nodes">
|
||||
<div v-for="node in dag.nodes" :key="node.id" class="wf-dag-node">
|
||||
<span class="wf-dag-node-icon">⬡</span>
|
||||
<span class="wf-dag-node-name">{{ node.id }}</span>
|
||||
<span class="wf-dag-node-type">{{ nodeTypeLabel[node.node_type] || node.node_type }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 边列表(带条件) -->
|
||||
<div class="wf-dag-edges">
|
||||
<div v-for="(edge, i) in dag.edges" :key="i" class="wf-dag-edge">
|
||||
<span class="wf-dag-edge-arrow">{{ edge.source }} → {{ edge.target }}</span>
|
||||
<span v-if="edge.condition" class="wf-dag-edge-cond" :title="edge.condition">
|
||||
{{ edge.condition }}
|
||||
</span>
|
||||
<span v-else class="wf-dag-edge-cond wf-dag-edge-cond--uncond">
|
||||
无条件
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="wf-dag wf-dag--empty">
|
||||
{{ $t('taskDetail.workflowDagEmpty') || '暂无工作流数据' }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface DagNode {
|
||||
id: string
|
||||
node_type: string
|
||||
config?: Record<string, unknown>
|
||||
}
|
||||
|
||||
interface EdgeDef {
|
||||
source: string
|
||||
target: string
|
||||
condition?: string | null
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
dagJson: string
|
||||
}>()
|
||||
|
||||
const dag = computed<{ nodes: DagNode[]; edges: EdgeDef[] } | null>(() => {
|
||||
try {
|
||||
return JSON.parse(props.dagJson)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
const nodeTypeLabel: Record<string, string> = {
|
||||
'AiNode': 'AI',
|
||||
'HumanNode': '人工审批',
|
||||
'ScriptNode': '脚本',
|
||||
'AiSelfReviewNode': 'AI 自审',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wf-dag {
|
||||
margin-top: 8px;
|
||||
padding: 8px 12px;
|
||||
background: var(--df-bg-card, #fafafa);
|
||||
border-radius: var(--df-radius-sm, 4px);
|
||||
font-size: 12px;
|
||||
}
|
||||
.wf-dag--empty {
|
||||
color: var(--df-text-dim, #888);
|
||||
}
|
||||
.wf-dag-title {
|
||||
margin: 0 0 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.wf-dag-nodes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.wf-dag-node {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 3px 8px;
|
||||
background: var(--df-bg, #fff);
|
||||
border: 0.5px solid var(--df-border, #ddd);
|
||||
border-radius: var(--df-radius-sm, 4px);
|
||||
}
|
||||
.wf-dag-node-icon { color: var(--df-text-dim, #888); }
|
||||
.wf-dag-node-name { font-weight: 500; }
|
||||
.wf-dag-node-type {
|
||||
font-size: 10px;
|
||||
color: var(--df-text-dim, #888);
|
||||
background: var(--df-bg-card, #f0f0f0);
|
||||
padding: 0 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.wf-dag-edges {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
}
|
||||
.wf-dag-edge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
.wf-dag-edge-arrow { color: var(--df-text-primary); }
|
||||
.wf-dag-edge-cond {
|
||||
font-family: var(--df-font-mono);
|
||||
font-size: 11px;
|
||||
padding: 1px 6px;
|
||||
background: rgba(74, 144, 226, 0.08);
|
||||
border-radius: 3px;
|
||||
color: var(--df-info, #4a90e2);
|
||||
}
|
||||
.wf-dag-edge-cond--uncond {
|
||||
background: transparent;
|
||||
color: var(--df-text-dim, #aaa);
|
||||
}
|
||||
</style>
|
||||
@@ -168,6 +168,7 @@ import {
|
||||
} from '../constants/project'
|
||||
import type { TaskRecord, ProjectRecord, IdeaRecord, DfDataChangedPayload, WorkflowEventPayload } from '@/api/types'
|
||||
import TaskOutputCard from '@/components/task/TaskOutputCard.vue'
|
||||
import WorkflowDagDisplay from '@/components/workflow/WorkflowDagDisplay.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
@@ -198,6 +199,15 @@ let _wfResultTimer: ReturnType<typeof setTimeout> | null = null
|
||||
const wfProgressHint = computed(() => wfResult.value !== null)
|
||||
const wfCompletedHint = computed(() => wfResult.value === 'completed')
|
||||
const wfFailedHint = computed(() => wfResult.value === 'failed')
|
||||
|
||||
// 工作流 DAG 结构展示
|
||||
const wfDagJson = ref('')
|
||||
async function refreshWorkflowDag(execId: string) {
|
||||
try {
|
||||
const record = await workflowApi.getExecution(execId)
|
||||
if (record?.dag_json) wfDagJson.value = record.dag_json
|
||||
} catch { /* 静默 */ }
|
||||
}
|
||||
const wfDoneTotal = computed(() => wfTotalNodes.value)
|
||||
|
||||
const taskId = computed(() => route.params.id as string)
|
||||
@@ -337,6 +347,7 @@ async function handleWorkflowAdvance(target: string) {
|
||||
)
|
||||
wfExecId.value = execId
|
||||
errorMsg.value = ''
|
||||
await refreshWorkflowDag(execId)
|
||||
} catch (e: any) {
|
||||
wfAdvancing.value = false
|
||||
wfExecId.value = null
|
||||
|
||||
Reference in New Issue
Block a user