feat:任务执行结果性能优化
This commit is contained in:
@@ -84,6 +84,9 @@ function resetTextareaHeight() {
|
||||
}
|
||||
|
||||
async function handleSearch() {
|
||||
// 用于标记大纲是否成功加载
|
||||
let outlineLoaded = false
|
||||
|
||||
try {
|
||||
triggerOnFocus.value = false
|
||||
if (!searchValue.value) {
|
||||
@@ -93,17 +96,102 @@ async function handleSearch() {
|
||||
emit('search-start')
|
||||
agentsStore.resetAgent()
|
||||
agentsStore.setAgentRawPlan({ loading: true })
|
||||
const data = await api.generateBasePlan({
|
||||
|
||||
// 获取大纲
|
||||
const outlineData = await api.generateBasePlan({
|
||||
goal: searchValue.value,
|
||||
inputs: []
|
||||
})
|
||||
data['Collaboration Process'] = changeBriefs(data['Collaboration Process'])
|
||||
agentsStore.setAgentRawPlan({ data })
|
||||
|
||||
// 处理简报数据格式
|
||||
outlineData['Collaboration Process'] = changeBriefs(outlineData['Collaboration Process'])
|
||||
|
||||
// 立即显示大纲
|
||||
agentsStore.setAgentRawPlan({ data: outlineData, loading: false })
|
||||
outlineLoaded = true
|
||||
emit('search', searchValue.value)
|
||||
|
||||
// 并行填充所有步骤的详情
|
||||
const steps = outlineData['Collaboration Process'] || []
|
||||
|
||||
// 带重试的填充函数
|
||||
const fillStepWithRetry = async (step: any, retryCount = 0): Promise<void> => {
|
||||
const maxRetries = 2 // 最多重试2次
|
||||
|
||||
try {
|
||||
if (!step.StepName) {
|
||||
console.warn('步骤缺少 StepName,跳过填充详情')
|
||||
return
|
||||
}
|
||||
|
||||
// 使用现有的 fillStepTask API 填充每个步骤的详情
|
||||
const detailedStep = await api.fillStepTask({
|
||||
goal: searchValue.value,
|
||||
stepTask: {
|
||||
StepName: step.StepName,
|
||||
TaskContent: step.TaskContent,
|
||||
InputObject_List: step.InputObject_List,
|
||||
OutputObject: step.OutputObject
|
||||
}
|
||||
})
|
||||
|
||||
// 更新该步骤的详情到 store
|
||||
updateStepDetail(step.StepName, detailedStep)
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`填充步骤 ${step.StepName} 详情失败 (尝试 ${retryCount + 1}/${maxRetries + 1}):`,
|
||||
error
|
||||
)
|
||||
|
||||
// 如果未达到最大重试次数,延迟后重试
|
||||
if (retryCount < maxRetries) {
|
||||
console.log(`正在重试步骤 ${step.StepName}...`)
|
||||
// 延迟1秒后重试,避免立即重试导致同样的问题
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
return fillStepWithRetry(step, retryCount + 1)
|
||||
} else {
|
||||
console.error(`步骤 ${step.StepName} 在 ${maxRetries + 1} 次尝试后仍然失败`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // 为每个步骤并行填充详情(选人+过程)
|
||||
// const fillPromises = steps.map(step => fillStepWithRetry(step))
|
||||
// // 等待所有步骤填充完成(包括重试)
|
||||
// await Promise.all(fillPromises)
|
||||
|
||||
// 串行填充所有步骤的详情(避免字段混乱)
|
||||
for (const step of steps) {
|
||||
await fillStepWithRetry(step)
|
||||
}
|
||||
} finally {
|
||||
triggerOnFocus.value = true
|
||||
agentsStore.setAgentRawPlan({ loading: false })
|
||||
// 如果大纲加载失败,确保关闭loading
|
||||
if (!outlineLoaded) {
|
||||
agentsStore.setAgentRawPlan({ loading: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:更新单个步骤的详情
|
||||
function updateStepDetail(stepId: string, detailedStep: any) {
|
||||
const planData = agentsStore.agentRawPlan.data
|
||||
if (!planData) return
|
||||
|
||||
const collaborationProcess = planData['Collaboration Process']
|
||||
if (!collaborationProcess) return
|
||||
|
||||
const index = collaborationProcess.findIndex((s: any) => s.StepName === stepId)
|
||||
if (index !== -1 && collaborationProcess[index]) {
|
||||
// 保持响应式更新 - 使用 Vue 的响应式系统
|
||||
Object.assign(collaborationProcess[index], {
|
||||
AgentSelection: detailedStep.AgentSelection || [],
|
||||
TaskProcess: detailedStep.TaskProcess || [],
|
||||
Collaboration_Brief_frontEnd: detailedStep.Collaboration_Brief_frontEnd || {
|
||||
template: '',
|
||||
data: {}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,8 +303,6 @@ onMounted(() => {
|
||||
:deep(.el-autocomplete .el-textarea .el-textarea__inner) {
|
||||
overflow-y: auto !important;
|
||||
min-height: 56px !important;
|
||||
// overflow-y: hidden;
|
||||
// background-color: black;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { getActionTypeDisplay, getAgentMapIcon } from '@/layout/components/confi
|
||||
import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/utils.ts'
|
||||
import variables from '@/styles/variables.module.scss'
|
||||
import { type IRawStepTask, useAgentsStore } from '@/stores'
|
||||
import api from '@/api'
|
||||
import api, { type StreamingEvent } from '@/api'
|
||||
import ProcessCard from '../TaskProcess/ProcessCard.vue'
|
||||
import ExecutePlan from './ExecutePlan.vue'
|
||||
|
||||
@@ -182,14 +182,148 @@ function createInternalLine(id?: string) {
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const executionProgress = ref({
|
||||
currentStep: 0,
|
||||
totalSteps: 0,
|
||||
currentAction: 0,
|
||||
totalActions: 0,
|
||||
currentStepName: '',
|
||||
message: '正在执行...'
|
||||
})
|
||||
|
||||
async function handleRun() {
|
||||
// 清空之前的执行结果
|
||||
agentsStore.setExecutePlan([])
|
||||
const tempResults: any[] = []
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const d = await api.executePlan(agentsStore.agentRawPlan.data!)
|
||||
agentsStore.setExecutePlan(d)
|
||||
|
||||
// 使用优化版流式API(阶段1+2:步骤级流式 + 动作级智能并行)
|
||||
api.executePlanOptimized(
|
||||
agentsStore.agentRawPlan.data!,
|
||||
// onMessage: 处理每个事件
|
||||
(event: StreamingEvent) => {
|
||||
switch (event.type) {
|
||||
case 'step_start':
|
||||
// 步骤开始
|
||||
executionProgress.value = {
|
||||
currentStep: event.step_index + 1,
|
||||
totalSteps: event.total_steps,
|
||||
currentAction: 0,
|
||||
totalActions: 0,
|
||||
currentStepName: event.step_name,
|
||||
message: `正在执行步骤 ${event.step_index + 1}/${event.total_steps}: ${
|
||||
event.step_name
|
||||
}`
|
||||
}
|
||||
console.log(
|
||||
`📋 步骤 ${event.step_index + 1}/${event.total_steps} 开始: ${event.step_name}`
|
||||
)
|
||||
break
|
||||
|
||||
case 'action_complete':
|
||||
// 动作完成
|
||||
const parallelInfo = event.batch_info?.is_parallel
|
||||
? ` [批次 ${event.batch_info!.batch_index + 1}, 并行 ${
|
||||
event.batch_info!.batch_size
|
||||
} 个]`
|
||||
: ''
|
||||
|
||||
executionProgress.value = {
|
||||
...executionProgress.value,
|
||||
currentAction: event.completed_actions,
|
||||
totalActions: event.total_actions,
|
||||
message: `步骤 ${event.step_index + 1}/${executionProgress.value.totalSteps}: ${
|
||||
event.step_name
|
||||
} - 动作 ${event.completed_actions}/${event.total_actions} 完成${parallelInfo}`
|
||||
}
|
||||
|
||||
console.log(
|
||||
`✅ 动作 ${event.completed_actions}/${event.total_actions} 完成${parallelInfo}: ${event.action_result.ActionType} by ${event.action_result.AgentName}`
|
||||
)
|
||||
|
||||
// 实时更新到 store(找到对应的步骤并添加 ActionHistory)
|
||||
const step = collaborationProcess.value.find(s => s.StepName === event.step_name)
|
||||
if (step) {
|
||||
const stepLogNode = tempResults.find(
|
||||
r => r.NodeId === event.step_name && r.LogNodeType === 'step'
|
||||
)
|
||||
if (!stepLogNode) {
|
||||
// 创建步骤日志节点
|
||||
const newStepLog = {
|
||||
LogNodeType: 'step',
|
||||
NodeId: event.step_name,
|
||||
InputName_List: step.InputObject_List || [],
|
||||
OutputName: step.OutputObject || '',
|
||||
chatLog: [],
|
||||
inputObject_Record: [],
|
||||
ActionHistory: [event.action_result]
|
||||
}
|
||||
tempResults.push(newStepLog)
|
||||
} else {
|
||||
// 追加动作结果
|
||||
stepLogNode.ActionHistory.push(event.action_result)
|
||||
}
|
||||
|
||||
// 更新 store
|
||||
agentsStore.setExecutePlan([...tempResults])
|
||||
}
|
||||
break
|
||||
|
||||
case 'step_complete':
|
||||
// 步骤完成
|
||||
console.log(`🎯 步骤完成: ${event.step_name}`)
|
||||
|
||||
// 更新步骤日志节点
|
||||
const existingStepLog = tempResults.find(
|
||||
r => r.NodeId === event.step_name && r.LogNodeType === 'step'
|
||||
)
|
||||
if (existingStepLog) {
|
||||
existingStepLog.ActionHistory = event.step_log_node.ActionHistory
|
||||
} else {
|
||||
tempResults.push(event.step_log_node)
|
||||
}
|
||||
|
||||
// 添加对象日志节点
|
||||
tempResults.push(event.object_log_node)
|
||||
|
||||
// 更新 store
|
||||
agentsStore.setExecutePlan([...tempResults])
|
||||
break
|
||||
|
||||
case 'execution_complete':
|
||||
// 执行完成
|
||||
executionProgress.value.message = `执行完成!共 ${event.total_steps} 个步骤`
|
||||
console.log(`🎉 执行完成,共 ${event.total_steps} 个步骤`)
|
||||
|
||||
// 确保所有结果都保存到 store
|
||||
agentsStore.setExecutePlan([...tempResults])
|
||||
break
|
||||
|
||||
case 'error':
|
||||
// 错误
|
||||
console.error('❌ 执行错误:', event.message)
|
||||
executionProgress.value.message = `执行错误: ${event.message}`
|
||||
break
|
||||
}
|
||||
},
|
||||
// onError: 处理错误
|
||||
(error: Error) => {
|
||||
console.error('❌ 流式执行错误:', error)
|
||||
executionProgress.value.message = `执行失败: ${error.message}`
|
||||
},
|
||||
// onComplete: 执行完成
|
||||
() => {
|
||||
console.log('✅ 流式执行完成')
|
||||
loading.value = false
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('执行失败:', error)
|
||||
executionProgress.value.message = '执行失败,请重试'
|
||||
} finally {
|
||||
loading.value = false
|
||||
// loading 会在 onComplete 中设置为 false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,6 +515,15 @@ defineExpose({
|
||||
id="task-results"
|
||||
:class="{ 'is-running': agentsStore.executePlan.length > 0 }"
|
||||
>
|
||||
<!-- 执行进度提示 -->
|
||||
<div v-if="loading" class="execution-progress-hint">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<span>{{ executionProgress.message }}</span>
|
||||
<span v-if="executionProgress.totalSteps > 0" class="progress">
|
||||
{{ executionProgress.currentStep }}/{{ executionProgress.totalSteps }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 标题与执行按钮 -->
|
||||
<div class="text-[18px] font-bold mb-[7px] flex justify-between items-center px-[20px]">
|
||||
<span class="text-[var(--color-text-title-header)]">执行结果</span>
|
||||
@@ -606,6 +749,48 @@ defineExpose({
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// 执行进度提示样式
|
||||
.execution-progress-hint {
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
right: 20px;
|
||||
background: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
z-index: 1000;
|
||||
animation: slideInRight 0.3s ease-out;
|
||||
max-width: 400px;
|
||||
|
||||
.message {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.progress {
|
||||
color: var(--el-color-primary);
|
||||
font-weight: bold;
|
||||
margin-left: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
#task-results.is-running {
|
||||
--color-bg-detail-list: var(--color-bg-detail-list-run); // 直接指向 100 % 版本
|
||||
}
|
||||
|
||||
@@ -25,20 +25,20 @@ defineEmits<{
|
||||
<!-- 背景那一根线 -->
|
||||
<div class="h-full bg-[var(--color-bg-flow)] w-[5px]">
|
||||
<!-- 顶部加号区域 -->
|
||||
<div
|
||||
<!-- <div
|
||||
v-if="!isAdding"
|
||||
v-dev-only
|
||||
class="plus-area mt-[35px] ml-[-15px] w-[34px] h-[34px] flex items-center justify-center cursor-pointer rounded-full"
|
||||
@click="$emit('start-add-output')"
|
||||
>
|
||||
<!-- 加号图标 -->
|
||||
<svg-icon
|
||||
> -->
|
||||
<!-- 加号图标 -->
|
||||
<!-- <svg-icon
|
||||
icon-class="plus"
|
||||
color="var(--color-text)"
|
||||
size="20px"
|
||||
class="plus-icon opacity-0 transition-opacity duration-200"
|
||||
/>
|
||||
</div>
|
||||
</div> -->
|
||||
<!-- 线底部的小圆球 -->
|
||||
<div
|
||||
class="absolute bottom-0 left-1/2 transform -translate-x-1/2 bg-[var(--color-bg-flow)] w-[15px] h-[15px] rounded-full"
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<template>
|
||||
<div class="plan-modification">
|
||||
<!-- 全局加载提示 -->
|
||||
<div v-if="branchFillingStatus.isFilling" class="branch-loading-hint">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<span>{{ branchFillingStatus.message }}</span>
|
||||
<span class="progress">{{ branchFillingStatus.current }}/{{ branchFillingStatus.total }}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-loading="agentsStore.agentRawPlan.loading || branchLoading"
|
||||
class="flow-wrapper"
|
||||
@@ -49,6 +56,7 @@ import { VueFlow, useVueFlow } from '@vue-flow/core'
|
||||
import type { Node, Edge } from '@vue-flow/core'
|
||||
import { useAgentsStore, useSelectionStore, type IRawStepTask } from '@/stores'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Loading } from '@element-plus/icons-vue'
|
||||
import api from '@/api'
|
||||
import '@vue-flow/core/dist/style.css'
|
||||
import '@vue-flow/core/dist/theme-default.css'
|
||||
@@ -65,6 +73,14 @@ const selectionStore = useSelectionStore()
|
||||
// Mock 数据开关
|
||||
const USE_MOCK_DATA = false
|
||||
|
||||
// 分支详情填充状态
|
||||
const branchFillingStatus = ref({
|
||||
isFilling: false,
|
||||
current: 0,
|
||||
total: 0,
|
||||
message: ''
|
||||
})
|
||||
|
||||
// 获取协作流程数据
|
||||
const collaborationProcess = computed(() => {
|
||||
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
|
||||
@@ -782,8 +798,6 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
|
||||
})
|
||||
|
||||
// 直接获取协作流程数据
|
||||
// newTasks = response?.[0] || []
|
||||
// 直接获取协作流程数据:兼容返回数组或对象的情况,优先处理二维数组返回
|
||||
if (Array.isArray(response)) {
|
||||
// 可能是二维数组
|
||||
newTasks = (response as any[])[0] || []
|
||||
@@ -793,121 +807,169 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
|
||||
} else {
|
||||
newTasks = []
|
||||
}
|
||||
// ========== 填充每个任务的 TaskProcess ==========
|
||||
for (let i = 0; i < newTasks.length; i++) {
|
||||
const task = newTasks[i]
|
||||
if (!task) continue
|
||||
|
||||
try {
|
||||
const filledTask = await api.fillStepTask({
|
||||
goal: generalGoal,
|
||||
stepTask: task
|
||||
})
|
||||
// 更新任务的 AgentSelection 和 TaskProcess
|
||||
newTasks[i]!.AgentSelection = filledTask.AgentSelection
|
||||
newTasks[i]!.TaskProcess = filledTask.TaskProcess
|
||||
newTasks[i]!.Collaboration_Brief_frontEnd = filledTask.Collaboration_Brief_frontEnd
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
// ========== 立即创建节点显示大纲 ==========
|
||||
if (newTasks.length > 0) {
|
||||
const branchBaseX = START_X + ROOT_WIDTH + NODE_GAP
|
||||
const branchBaseY = START_Y + 300
|
||||
const timestamp = Date.now()
|
||||
const taskNodeIds: string[] = []
|
||||
|
||||
// 为每个任务创建节点(只显示大纲)
|
||||
newTasks.forEach((task, index) => {
|
||||
const taskNodeId = `branch-task-${taskId}-${timestamp}-${index}`
|
||||
taskNodeIds.push(taskNodeId)
|
||||
|
||||
const taskX = branchBaseX + index * (NODE_WIDTH + NODE_GAP)
|
||||
const taskY = branchBaseY
|
||||
|
||||
const newTaskNode: Node = {
|
||||
id: taskNodeId,
|
||||
type: 'task',
|
||||
position: { x: taskX, y: taskY },
|
||||
data: {
|
||||
task,
|
||||
isEditing: false,
|
||||
editingContent: '',
|
||||
isBranchTask: true,
|
||||
branchId: String(timestamp),
|
||||
updateKey: Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
nodes.value.push(newTaskNode)
|
||||
newBranchNodes.push(newTaskNode)
|
||||
|
||||
// 创建连接边
|
||||
if (index === 0) {
|
||||
const newEdge: Edge = {
|
||||
id: `edge-${taskId}-${taskNodeId}`,
|
||||
source: taskId,
|
||||
target: taskNodeId,
|
||||
sourceHandle: 'bottom',
|
||||
targetHandle: 'left',
|
||||
type: 'smoothstep',
|
||||
animated: true,
|
||||
style: { stroke: '#409eff', strokeWidth: 2, strokeDasharray: '5,5' },
|
||||
markerEnd: {
|
||||
type: 'arrow' as any,
|
||||
color: '#43a8aa',
|
||||
width: 20,
|
||||
height: 20,
|
||||
strokeWidth: 2
|
||||
}
|
||||
}
|
||||
edges.value.push(newEdge)
|
||||
newBranchEdges.push(newEdge)
|
||||
} else {
|
||||
const prevTaskNodeId = taskNodeIds[index - 1]
|
||||
const newEdge: Edge = {
|
||||
id: `edge-${prevTaskNodeId}-${taskNodeId}`,
|
||||
source: prevTaskNodeId,
|
||||
target: taskNodeId,
|
||||
sourceHandle: 'right',
|
||||
targetHandle: 'left',
|
||||
type: 'smoothstep',
|
||||
animated: true,
|
||||
style: { stroke: '#409eff', strokeWidth: 2 },
|
||||
markerEnd: {
|
||||
type: 'arrow' as any,
|
||||
color: '#43a8aa',
|
||||
width: 20,
|
||||
height: 20,
|
||||
strokeWidth: 2
|
||||
}
|
||||
}
|
||||
edges.value.push(newEdge)
|
||||
newBranchEdges.push(newEdge)
|
||||
}
|
||||
})
|
||||
|
||||
// 保存分支到 store(此时只有大纲)
|
||||
selectionStore.addFlowBranch({
|
||||
parentNodeId: taskId,
|
||||
branchContent: branchContent,
|
||||
branchType: 'root',
|
||||
nodes: newBranchNodes,
|
||||
edges: newBranchEdges,
|
||||
tasks: JSON.parse(JSON.stringify(newTasks))
|
||||
})
|
||||
|
||||
ElMessage.success('任务大纲分支创建成功')
|
||||
|
||||
// 适应视图,让用户先看到大纲
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
fit({ padding: 100, duration: 300 })
|
||||
}, 100)
|
||||
})
|
||||
|
||||
// ========== 关闭遮罩层Loading ==========
|
||||
branchLoading.value = false
|
||||
|
||||
// ========== 异步填充详情(后台进行)==========
|
||||
branchFillingStatus.value = {
|
||||
isFilling: true,
|
||||
current: 0,
|
||||
total: newTasks.length,
|
||||
message: '正在生成分支任务协作流程...'
|
||||
}
|
||||
|
||||
// 带重试的填充函数
|
||||
const fillStepWithRetry = async (task: IRawStepTask, index: number, retryCount = 0): Promise<void> => {
|
||||
const maxRetries = 2
|
||||
|
||||
try {
|
||||
const filledTask = await api.fillStepTask({
|
||||
goal: generalGoal,
|
||||
stepTask: task
|
||||
})
|
||||
|
||||
// 更新任务的 AgentSelection 和 TaskProcess
|
||||
newTasks[index]!.AgentSelection = filledTask.AgentSelection
|
||||
newTasks[index]!.TaskProcess = filledTask.TaskProcess
|
||||
newTasks[index]!.Collaboration_Brief_frontEnd = filledTask.Collaboration_Brief_frontEnd
|
||||
|
||||
// 更新节点数据
|
||||
const taskNodeId = taskNodeIds[index]
|
||||
updateNode(taskNodeId, {
|
||||
data: {
|
||||
...findNode(taskNodeId)?.data,
|
||||
task: { ...newTasks[index] },
|
||||
updateKey: Date.now()
|
||||
}
|
||||
})
|
||||
|
||||
// 更新进度
|
||||
branchFillingStatus.value.current = index + 1
|
||||
|
||||
} catch (error) {
|
||||
console.error(`填充分支任务 ${task.StepName} 详情失败 (尝试 ${retryCount + 1}/${maxRetries + 1}):`, error)
|
||||
|
||||
if (retryCount < maxRetries) {
|
||||
// 延迟后重试
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
return fillStepWithRetry(task, index, retryCount + 1)
|
||||
} else {
|
||||
console.error(`分支任务 ${task.StepName} 在 ${maxRetries + 1} 次尝试后仍然失败`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 串行填充所有任务的详情(避免并发问题导致字段混乱)
|
||||
for (let i = 0; i < newTasks.length; i++) {
|
||||
await fillStepWithRetry(newTasks[i], i)
|
||||
}
|
||||
|
||||
// 更新 store 中的任务数据
|
||||
const allBranches = selectionStore.getAllFlowBranches()
|
||||
const currentBranch = allBranches.find(b => b.branchContent === branchContent && b.parentNodeId === taskId)
|
||||
if (currentBranch) {
|
||||
currentBranch.tasks = JSON.parse(JSON.stringify(newTasks))
|
||||
}
|
||||
|
||||
branchFillingStatus.value.isFilling = false
|
||||
}
|
||||
|
||||
ElMessage.success('任务大纲分支创建成功')
|
||||
}
|
||||
|
||||
// 创建多个任务节点,每个任务显示为单独的流程卡片
|
||||
if (newTasks.length > 0) {
|
||||
// 分支任务与主流程任务对齐:从第一个主流程任务的位置开始
|
||||
const branchBaseX = START_X + ROOT_WIDTH + NODE_GAP
|
||||
const branchBaseY = START_Y + 300 // 向下偏移,与主流程垂直排列
|
||||
const timestamp = Date.now()
|
||||
const taskNodeIds: string[] = []
|
||||
|
||||
// 为每个任务创建节点
|
||||
newTasks.forEach((task, index) => {
|
||||
const taskNodeId = `branch-task-${taskId}-${timestamp}-${index}`
|
||||
taskNodeIds.push(taskNodeId)
|
||||
|
||||
// 计算位置:横向排列,与主流程任务对齐
|
||||
const taskX = branchBaseX + index * (NODE_WIDTH + NODE_GAP)
|
||||
const taskY = branchBaseY
|
||||
|
||||
// 创建任务节点(只显示 StepName)
|
||||
const newTaskNode: Node = {
|
||||
id: taskNodeId,
|
||||
type: 'task',
|
||||
position: { x: taskX, y: taskY },
|
||||
data: {
|
||||
task,
|
||||
isEditing: false,
|
||||
editingContent: '',
|
||||
isBranchTask: true, // 标记为分支任务,可用于特殊样式
|
||||
branchId: String(timestamp), // 添加分支ID,用于识别和分组
|
||||
updateKey: Date.now() // 添加更新时间戳,用于强制组件重新渲染
|
||||
}
|
||||
}
|
||||
|
||||
nodes.value.push(newTaskNode)
|
||||
newBranchNodes.push(newTaskNode)
|
||||
|
||||
// 创建连接边
|
||||
if (index === 0) {
|
||||
// 连接到父节点
|
||||
const newEdge: Edge = {
|
||||
id: `edge-${taskId}-${taskNodeId}`,
|
||||
source: taskId,
|
||||
target: taskNodeId,
|
||||
sourceHandle: 'bottom',
|
||||
targetHandle: 'left',
|
||||
type: 'smoothstep',
|
||||
animated: true,
|
||||
style: { stroke: '#409eff', strokeWidth: 2, strokeDasharray: '5,5' },
|
||||
markerEnd: {
|
||||
type: 'arrow' as any,
|
||||
color: '#43a8aa',
|
||||
width: 20,
|
||||
height: 20,
|
||||
strokeWidth: 2
|
||||
}
|
||||
}
|
||||
edges.value.push(newEdge)
|
||||
newBranchEdges.push(newEdge)
|
||||
} else {
|
||||
// 后续任务连接到前一个任务(左右连接)
|
||||
const prevTaskNodeId = taskNodeIds[index - 1]
|
||||
const newEdge: Edge = {
|
||||
id: `edge-${prevTaskNodeId}-${taskNodeId}`,
|
||||
source: prevTaskNodeId,
|
||||
target: taskNodeId,
|
||||
sourceHandle: 'right',
|
||||
targetHandle: 'left',
|
||||
type: 'smoothstep',
|
||||
animated: true,
|
||||
style: { stroke: '#409eff', strokeWidth: 2 },
|
||||
markerEnd: {
|
||||
type: 'arrow' as any,
|
||||
color: '#43a8aa',
|
||||
width: 20,
|
||||
height: 20,
|
||||
strokeWidth: 2
|
||||
}
|
||||
}
|
||||
edges.value.push(newEdge)
|
||||
newBranchEdges.push(newEdge)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 保存分支到 store
|
||||
if (newBranchNodes.length > 0) {
|
||||
selectionStore.addFlowBranch({
|
||||
parentNodeId: taskId,
|
||||
branchContent: branchContent,
|
||||
branchType: 'root',
|
||||
nodes: newBranchNodes,
|
||||
edges: newBranchEdges,
|
||||
tasks: JSON.parse(JSON.stringify(newTasks))
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// ========== 任务节点级别分支 ==========
|
||||
@@ -1085,119 +1147,173 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
|
||||
} else {
|
||||
newTasks = []
|
||||
}
|
||||
for (let i = 0; i < newTasks.length; i++) {
|
||||
const task = newTasks[i]
|
||||
if (!task) continue
|
||||
|
||||
try {
|
||||
const filledTask = await api.fillStepTask({
|
||||
goal: generalGoal,
|
||||
stepTask: task
|
||||
})
|
||||
// 更新任务的 AgentSelection 和 TaskProcess
|
||||
newTasks[i]!.AgentSelection = filledTask.AgentSelection
|
||||
newTasks[i]!.TaskProcess = filledTask.TaskProcess
|
||||
newTasks[i]!.Collaboration_Brief_frontEnd = filledTask.Collaboration_Brief_frontEnd
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
// ========== 立即创建节点显示大纲 ==========
|
||||
if (newTasks.length > 0) {
|
||||
// 计算分支起始位置:在父任务节点的右下方
|
||||
const branchBaseX = parentNode.position.x + NODE_WIDTH + NODE_GAP
|
||||
const branchBaseY = parentNode.position.y + 300 // 向下偏移,增加距离避免重叠
|
||||
const timestamp = Date.now()
|
||||
const taskNodeIds: string[] = []
|
||||
|
||||
// 为每个任务创建节点(只显示大纲)
|
||||
newTasks.forEach((task, index) => {
|
||||
const taskNodeId = `branch-task-${taskId}-${timestamp}-${index}`
|
||||
taskNodeIds.push(taskNodeId)
|
||||
|
||||
// 计算位置:横向排列
|
||||
const taskX = branchBaseX + index * (NODE_WIDTH + NODE_GAP)
|
||||
const taskY = branchBaseY
|
||||
|
||||
// 创建任务节点
|
||||
const newTaskNode: Node = {
|
||||
id: taskNodeId,
|
||||
type: 'task',
|
||||
position: { x: taskX, y: taskY },
|
||||
data: {
|
||||
task,
|
||||
isEditing: false,
|
||||
editingContent: '',
|
||||
isBranchTask: true,
|
||||
updateKey: Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
nodes.value.push(newTaskNode)
|
||||
newBranchNodes.push(newTaskNode)
|
||||
|
||||
// 创建连接边
|
||||
if (index === 0) {
|
||||
// 第一个任务连接到父节点(从父任务底部到第一个分支任务左边)
|
||||
const newEdge: Edge = {
|
||||
id: `edge-${taskId}-${taskNodeId}`,
|
||||
source: taskId,
|
||||
target: taskNodeId,
|
||||
sourceHandle: 'bottom',
|
||||
targetHandle: 'left',
|
||||
type: 'smoothstep',
|
||||
animated: true,
|
||||
style: { stroke: '#67c23a', strokeWidth: 2, strokeDasharray: '5,5' },
|
||||
markerEnd: {
|
||||
type: 'arrow' as any,
|
||||
color: '#43a8aa',
|
||||
width: 20,
|
||||
height: 20,
|
||||
strokeWidth: 2
|
||||
}
|
||||
}
|
||||
edges.value.push(newEdge)
|
||||
newBranchEdges.push(newEdge)
|
||||
} else {
|
||||
// 后续任务连接到前一个任务(左右连接)
|
||||
const prevTaskNodeId = taskNodeIds[index - 1]
|
||||
const newEdge: Edge = {
|
||||
id: `edge-${prevTaskNodeId}-${taskNodeId}`,
|
||||
source: prevTaskNodeId,
|
||||
target: taskNodeId,
|
||||
sourceHandle: 'right',
|
||||
targetHandle: 'left',
|
||||
type: 'smoothstep',
|
||||
animated: true,
|
||||
style: { stroke: '#67c23a', strokeWidth: 2 },
|
||||
markerEnd: {
|
||||
type: 'arrow' as any,
|
||||
color: '#43a8aa',
|
||||
width: 20,
|
||||
height: 20,
|
||||
strokeWidth: 2
|
||||
}
|
||||
}
|
||||
edges.value.push(newEdge)
|
||||
newBranchEdges.push(newEdge)
|
||||
}
|
||||
})
|
||||
|
||||
// 保存分支数据到 store(此时只有大纲)
|
||||
selectionStore.addFlowBranch({
|
||||
parentNodeId: taskId,
|
||||
branchContent: branchContent,
|
||||
branchType: 'task',
|
||||
nodes: newBranchNodes,
|
||||
edges: newBranchEdges,
|
||||
tasks: JSON.parse(JSON.stringify(newTasks))
|
||||
})
|
||||
|
||||
ElMessage.success('任务大纲分支创建成功')
|
||||
|
||||
// 适应视图,让用户先看到大纲
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
fit({ padding: 100, duration: 300 })
|
||||
}, 100)
|
||||
})
|
||||
|
||||
// ========== 关闭遮罩层Loading ==========
|
||||
branchLoading.value = false
|
||||
|
||||
// ========== 异步填充详情(后台进行)==========
|
||||
branchFillingStatus.value = {
|
||||
isFilling: true,
|
||||
current: 0,
|
||||
total: newTasks.length,
|
||||
message: '正在生成分支任务协作流程...'
|
||||
}
|
||||
|
||||
// 带重试的填充函数
|
||||
const fillStepWithRetry = async (task: IRawStepTask, index: number, retryCount = 0): Promise<void> => {
|
||||
const maxRetries = 2
|
||||
|
||||
try {
|
||||
const filledTask = await api.fillStepTask({
|
||||
goal: generalGoal,
|
||||
stepTask: task
|
||||
})
|
||||
|
||||
// 更新任务的 AgentSelection 和 TaskProcess
|
||||
newTasks[index]!.AgentSelection = filledTask.AgentSelection
|
||||
newTasks[index]!.TaskProcess = filledTask.TaskProcess
|
||||
newTasks[index]!.Collaboration_Brief_frontEnd = filledTask.Collaboration_Brief_frontEnd
|
||||
|
||||
// 更新节点数据
|
||||
const taskNodeId = taskNodeIds[index]
|
||||
updateNode(taskNodeId, {
|
||||
data: {
|
||||
...findNode(taskNodeId)?.data,
|
||||
task: { ...newTasks[index] },
|
||||
updateKey: Date.now()
|
||||
}
|
||||
})
|
||||
|
||||
// 更新进度
|
||||
branchFillingStatus.value.current = index + 1
|
||||
|
||||
} catch (error) {
|
||||
console.error(`填充分支任务 ${task.StepName} 详情失败 (尝试 ${retryCount + 1}/${maxRetries + 1}):`, error)
|
||||
|
||||
if (retryCount < maxRetries) {
|
||||
// 延迟后重试
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
return fillStepWithRetry(task, index, retryCount + 1)
|
||||
} else {
|
||||
console.error(`分支任务 ${task.StepName} 在 ${maxRetries + 1} 次尝试后仍然失败`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 串行填充所有任务的详情(避免并发问题导致字段混乱)
|
||||
for (let i = 0; i < newTasks.length; i++) {
|
||||
await fillStepWithRetry(newTasks[i], i)
|
||||
}
|
||||
|
||||
// 更新 store 中的任务数据
|
||||
const allBranches = selectionStore.getAllFlowBranches()
|
||||
const currentBranch = allBranches.find(b => b.branchContent === branchContent && b.parentNodeId === taskId)
|
||||
if (currentBranch) {
|
||||
currentBranch.tasks = JSON.parse(JSON.stringify(newTasks))
|
||||
}
|
||||
|
||||
branchFillingStatus.value.isFilling = false
|
||||
}
|
||||
|
||||
ElMessage.success('任务大纲分支创建成功')
|
||||
}
|
||||
|
||||
// 创建多个任务节点,每个任务显示为单独的流程卡片
|
||||
if (newTasks.length > 0) {
|
||||
// 计算分支起始位置:在父任务节点的右下方
|
||||
const branchBaseX = parentNode.position.x + NODE_WIDTH + NODE_GAP
|
||||
const branchBaseY = parentNode.position.y + 300 // 向下偏移,增加距离避免重叠
|
||||
const timestamp = Date.now()
|
||||
const taskNodeIds: string[] = []
|
||||
|
||||
// 为每个任务创建节点
|
||||
newTasks.forEach((task, index) => {
|
||||
const taskNodeId = `branch-task-${taskId}-${timestamp}-${index}`
|
||||
taskNodeIds.push(taskNodeId)
|
||||
|
||||
// 计算位置:横向排列
|
||||
const taskX = branchBaseX + index * (NODE_WIDTH + NODE_GAP)
|
||||
const taskY = branchBaseY
|
||||
|
||||
// 创建任务节点(只显示 StepName)
|
||||
const newTaskNode: Node = {
|
||||
id: taskNodeId,
|
||||
type: 'task',
|
||||
position: { x: taskX, y: taskY },
|
||||
data: {
|
||||
task,
|
||||
isEditing: false,
|
||||
editingContent: '',
|
||||
isBranchTask: true, // 标记为分支任务,可用于特殊样式
|
||||
updateKey: Date.now() // 添加更新时间戳,用于强制组件重新渲染
|
||||
}
|
||||
}
|
||||
|
||||
nodes.value.push(newTaskNode)
|
||||
newBranchNodes.push(newTaskNode)
|
||||
|
||||
// 创建连接边
|
||||
if (index === 0) {
|
||||
// 第一个任务连接到父节点(从父任务底部到第一个分支任务左边)
|
||||
const newEdge: Edge = {
|
||||
id: `edge-${taskId}-${taskNodeId}`,
|
||||
source: taskId,
|
||||
target: taskNodeId,
|
||||
sourceHandle: 'bottom',
|
||||
targetHandle: 'left',
|
||||
type: 'smoothstep',
|
||||
animated: true,
|
||||
style: { stroke: '#67c23a', strokeWidth: 2, strokeDasharray: '5,5' },
|
||||
markerEnd: {
|
||||
type: 'arrow' as any,
|
||||
color: '#43a8aa',
|
||||
width: 20,
|
||||
height: 20,
|
||||
strokeWidth: 2
|
||||
}
|
||||
}
|
||||
edges.value.push(newEdge)
|
||||
newBranchEdges.push(newEdge)
|
||||
} else {
|
||||
// 后续任务连接到前一个任务(左右连接)
|
||||
const prevTaskNodeId = taskNodeIds[index - 1]
|
||||
const newEdge: Edge = {
|
||||
id: `edge-${prevTaskNodeId}-${taskNodeId}`,
|
||||
source: prevTaskNodeId,
|
||||
target: taskNodeId,
|
||||
sourceHandle: 'right',
|
||||
targetHandle: 'left',
|
||||
type: 'smoothstep',
|
||||
animated: true,
|
||||
style: { stroke: '#67c23a', strokeWidth: 2 },
|
||||
markerEnd: {
|
||||
type: 'arrow' as any,
|
||||
color: '#43a8aa',
|
||||
width: 20,
|
||||
height: 20,
|
||||
strokeWidth: 2
|
||||
}
|
||||
}
|
||||
edges.value.push(newEdge)
|
||||
newBranchEdges.push(newEdge)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 保存分支数据到 store
|
||||
if (newBranchNodes.length > 0) {
|
||||
selectionStore.addFlowBranch({
|
||||
parentNodeId: taskId,
|
||||
branchContent: branchContent,
|
||||
branchType: 'task',
|
||||
nodes: newBranchNodes,
|
||||
edges: newBranchEdges,
|
||||
tasks: JSON.parse(JSON.stringify(newTasks))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1297,4 +1413,38 @@ defineExpose({
|
||||
:deep(.vue-flow__edge.selected .vue-flow__edge-path) {
|
||||
stroke: #409eff;
|
||||
}
|
||||
|
||||
// 分支加载提示样式
|
||||
.branch-loading-hint {
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
right: 20px;
|
||||
background: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
z-index: 1000;
|
||||
animation: slideInRight 0.3s ease-out;
|
||||
|
||||
.progress {
|
||||
color: var(--el-color-primary);
|
||||
font-weight: bold;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/
|
||||
import { type IRawStepTask, useAgentsStore } from '@/stores'
|
||||
import { computed, ref, nextTick, watch, onMounted } from 'vue'
|
||||
import { AnchorLocations } from '@jsplumb/browser-ui'
|
||||
import { Loading } from '@element-plus/icons-vue'
|
||||
import MultiLineTooltip from '@/components/MultiLineTooltip/index.vue'
|
||||
import Bg from './Bg.vue'
|
||||
import BranchButton from './components/BranchButton.vue'
|
||||
@@ -37,6 +38,22 @@ const collaborationProcess = computed(() => {
|
||||
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
|
||||
})
|
||||
|
||||
// 检测是否正在填充详情(有步骤但没有 AgentSelection)
|
||||
const isFillingDetails = computed(() => {
|
||||
const process = agentsStore.agentRawPlan.data?.['Collaboration Process'] || []
|
||||
return process.length > 0 && process.some(step => !step.AgentSelection || step.AgentSelection.length === 0)
|
||||
})
|
||||
|
||||
// 计算填充进度
|
||||
const completedSteps = computed(() => {
|
||||
const process = agentsStore.agentRawPlan.data?.['Collaboration Process'] || []
|
||||
return process.filter(step => step.AgentSelection && step.AgentSelection.length > 0).length
|
||||
})
|
||||
|
||||
const totalSteps = computed(() => {
|
||||
return agentsStore.agentRawPlan.data?.['Collaboration Process']?.length || 0
|
||||
})
|
||||
|
||||
// 编辑状态管理
|
||||
const editingTaskId = ref<string | null>(null)
|
||||
const editingContent = ref('')
|
||||
@@ -258,6 +275,13 @@ defineExpose({
|
||||
任务大纲
|
||||
</div>
|
||||
|
||||
<!-- 加载详情提示 -->
|
||||
<div v-if="isFillingDetails" class="detail-loading-hint">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<span>正在生成任务协作流程...</span>
|
||||
<span class="progress">{{ completedSteps }}/{{ totalSteps }}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-loading="agentsStore.agentRawPlan.loading"
|
||||
class="flex-1 w-full overflow-y-auto relative"
|
||||
@@ -556,6 +580,40 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
|
||||
// 加载详情提示样式
|
||||
.detail-loading-hint {
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
right: 20px;
|
||||
background: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
z-index: 1000;
|
||||
animation: slideInRight 0.3s ease-out;
|
||||
|
||||
.progress {
|
||||
color: var(--el-color-primary);
|
||||
font-weight: bold;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 输入框样式
|
||||
:deep(.el-input__wrapper) {
|
||||
background: transparent;
|
||||
|
||||
Reference in New Issue
Block a user