新增: Conditions 启用 + 工作流 DAG 展示 + 决策记录

- 开启 conditions-eval feature flag(df-workflow 默认启用)
- 新增 WorkflowDagDisplay.vue 展示工作流节点/边/条件
- TaskDetail 工作流推进后自动获取并显示 DAG 结构
- 记录 2026-06-27 讨论决策到待决策.md
- 记录 Inbox 设计想法到 todo.md
This commit is contained in:
2026-06-28 01:17:36 +08:00
parent 22362a77b8
commit e4ceb0015b
5 changed files with 200 additions and 5 deletions

View File

@@ -11,7 +11,7 @@ edition = "2021"
# 开启时:节点收集 inputs 前,对带 condition 的入边以 source output 为 context 求值; # 开启时:节点收集 inputs 前,对带 condition 的入边以 source output 为 context 求值;
# 求值 false 则该前驱 input 不收集,且若该节点所有入边(含 condition)均被过滤, # 求值 false 则该前驱 input 不收集,且若该节点所有入边(含 condition)均被过滤,
# 则跳过该节点执行(保持 Pending),实现条件路由。 # 则跳过该节点执行(保持 Pending),实现条件路由。
default = [] default = ["conditions-eval"]
conditions-eval = [] conditions-eval = []
[dependencies] [dependencies]

View File

@@ -15,7 +15,13 @@
- [ ] **L2 Enrichment 可见**: @[项目] 发送前展开 enrichment 摘要 - [ ] **L2 Enrichment 可见**: @[项目] 发送前展开 enrichment 摘要
- [ ] **L3 完整上下文**: 可展开面板查看 system_prompt / augmentations - [ ] **L3 完整上下文**: 可展开面板查看 system_prompt / augmentations
--- ### 💡 2026-06-27 统一 Inbox 设计(讨论产出)
> 思路:所有需要用户注意的事项(审批/知识待审/灵感确认/其他通知)集中到一个统一入口,
> 不阻塞对话。
>
> 审批留在对话内(需要即时上下文),知识/灵感/通知放 Inbox。
> 后续做详细设计方案。
## 🎯 统一规划2026-06-26 全景) ## 🎯 统一规划2026-06-26 全景)
@@ -65,10 +71,10 @@ graph TD
| **G1** 目标钉扎 | 🔨 代码✅`8ce18cb` 待实测 | G1/G2/G4 落地 | — | | **G1** 目标钉扎 | 🔨 代码✅`8ce18cb` 待实测 | G1/G2/G4 落地 | — |
| **父①** 小bug攒批 | ✅ 完成 | ①.2 白名单✅(settings.rs) / ①.3 priority✅(idea.rs) / ①.1 BUG层1❌过时(F-260619-03 方案①取代,层2待决策) / ①.4 雷达图→归父⑤ | — | | **父①** 小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(弱) | | **父②** 知识图谱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) | — | | **父④** 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 | | **父⑤** 灵感模块 | ✅ 完成 | ⑤.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 / 双监听器 | — (穿插) | | **父⑦** 技术债 | 📋 待办 | SMELL-P1-6 / conditions / CR缓存 / UX分页 / 审批超时 / miniapp / 双监听器 | — (穿插) |
> **推进路径**:父①先行(攒批提交)→ 父②主线 workflow → 父③/④并行 → 父⑤/⑥/⑦穿插。每父任务一个 workflow 批,子任务相关文件批量读改减少交互。**状态更新约定**:子项完成→父状态 🔨;全子完成→父 ✅;每批提交后同步此表。 > **推进路径**:父①先行(攒批提交)→ 父②主线 workflow → 父③/④并行 → 父⑤/⑥/⑦穿插。每父任务一个 workflow 批,子任务相关文件批量读改减少交互。**状态更新约定**:子项完成→父状态 🔨;全子完成→父 ✅;每批提交后同步此表。

View File

@@ -244,4 +244,51 @@
- [2026-06.md](./07-项目管理/待决策归档/2026-06.md) — 2026-06 已决策/已实施/已排期/已解决历史(③类产品取舍 12 项 / ④类设计方向 6 项 / 重投入排期 8 项 / ④类续 5 项 / C类已解决 2 项) - [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 调用,但灵感需用户确认后才创建(非自动写入)。需前端通知机制,当前不做
- **状态**:✅ 已决 — 暂缓
---
## 已决归档
按月归档(随时间增长追加月份文件,防主文件膨胀):

View 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>

View File

@@ -168,6 +168,7 @@ import {
} from '../constants/project' } from '../constants/project'
import type { TaskRecord, ProjectRecord, IdeaRecord, DfDataChangedPayload, WorkflowEventPayload } from '@/api/types' import type { TaskRecord, ProjectRecord, IdeaRecord, DfDataChangedPayload, WorkflowEventPayload } from '@/api/types'
import TaskOutputCard from '@/components/task/TaskOutputCard.vue' import TaskOutputCard from '@/components/task/TaskOutputCard.vue'
import WorkflowDagDisplay from '@/components/workflow/WorkflowDagDisplay.vue'
const { t } = useI18n() const { t } = useI18n()
const route = useRoute() const route = useRoute()
@@ -198,6 +199,15 @@ let _wfResultTimer: ReturnType<typeof setTimeout> | null = null
const wfProgressHint = computed(() => wfResult.value !== null) const wfProgressHint = computed(() => wfResult.value !== null)
const wfCompletedHint = computed(() => wfResult.value === 'completed') const wfCompletedHint = computed(() => wfResult.value === 'completed')
const wfFailedHint = computed(() => wfResult.value === 'failed') 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 wfDoneTotal = computed(() => wfTotalNodes.value)
const taskId = computed(() => route.params.id as string) const taskId = computed(() => route.params.id as string)
@@ -337,6 +347,7 @@ async function handleWorkflowAdvance(target: string) {
) )
wfExecId.value = execId wfExecId.value = execId
errorMsg.value = '' errorMsg.value = ''
await refreshWorkflowDag(execId)
} catch (e: any) { } catch (e: any) {
wfAdvancing.value = false wfAdvancing.value = false
wfExecId.value = null wfExecId.value = null