feat:任务执行结果性能优化

This commit is contained in:
liailing1026
2026-01-21 15:18:15 +08:00
parent c5848410c1
commit 45314b7be6
9 changed files with 952 additions and 338 deletions

View File

@@ -137,5 +137,6 @@ def AgentSelectModify_init(stepTask, General_Goal, Agent_Board):
def AgentSelectModify_addAspect(aspectList, Agent_Board): def AgentSelectModify_addAspect(aspectList, Agent_Board):
scoreTable = agentAbilityScoring(Agent_Board, aspectList) newAspect = aspectList[-1]
scoreTable = agentAbilityScoring(Agent_Board, [newAspect])
return scoreTable return scoreTable

View File

@@ -1,55 +1,53 @@
from AgentCoord.PlanEngine.planOutline_Generator import generate_PlanOutline from AgentCoord.PlanEngine.planOutline_Generator import generate_PlanOutline
from AgentCoord.PlanEngine.AgentSelection_Generator import ( # from AgentCoord.PlanEngine.AgentSelection_Generator import (
generate_AgentSelection, # generate_AgentSelection,
) # )
from AgentCoord.PlanEngine.taskProcess_Generator import generate_TaskProcess
import AgentCoord.util as util
def generate_basePlan( def generate_basePlan(
General_Goal, Agent_Board, AgentProfile_Dict, InitialObject_List General_Goal, Agent_Board, AgentProfile_Dict, InitialObject_List
): ):
basePlan = { """
"Initial Input Object": InitialObject_List, 优化模式:生成大纲 + 智能体选择,但不生成任务流程
"Collaboration Process": [], 优化用户体验:
} 1. 快速生成大纲和分配智能体
2. 用户可以看到完整的大纲和智能体图标
3. TaskProcess由前端通过 fillStepTask API 异步填充
"""
# 参数保留以保持接口兼容性
_ = AgentProfile_Dict
PlanOutline = generate_PlanOutline( PlanOutline = generate_PlanOutline(
InitialObject_List=[], General_Goal=General_Goal InitialObject_List=InitialObject_List, General_Goal=General_Goal
) )
basePlan = {
"General Goal": General_Goal,
"Initial Input Object": InitialObject_List,
"Collaboration Process": []
}
for stepItem in PlanOutline: for stepItem in PlanOutline:
Current_Task = { # # 为每个步骤分配智能体
"TaskName": stepItem["StepName"], # Current_Task = {
"InputObject_List": stepItem["InputObject_List"], # "TaskName": stepItem["StepName"],
"OutputObject": stepItem["OutputObject"], # "InputObject_List": stepItem["InputObject_List"],
"TaskContent": stepItem["TaskContent"], # "OutputObject": stepItem["OutputObject"],
# "TaskContent": stepItem["TaskContent"],
# }
# AgentSelection = generate_AgentSelection(
# General_Goal=General_Goal,
# Current_Task=Current_Task,
# Agent_Board=Agent_Board,
# )
# 添加智能体选择,但不添加任务流程
stepItem["AgentSelection"] = []
stepItem["TaskProcess"] = [] # 空数组,由前端异步填充
stepItem["Collaboration_Brief_frontEnd"] = {
"template": "",
"data": {}
} }
AgentSelection = generate_AgentSelection(
General_Goal=General_Goal,
Current_Task=Current_Task,
Agent_Board=Agent_Board,
)
Current_Task_Description = {
"TaskName": stepItem["StepName"],
"AgentInvolved": [
{"Name": name, "Profile": AgentProfile_Dict[name]}
for name in AgentSelection
],
"InputObject_List": stepItem["InputObject_List"],
"OutputObject": stepItem["OutputObject"],
"CurrentTaskDescription": util.generate_template_sentence_for_CollaborationBrief(
stepItem["InputObject_List"],
stepItem["OutputObject"],
AgentSelection,
stepItem["TaskContent"],
),
}
TaskProcess = generate_TaskProcess(
General_Goal=General_Goal,
Current_Task_Description=Current_Task_Description,
)
# add the generated AgentSelection and TaskProcess to the stepItem
stepItem["AgentSelection"] = AgentSelection
stepItem["TaskProcess"] = TaskProcess
basePlan["Collaboration Process"].append(stepItem) basePlan["Collaboration Process"].append(stepItem)
basePlan["General Goal"] = General_Goal
return basePlan return basePlan

View File

@@ -1,7 +1,8 @@
from flask import Flask, request, jsonify from flask import Flask, request, jsonify, Response, stream_with_context
import json import json
from DataProcess import Add_Collaboration_Brief_FrontEnd from DataProcess import Add_Collaboration_Brief_FrontEnd
from AgentCoord.RehearsalEngine_V2.ExecutePlan import executePlan from AgentCoord.RehearsalEngine_V2.ExecutePlan import executePlan
from AgentCoord.RehearsalEngine_V2.ExecutePlan_Optimized import executePlan_streaming
from AgentCoord.PlanEngine.basePlan_Generator import generate_basePlan from AgentCoord.PlanEngine.basePlan_Generator import generate_basePlan
from AgentCoord.PlanEngine.fill_stepTask import fill_stepTask from AgentCoord.PlanEngine.fill_stepTask import fill_stepTask
from AgentCoord.PlanEngine.fill_stepTask_TaskProcess import ( from AgentCoord.PlanEngine.fill_stepTask_TaskProcess import (
@@ -257,6 +258,45 @@ def Handle_executePlan():
return response return response
@app.route("/executePlanOptimized", methods=["post"])
def Handle_executePlanOptimized():
"""
优化版流式执行计划阶段1+2步骤级流式 + 动作级智能并行)
返回 SSE 流,每完成一个动作就返回结果
- 无依赖关系的动作并行执行
- 有依赖关系的动作串行执行
前端使用 EventSource 接收
"""
incoming_data = request.get_json()
def generate():
try:
for chunk in executePlan_streaming(
plan=incoming_data["plan"],
num_StepToRun=incoming_data.get("num_StepToRun"),
RehearsalLog=incoming_data.get("RehearsalLog", []),
AgentProfile_Dict=AgentProfile_Dict,
):
yield chunk
except Exception as e:
error_event = json.dumps({
"type": "error",
"message": str(e)
}, ensure_ascii=False)
yield f"data: {error_event}\n\n"
return Response(
stream_with_context(generate()),
mimetype="text/event-stream",
headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
}
)
@app.route("/_saveRequestCashe", methods=["post"]) @app.route("/_saveRequestCashe", methods=["post"])
def Handle_saveRequestCashe(): def Handle_saveRequestCashe():
with open( with open(

View File

@@ -34,6 +34,47 @@ export type IExecuteRawResponse = {
ActionHistory: ActionHistory[] ActionHistory: ActionHistory[]
} }
/**
* SSE 流式事件类型
*/
export type StreamingEvent =
| {
type: 'step_start'
step_index: number
total_steps: number
step_name: string
task_description?: string
}
| {
type: 'action_complete'
step_index: number
step_name: string
action_index: number
total_actions: number
completed_actions: number
action_result: ActionHistory
batch_info?: {
batch_index: number
batch_size: number
is_parallel: boolean
}
}
| {
type: 'step_complete'
step_index: number
step_name: string
step_log_node: any
object_log_node: any
}
| {
type: 'execution_complete'
total_steps: number
}
| {
type: 'error'
message: string
}
export interface IFillAgentSelectionRequest { export interface IFillAgentSelectionRequest {
goal: string goal: string
stepTask: IApiStepTask stepTask: IApiStepTask
@@ -99,7 +140,95 @@ class Api {
}) })
} }
// 分支任务大纲 /**
* 优化版流式执行计划阶段1+2步骤级流式 + 动作级智能并行)
* 无依赖关系的动作并行执行,有依赖关系的动作串行执行
*/
executePlanOptimized = (
plan: IRawPlanResponse,
onMessage: (event: StreamingEvent) => void,
onError?: (error: Error) => void,
onComplete?: () => void,
) => {
const data = {
RehearsalLog: [],
num_StepToRun: null,
plan: {
'Initial Input Object': plan['Initial Input Object'],
'General Goal': plan['General Goal'],
'Collaboration Process': plan['Collaboration Process']?.map((step) => ({
StepName: step.StepName,
TaskContent: step.TaskContent,
InputObject_List: step.InputObject_List,
OutputObject: step.OutputObject,
AgentSelection: step.AgentSelection,
Collaboration_Brief_frontEnd: step.Collaboration_Brief_frontEnd,
TaskProcess: step.TaskProcess.map((action) => ({
ActionType: action.ActionType,
AgentName: action.AgentName,
Description: action.Description,
ID: action.ID,
ImportantInput: action.ImportantInput,
})),
})),
},
}
fetch('/api/executePlanOptimized', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(async (response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const reader = response.body?.getReader()
const decoder = new TextDecoder()
if (!reader) {
throw new Error('Response body is null')
}
let buffer = ''
while (true) {
const { done, value } = await reader.read()
if (done) {
onComplete?.()
break
}
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n')
buffer = lines.pop() || ''
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6)
try {
const event = JSON.parse(data)
onMessage(event)
} catch (e) {
console.error('Failed to parse SSE data:', e)
}
}
}
}
})
.catch((error) => {
onError?.(error)
})
}
/**
* 分支任务大纲
*/
branchPlanOutline = (data: { branchPlanOutline = (data: {
branch_Number: number branch_Number: number
Modification_Requirement: string Modification_Requirement: string
@@ -122,7 +251,9 @@ class Api {
}) })
} }
// 分支任务流程 /**
* 分支任务流程
*/
branchTaskProcess = (data: { branchTaskProcess = (data: {
branch_Number: number branch_Number: number
Modification_Requirement: string Modification_Requirement: string
@@ -146,7 +277,6 @@ class Api {
} }
fillStepTask = async (data: { goal: string; stepTask: any }): Promise<IRawStepTask> => { fillStepTask = async (data: { goal: string; stepTask: any }): Promise<IRawStepTask> => {
// 后端返回格式:包含 Collaboration_Brief_FrontEnd大写 FrontEnd
const response = await request< const response = await request<
{ {
'General Goal': string 'General Goal': string
@@ -179,14 +309,11 @@ class Api {
}, },
}) })
// 数据转换:后端的 Collaboration_Brief_FrontEnd → 前端的 Collaboration_Brief_frontEnd
const vec2Hsl = (color: number[]): string => { const vec2Hsl = (color: number[]): string => {
const [h, s, l] = color const [h, s, l] = color
return `hsl(${h}, ${s}%, ${l}%)` return `hsl(${h}, ${s}%, ${l}%)`
} }
// 转换 brief.data后端格式 { "0": { text, color: [h,s,l] } } → 前端格式 { "0": { text, style: { background } } }
const briefData: Record<string, { text: string; style?: Record<string, string> }> = {} const briefData: Record<string, { text: string; style?: Record<string, string> }> = {}
if (response.Collaboration_Brief_FrontEnd?.data) { if (response.Collaboration_Brief_FrontEnd?.data) {
for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) { for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) {
@@ -199,7 +326,9 @@ class Api {
} }
} }
// 构建前端格式的 IRawStepTask /**
* 构建前端格式的 IRawStepTask
*/
return { return {
StepName: response.StepName || '', StepName: response.StepName || '',
TaskContent: response.TaskContent || '', TaskContent: response.TaskContent || '',
@@ -219,7 +348,6 @@ class Api {
stepTask: IApiStepTask stepTask: IApiStepTask
agents: string[] agents: string[]
}): Promise<IApiStepTask> => { }): Promise<IApiStepTask> => {
// 后端返回格式: IRawStepTask
const response = await request< const response = await request<
{ {
'General Goal': string 'General Goal': string
@@ -264,16 +392,11 @@ class Api {
}, },
}) })
// 数据转换:后端格式 (IRawStepTask) → 前端格式 (IApiStepTask)
// 注意此转换逻辑与Mock API完全一致
// 1. 转换颜色格式:[h, s, l] → "hsl(h, s%, l%)"
const vec2Hsl = (color: number[]): string => { const vec2Hsl = (color: number[]): string => {
const [h, s, l] = color const [h, s, l] = color
return `hsl(${h}, ${s}%, ${l}%)` return `hsl(${h}, ${s}%, ${l}%)`
} }
// 2. 转换 brief.data: { "0": { text, color: [h,s,l] } } → { "0": { text, style: { background } } }
const briefData: Record<string, { text: string; style: { background: string } }> = {} const briefData: Record<string, { text: string; style: { background: string } }> = {}
if (response.Collaboration_Brief_FrontEnd?.data) { if (response.Collaboration_Brief_FrontEnd?.data) {
for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) { for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) {
@@ -286,7 +409,6 @@ class Api {
} }
} }
// 3. 转换 process: { ID, ActionType, AgentName, Description, ImportantInput } → { id, type, agent, description, inputs }
const process = (response.TaskProcess || []).map((action) => ({ const process = (response.TaskProcess || []).map((action) => ({
id: action.ID, id: action.ID,
type: action.ActionType, type: action.ActionType,
@@ -295,7 +417,6 @@ class Api {
inputs: action.ImportantInput, inputs: action.ImportantInput,
})) }))
// 4. 构建前端格式的 IApiStepTask
return { return {
name: response.StepName || '', name: response.StepName || '',
content: response.TaskContent || '', content: response.TaskContent || '',
@@ -310,12 +431,13 @@ class Api {
} }
} }
// 为每个智能体评分 /**
* 为每个智能体评分
*/
agentSelectModifyInit = async (data: { agentSelectModifyInit = async (data: {
goal: string goal: string
stepTask: any stepTask: any
}): Promise<Record<string, Record<string, { reason: string; score: number }>>> => { }): Promise<Record<string, Record<string, { reason: string; score: number }>>> => {
// 后端返回:维度 -> agent -> { Reason, Score }
const response = await request< const response = await request<
{ {
'General Goal': string 'General Goal': string
@@ -336,11 +458,9 @@ class Api {
}, },
}) })
// 数据转换:后端格式 (维度->agent) → 前端格式 (agent->维度)
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {} const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
for (const [aspect, agents] of Object.entries(response)) { for (const [aspect, agents] of Object.entries(response)) {
// aspect: 维度名称, agents: { agentName: { Reason, Score } }
for (const [agentName, scoreInfo] of Object.entries(agents)) { for (const [agentName, scoreInfo] of Object.entries(agents)) {
if (!transformedData[agentName]) { if (!transformedData[agentName]) {
transformedData[agentName] = {} transformedData[agentName] = {}
@@ -355,15 +475,15 @@ class Api {
return transformedData return transformedData
} }
// 添加新的评估维度 /**
// 定义返回类型(与 Mock 数据格式一致) * 添加新的评估维度
*/
agentSelectModifyAddAspect = async (data: { agentSelectModifyAddAspect = async (data: {
aspectList: string[] aspectList: string[]
}): Promise<{ }): Promise<{
aspectName: string aspectName: string
agentScores: Record<string, { score: number; reason: string }> agentScores: Record<string, { score: number; reason: string }>
}> => { }> => {
// 后端返回:维度 -> agent -> { Reason, Score }
const response = await request< const response = await request<
{ {
aspectList: string[] aspectList: string[]
@@ -377,13 +497,14 @@ class Api {
}, },
}) })
// 获取新添加的维度(最后一个) /**
* 获取新添加的维度
*/
const newAspect = data.aspectList[data.aspectList.length - 1] const newAspect = data.aspectList[data.aspectList.length - 1]
if (!newAspect) { if (!newAspect) {
throw new Error('aspectList is empty') throw new Error('aspectList is empty')
} }
// 提取该维度的数据:维度 -> agent -> { Reason, Score } → agent -> { score, reason }
const newAspectAgents = response[newAspect] const newAspectAgents = response[newAspect]
const agentScores: Record<string, { score: number; reason: string }> = {} const agentScores: Record<string, { score: number; reason: string }> = {}
@@ -401,23 +522,18 @@ class Api {
agentScores, agentScores,
} }
} }
/**
// ==================== Mock API开发阶段使用==================== * ==================== Mock API开发阶段使用====================
// Mock API 使用与真实 API 相同的数据转换逻辑,确保将来切换无缝 *为每个智能体评分
*/
// Mock: 为每个智能体评分
mockAgentSelectModifyInit = async (): Promise< mockAgentSelectModifyInit = async (): Promise<
Record<string, Record<string, { reason: string; score: number }>> Record<string, Record<string, { reason: string; score: number }>>
> => { > => {
// 调用Mock后端数据维度 -> agent -> { Reason, Score }
const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyInit() const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyInit()
// 数据转换:后端格式 (维度->agent) → 前端格式 (agent->维度)
// 注意此转换逻辑与真实API完全一致
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {} const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
for (const [aspect, agents] of Object.entries(response)) { for (const [aspect, agents] of Object.entries(response)) {
// aspect: 维度名称, agents: { agentName: { Reason, Score } }
for (const [agentName, scoreInfo] of Object.entries(agents)) { for (const [agentName, scoreInfo] of Object.entries(agents)) {
if (!transformedData[agentName]) { if (!transformedData[agentName]) {
transformedData[agentName] = {} transformedData[agentName] = {}
@@ -432,26 +548,21 @@ class Api {
return transformedData return transformedData
} }
// Mock: 添加新的评估维度
mockAgentSelectModifyAddAspect = async (data: { mockAgentSelectModifyAddAspect = async (data: {
aspectList: string[] aspectList: string[]
}): Promise<{ }): Promise<{
aspectName: string aspectName: string
agentScores: Record<string, { score: number; reason: string }> agentScores: Record<string, { score: number; reason: string }>
}> => { }> => {
// 调用Mock后端数据维度 -> agent -> { Reason, Score }
const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyAddAspect( const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyAddAspect(
data.aspectList, data.aspectList,
) )
// 获取新添加的维度(最后一个)
const newAspect = data.aspectList[data.aspectList.length - 1] const newAspect = data.aspectList[data.aspectList.length - 1]
if (!newAspect) { if (!newAspect) {
throw new Error('aspectList is empty') throw new Error('aspectList is empty')
} }
// 提取该维度的数据:维度 -> agent -> { Reason, Score } → agent -> { score, reason }
// 注意此转换逻辑与真实API完全一致
const newAspectAgents = response[newAspect] const newAspectAgents = response[newAspect]
const agentScores: Record<string, { score: number; reason: string }> = {} const agentScores: Record<string, { score: number; reason: string }> = {}
@@ -470,29 +581,22 @@ class Api {
} }
} }
// Mock: 填充智能体任务流程
mockFillStepTaskTaskProcess = async (data: { mockFillStepTaskTaskProcess = async (data: {
goal: string goal: string
stepTask: IApiStepTask stepTask: IApiStepTask
agents: string[] agents: string[]
}): Promise<IApiStepTask> => { }): Promise<IApiStepTask> => {
// 调用Mock后端数据后端原始格式
const response: RawAgentTaskProcessResponse = await mockBackendFillAgentTaskProcess( const response: RawAgentTaskProcessResponse = await mockBackendFillAgentTaskProcess(
data.goal, data.goal,
data.stepTask, data.stepTask,
data.agents, data.agents,
) )
// 数据转换:后端格式 → 前端格式
// 注意此转换逻辑与真实API完全一致
// 1. 转换颜色格式:[h, s, l] → "hsl(h, s%, l%)"
const vec2Hsl = (color: number[]): string => { const vec2Hsl = (color: number[]): string => {
const [h, s, l] = color const [h, s, l] = color
return `hsl(${h}, ${s}%, ${l}%)` return `hsl(${h}, ${s}%, ${l}%)`
} }
// 2. 转换 brief.data: { "0": { text, color: [h,s,l] } } → { "0": { text, style: { background } } }
const briefData: Record<string, { text: string; style: { background: string } }> = {} const briefData: Record<string, { text: string; style: { background: string } }> = {}
if (response.Collaboration_Brief_frontEnd?.data) { if (response.Collaboration_Brief_frontEnd?.data) {
for (const [key, value] of Object.entries(response.Collaboration_Brief_frontEnd.data)) { for (const [key, value] of Object.entries(response.Collaboration_Brief_frontEnd.data)) {
@@ -505,7 +609,6 @@ class Api {
} }
} }
// 3. 转换 process: { ID, ActionType, AgentName, Description, ImportantInput } → { id, type, agent, description, inputs }
const process = (response.TaskProcess || []).map((action) => ({ const process = (response.TaskProcess || []).map((action) => ({
id: action.ID, id: action.ID,
type: action.ActionType, type: action.ActionType,
@@ -514,7 +617,6 @@ class Api {
inputs: action.ImportantInput, inputs: action.ImportantInput,
})) }))
// 4. 构建前端格式的 IApiStepTask
return { return {
name: response.StepName || '', name: response.StepName || '',
content: response.TaskContent || '', content: response.TaskContent || '',
@@ -529,7 +631,6 @@ class Api {
} }
} }
// Mock: 分支任务大纲
mockBranchPlanOutline = async (data: { mockBranchPlanOutline = async (data: {
branch_Number: number branch_Number: number
Modification_Requirement: string Modification_Requirement: string
@@ -538,7 +639,6 @@ class Api {
initialInputs: string[] initialInputs: string[]
goal: string goal: string
}): Promise<IRawPlanResponse> => { }): Promise<IRawPlanResponse> => {
// 直接调用 Mock API已经返回 IRawPlanResponse 格式)
const response = await mockBranchPlanOutlineAPI({ const response = await mockBranchPlanOutlineAPI({
branch_Number: data.branch_Number, branch_Number: data.branch_Number,
Modification_Requirement: data.Modification_Requirement, Modification_Requirement: data.Modification_Requirement,
@@ -551,9 +651,7 @@ class Api {
return response return response
} }
// Mock: 填充任务流程
mockFillStepTask = async (data: { goal: string; stepTask: any }): Promise<any> => { mockFillStepTask = async (data: { goal: string; stepTask: any }): Promise<any> => {
// 直接调用 Mock API已经返回 IRawStepTask 格式)
const response = await mockFillStepTaskAPI({ const response = await mockFillStepTaskAPI({
General_Goal: data.goal, General_Goal: data.goal,
stepTask: data.stepTask, stepTask: data.stepTask,
@@ -562,7 +660,6 @@ class Api {
return response return response
} }
// Mock: 分支任务流程
mockBranchTaskProcess = async (data: { mockBranchTaskProcess = async (data: {
branch_Number: number branch_Number: number
Modification_Requirement: string Modification_Requirement: string
@@ -571,7 +668,6 @@ class Api {
stepTaskExisting: any stepTaskExisting: any
goal: string goal: string
}): Promise<BranchAction[][]> => { }): Promise<BranchAction[][]> => {
// 直接调用 Mock API已经返回 BranchAction[][] 格式,与后端完全一致)
const response = await mockBranchTaskProcessAPI({ const response = await mockBranchTaskProcessAPI({
branch_Number: data.branch_Number, branch_Number: data.branch_Number,
Modification_Requirement: data.Modification_Requirement, Modification_Requirement: data.Modification_Requirement,

View File

@@ -84,6 +84,9 @@ function resetTextareaHeight() {
} }
async function handleSearch() { async function handleSearch() {
// 用于标记大纲是否成功加载
let outlineLoaded = false
try { try {
triggerOnFocus.value = false triggerOnFocus.value = false
if (!searchValue.value) { if (!searchValue.value) {
@@ -93,18 +96,103 @@ async function handleSearch() {
emit('search-start') emit('search-start')
agentsStore.resetAgent() agentsStore.resetAgent()
agentsStore.setAgentRawPlan({ loading: true }) agentsStore.setAgentRawPlan({ loading: true })
const data = await api.generateBasePlan({
// 获取大纲
const outlineData = await api.generateBasePlan({
goal: searchValue.value, goal: searchValue.value,
inputs: [] 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) 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 { } finally {
triggerOnFocus.value = true triggerOnFocus.value = true
// 如果大纲加载失败确保关闭loading
if (!outlineLoaded) {
agentsStore.setAgentRawPlan({ loading: false }) 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: {}
}
})
}
} }
const querySearch = (queryString: string, cb: (v: { value: string }[]) => void) => { const querySearch = (queryString: string, cb: (v: { value: string }[]) => void) => {
@@ -215,8 +303,6 @@ onMounted(() => {
:deep(.el-autocomplete .el-textarea .el-textarea__inner) { :deep(.el-autocomplete .el-textarea .el-textarea__inner) {
overflow-y: auto !important; overflow-y: auto !important;
min-height: 56px !important; min-height: 56px !important;
// overflow-y: hidden;
// background-color: black;
} }
} }

View File

@@ -8,7 +8,7 @@ import { getActionTypeDisplay, getAgentMapIcon } from '@/layout/components/confi
import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/utils.ts' import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/utils.ts'
import variables from '@/styles/variables.module.scss' import variables from '@/styles/variables.module.scss'
import { type IRawStepTask, useAgentsStore } from '@/stores' import { type IRawStepTask, useAgentsStore } from '@/stores'
import api from '@/api' import api, { type StreamingEvent } from '@/api'
import ProcessCard from '../TaskProcess/ProcessCard.vue' import ProcessCard from '../TaskProcess/ProcessCard.vue'
import ExecutePlan from './ExecutePlan.vue' import ExecutePlan from './ExecutePlan.vue'
@@ -182,15 +182,149 @@ function createInternalLine(id?: string) {
} }
const loading = ref(false) const loading = ref(false)
const executionProgress = ref({
currentStep: 0,
totalSteps: 0,
currentAction: 0,
totalActions: 0,
currentStepName: '',
message: '正在执行...'
})
async function handleRun() { async function handleRun() {
// 清空之前的执行结果
agentsStore.setExecutePlan([])
const tempResults: any[] = []
try { try {
loading.value = true loading.value = true
const d = await api.executePlan(agentsStore.agentRawPlan.data!)
agentsStore.setExecutePlan(d) // 使用优化版流式API阶段1+2步骤级流式 + 动作级智能并行)
} finally { 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 loading.value = false
} }
)
} catch (error) {
console.error('执行失败:', error)
executionProgress.value.message = '执行失败,请重试'
} finally {
// loading 会在 onComplete 中设置为 false
}
} }
// 查看任务过程 // 查看任务过程
@@ -381,6 +515,15 @@ defineExpose({
id="task-results" id="task-results"
:class="{ 'is-running': agentsStore.executePlan.length > 0 }" :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]"> <div class="text-[18px] font-bold mb-[7px] flex justify-between items-center px-[20px]">
<span class="text-[var(--color-text-title-header)]">执行结果</span> <span class="text-[var(--color-text-title-header)]">执行结果</span>
@@ -606,6 +749,48 @@ defineExpose({
</template> </template>
<style scoped lang="scss"> <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 { #task-results.is-running {
--color-bg-detail-list: var(--color-bg-detail-list-run); // 直接指向 100 % 版本 --color-bg-detail-list: var(--color-bg-detail-list-run); // 直接指向 100 % 版本
} }

View File

@@ -25,20 +25,20 @@ defineEmits<{
<!-- 背景那一根线 --> <!-- 背景那一根线 -->
<div class="h-full bg-[var(--color-bg-flow)] w-[5px]"> <div class="h-full bg-[var(--color-bg-flow)] w-[5px]">
<!-- 顶部加号区域 --> <!-- 顶部加号区域 -->
<div <!-- <div
v-if="!isAdding" v-if="!isAdding"
v-dev-only v-dev-only
class="plus-area mt-[35px] ml-[-15px] w-[34px] h-[34px] flex items-center justify-center cursor-pointer rounded-full" 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')" @click="$emit('start-add-output')"
> > -->
<!-- 加号图标 --> <!-- 加号图标 -->
<svg-icon <!-- <svg-icon
icon-class="plus" icon-class="plus"
color="var(--color-text)" color="var(--color-text)"
size="20px" size="20px"
class="plus-icon opacity-0 transition-opacity duration-200" class="plus-icon opacity-0 transition-opacity duration-200"
/> />
</div> </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" class="absolute bottom-0 left-1/2 transform -translate-x-1/2 bg-[var(--color-bg-flow)] w-[15px] h-[15px] rounded-full"

View File

@@ -1,5 +1,12 @@
<template> <template>
<div class="plan-modification"> <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 <div
v-loading="agentsStore.agentRawPlan.loading || branchLoading" v-loading="agentsStore.agentRawPlan.loading || branchLoading"
class="flow-wrapper" class="flow-wrapper"
@@ -49,6 +56,7 @@ import { VueFlow, useVueFlow } from '@vue-flow/core'
import type { Node, Edge } from '@vue-flow/core' import type { Node, Edge } from '@vue-flow/core'
import { useAgentsStore, useSelectionStore, type IRawStepTask } from '@/stores' import { useAgentsStore, useSelectionStore, type IRawStepTask } from '@/stores'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { Loading } from '@element-plus/icons-vue'
import api from '@/api' import api from '@/api'
import '@vue-flow/core/dist/style.css' import '@vue-flow/core/dist/style.css'
import '@vue-flow/core/dist/theme-default.css' import '@vue-flow/core/dist/theme-default.css'
@@ -65,6 +73,14 @@ const selectionStore = useSelectionStore()
// Mock 数据开关 // Mock 数据开关
const USE_MOCK_DATA = false const USE_MOCK_DATA = false
// 分支详情填充状态
const branchFillingStatus = ref({
isFilling: false,
current: 0,
total: 0,
message: ''
})
// 获取协作流程数据 // 获取协作流程数据
const collaborationProcess = computed(() => { const collaborationProcess = computed(() => {
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? [] return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
@@ -782,8 +798,6 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
}) })
// 直接获取协作流程数据 // 直接获取协作流程数据
// newTasks = response?.[0] || []
// 直接获取协作流程数据:兼容返回数组或对象的情况,优先处理二维数组返回
if (Array.isArray(response)) { if (Array.isArray(response)) {
// 可能是二维数组 // 可能是二维数组
newTasks = (response as any[])[0] || [] newTasks = (response as any[])[0] || []
@@ -793,46 +807,22 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
} else { } else {
newTasks = [] 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)
}
}
ElMessage.success('任务大纲分支创建成功')
}
// 创建多个任务节点,每个任务显示为单独的流程卡片
if (newTasks.length > 0) { if (newTasks.length > 0) {
// 分支任务与主流程任务对齐:从第一个主流程任务的位置开始
const branchBaseX = START_X + ROOT_WIDTH + NODE_GAP const branchBaseX = START_X + ROOT_WIDTH + NODE_GAP
const branchBaseY = START_Y + 300 // 向下偏移,与主流程垂直排列 const branchBaseY = START_Y + 300
const timestamp = Date.now() const timestamp = Date.now()
const taskNodeIds: string[] = [] const taskNodeIds: string[] = []
// 为每个任务创建节点 // 为每个任务创建节点(只显示大纲)
newTasks.forEach((task, index) => { newTasks.forEach((task, index) => {
const taskNodeId = `branch-task-${taskId}-${timestamp}-${index}` const taskNodeId = `branch-task-${taskId}-${timestamp}-${index}`
taskNodeIds.push(taskNodeId) taskNodeIds.push(taskNodeId)
// 计算位置:横向排列,与主流程任务对齐
const taskX = branchBaseX + index * (NODE_WIDTH + NODE_GAP) const taskX = branchBaseX + index * (NODE_WIDTH + NODE_GAP)
const taskY = branchBaseY const taskY = branchBaseY
// 创建任务节点(只显示 StepName
const newTaskNode: Node = { const newTaskNode: Node = {
id: taskNodeId, id: taskNodeId,
type: 'task', type: 'task',
@@ -841,9 +831,9 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
task, task,
isEditing: false, isEditing: false,
editingContent: '', editingContent: '',
isBranchTask: true, // 标记为分支任务,可用于特殊样式 isBranchTask: true,
branchId: String(timestamp), // 添加分支ID用于识别和分组 branchId: String(timestamp),
updateKey: Date.now() // 添加更新时间戳,用于强制组件重新渲染 updateKey: Date.now()
} }
} }
@@ -852,7 +842,6 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
// 创建连接边 // 创建连接边
if (index === 0) { if (index === 0) {
// 连接到父节点
const newEdge: Edge = { const newEdge: Edge = {
id: `edge-${taskId}-${taskNodeId}`, id: `edge-${taskId}-${taskNodeId}`,
source: taskId, source: taskId,
@@ -873,7 +862,6 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
edges.value.push(newEdge) edges.value.push(newEdge)
newBranchEdges.push(newEdge) newBranchEdges.push(newEdge)
} else { } else {
// 后续任务连接到前一个任务(左右连接)
const prevTaskNodeId = taskNodeIds[index - 1] const prevTaskNodeId = taskNodeIds[index - 1]
const newEdge: Edge = { const newEdge: Edge = {
id: `edge-${prevTaskNodeId}-${taskNodeId}`, id: `edge-${prevTaskNodeId}-${taskNodeId}`,
@@ -896,10 +884,8 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
newBranchEdges.push(newEdge) newBranchEdges.push(newEdge)
} }
}) })
}
// 保存分支到 store // 保存分支到 store(此时只有大纲)
if (newBranchNodes.length > 0) {
selectionStore.addFlowBranch({ selectionStore.addFlowBranch({
parentNodeId: taskId, parentNodeId: taskId,
branchContent: branchContent, branchContent: branchContent,
@@ -908,6 +894,82 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
edges: newBranchEdges, edges: newBranchEdges,
tasks: JSON.parse(JSON.stringify(newTasks)) 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
}
} }
} else { } else {
// ========== 任务节点级别分支 ========== // ========== 任务节点级别分支 ==========
@@ -1085,28 +1147,8 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
} else { } else {
newTasks = [] 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)
}
}
ElMessage.success('任务大纲分支创建成功')
}
// 创建多个任务节点,每个任务显示为单独的流程卡片
if (newTasks.length > 0) { if (newTasks.length > 0) {
// 计算分支起始位置:在父任务节点的右下方 // 计算分支起始位置:在父任务节点的右下方
const branchBaseX = parentNode.position.x + NODE_WIDTH + NODE_GAP const branchBaseX = parentNode.position.x + NODE_WIDTH + NODE_GAP
@@ -1114,7 +1156,7 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
const timestamp = Date.now() const timestamp = Date.now()
const taskNodeIds: string[] = [] const taskNodeIds: string[] = []
// 为每个任务创建节点 // 为每个任务创建节点(只显示大纲)
newTasks.forEach((task, index) => { newTasks.forEach((task, index) => {
const taskNodeId = `branch-task-${taskId}-${timestamp}-${index}` const taskNodeId = `branch-task-${taskId}-${timestamp}-${index}`
taskNodeIds.push(taskNodeId) taskNodeIds.push(taskNodeId)
@@ -1123,7 +1165,7 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
const taskX = branchBaseX + index * (NODE_WIDTH + NODE_GAP) const taskX = branchBaseX + index * (NODE_WIDTH + NODE_GAP)
const taskY = branchBaseY const taskY = branchBaseY
// 创建任务节点(只显示 StepName // 创建任务节点
const newTaskNode: Node = { const newTaskNode: Node = {
id: taskNodeId, id: taskNodeId,
type: 'task', type: 'task',
@@ -1132,8 +1174,8 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
task, task,
isEditing: false, isEditing: false,
editingContent: '', editingContent: '',
isBranchTask: true, // 标记为分支任务,可用于特殊样式 isBranchTask: true,
updateKey: Date.now() // 添加更新时间戳,用于强制组件重新渲染 updateKey: Date.now()
} }
} }
@@ -1186,10 +1228,8 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
newBranchEdges.push(newEdge) newBranchEdges.push(newEdge)
} }
}) })
}
// 保存分支数据到 store // 保存分支数据到 store(此时只有大纲)
if (newBranchNodes.length > 0) {
selectionStore.addFlowBranch({ selectionStore.addFlowBranch({
parentNodeId: taskId, parentNodeId: taskId,
branchContent: branchContent, branchContent: branchContent,
@@ -1198,6 +1238,82 @@ const handleAddBranch = async (taskId: string, branchContent: string) => {
edges: newBranchEdges, edges: newBranchEdges,
tasks: JSON.parse(JSON.stringify(newTasks)) 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
}
} }
} }
@@ -1297,4 +1413,38 @@ defineExpose({
:deep(.vue-flow__edge.selected .vue-flow__edge-path) { :deep(.vue-flow__edge.selected .vue-flow__edge-path) {
stroke: #409eff; 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> </style>

View File

@@ -5,6 +5,7 @@ import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/
import { type IRawStepTask, useAgentsStore } from '@/stores' import { type IRawStepTask, useAgentsStore } from '@/stores'
import { computed, ref, nextTick, watch, onMounted } from 'vue' import { computed, ref, nextTick, watch, onMounted } from 'vue'
import { AnchorLocations } from '@jsplumb/browser-ui' import { AnchorLocations } from '@jsplumb/browser-ui'
import { Loading } from '@element-plus/icons-vue'
import MultiLineTooltip from '@/components/MultiLineTooltip/index.vue' import MultiLineTooltip from '@/components/MultiLineTooltip/index.vue'
import Bg from './Bg.vue' import Bg from './Bg.vue'
import BranchButton from './components/BranchButton.vue' import BranchButton from './components/BranchButton.vue'
@@ -37,6 +38,22 @@ const collaborationProcess = computed(() => {
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? [] 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 editingTaskId = ref<string | null>(null)
const editingContent = ref('') const editingContent = ref('')
@@ -258,6 +275,13 @@ defineExpose({
任务大纲 任务大纲
</div> </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 <div
v-loading="agentsStore.agentRawPlan.loading" v-loading="agentsStore.agentRawPlan.loading"
class="flex-1 w-full overflow-y-auto relative" 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) { :deep(.el-input__wrapper) {
background: transparent; background: transparent;