feat:任务执行结果性能优化
This commit is contained in:
@@ -137,5 +137,6 @@ def AgentSelectModify_init(stepTask, General_Goal, Agent_Board):
|
||||
|
||||
|
||||
def AgentSelectModify_addAspect(aspectList, Agent_Board):
|
||||
scoreTable = agentAbilityScoring(Agent_Board, aspectList)
|
||||
newAspect = aspectList[-1]
|
||||
scoreTable = agentAbilityScoring(Agent_Board, [newAspect])
|
||||
return scoreTable
|
||||
|
||||
@@ -1,55 +1,53 @@
|
||||
from AgentCoord.PlanEngine.planOutline_Generator import generate_PlanOutline
|
||||
from AgentCoord.PlanEngine.AgentSelection_Generator import (
|
||||
generate_AgentSelection,
|
||||
)
|
||||
from AgentCoord.PlanEngine.taskProcess_Generator import generate_TaskProcess
|
||||
import AgentCoord.util as util
|
||||
# from AgentCoord.PlanEngine.AgentSelection_Generator import (
|
||||
# generate_AgentSelection,
|
||||
# )
|
||||
|
||||
|
||||
def generate_basePlan(
|
||||
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(
|
||||
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:
|
||||
Current_Task = {
|
||||
"TaskName": stepItem["StepName"],
|
||||
"InputObject_List": stepItem["InputObject_List"],
|
||||
"OutputObject": stepItem["OutputObject"],
|
||||
"TaskContent": stepItem["TaskContent"],
|
||||
# # 为每个步骤分配智能体
|
||||
# Current_Task = {
|
||||
# "TaskName": stepItem["StepName"],
|
||||
# "InputObject_List": stepItem["InputObject_List"],
|
||||
# "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["General Goal"] = General_Goal
|
||||
return basePlan
|
||||
|
||||
return basePlan
|
||||
@@ -1,7 +1,8 @@
|
||||
from flask import Flask, request, jsonify
|
||||
from flask import Flask, request, jsonify, Response, stream_with_context
|
||||
import json
|
||||
from DataProcess import Add_Collaboration_Brief_FrontEnd
|
||||
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.fill_stepTask import fill_stepTask
|
||||
from AgentCoord.PlanEngine.fill_stepTask_TaskProcess import (
|
||||
@@ -257,6 +258,45 @@ def Handle_executePlan():
|
||||
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"])
|
||||
def Handle_saveRequestCashe():
|
||||
with open(
|
||||
|
||||
@@ -34,6 +34,47 @@ export type IExecuteRawResponse = {
|
||||
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 {
|
||||
goal: string
|
||||
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: {
|
||||
branch_Number: number
|
||||
Modification_Requirement: string
|
||||
@@ -122,7 +251,9 @@ class Api {
|
||||
})
|
||||
}
|
||||
|
||||
// 分支任务流程
|
||||
/**
|
||||
* 分支任务流程
|
||||
*/
|
||||
branchTaskProcess = (data: {
|
||||
branch_Number: number
|
||||
Modification_Requirement: string
|
||||
@@ -146,7 +277,6 @@ class Api {
|
||||
}
|
||||
|
||||
fillStepTask = async (data: { goal: string; stepTask: any }): Promise<IRawStepTask> => {
|
||||
// 后端返回格式:包含 Collaboration_Brief_FrontEnd(大写 FrontEnd)
|
||||
const response = await request<
|
||||
{
|
||||
'General Goal': string
|
||||
@@ -179,14 +309,11 @@ class Api {
|
||||
},
|
||||
})
|
||||
|
||||
// 数据转换:后端的 Collaboration_Brief_FrontEnd → 前端的 Collaboration_Brief_frontEnd
|
||||
|
||||
const vec2Hsl = (color: number[]): string => {
|
||||
const [h, s, l] = color
|
||||
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> }> = {}
|
||||
if (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 {
|
||||
StepName: response.StepName || '',
|
||||
TaskContent: response.TaskContent || '',
|
||||
@@ -219,7 +348,6 @@ class Api {
|
||||
stepTask: IApiStepTask
|
||||
agents: string[]
|
||||
}): Promise<IApiStepTask> => {
|
||||
// 后端返回格式: IRawStepTask
|
||||
const response = await request<
|
||||
{
|
||||
'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 [h, s, l] = color
|
||||
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 } }> = {}
|
||||
if (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) => ({
|
||||
id: action.ID,
|
||||
type: action.ActionType,
|
||||
@@ -295,7 +417,6 @@ class Api {
|
||||
inputs: action.ImportantInput,
|
||||
}))
|
||||
|
||||
// 4. 构建前端格式的 IApiStepTask
|
||||
return {
|
||||
name: response.StepName || '',
|
||||
content: response.TaskContent || '',
|
||||
@@ -310,12 +431,13 @@ class Api {
|
||||
}
|
||||
}
|
||||
|
||||
// 为每个智能体评分
|
||||
/**
|
||||
* 为每个智能体评分
|
||||
*/
|
||||
agentSelectModifyInit = async (data: {
|
||||
goal: string
|
||||
stepTask: any
|
||||
}): Promise<Record<string, Record<string, { reason: string; score: number }>>> => {
|
||||
// 后端返回:维度 -> agent -> { Reason, Score }
|
||||
const response = await request<
|
||||
{
|
||||
'General Goal': string
|
||||
@@ -336,11 +458,9 @@ class Api {
|
||||
},
|
||||
})
|
||||
|
||||
// 数据转换:后端格式 (维度->agent) → 前端格式 (agent->维度)
|
||||
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
|
||||
|
||||
for (const [aspect, agents] of Object.entries(response)) {
|
||||
// aspect: 维度名称, agents: { agentName: { Reason, Score } }
|
||||
for (const [agentName, scoreInfo] of Object.entries(agents)) {
|
||||
if (!transformedData[agentName]) {
|
||||
transformedData[agentName] = {}
|
||||
@@ -355,15 +475,15 @@ class Api {
|
||||
return transformedData
|
||||
}
|
||||
|
||||
// 添加新的评估维度
|
||||
// 定义返回类型(与 Mock 数据格式一致)
|
||||
/**
|
||||
* 添加新的评估维度
|
||||
*/
|
||||
agentSelectModifyAddAspect = async (data: {
|
||||
aspectList: string[]
|
||||
}): Promise<{
|
||||
aspectName: string
|
||||
agentScores: Record<string, { score: number; reason: string }>
|
||||
}> => {
|
||||
// 后端返回:维度 -> agent -> { Reason, Score }
|
||||
const response = await request<
|
||||
{
|
||||
aspectList: string[]
|
||||
@@ -377,13 +497,14 @@ class Api {
|
||||
},
|
||||
})
|
||||
|
||||
// 获取新添加的维度(最后一个)
|
||||
/**
|
||||
* 获取新添加的维度
|
||||
*/
|
||||
const newAspect = data.aspectList[data.aspectList.length - 1]
|
||||
if (!newAspect) {
|
||||
throw new Error('aspectList is empty')
|
||||
}
|
||||
|
||||
// 提取该维度的数据:维度 -> agent -> { Reason, Score } → agent -> { score, reason }
|
||||
const newAspectAgents = response[newAspect]
|
||||
const agentScores: Record<string, { score: number; reason: string }> = {}
|
||||
|
||||
@@ -401,23 +522,18 @@ class Api {
|
||||
agentScores,
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Mock API(开发阶段使用)====================
|
||||
// Mock API 使用与真实 API 相同的数据转换逻辑,确保将来切换无缝
|
||||
|
||||
// Mock: 为每个智能体评分
|
||||
/**
|
||||
* ==================== Mock API(开发阶段使用)====================
|
||||
*为每个智能体评分
|
||||
*/
|
||||
mockAgentSelectModifyInit = async (): Promise<
|
||||
Record<string, Record<string, { reason: string; score: number }>>
|
||||
> => {
|
||||
// 调用Mock后端数据(维度 -> agent -> { Reason, Score })
|
||||
const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyInit()
|
||||
|
||||
// 数据转换:后端格式 (维度->agent) → 前端格式 (agent->维度)
|
||||
// 注意:此转换逻辑与真实API完全一致
|
||||
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
|
||||
|
||||
for (const [aspect, agents] of Object.entries(response)) {
|
||||
// aspect: 维度名称, agents: { agentName: { Reason, Score } }
|
||||
for (const [agentName, scoreInfo] of Object.entries(agents)) {
|
||||
if (!transformedData[agentName]) {
|
||||
transformedData[agentName] = {}
|
||||
@@ -432,26 +548,21 @@ class Api {
|
||||
return transformedData
|
||||
}
|
||||
|
||||
// Mock: 添加新的评估维度
|
||||
mockAgentSelectModifyAddAspect = async (data: {
|
||||
aspectList: string[]
|
||||
}): Promise<{
|
||||
aspectName: string
|
||||
agentScores: Record<string, { score: number; reason: string }>
|
||||
}> => {
|
||||
// 调用Mock后端数据(维度 -> agent -> { Reason, Score })
|
||||
const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyAddAspect(
|
||||
data.aspectList,
|
||||
)
|
||||
|
||||
// 获取新添加的维度(最后一个)
|
||||
const newAspect = data.aspectList[data.aspectList.length - 1]
|
||||
if (!newAspect) {
|
||||
throw new Error('aspectList is empty')
|
||||
}
|
||||
|
||||
// 提取该维度的数据:维度 -> agent -> { Reason, Score } → agent -> { score, reason }
|
||||
// 注意:此转换逻辑与真实API完全一致
|
||||
const newAspectAgents = response[newAspect]
|
||||
const agentScores: Record<string, { score: number; reason: string }> = {}
|
||||
|
||||
@@ -470,29 +581,22 @@ class Api {
|
||||
}
|
||||
}
|
||||
|
||||
// Mock: 填充智能体任务流程
|
||||
mockFillStepTaskTaskProcess = async (data: {
|
||||
goal: string
|
||||
stepTask: IApiStepTask
|
||||
agents: string[]
|
||||
}): Promise<IApiStepTask> => {
|
||||
// 调用Mock后端数据(后端原始格式)
|
||||
const response: RawAgentTaskProcessResponse = await mockBackendFillAgentTaskProcess(
|
||||
data.goal,
|
||||
data.stepTask,
|
||||
data.agents,
|
||||
)
|
||||
|
||||
// 数据转换:后端格式 → 前端格式
|
||||
// 注意:此转换逻辑与真实API完全一致
|
||||
|
||||
// 1. 转换颜色格式:[h, s, l] → "hsl(h, s%, l%)"
|
||||
const vec2Hsl = (color: number[]): string => {
|
||||
const [h, s, l] = color
|
||||
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 } }> = {}
|
||||
if (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) => ({
|
||||
id: action.ID,
|
||||
type: action.ActionType,
|
||||
@@ -514,7 +617,6 @@ class Api {
|
||||
inputs: action.ImportantInput,
|
||||
}))
|
||||
|
||||
// 4. 构建前端格式的 IApiStepTask
|
||||
return {
|
||||
name: response.StepName || '',
|
||||
content: response.TaskContent || '',
|
||||
@@ -529,7 +631,6 @@ class Api {
|
||||
}
|
||||
}
|
||||
|
||||
// Mock: 分支任务大纲
|
||||
mockBranchPlanOutline = async (data: {
|
||||
branch_Number: number
|
||||
Modification_Requirement: string
|
||||
@@ -538,7 +639,6 @@ class Api {
|
||||
initialInputs: string[]
|
||||
goal: string
|
||||
}): Promise<IRawPlanResponse> => {
|
||||
// 直接调用 Mock API(已经返回 IRawPlanResponse 格式)
|
||||
const response = await mockBranchPlanOutlineAPI({
|
||||
branch_Number: data.branch_Number,
|
||||
Modification_Requirement: data.Modification_Requirement,
|
||||
@@ -551,9 +651,7 @@ class Api {
|
||||
return response
|
||||
}
|
||||
|
||||
// Mock: 填充任务流程
|
||||
mockFillStepTask = async (data: { goal: string; stepTask: any }): Promise<any> => {
|
||||
// 直接调用 Mock API(已经返回 IRawStepTask 格式)
|
||||
const response = await mockFillStepTaskAPI({
|
||||
General_Goal: data.goal,
|
||||
stepTask: data.stepTask,
|
||||
@@ -562,7 +660,6 @@ class Api {
|
||||
return response
|
||||
}
|
||||
|
||||
// Mock: 分支任务流程
|
||||
mockBranchTaskProcess = async (data: {
|
||||
branch_Number: number
|
||||
Modification_Requirement: string
|
||||
@@ -571,7 +668,6 @@ class Api {
|
||||
stepTaskExisting: any
|
||||
goal: string
|
||||
}): Promise<BranchAction[][]> => {
|
||||
// 直接调用 Mock API(已经返回 BranchAction[][] 格式,与后端完全一致)
|
||||
const response = await mockBranchTaskProcessAPI({
|
||||
branch_Number: data.branch_Number,
|
||||
Modification_Requirement: data.Modification_Requirement,
|
||||
|
||||
@@ -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