feat:1.数据库存储功能添加(初版)2.后端REST API版本代码清理

This commit is contained in:
liailing1026
2026-02-25 10:55:51 +08:00
parent f736cd104a
commit 2140cfaf92
35 changed files with 3912 additions and 2981 deletions

View File

@@ -1,4 +1,3 @@
import request from '@/utils/request'
import websocket from '@/utils/websocket'
import type { Agent, IApiStepTask, IRawPlanResponse, IRawStepTask } from '@/stores'
import { withRetry } from '@/utils/retry'
@@ -30,7 +29,7 @@ export type IExecuteRawResponse = {
}
/**
* SSE 流式事件类型
* WebSocket 流式事件类型
*/
export type StreamingEvent =
| {
@@ -77,101 +76,47 @@ export interface IFillAgentSelectionRequest {
}
class Api {
// 默认使用WebSocket
private useWebSocketDefault = true
setAgents = (
data: Pick<Agent, 'Name' | 'Profile' | 'apiUrl' | 'apiKey' | 'apiModel'>[],
useWebSocket: boolean = this.useWebSocketDefault,
) => {
// 如果启用WebSocket且已连接使用WebSocket
if (useWebSocket && websocket.connected) {
return websocket.send('set_agents', data)
}
// 否则使用REST API
return request({
url: '/setAgents',
data,
method: 'POST',
})
// 提取响应数据的公共方法
private extractResponse<T>(raw: any): T {
return (raw.data || raw) as T
}
// 颜色向量转 HSL 字符串
private vec2Hsl = (color: number[]): string => {
const [h, s, l] = color
return `hsl(${h}, ${s}%, ${l}%)`
}
setAgents = (data: Pick<Agent, 'Name' | 'Profile' | 'apiUrl' | 'apiKey' | 'apiModel'>[]) =>
websocket.send('set_agents', data)
getAgents = (user_id: string = 'default_user') => websocket.send('get_agents', { user_id })
generateBasePlan = (data: {
goal: string
inputs: string[]
apiUrl?: string
apiKey?: string
apiModel?: string
useWebSocket?: boolean
onProgress?: (progress: {
status: string
stage?: string
message?: string
[key: string]: any
}) => void
}) => {
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
// 如果启用WebSocket且已连接使用WebSocket
if (useWs && websocket.connected) {
return websocket.send(
'generate_base_plan',
{
'General Goal': data.goal,
'Initial Input Object': data.inputs,
apiUrl: data.apiUrl,
apiKey: data.apiKey,
apiModel: data.apiModel,
},
undefined,
data.onProgress,
)
}
// 否则使用REST API
return request<unknown, IRawPlanResponse>({
url: '/generate_basePlan',
method: 'POST',
data: {
}) =>
websocket.send(
'generate_base_plan',
{
'General Goal': data.goal,
'Initial Input Object': data.inputs,
apiUrl: data.apiUrl,
apiKey: data.apiKey,
apiModel: data.apiModel,
},
})
}
executePlan = (plan: IRawPlanResponse) => {
return request<unknown, IExecuteRawResponse[]>({
url: '/executePlan',
method: 'POST',
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,
})),
})),
},
},
})
}
undefined,
data.onProgress,
)
/**
* 优化版流式执行计划(支持动态追加步骤)
@@ -182,23 +127,25 @@ class Api {
onMessage: (event: StreamingEvent) => void,
onError?: (error: Error) => void,
onComplete?: () => void,
useWebSocket?: boolean,
_useWebSocket?: boolean,
existingKeyObjects?: Record<string, any>,
enableDynamic?: boolean,
onExecutionStarted?: (executionId: string) => void,
executionId?: string,
restartFromStepIndex?: number, // 新增:从指定步骤重新执行的索引
rehearsalLog?: any[], // 新增:传递截断后的 RehearsalLog
restartFromStepIndex?: number,
rehearsalLog?: any[],
TaskID?: string,
) => {
const useWs = useWebSocket !== undefined ? useWebSocket : this.useWebSocketDefault
// eslint-disable-next-line @typescript-eslint/no-unused-vars
void _useWebSocket // 保留参数位置以保持兼容性
const data = {
RehearsalLog: rehearsalLog || [], // 使用传递的 RehearsalLog
RehearsalLog: rehearsalLog || [], // 使用传递的 RehearsalLog
num_StepToRun: null,
existingKeyObjects: existingKeyObjects || {},
enable_dynamic: enableDynamic || false,
execution_id: executionId || null,
restart_from_step_index: restartFromStepIndex ?? null, // 新增:传递重新执行索引
task_id: TaskID || null, // 任务唯一标识,用于写入数据库
plan: {
'Initial Input Object': plan['Initial Input Object'],
'General Goal': plan['General Goal'],
@@ -220,103 +167,46 @@ class Api {
},
}
// 如果启用WebSocket且已连接使用WebSocket
if (useWs && websocket.connected) {
websocket.subscribe(
'execute_plan_optimized',
data,
// onProgress
(progressData) => {
try {
let event: StreamingEvent
websocket.subscribe(
'execute_plan_optimized',
data,
// onProgress
(progressData) => {
try {
let event: StreamingEvent
// 处理不同类型的progress数据
if (typeof progressData === 'string') {
event = JSON.parse(progressData)
} else {
event = progressData as StreamingEvent
}
// 处理特殊事件类型
if (event && typeof event === 'object') {
// 检查是否是execution_started事件
if ('status' in event && event.status === 'execution_started') {
if ('execution_id' in event && onExecutionStarted) {
onExecutionStarted(event.execution_id as string)
}
return
}
}
onMessage(event)
} catch (e) {
// Failed to parse WebSocket data
// 处理不同类型的progress数据
if (typeof progressData === 'string') {
event = JSON.parse(progressData)
} else {
event = progressData as StreamingEvent
}
},
// onComplete
() => {
onComplete?.()
},
// onError
(error) => {
onError?.(error)
},
)
return
}
// 否则使用原有的SSE方式
// 处理特殊事件类型
if (event && typeof event === 'object') {
// 检查是否是execution_started事件
if ('status' in event && event.status === 'execution_started') {
if ('execution_id' in event && onExecutionStarted) {
onExecutionStarted(event.execution_id as string)
}
return
}
}
fetch('/api/executePlanOptimized', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
onMessage(event)
} catch (e) {
// Failed to parse WebSocket data
}
},
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) {
// Failed to parse SSE data
}
}
}
}
})
.catch((error) => {
// onComplete
() => {
onComplete?.()
},
// onError
(error) => {
onError?.(error)
})
},
)
}
/**
@@ -329,38 +219,16 @@ class Api {
Baseline_Completion: number
initialInputs: string[]
goal: string
useWebSocket?: boolean
onProgress?: (progress: {
status: string
stage?: string
message?: string
[key: string]: any
}) => void
}) => {
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
// 如果启用WebSocket且已连接使用WebSocket
if (useWs && websocket.connected) {
return websocket.send(
'branch_plan_outline',
{
branch_Number: data.branch_Number,
Modification_Requirement: data.Modification_Requirement,
Existing_Steps: data.Existing_Steps,
Baseline_Completion: data.Baseline_Completion,
'Initial Input Object': data.initialInputs,
'General Goal': data.goal,
},
undefined,
data.onProgress,
)
}
// 否则使用REST API
return request<unknown, IRawPlanResponse>({
url: '/branch_PlanOutline',
method: 'POST',
data: {
}) =>
websocket.send(
'branch_plan_outline',
{
branch_Number: data.branch_Number,
Modification_Requirement: data.Modification_Requirement,
Existing_Steps: data.Existing_Steps,
@@ -368,8 +236,9 @@ class Api {
'Initial Input Object': data.initialInputs,
'General Goal': data.goal,
},
})
}
undefined,
data.onProgress,
)
/**
* 分支任务流程
@@ -381,38 +250,16 @@ class Api {
Baseline_Completion: number
stepTaskExisting: any
goal: string
useWebSocket?: boolean
onProgress?: (progress: {
status: string
stage?: string
message?: string
[key: string]: any
}) => void
}) => {
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
// 如果启用WebSocket且已连接使用WebSocket
if (useWs && websocket.connected) {
return websocket.send(
'branch_task_process',
{
branch_Number: data.branch_Number,
Modification_Requirement: data.Modification_Requirement,
Existing_Steps: data.Existing_Steps,
Baseline_Completion: data.Baseline_Completion,
stepTaskExisting: data.stepTaskExisting,
'General Goal': data.goal,
},
undefined,
data.onProgress,
)
}
// 否则使用REST API
return request<unknown, BranchAction[][]>({
url: '/branch_TaskProcess',
method: 'POST',
data: {
}) =>
websocket.send(
'branch_task_process',
{
branch_Number: data.branch_Number,
Modification_Requirement: data.Modification_Requirement,
Existing_Steps: data.Existing_Steps,
@@ -420,14 +267,15 @@ class Api {
stepTaskExisting: data.stepTaskExisting,
'General Goal': data.goal,
},
})
}
undefined,
data.onProgress,
)
fillStepTask = async (data: {
goal: string
stepTask: any
generation_id?: string
useWebSocket?: boolean
TaskID?: string
onProgress?: (progress: {
status: string
stage?: string
@@ -435,72 +283,31 @@ class Api {
[key: string]: any
}) => void
}): Promise<IRawStepTask> => {
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
// 定义实际的 API 调用逻辑
const executeRequest = async (): Promise<any> => {
if (useWs && websocket.connected) {
return await websocket.send(
const rawResponse = await withRetry(
() =>
websocket.send(
'fill_step_task',
{
'General Goal': data.goal,
stepTask: data.stepTask,
generation_id: data.generation_id || '',
task_id: data.TaskID || '',
},
undefined,
data.onProgress,
)
}
// 否则使用REST API
return await request<
{
'General Goal': string
stepTask: any
),
{
maxRetries: 3,
initialDelayMs: 2000,
onRetry: (error, attempt, delay) => {
console.warn(` [fillStepTask] 第${attempt}次重试,等待 ${delay}ms...`, error?.message)
},
{
AgentSelection?: string[]
Collaboration_Brief_FrontEnd?: {
template: string
data: Record<string, { text: string; color: number[] }>
}
InputObject_List?: string[]
OutputObject?: string
StepName?: string
TaskContent?: string
TaskProcess?: Array<{
ID: string
ActionType: string
AgentName: string
Description: string
ImportantInput: string[]
}>
}
>({
url: '/fill_stepTask',
method: 'POST',
data: {
'General Goal': data.goal,
stepTask: data.stepTask,
},
})
}
// 使用重试机制执行请求
const rawResponse = await withRetry(executeRequest, {
maxRetries: 3,
initialDelayMs: 2000,
onRetry: (error, attempt, delay) => {
console.warn(`⚠️ [fillStepTask] 第${attempt}次重试,等待 ${delay}ms...`, error?.message)
},
})
)
// WebSocket 返回格式: { data: {...}, generation_id, execution_id }
// REST API 返回格式: {...}
const response = rawResponse.data || rawResponse
const vec2Hsl = (color: number[]): string => {
const [h, s, l] = color
return `hsl(${h}, ${s}%, ${l}%)`
let response = this.extractResponse<any>(rawResponse)
if (response?.filled_stepTask) {
response = response.filled_stepTask
}
const briefData: Record<string, { text: string; style?: Record<string, string> }> = {}
@@ -509,15 +316,12 @@ class Api {
briefData[key] = {
text: (value as { text: string; color: number[] }).text,
style: {
background: vec2Hsl((value as { text: string; color: number[] }).color),
background: this.vec2Hsl((value as { text: string; color: number[] }).color),
},
}
}
}
/**
* 构建前端格式的 IRawStepTask
*/
return {
StepName: response.StepName || '',
TaskContent: response.TaskContent || '',
@@ -536,7 +340,7 @@ class Api {
goal: string
stepTask: IApiStepTask
agents: string[]
useWebSocket?: boolean
TaskID?: string
onProgress?: (progress: {
status: string
stage?: string
@@ -544,15 +348,13 @@ class Api {
[key: string]: any
}) => void
}): Promise<IApiStepTask> => {
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
// 定义实际的 API 调用逻辑
const executeRequest = async (): Promise<any> => {
if (useWs && websocket.connected) {
return await websocket.send(
const rawResponse = await withRetry(
() =>
websocket.send(
'fill_step_task_process',
{
'General Goal': data.goal,
task_id: data.TaskID || undefined,
stepTask_lackTaskProcess: {
StepName: data.stepTask.name,
TaskContent: data.stepTask.content,
@@ -560,76 +362,26 @@ class Api {
OutputObject: data.stepTask.output,
AgentSelection: data.agents,
},
agents: data.agents,
},
undefined,
data.onProgress,
)
}
// 否则使用REST API
return await request<
{
'General Goal': string
stepTask_lackTaskProcess: {
StepName: string
TaskContent: string
InputObject_List: string[]
OutputObject: string
AgentSelection: string[]
}
),
{
maxRetries: 3,
initialDelayMs: 2000,
onRetry: (error, attempt, delay) => {
console.warn(
`[fillStepTaskTaskProcess] 第${attempt}次重试,等待 ${delay}ms...`,
error?.message,
)
},
{
StepName?: string
TaskContent?: string
InputObject_List?: string[]
OutputObject?: string
AgentSelection?: string[]
TaskProcess?: Array<{
ID: string
ActionType: string
AgentName: string
Description: string
ImportantInput: string[]
}>
Collaboration_Brief_FrontEnd?: {
template: string
data: Record<string, { text: string; color: number[] }>
}
}
>({
url: '/fill_stepTask_TaskProcess',
method: 'POST',
data: {
'General Goal': data.goal,
stepTask_lackTaskProcess: {
StepName: data.stepTask.name,
TaskContent: data.stepTask.content,
InputObject_List: data.stepTask.inputs,
OutputObject: data.stepTask.output,
AgentSelection: data.agents,
},
},
})
}
// 使用重试机制执行请求
const rawResponse = await withRetry(executeRequest, {
maxRetries: 3,
initialDelayMs: 2000,
onRetry: (error, attempt, delay) => {
console.warn(
`⚠️ [fillStepTaskTaskProcess] 第${attempt}次重试,等待 ${delay}ms...`,
error?.message,
)
},
})
)
// WebSocket 返回格式: { data: {...}, generation_id, execution_id }
// REST API 返回格式: {...}
const response = rawResponse.data || rawResponse
const vec2Hsl = (color: number[]): string => {
const [h, s, l] = color
return `hsl(${h}, ${s}%, ${l}%)`
let response = this.extractResponse<any>(rawResponse)
if (response?.filled_stepTask) {
response = response.filled_stepTask
}
const briefData: Record<string, { text: string; style: { background: string } }> = {}
@@ -638,7 +390,7 @@ class Api {
briefData[key] = {
text: (value as { text: string; color: number[] }).text,
style: {
background: vec2Hsl((value as { text: string; color: number[] }).color),
background: this.vec2Hsl((value as { text: string; color: number[] }).color),
},
}
}
@@ -672,7 +424,7 @@ class Api {
agentSelectModifyInit = async (data: {
goal: string
stepTask: any
useWebSocket?: boolean
TaskID?: string
onProgress?: (progress: {
status: string
stage?: string
@@ -680,78 +432,54 @@ class Api {
[key: string]: any
}) => void
}): Promise<Record<string, Record<string, { reason: string; score: number }>>> => {
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
// 调试日志:打印请求参数
const requestPayload = {
'General Goal': data.goal,
stepTask: {
Id: data.stepTask.Id || data.stepTask.id,
StepName: data.stepTask.StepName || data.stepTask.name,
TaskContent: data.stepTask.TaskContent || data.stepTask.content,
InputObject_List: data.stepTask.InputObject_List || data.stepTask.inputs,
OutputObject: data.stepTask.OutputObject || data.stepTask.output,
},
task_id: data.TaskID || '',
}
console.log('🔍 [agentSelectModifyInit] 请求参数:', {
goal: requestPayload['General Goal'],
stepTaskName: requestPayload.stepTask.StepName,
stepTaskContentLength: requestPayload.stepTask.TaskContent?.length,
useWebSocket: useWs && websocket.connected,
wsConnected: websocket.connected,
})
// 定义实际的 API 调用逻辑
const executeRequest = async (): Promise<
Record<string, Record<string, { Reason: string; Score: number }>>
> => {
if (useWs && websocket.connected) {
return await websocket.send(
const rawResponse = await withRetry(
() =>
websocket.send(
'agent_select_modify_init',
requestPayload,
undefined,
data.onProgress,
)
}
// 否则使用REST API
return await request<
{
'General Goal': string
stepTask: any
),
{
maxRetries: 3,
initialDelayMs: 2000,
onRetry: (error, attempt, delay) => {
console.warn(
`[agentSelectModifyInit] 第${attempt}次重试,等待 ${delay}ms...`,
error?.message,
)
},
Record<string, Record<string, { Reason: string; Score: number }>>
>({
url: '/agentSelectModify_init',
method: 'POST',
data: requestPayload,
})
}
// 使用重试机制执行请求
const rawResponse = await withRetry(executeRequest, {
maxRetries: 3,
initialDelayMs: 2000,
onRetry: (error, attempt, delay) => {
console.warn(
`⚠️ [agentSelectModifyInit] 第${attempt}次重试,等待 ${delay}ms...`,
error?.message,
)
},
})
)
// WebSocket 返回格式: { data: {...}, generation_id, execution_id }
// REST API 返回格式: {...}
const response = rawResponse.data || rawResponse
let response = this.extractResponse<any>(rawResponse)
if (response?.scoreTable) {
response = response.scoreTable
}
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
// 确保 response 存在且是有效对象
if (!response || typeof response !== 'object' || Array.isArray(response)) {
console.warn('[agentSelectModifyInit] 后端返回数据格式异常:', response)
return transformedData
}
for (const [aspect, agents] of Object.entries(response)) {
for (const [agentName, scoreInfo] of Object.entries(agents as Record<string, { Reason: string; Score: number }> || {})) {
for (const [agentName, scoreInfo] of Object.entries(
(agents as Record<string, { Reason: string; Score: number }>) || {},
)) {
if (!transformedData[agentName]) {
transformedData[agentName] = {}
}
@@ -770,7 +498,14 @@ class Api {
*/
agentSelectModifyAddAspect = async (data: {
aspectList: string[]
useWebSocket?: boolean
stepTask?: {
Id?: string
StepName?: string
TaskContent?: string
InputObject_List?: string[]
OutputObject?: string
}
TaskID?: string
onProgress?: (progress: {
status: string
stage?: string
@@ -781,53 +516,33 @@ class Api {
aspectName: string
agentScores: Record<string, { score: number; reason: string }>
}> => {
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
let response: Record<string, Record<string, { Reason: string; Score: number }>>
const rawResponse = await websocket.send(
'agent_select_modify_add_aspect',
{
aspectList: data.aspectList,
stepTask: data.stepTask,
task_id: data.TaskID || '',
},
undefined,
data.onProgress,
)
// 如果启用WebSocket且已连接使用WebSocket
if (useWs && websocket.connected) {
const rawResponse = await websocket.send(
'agent_select_modify_add_aspect',
{
aspectList: data.aspectList,
},
undefined,
data.onProgress,
)
// WebSocket 返回格式: { data: {...}, generation_id, execution_id }
response = rawResponse.data || rawResponse
} else {
// 否则使用REST API
response = await request<
{
aspectList: string[]
},
Record<string, Record<string, { Reason: string; Score: number }>>
>({
url: '/agentSelectModify_addAspect',
method: 'POST',
data: {
aspectList: data.aspectList,
},
})
}
const response = this.extractResponse<any>(rawResponse)
/**
* 获取新添加的维度
*/
const newAspect = data.aspectList[data.aspectList.length - 1]
if (!newAspect) {
throw new Error('aspectList is empty')
}
const newAspectAgents = response[newAspect]
const scoreTable = response.scoreTable || response
const newAspectAgents = scoreTable[newAspect]
const agentScores: Record<string, { score: number; reason: string }> = {}
if (newAspectAgents) {
for (const [agentName, scoreInfo] of Object.entries(newAspectAgents)) {
for (const [agentName, scoreInfo] of Object.entries(newAspectAgents as Record<string, { Score?: number; score?: number; Reason?: string; reason?: string }>)) {
agentScores[agentName] = {
score: scoreInfo.Score,
reason: scoreInfo.Reason,
score: scoreInfo.Score || scoreInfo.score || 0,
reason: scoreInfo.Reason || scoreInfo.reason || '',
}
}
}
@@ -867,12 +582,126 @@ class Api {
})),
})
// WebSocket 返回格式: { data: {...}, generation_id, execution_id }
// REST API 返回格式: {...}
const response = (rawResponse.data || rawResponse) as { added_count: number }
const response = this.extractResponse<{ added_count: number }>(rawResponse)
return response?.added_count || 0
}
/**
* 保存任务分支数据
* @param taskId 任务ID
* @param branches 分支数据数组
* @returns 是否保存成功
*/
saveBranches = async (taskId: string, branches: any[]): Promise<boolean> => {
if (!websocket.connected) {
throw new Error('WebSocket未连接')
}
try {
const rawResponse = await websocket.send('save_branches', {
task_id: taskId,
branches,
})
const response = this.extractResponse<{ status: string }>(rawResponse)
return response?.status === 'success' || false
} catch (error) {
console.error('保存分支数据失败:', error)
return false
}
}
/**
* 保存任务过程分支数据
* @param TaskID 大任务ID数据库主键
* @param branches 任务过程分支数据Map结构转对象
* @returns 是否保存成功
*/
saveTaskProcessBranches = async (
TaskID: string,
branches: Record<string, Record<string, any[]>>,
): Promise<boolean> => {
if (!websocket.connected) {
throw new Error('WebSocket未连接')
}
try {
const rawResponse = await websocket.send('save_task_process_branches', {
task_id: TaskID,
branches,
})
const response = this.extractResponse<{ status: string }>(rawResponse)
return response?.status === 'success' || false
} catch (error) {
console.error('保存任务过程分支数据失败:', error)
return false
}
}
/**
* 更新任务大纲数据
* @param taskId 任务ID
* @param taskOutline 完整的大纲数据
* @returns 是否更新成功
*/
updateTaskOutline = async (taskId: string, taskOutline: any): Promise<boolean> => {
if (!websocket.connected) {
throw new Error('WebSocket未连接')
}
try {
const rawResponse = await websocket.send('save_task_outline', {
task_id: taskId,
task_outline: taskOutline,
})
const response = this.extractResponse<{ status: string }>(rawResponse)
return response?.status === 'success' || false
} catch (error) {
console.error('更新任务大纲失败:', error)
return false
}
}
/**
* 更新指定步骤的 assigned_agents
* @param params 参数对象
* @returns 是否更新成功
*/
updateAssignedAgents = async (params: {
task_id: string // 大任务ID数据库主键
step_id: string // 步骤级ID小任务UUID
agents: string[] // 选中的 agent 列表
confirmed_groups?: string[][] // 可选:确认的 agent 组合列表
agent_combinations?: Record<string, { process: any; brief: any }> // 可选agent 组合的 TaskProcess 数据
}): Promise<boolean> => {
if (!websocket.connected) {
throw new Error('WebSocket未连接')
}
try {
const rawResponse = await websocket.send('update_assigned_agents', {
task_id: params.task_id,
step_id: params.step_id,
agents: params.agents,
confirmed_groups: params.confirmed_groups,
agent_combinations: params.agent_combinations,
})
const response = this.extractResponse<{ status: string; error?: string }>(rawResponse)
if (response?.status === 'success') {
console.log('更新 assigned_agents 成功:', params)
return true
}
console.warn('更新 assigned_agents 失败:', response?.error)
return false
} catch (error) {
console.error('更新 assigned_agents 失败:', error)
return false
}
}
}
export default new Api()

View File

@@ -1,13 +1,15 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed, reactive, nextTick } from 'vue'
import SvgIcon from '@/components/SvgIcon/index.vue'
import { useAgentsStore, useConfigStore } from '@/stores'
import { useAgentsStore, useConfigStore, useSelectionStore } from '@/stores'
import api from '@/api'
import websocket from '@/utils/websocket'
import { changeBriefs } from '@/utils/collaboration_Brief_FrontEnd.ts'
import { useNotification } from '@/composables/useNotification'
import AssignmentButton from './TaskTemplate/TaskSyllabus/components/AssignmentButton.vue'
import HistoryList from './TaskTemplate/HistoryList/index.vue'
import { withRetry } from '@/utils/retry'
import { Clock } from '@element-plus/icons-vue'
const emit = defineEmits<{
(e: 'search-start'): void
(e: 'search', value: string): void
@@ -15,6 +17,7 @@ const emit = defineEmits<{
const agentsStore = useAgentsStore()
const configStore = useConfigStore()
const selectionStore = useSelectionStore()
const { success, warning, error: notifyError } = useNotification()
const searchValue = ref('')
const triggerOnFocus = ref(true)
@@ -26,6 +29,16 @@ const isStopping = ref(false)
const isStopPending = ref(false)
const currentStepAbortController = ref<{ cancel: () => void } | null>(null)
const currentGenerationId = ref('')
const currentTaskID = ref('') // 后端数据库主键
// 监听 currentTaskID 变化,同步到全局变量(供分支保存使用)
watch(currentTaskID, (newVal) => {
;(window as any).__CURRENT_TASK_ID__ = newVal || ''
console.log('♻️ [Task] currentTaskID 同步到全局变量:', (window as any).__CURRENT_TASK_ID__)
})
const currentPlanOutline = ref<any>(null) // 用于恢复的完整大纲数据
const historyDialogVisible = ref(false) // 历史记录弹窗控制
// 解析URL参数
function getUrlParam(param: string): string | null {
@@ -73,7 +86,7 @@ function handleBlur() {
}
// 预加载所有任务的智能体评分数据
async function preloadAllTaskAgentScores(outlineData: any, goal: string) {
async function preloadAllTaskAgentScores(outlineData: any, goal: string, TaskID: string) {
const tasks = outlineData['Collaboration Process'] || []
if (tasks.length === 0) {
@@ -96,11 +109,13 @@ async function preloadAllTaskAgentScores(outlineData: any, goal: string) {
const agentScores = await api.agentSelectModifyInit({
goal: goal,
stepTask: {
Id: task.Id, // 小任务步骤ID用于数据库存储
StepName: task.StepName,
TaskContent: task.TaskContent,
InputObject_List: task.InputObject_List,
OutputObject: task.OutputObject
}
},
TaskID: TaskID // 后端数据库主键大任务ID
})
// 提取维度列表并存储
@@ -205,14 +220,26 @@ async function handleSearch() {
inputs: []
})
// WebSocket 返回格式: { data: {...}, generation_id, execution_id }
// WebSocket 返回格式: { data: { task_id, generation_id, basePlan }, generation_id, execution_id }
// REST API 返回格式: {...}
const outlineData = response.data || response
// 保存 generation_id
if (response && response.generation_id) {
currentGenerationId.value = response.generation_id
console.log('📋 保存 generation_id:', currentGenerationId.value)
// 提取真正的outline数据
let outlineData: any
if (response?.data?.basePlan) {
// WebSocket新格式数据嵌套在basePlan中
outlineData = response.data.basePlan
currentGenerationId.value = response.data.generation_id || ''
currentTaskID.value = response.data.task_id || ''
console.log('📋 WebSocket格式: 从basePlan提取数据, generation_id:', currentGenerationId.value, 'TaskID:', currentTaskID.value)
} else if (response?.data) {
// 可能是WebSocket旧格式或REST API格式
outlineData = response.data
currentGenerationId.value = response.generation_id || ''
currentTaskID.value = ''
console.log('📋 直接格式: generation_id:', currentGenerationId.value)
} else {
outlineData = response
currentGenerationId.value = ''
currentTaskID.value = ''
}
// 处理简报数据格式
@@ -223,15 +250,22 @@ async function handleSearch() {
emit('search', searchValue.value)
// 预加载所有任务的智能体评分数据
preloadAllTaskAgentScores(outlineData, searchValue.value)
preloadAllTaskAgentScores(outlineData, searchValue.value, currentTaskID.value)
// 填充步骤详情
isFillingSteps.value = true
const steps = outlineData['Collaboration Process'] || []
// 保存 generation_id 到本地变量,用于 fillStepTask 调用
// 保存 generation_id 和 TaskID 到本地变量,用于 fillStepTask 调用
// 这样即使前端停止时清空了 currentGenerationId当前的 fillStepTask 仍能正确停止
const fillTaskGenerationId = currentGenerationId.value
const fillTaskTaskID = currentTaskID.value
console.log('📋 开始填充步骤详情', {
generationId: fillTaskGenerationId,
TaskID: fillTaskTaskID,
stepsCount: steps.length
})
// 串行填充所有步骤的详情
try {
@@ -243,6 +277,7 @@ async function handleSearch() {
await withRetry(
async () => {
console.log(`📤 调用 fillStepTask: 步骤=${step.StepName}, TaskID=${fillTaskTaskID}`)
const detailedStep = await api.fillStepTask({
goal: searchValue.value,
stepTask: {
@@ -250,9 +285,12 @@ async function handleSearch() {
TaskContent: step.TaskContent,
InputObject_List: step.InputObject_List,
OutputObject: step.OutputObject,
Id: step.Id, // 传递步骤ID用于后端定位
},
generation_id: fillTaskGenerationId,
TaskID: fillTaskTaskID, // 后端数据库主键
})
console.log(`📥 fillStepTask 返回完成: 步骤=${step.StepName}`)
updateStepDetail(step.StepName, detailedStep)
},
{
@@ -326,6 +364,145 @@ onMounted(() => {
onUnmounted(() => {
websocket.off('generation_stopped', onGenerationStopped)
})
// 打开历史记录弹窗
const openHistoryDialog = () => {
historyDialogVisible.value = true
}
// 处理历史任务恢复
const handleRestorePlan = (plan: any) => {
console.log('♻️ [Task] 恢复历史任务:', plan)
// 1. 关闭弹窗
historyDialogVisible.value = false
// 2. 设置搜索值为历史任务的目标
searchValue.value = plan.general_goal
// 3. 设置当前任务的 task_id供分支保存使用
currentTaskID.value = plan.id || ''
if (plan.id) {
;(window as any).__CURRENT_TASK_ID__ = plan.id
console.log('♻️ [Task] 已设置 currentTaskID:', currentTaskID.value)
}
// 4. 设置分支初始化标记,告诉 PlanModification.vue 这是从历史记录恢复
// 这样 initializeFlow 会走"恢复分支"逻辑,而不是"首次初始化"
sessionStorage.setItem('plan-modification-branches-initialized', 'true')
// 4. 保存完整的任务大纲数据
currentPlanOutline.value = plan.task_outline || null
// 4. 如果有 agents_info更新到 store
if (plan.agents_info && Array.isArray(plan.agents_info)) {
agentsStore.setAgents(plan.agents_info)
}
// 5. 如果有智能体评分数据,更新到 store
if (plan.agent_scores && currentPlanOutline.value) {
const tasks = currentPlanOutline.value['Collaboration Process'] || []
console.log('🔍 [Task] 所有步骤名称:', tasks.map((t: any) => t.StepName))
console.log('🔍 [Task] agent_scores 的 key:', Object.keys(plan.agent_scores))
for (const task of tasks) {
// 使用 task.Id 作为 key
const taskId = task.Id
console.log(`🔍 [Task] 步骤使用 taskId: ${taskId}`)
console.log(`🔍 [Task] plan.agent_scores[taskId] =`, plan.agent_scores[taskId])
if (taskId && plan.agent_scores[taskId]) {
const stepScores = plan.agent_scores[taskId]
console.log(`✅ [Task] 找到步骤 ${taskId} 的评分数据`)
// 后端返回格式: { aspectList: [...], agentScores: { "Agent1": { "Aspect1": {...} } } }
// 解构获取 aspectList 和 agentScores
const { aspectList, agentScores } = stepScores
// agentScores 格式: { "Agent1": { "Aspect1": { score, reason } } }
// 直接使用,无需转换(已经是前端期望的格式)
console.log(`🔍 [Task] 步骤 ${taskId} 的 agentScores:`, agentScores)
console.log(`🔍 [Task] 步骤 ${taskId} 的维度列表:`, aspectList)
// 使用 taskId 作为 key 存储
console.log(`🔍 [Task] 调用 setTaskScoreData: taskId=${taskId}`)
agentsStore.setTaskScoreData(taskId, {
aspectList,
agentScores
})
console.log(`🔍 [Task] setTaskScoreData 完成,检查存储结果:`)
const storedData = agentsStore.getTaskScoreData(taskId)
console.log(` taskId=${taskId} 的存储结果:`, storedData)
} else {
console.log(`❌ [Task] 未找到步骤 "${taskId}" 的评分数据,尝试用 StepName 查找`)
// 兼容处理:如果找不到,尝试用 StepName
const stepName = task.StepName
if (stepName && plan.agent_scores[stepName]) {
console.log(`✅ [Task] 兼容模式:使用 StepName ${stepName} 找到评分数据`)
const stepScores = plan.agent_scores[stepName]
// 后端返回格式: { aspectList: [...], agentScores: {...} }
const { aspectList, agentScores } = stepScores
agentsStore.setTaskScoreData(taskId, {
aspectList,
agentScores
})
}
}
}
}
// 6. 将完整的大纲数据设置到 store跳过重新生成
if (currentPlanOutline.value) {
agentsStore.setAgentRawPlan({
data: currentPlanOutline.value,
loading: false
})
}
// 7. 如果有分支数据,恢复到 selection store
if (plan.branches && Array.isArray(plan.branches)) {
// 旧格式:数组,恢复任务大纲探索分支
console.log('♻️ [Task] 恢复分支数据:', { count: plan.branches.length })
selectionStore.restoreBranchesFromDB(plan.branches)
} else if (plan.branches && typeof plan.branches === 'object') {
// 新格式:对象,可能包含 flow_branches 和 task_process_branches
// 恢复任务大纲探索分支
if (plan.branches.flow_branches && Array.isArray(plan.branches.flow_branches)) {
console.log('♻️ [Task] 恢复任务大纲探索分支:', { count: plan.branches.flow_branches.length })
selectionStore.restoreBranchesFromDB(plan.branches.flow_branches)
}
// 恢复任务过程分支
if (plan.branches.task_process_branches && typeof plan.branches.task_process_branches === 'object') {
console.log('♻️ [Task] 恢复任务过程分支:', { stepCount: Object.keys(plan.branches.task_process_branches).length })
selectionStore.restoreTaskProcessBranchesFromDB(plan.branches.task_process_branches)
}
}
// 8. 如果有 agent 组合数据,恢复到 selection store
if (plan.assigned_agents && typeof plan.assigned_agents === 'object') {
console.log('♻️ [Task] 恢复 agent 组合数据:', plan.assigned_agents)
selectionStore.restoreAgentCombinationsFromDB(plan.assigned_agents, plan.id)
}
// 9. 如果有 rehearsal_log恢复执行结果
if (plan.rehearsal_log && Array.isArray(plan.rehearsal_log)) {
console.log('♻️ [Task] 恢复执行结果:', { count: plan.rehearsal_log.length })
// rehearsal_log 本身就是 IExecuteRawResponse 格式的数组
agentsStore.setExecutePlan(plan.rehearsal_log)
}
// 10. 触发搜索事件
emit('search', plan.general_goal)
success('成功', '已恢复历史任务')
}
// 暴露给父组件
defineExpose({
currentTaskID,
openHistoryDialog
})
</script>
<template>
@@ -389,8 +566,29 @@ onUnmounted(() => {
</el-button>
</div>
<AssignmentButton v-if="planReady" @click="openAgentAllocationDialog" />
<!-- 历史记录按钮 -->
<el-button
class="history-button"
circle
:title="'历史记录'"
@click.stop="openHistoryDialog"
>
<el-icon size="18px"><Clock /></el-icon>
</el-button>
</div>
</el-tooltip>
<!-- 历史记录弹窗 -->
<el-dialog
v-model="historyDialogVisible"
title="历史任务"
width="600px"
:close-on-click-modal="true"
destroy-on-close
append-to-body
>
<HistoryList @restore="handleRestorePlan" />
</el-dialog>
</template>
<style scoped lang="scss">
@@ -529,4 +727,21 @@ onUnmounted(() => {
}
}
}
// 历史记录按钮
.history-button {
position: absolute;
right: 70px;
top: 28px;
transform: translateY(-50%);
z-index: 999;
background: var(--color-bg-taskbar);
border: 1px solid var(--color-border);
color: var(--color-text-taskbar);
&:hover {
background: var(--color-bg-hover);
color: var(--color-text-hover);
}
}
</style>

View File

@@ -36,6 +36,7 @@ const handleFileSelect = (event: Event) => {
if (input.files && input.files[0]) {
const file = input.files[0]
readFileContent(file)
input.value = ''
}
}

View File

@@ -0,0 +1,376 @@
<template>
<div class="history-list">
<div class="header">
<h3>📋 历史任务</h3>
<el-button type="primary" link @click="fetchPlans">
<el-icon><Refresh /></el-icon>
刷新
</el-button>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="loading">
<el-icon class="is-loading"><Loading /></el-icon>
<span>加载中...</span>
</div>
<!-- 空状态 -->
<el-empty v-else-if="plans.length === 0" description="暂无历史任务" />
<!-- 任务列表 -->
<div v-else class="plan-list">
<div
v-for="plan in plans"
:key="plan.id"
class="plan-item"
:class="{ active: selectedPlanId === plan.id }"
@click="selectPlan(plan)"
>
<div class="plan-info">
<div class="plan-goal">{{ plan.general_goal || '未知任务' }}</div>
<div class="plan-meta">
<el-tag size="small" :type="getStatusType(plan.status)">
{{ getStatusText(plan.status) }}
</el-tag>
<span class="plan-time">{{ formatTime(plan.created_at) }}</span>
</div>
<div class="plan-stats">
<span>执行次数: {{ plan.execution_count }}</span>
</div>
</div>
<div class="plan-actions">
<el-button
type="primary"
size="small"
@click.stop="restorePlan(plan)"
:disabled="restoring"
>
恢复
</el-button>
<el-button
type="danger"
size="small"
link
@click.stop="deletePlan(plan)"
>
<el-icon><Delete /></el-icon>
</el-button>
</div>
</div>
</div>
<!-- 分页 -->
<div v-if="plans.length > 0" class="pagination">
<span> {{ total }} 个任务</span>
</div>
<!-- 删除确认对话框 -->
<el-dialog
v-model="dialogVisible"
title="删除确认"
width="400px"
:close-on-click-modal="false"
>
<span>确定要删除任务 "{{ planToDelete?.general_goal }}" 此操作不可恢复</span>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="danger" @click="confirmDelete" :loading="deleting">确定删除</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { ElMessage } from 'element-plus'
import { Refresh, Loading, Delete } from '@element-plus/icons-vue'
import websocket from '@/utils/websocket'
// 事件定义
const emit = defineEmits<{
(e: 'restore', plan: PlanInfo): void
(e: 'close'): void
}>()
// 数据类型
interface PlanInfo {
id: string // 对应数据库 task_id
general_goal: string // 对应数据库 query
status: string
execution_count: number
created_at: string
// 完整恢复数据
task_outline?: any
assigned_agents?: any
agent_scores?: any
agents_info?: any[]
}
// 响应式数据
const plans = ref<PlanInfo[]>([])
const loading = ref(false)
const restoring = ref(false)
const selectedPlanId = ref<string | null>(null)
const total = ref(0)
const isConnected = ref(false)
// 删除对话框相关
const dialogVisible = ref(false)
const planToDelete = ref<PlanInfo | null>(null)
const deleting = ref(false)
// 生成唯一请求ID
let requestIdCounter = 0
const generateRequestId = () => `ws_req_${Date.now()}_${++requestIdCounter}`
// WebSocket 监听器引用(用于清理)
const historyUpdatedHandler = () => {
console.log('📡 收到历史列表更新通知,自动刷新')
fetchPlans()
}
// 获取任务列表
const fetchPlans = async () => {
loading.value = true
const reqId = generateRequestId()
try {
const result = await websocket.send('get_plans', { id: reqId })
plans.value = (result.data || []) as PlanInfo[]
total.value = plans.value.length
isConnected.value = true
} catch (error) {
console.error('获取任务列表失败:', error)
ElMessage.error('获取任务列表失败')
isConnected.value = false
} finally {
loading.value = false
}
}
// 选择任务
const selectPlan = (plan: PlanInfo) => {
selectedPlanId.value = plan.id
}
// 恢复任务
const restorePlan = async (plan: PlanInfo) => {
if (restoring.value) return
console.log('🔍 [HistoryList] 恢复计划:', plan)
console.log('🔍 [HistoryList] plan.id:', plan.id)
if (!plan.id) {
ElMessage.error('任务 ID 为空,无法恢复')
return
}
restoring.value = true
selectedPlanId.value = plan.id
const reqId = generateRequestId()
try {
const result = await websocket.send('restore_plan', {
id: reqId,
data: { plan_id: plan.id }
})
const planData = (result.data || result) as any
// 发送恢复事件
emit('restore', {
...plan,
...planData
})
ElMessage.success('任务已恢复')
} catch (error) {
console.error('恢复任务失败:', error)
ElMessage.error('恢复任务失败')
} finally {
restoring.value = false
}
}
// 删除任务
const deletePlan = (plan: PlanInfo) => {
planToDelete.value = plan
dialogVisible.value = true
}
// 确认删除
const confirmDelete = async () => {
if (!planToDelete.value) return
deleting.value = true
const reqId = generateRequestId()
try {
await websocket.send('delete_plan', {
id: reqId,
data: { plan_id: planToDelete.value.id }
})
dialogVisible.value = false
ElMessage.success('删除成功')
// 删除成功后会自动通过 history_updated 事件刷新列表
} catch (error) {
console.error('删除任务失败:', error)
ElMessage.error('删除任务失败')
} finally {
deleting.value = false
}
}
// 格式化时间
const formatTime = (timeStr: string | undefined) => {
if (!timeStr) return ''
try {
const date = new Date(timeStr)
return date.toLocaleString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})
} catch {
return timeStr
}
}
// 获取状态类型
const getStatusType = (status: string): string => {
const statusMap: Record<string, string> = {
'generating': 'warning',
'executing': 'warning',
'completed': 'success',
'stopped': 'danger'
}
return statusMap[status] || 'info'
}
// 获取状态文本
const getStatusText = (status: string): string => {
const statusMap: Record<string, string> = {
'generating': '生成中',
'executing': '执行中',
'completed': '已完成',
'stopped': '已停止'
}
return statusMap[status] || status
}
// 生命周期
onMounted(() => {
fetchPlans()
// 监听历史列表更新事件(多标签页实时同步)
websocket.on('history_updated', historyUpdatedHandler)
})
onUnmounted(() => {
// 移除事件监听
websocket.off('history_updated', historyUpdatedHandler)
})
</script>
<style scoped lang="scss">
.history-list {
padding: 16px;
height: 100%;
display: flex;
flex-direction: column;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
}
.loading {
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
color: #909399;
gap: 8px;
}
.plan-list {
flex: 1;
overflow-y: auto;
}
.plan-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
margin-bottom: 8px;
border-radius: 8px;
background: #f5f7fa;
cursor: pointer;
transition: all 0.2s;
&:hover {
background: #ecf5ff;
}
&.active {
background: #ecf5ff;
border-left: 3px solid #409eff;
}
}
.plan-info {
flex: 1;
min-width: 0;
}
.plan-goal {
font-size: 14px;
font-weight: 500;
margin-bottom: 6px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.plan-meta {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 4px;
}
.plan-time {
font-size: 12px;
color: #909399;
}
.plan-stats {
font-size: 12px;
color: #606266;
}
.plan-actions {
display: flex;
gap: 8px;
margin-left: 12px;
}
.pagination {
display: flex;
justify-content: center;
margin-top: 16px;
color: #909399;
font-size: 13px;
}
</style>

View File

@@ -7,6 +7,7 @@ import {
useAgentsStore,
useSelectionStore,
type IRawStepTask,
type TaskProcess,
type IApiAgentAction
} from '@/stores'
import { getAgentMapIcon, getActionTypeDisplay } from '@/layout/components/config.ts'
@@ -32,9 +33,6 @@ const branchInputRef = ref<InstanceType<typeof HTMLInputElement>>()
// 分支加载状态
const branchLoading = ref(false)
// Mock 数据配置
const USE_MOCK_DATA = false
// 节点和边数据
const nodes = ref<Node[]>([])
const edges = ref<Edge[]>([])
@@ -51,6 +49,191 @@ const BRANCHES_INIT_KEY_PREFIX = 'plan-task-branches-initialized-'
//最后选中的分支ID
const LAST_SELECTED_BRANCH_KEY = 'plan-task-last-selected-branch'
// ==================== 公共函数提取 ====================
// 获取 agent 个人简介
const getAgentProfile = (agentName: string): string => {
return (
agentsStore.agents.find((agent: any) => agent.Name === agentName)?.Profile || '暂无个人简介'
)
}
// 创建 VueFlow 边的通用函数
const createFlowEdge = (
source: string,
target: string,
sourceHandle: string = 'right',
targetHandle: string = 'left',
strokeColor: string = '#43a8aa',
isFirstNode: boolean = false
): Edge => {
return {
id: `edge-${source}-${target}`,
source,
target,
sourceHandle,
targetHandle,
type: 'smoothstep',
animated: true,
style: {
stroke: strokeColor,
strokeWidth: 2,
...(isFirstNode ? { strokeDasharray: '5,5' } : {})
},
markerEnd: {
type: 'arrow' as any,
color: '#43a8aa',
width: 20,
height: 20,
strokeWidth: 2
}
}
}
// 创建分支 agent 节点的通用函数
const createBranchAgentNode = (
agentNodeId: string,
action: IApiAgentAction,
position: { x: number; y: number }
): Node => {
const agentInfo = getAgentMapIcon(action.agent)
const actionTypeInfo = getActionTypeDisplay(action.type)
const agentProfile = getAgentProfile(action.agent)
return {
id: agentNodeId,
type: 'agent',
position,
data: {
agentName: action.agent,
agentIcon: agentInfo.icon,
agentColor: agentInfo.color,
actionTypeColor: actionTypeInfo?.color || '#909399',
agentDescription: action.description || '暂无描述',
agentProfile: agentProfile,
actionTypeName: actionTypeInfo?.name || '未知职责',
actionTypeKey: actionTypeInfo?.key || 'unknown',
isRoot: false,
isBranchTask: true
}
}
}
// 保存分支到 store 的通用函数
const saveBranchToStore = (
branchType: 'root' | 'task',
newBranchNodes: Node[],
newBranchEdges: Edge[],
branchTasks: any[]
) => {
if (newBranchNodes.length === 0) return
const taskStepId = currentTask.value?.Id || ''
const currentAgents = currentTask.value?.AgentSelection || []
isSyncing = true
selectionStore.addTaskProcessBranch(taskStepId, currentAgents, {
parentNodeId: addingBranchNodeId.value!,
branchContent: branchInput.value.trim(),
branchType,
nodes: JSON.parse(JSON.stringify(newBranchNodes)),
edges: JSON.parse(JSON.stringify(newBranchEdges)),
tasks: JSON.parse(JSON.stringify(branchTasks))
})
setTimeout(() => {
isSyncing = false
}, 100)
saveTaskProcessBranchesToDB()
}
// 解析分支 API 响应的通用函数
const parseBranchResponse = (response: any): IApiAgentAction[] => {
const newAgentActions: IApiAgentAction[] = []
const responseData = response.data || response
if (responseData && responseData.length > 0) {
const firstBranch = responseData[0]
firstBranch.forEach((action: any) => {
newAgentActions.push({
id: action.ID || uuidv4(),
type: action.ActionType,
agent: action.AgentName,
description: action.Description,
inputs: action.ImportantInput || []
})
})
}
return newAgentActions
}
// 获取主流程节点 ID 列表的通用函数
const getMainProcessNodeIds = (nodeList?: Node[]): string[] => {
const targetNodes = nodeList || nodes.value
return targetNodes.filter(n => !n.data.isBranchTask && n.id !== 'root').map(n => n.id)
}
// 设置主流程高亮的通用函数
const highlightMainProcess = (nodeList?: Node[]) => {
const mainProcessNodes = getMainProcessNodeIds(nodeList)
selectedNodeIds.value = new Set(mainProcessNodes)
}
// 创建分支节点和边的通用函数
const createBranchNodesAndEdges = (
newAgentActions: IApiAgentAction[],
branchStartX: number,
branchStartY: number,
parentNodeId: string,
strokeColor: string = '#67c23a'
): { nodes: Node[]; edges: Edge[] } => {
const newBranchNodes: Node[] = []
const newBranchEdges: Edge[] = []
const timestamp = Date.now()
const agentNodeIds: string[] = []
newAgentActions.forEach((action, index) => {
const agentNodeId = `branch-agent-${parentNodeId}-${timestamp}-${index}`
agentNodeIds.push(agentNodeId)
// 计算位置:横向排列
const nodeX = branchStartX + (parentNodeId === 'root' ? 100 : 0) + index * 120
const nodeY = branchStartY
// 创建 agent 节点
const newAgentNode = createBranchAgentNode(agentNodeId, action, { x: nodeX, y: nodeY })
nodes.value.push(newAgentNode)
newBranchNodes.push(newAgentNode)
// 创建连接边
if (index === 0) {
// 第一个 agent 连接到父节点
const newEdge = createFlowEdge(parentNodeId, agentNodeId, 'bottom', 'left', strokeColor, true)
edges.value.push(newEdge)
newBranchEdges.push(newEdge)
} else {
// 后续 agent 连接到前一个 agent
const prevAgentNodeId = agentNodeIds[index - 1]!
const newEdge = createFlowEdge(
prevAgentNodeId,
agentNodeId,
'right',
'left',
strokeColor,
false
)
edges.value.push(newEdge)
newBranchEdges.push(newEdge)
}
})
return { nodes: newBranchNodes, edges: newBranchEdges }
}
// ==================== 原有函数保持不变 ====================
// 获取分支的所有节点
const getAllBranchNodes = (startNodeId: string): string[] => {
const visited = new Set<string>()
@@ -272,14 +455,44 @@ const initializeFlow = () => {
)}`
const branchesInitialized = sessionStorage.getItem(branchesInitKey) === 'true'
if (branchesInitialized && savedBranches.length > 0) {
nodes.value = []
edges.value = []
// 【修复】优先检查 store 中是否有数据,而不是依赖 sessionStorage 标记
// 原逻辑if (branchesInitialized && savedBranches.length > 0)
// 问题从历史记录恢复时store 有数据但 sessionStorage 无标记,导致重复创建"初始流程"分支
if (savedBranches.length > 0) {
// 先创建根节点和主流程节点(作为分支的基础)
nodes.value = newNodes
edges.value = newEdges
savedBranches.forEach(branch => {
// 恢复节点
branch.nodes.forEach(node => {
nodes.value.push(JSON.parse(JSON.stringify(node)))
// 找出非初始流程的分支(只处理真正需要偏移的分支)
const actualBranches = savedBranches.filter(branch => branch.branchContent !== '初始流程')
// 统计每个父节点下有多少个分支,用于计算垂直偏移
const parentBranchIndex: Record<string, number> = {}
actualBranches.forEach(branch => {
parentBranchIndex[branch.parentNodeId] = parentBranchIndex[branch.parentNodeId] || 0
})
// 恢复分支时,按顺序处理,同一父节点的分支依次向下偏移
actualBranches.forEach(branch => {
const parentNodeId = branch.parentNodeId
// 获取父节点的Y坐标
const parentNode = nodes.value.find(n => n.id === parentNodeId)
const parentNodeY = parentNode?.position.y || 150
// 获取该分支在同一父节点下的索引
const branchIndex = parentBranchIndex[parentNodeId] ?? 0
parentBranchIndex[parentNodeId] = branchIndex + 1
// 计算分支起始Y坐标父节点Y + 200 + 分支索引 * 250
const branchStartY = parentNodeY + 200 + branchIndex * 250
// 恢复节点计算新的Y坐标
branch.nodes.forEach((node, nodeIndex) => {
const restoredNode = JSON.parse(JSON.stringify(node))
// 计算新位置分支起始Y + 节点索引 * 120横向排列
restoredNode.position.y = branchStartY
nodes.value.push(restoredNode)
})
// 恢复边
branch.edges.forEach(edge => {
@@ -287,6 +500,11 @@ const initializeFlow = () => {
})
})
// 【修复】补充标记已初始化,避免后续重复创建"初始流程"分支
if (!branchesInitialized) {
sessionStorage.setItem(branchesInitKey, 'true')
}
// 恢复最后选中的分支
const lastSelectedBranchId = sessionStorage.getItem(LAST_SELECTED_BRANCH_KEY)
if (lastSelectedBranchId) {
@@ -297,9 +515,7 @@ const initializeFlow = () => {
if (lastSelectedBranch.branchContent === '初始流程') {
// 初始流程高亮所有主流程节点从恢复的nodes中获取
nodesToHighlight = nodes.value
.filter(n => !n.data.isBranchTask && n.id !== 'root')
.map(n => n.id)
nodesToHighlight = getMainProcessNodeIds()
} else {
// 其他分支:高亮主流程路径 + 分支节点(支持多级分支)
const firstBranchNode = lastSelectedBranch.nodes[0]
@@ -311,7 +527,7 @@ const initializeFlow = () => {
let currentNode = nodes.value.find(n => n.id === incomingEdge.source)
while (currentNode && currentNode.id !== 'root') {
nodesToHighlight.unshift(currentNode.id)
const prevEdge = edges.value.find(e => e.target === currentNode.id)
const prevEdge = edges.value.find(e => e.target === currentNode?.id)
currentNode = prevEdge
? nodes.value.find(n => n.id === prevEdge.source)
: undefined
@@ -330,17 +546,11 @@ const initializeFlow = () => {
selectedNodeIds.value = new Set(nodesToHighlight)
} else {
// 找不到最后选中的分支,默认选中初始流程的高亮状态
const mainProcessNodes = nodes.value
.filter(n => !n.data.isBranchTask && n.id !== 'root')
.map(n => n.id)
selectedNodeIds.value = new Set(mainProcessNodes)
highlightMainProcess()
}
} else {
// 没有保存的选中分支,默认选中初始流程的高亮状态
const mainProcessNodes = nodes.value
.filter(n => !n.data.isBranchTask && n.id !== 'root')
.map(n => n.id)
selectedNodeIds.value = new Set(mainProcessNodes)
highlightMainProcess()
}
} else {
// 首次初始化:设置节点和边,并保存为"初始流程"分支
@@ -362,14 +572,14 @@ const initializeFlow = () => {
// 标记已初始化(针对该任务步骤和 agent 组合)
sessionStorage.setItem(branchesInitKey, 'true')
// 保存任务过程分支到数据库
saveTaskProcessBranchesToDB()
// 首次初始化时,设置初始流程为当前选中分支
selectionStore.setActiveTaskProcessBranch(taskStepId, currentAgents, initialBranchId)
// 默认选中"初始流程"的高亮状态
const mainProcessNodes = newNodes
.filter(n => !n.data.isBranchTask && n.id !== 'root')
.map(n => n.id)
selectedNodeIds.value = new Set(mainProcessNodes)
highlightMainProcess(newNodes)
}
}
}
@@ -426,7 +636,7 @@ const onNodeClick = (event: any) => {
let topBranchNodeId: string | null = null
if (branchParentChain.length > 0) {
// 取父节点链的最后一个(最顶层的分支节点)
topBranchNodeId = branchParentChain[branchParentChain.length - 1]
topBranchNodeId = branchParentChain[branchParentChain.length - 1]!
} else {
// 如果没有分支父节点,当前节点就是最顶层
topBranchNodeId = nodeId
@@ -460,8 +670,12 @@ const onNodeClick = (event: any) => {
if (currentTask.value) {
const completeTaskProcess: any[] = []
// 类型守卫:检查是否为 TaskProcess 类型(有 Description 字段)
const isTaskProcess = (data: any): data is TaskProcess =>
data && 'Description' in data
// 从 store 中获取"初始流程"副本(而不是使用 taskProcess.value
const taskStepId = currentTask.value.Id
const taskStepId = currentTask.value.Id!
const currentAgents = currentTask.value.AgentSelection || []
const branches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents)
const initialBranch = branches.find(branch => branch.branchContent === '初始流程')
@@ -478,7 +692,7 @@ const onNodeClick = (event: any) => {
// 主流程节点:从初始流程数据中获取
const originalIndex = node.data.originalIndex
const processData = mainProcessData[originalIndex]
if (processData && processData.ID && processData.AgentName && processData.Description) {
if (processData && isTaskProcess(processData) && processData.ID && processData.AgentName && processData.Description) {
completeTaskProcess.push(processData)
}
} else if (node.data.isBranchTask) {
@@ -490,7 +704,7 @@ const onNodeClick = (event: any) => {
const nodeIndex = parentBranch.nodes.findIndex(n => n.id === nodeId)
if (nodeIndex !== -1 && parentBranch.tasks[nodeIndex]) {
const taskData = parentBranch.tasks[nodeIndex]
if (taskData.ID && taskData.AgentName && taskData.Description) {
if (isTaskProcess(taskData) && taskData.ID && taskData.AgentName && taskData.Description) {
completeTaskProcess.push(taskData)
}
}
@@ -508,7 +722,7 @@ const onNodeClick = (event: any) => {
}
selectionStore.setActiveTaskProcessData(
currentTask.value.Id,
taskStepId,
currentAgents,
completeTaskProcess
)
@@ -521,11 +735,7 @@ const onNodeClick = (event: any) => {
}
} else {
// 点击的是主流程节点,高亮所有主流程节点(初始流程)
const mainProcessNodes = nodes.value
.filter(n => !n.data.isBranchTask && n.id !== 'root')
.map(n => n.id)
selectedNodeIds.value = new Set(mainProcessNodes)
highlightMainProcess()
// 点击主流程节点时,从 store 读取"初始流程"分支的副本
if (currentTask.value) {
@@ -542,7 +752,7 @@ const onNodeClick = (event: any) => {
selectionStore.setActiveTaskProcessData(taskStepId, currentAgents, initialBranch.tasks)
// 同步更新 currentTask.TaskProcess实现全局数据联动
agentsStore.setCurrentTaskProcess(initialBranch.tasks)
agentsStore.setCurrentTaskProcess(initialBranch.tasks as unknown as TaskProcess[])
// 保存选中的分支ID到 sessionStorage
sessionStorage.setItem(LAST_SELECTED_BRANCH_KEY, initialBranch.id)
@@ -626,181 +836,48 @@ const submitBranch = async () => {
// 判断是根节点还是 agent 节点
if (parentNodeId === 'root') {
// 根节点分支
let newAgentActions: IApiAgentAction[] = []
if (USE_MOCK_DATA) {
// 使用 Mock API
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
// 调用真实 API
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
// 根节点分支:从零开始生成完整方案
// Baseline_Completion = 0 表示没有已完成的部分,需要生成所有阶段
// Existing_Steps 传空数组,不传递初始流程信息
const response = await api.mockBranchTaskProcess({
branch_Number: 1,
Modification_Requirement: branchContent,
Existing_Steps: [], // ← 根节点分支不传递现有步骤
Baseline_Completion: 0, // ← 从零开始
stepTaskExisting: currentTask.value,
goal: generalGoal
})
// 根节点分支:从零开始生成完整方案
// Baseline_Completion = 0 表示没有已完成的部分,需要生成所有阶段
// Existing_Steps 传空数组,不传递初始流程信息
const response = await api.branchTaskProcess({
branch_Number: 1,
Modification_Requirement: branchContent,
Existing_Steps: [], // ← 根节点分支不传递现有步骤
Baseline_Completion: 0, // ← 从零开始
stepTaskExisting: currentTask.value,
goal: generalGoal
})
// 后端返回格式: [[action1, action2], [action3, action4]]
// 取第一个分支
if (response && response.length > 0) {
const firstBranch = response[0]
// WebSocket 返回格式: { data: [[action1, action2], [action3, action4]], ... }
// REST API 返回格式: [[action1, action2], [action3, action4]]
// 使用通用函数解析 API 响应
const newAgentActions = parseBranchResponse(response)
// 直接遍历 action 数组
firstBranch.forEach((action: any) => {
// 直接使用接口返回的 ActionType
newAgentActions.push({
id: action.ID || uuidv4(),
type: action.ActionType,
agent: action.AgentName,
description: action.Description,
inputs: action.ImportantInput || []
})
})
}
ElMessage.success('[Mock] 任务流程分支创建成功')
} else {
// 调用真实 API
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
// 根节点分支:从零开始生成完整方案
// Baseline_Completion = 0 表示没有已完成的部分,需要生成所有阶段
// Existing_Steps 传空数组,不传递初始流程信息
const response = await api.branchTaskProcess({
branch_Number: 1,
Modification_Requirement: branchContent,
Existing_Steps: [], // ← 根节点分支不传递现有步骤
Baseline_Completion: 0, // ← 从零开始
stepTaskExisting: currentTask.value,
goal: generalGoal
})
// WebSocket 返回格式: { data: [[action1, action2], [action3, action4]], ... }
// REST API 返回格式: [[action1, action2], [action3, action4]]
const responseData = response.data || response
// 后端返回格式: [[action1, action2], [action3, action4]]
// 取第一个分支
if (responseData && responseData.length > 0) {
const firstBranch = responseData[0]
// 直接遍历 action 数组
firstBranch.forEach((action: any) => {
// 直接使用接口返回的 ActionType
newAgentActions.push({
id: action.ID || uuidv4(),
type: action.ActionType,
agent: action.AgentName,
description: action.Description,
inputs: action.ImportantInput || []
})
})
}
ElMessage.success('任务流程分支创建成功')
}
ElMessage.success('任务流程分支创建成功')
// 创建新的 agent 节点
if (newAgentActions.length > 0) {
// 计算分支起始位置:在根节点下方(固定位置)
const branchStartX = parentNode.position.x
const branchStartY = parentNode.position.y + 200 // 固定位置父节点下方200px
const timestamp = Date.now()
const agentNodeIds: string[] = []
const branchStartY = parentNode.position.y + 200
// 为每个动作创建一个 agent 节点
newAgentActions.forEach((action, index) => {
const agentNodeId = `branch-agent-${parentNodeId}-${timestamp}-${index}`
agentNodeIds.push(agentNodeId)
const agentInfo = getAgentMapIcon(action.agent)
const actionTypeInfo = getActionTypeDisplay(action.type)
// 从 agentsStore 中获取 agent 的个人简介
const agentProfile =
agentsStore.agents.find((agent: any) => agent.Name === action.agent)?.Profile ||
'暂无个人简介'
// 计算位置:横向排列
const nodeX = branchStartX + 100 + index * 120
const nodeY = branchStartY
// 创建 agent 节点
const newAgentNode: Node = {
id: agentNodeId,
type: 'agent',
position: { x: nodeX, y: nodeY },
data: {
agentName: action.agent,
agentIcon: agentInfo.icon,
agentColor: agentInfo.color,
actionTypeColor: actionTypeInfo?.color || '#909399',
agentDescription: action.description || '暂无描述',
agentProfile: agentProfile,
actionTypeName: actionTypeInfo?.name || '未知职责',
actionTypeKey: actionTypeInfo?.key || 'unknown',
isRoot: false,
isBranchTask: true // 标记为分支任务
}
}
nodes.value.push(newAgentNode)
newBranchNodes.push(newAgentNode)
// 创建连接边
if (index === 0) {
// 第一个 agent 连接到父节点(根节点)
const newEdge: Edge = {
id: `edge-${parentNodeId}-${agentNodeId}`,
source: parentNodeId,
target: agentNodeId,
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 {
// 后续 agent 连接到前一个 agent
const prevAgentNodeId = agentNodeIds[index - 1]
const newEdge: Edge = {
id: `edge-${prevAgentNodeId}-${agentNodeId}`,
source: prevAgentNodeId,
target: agentNodeId,
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)
}
})
// 使用通用函数创建分支节点和边
const result = createBranchNodesAndEdges(
newAgentActions,
branchStartX,
branchStartY,
parentNodeId,
'#67c23a' // 根节点分支颜色
)
newBranchNodes.push(...result.nodes)
newBranchEdges.push(...result.edges)
// 保存分支数据到 store
if (newBranchNodes.length > 0) {
// 将 IApiAgentAction 转换为 TaskProcess 格式用于存储
// 与 fill-step-task-mock.ts 中的 TaskProcess 格式保持一致
const branchTasks = newAgentActions.map(action => ({
ID: action.id || uuidv4(),
ActionType: action.type,
@@ -809,291 +886,94 @@ const submitBranch = async () => {
ImportantInput: action.inputs || []
}))
// 使用任务过程分支存储
// 注意:需要对 nodes 和 edges 进行深拷贝,避免保存响应式引用
const taskStepId = currentTask.value?.Id || ''
const currentAgents = currentTask.value?.AgentSelection || []
isSyncing = true // 设置标志,避免 watch 触发重复同步
selectionStore.addTaskProcessBranch(taskStepId, currentAgents, {
parentNodeId: parentNodeId,
branchContent: branchContent,
branchType: 'root',
nodes: JSON.parse(JSON.stringify(newBranchNodes)),
edges: JSON.parse(JSON.stringify(newBranchEdges)),
tasks: JSON.parse(JSON.stringify(branchTasks))
})
setTimeout(() => {
isSyncing = false
}, 100)
saveBranchToStore('root', newBranchNodes, newBranchEdges, branchTasks)
}
}
} else {
// Agent 节点分支
const parentIsBranchTask = parentNode.data.isBranchTask || false
const parentOriginalIndex = parentNode.data.originalIndex ?? 0
let newAgentActions: IApiAgentAction[] = []
if (USE_MOCK_DATA) {
// 使用 Mock API
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
const currentTaskProcess = taskProcess.value || []
// 调用真实 API
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
const currentTaskProcess = taskProcess.value || []
// 根据父节点类型构建 existingSteps
let existingSteps: any[] = []
let baselineCompletion = 100
// 根据父节点类型构建 existingSteps
let existingSteps: any[] = []
let baselineCompletion = 100
if (!parentIsBranchTask) {
// 父节点是主流程节点:传递主流程从 0 到父节点的步骤
baselineCompletion =
currentTaskProcess.length > 0
? Math.round(((parentOriginalIndex + 1) / currentTaskProcess.length) * 100)
: 100
if (!parentIsBranchTask) {
// 父节点是主流程节点:传递主流程从 0 到父节点的步骤
baselineCompletion =
currentTaskProcess.length > 0
? Math.round(((parentOriginalIndex + 1) / currentTaskProcess.length) * 100)
: 100
existingSteps = currentTaskProcess.slice(0, parentOriginalIndex + 1).map((p: any) => ({
ID: p.ID,
ActionType: p.ActionType,
AgentName: p.AgentName,
Description: p.Description,
ImportantInput: p.ImportantInput || []
}))
} else {
// 父节点是分支节点:从分支数据中获取步骤
const taskStepId = currentTask.value?.Id || ''
const currentAgents = currentTask.value?.AgentSelection || []
const branches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents)
// 找到父节点所属的分支
const parentBranch = branches.find(branch =>
branch.nodes.some(n => n.id === parentNode.id)
)
if (parentBranch && parentBranch.tasks) {
// 获取分支中从第一个节点到父节点的所有步骤
const parentIndexInBranch = parentBranch.nodes.findIndex(n => n.id === parentNode.id)
existingSteps = parentBranch.tasks.slice(0, parentIndexInBranch + 1).map((p: any) => ({
ID: p.ID,
ActionType: p.ActionType,
AgentName: p.AgentName,
Description: p.Description,
ImportantInput: p.ImportantInput || []
}))
// 分支节点的基线完成度:根据主流程进度计算
baselineCompletion =
currentTaskProcess.length > 0
? Math.round(((parentOriginalIndex + 1) / currentTaskProcess.length) * 100)
: 100
}
}
// 调用 Mock API
const response = await api.mockBranchTaskProcess({
branch_Number: 1,
Modification_Requirement: branchContent,
Existing_Steps: existingSteps,
Baseline_Completion: baselineCompletion,
stepTaskExisting: currentTask.value,
goal: generalGoal
})
// 后端返回格式: [[action1, action2], [action3, action4]]
// 取第一个分支
if (response && response.length > 0) {
const firstBranch = response[0]
// 直接遍历 action 数组
firstBranch.forEach((action: any) => {
// 直接使用接口返回的 ActionType
newAgentActions.push({
id: action.ID || uuidv4(),
type: action.ActionType,
agent: action.AgentName,
description: action.Description,
inputs: action.ImportantInput || []
})
})
}
ElMessage.success('[Mock] 任务流程分支创建成功')
existingSteps = currentTaskProcess.slice(0, parentOriginalIndex + 1).map((p: any) => ({
ID: p.ID,
ActionType: p.ActionType,
AgentName: p.AgentName,
Description: p.Description,
ImportantInput: p.ImportantInput || []
}))
} else {
// 调用真实 API
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
const currentTaskProcess = taskProcess.value || []
// 父节点是分支节点:从分支数据中获取步骤
const taskStepId = currentTask.value?.Id || ''
const currentAgents = currentTask.value?.AgentSelection || []
const branches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents)
// 根据父节点类型构建 existingSteps
let existingSteps: any[] = []
let baselineCompletion = 100
// 找到父节点所属的分支
const parentBranch = branches.find(branch => branch.nodes.some(n => n.id === parentNode.id))
if (!parentIsBranchTask) {
// 父节点是主流程节点:传递主流程从 0 到父节点的步骤
baselineCompletion =
currentTaskProcess.length > 0
? Math.round(((parentOriginalIndex + 1) / currentTaskProcess.length) * 100)
: 100
existingSteps = currentTaskProcess.slice(0, parentOriginalIndex + 1).map((p: any) => ({
if (parentBranch && parentBranch.tasks) {
// 获取分支中从第一个节点到父节点的所有步骤
const parentIndexInBranch = parentBranch.nodes.findIndex(n => n.id === parentNode.id)
existingSteps = parentBranch.tasks.slice(0, parentIndexInBranch + 1).map((p: any) => ({
ID: p.ID,
ActionType: p.ActionType,
AgentName: p.AgentName,
Description: p.Description,
ImportantInput: p.ImportantInput || []
}))
} else {
// 父节点是分支节点:从分支数据中获取步骤
const taskStepId = currentTask.value?.Id || ''
const currentAgents = currentTask.value?.AgentSelection || []
const branches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents)
// 找到父节点所属的分支
const parentBranch = branches.find(branch =>
branch.nodes.some(n => n.id === parentNode.id)
)
if (parentBranch && parentBranch.tasks) {
// 获取分支中从第一个节点到父节点的所有步骤
const parentIndexInBranch = parentBranch.nodes.findIndex(n => n.id === parentNode.id)
existingSteps = parentBranch.tasks.slice(0, parentIndexInBranch + 1).map((p: any) => ({
ID: p.ID,
ActionType: p.ActionType,
AgentName: p.AgentName,
Description: p.Description,
ImportantInput: p.ImportantInput || []
}))
// 分支节点的基线完成度:根据主流程进度计算
baselineCompletion =
currentTaskProcess.length > 0
? Math.round(((parentOriginalIndex + 1) / currentTaskProcess.length) * 100)
: 100
}
// 分支节点的基线完成度:根据主流程进度计算
baselineCompletion =
currentTaskProcess.length > 0
? Math.round(((parentOriginalIndex + 1) / currentTaskProcess.length) * 100)
: 100
}
const response = await api.branchTaskProcess({
branch_Number: 1,
Modification_Requirement: branchContent,
Existing_Steps: existingSteps,
Baseline_Completion: baselineCompletion,
stepTaskExisting: currentTask.value,
goal: generalGoal
})
// WebSocket 返回格式: { data: [[action1, action2], [action3, action4]], ... }
// REST API 返回格式: [[action1, action2], [action3, action4]]
const responseData = response.data || response
// 后端返回格式: [[action1, action2], [action3, action4]]
// 取第一个分支
if (responseData && responseData.length > 0) {
const firstBranch = responseData[0]
// 直接遍历 action 数组
firstBranch.forEach((action: any) => {
// 直接使用接口返回的 ActionType
newAgentActions.push({
id: action.ID || uuidv4(),
type: action.ActionType,
agent: action.AgentName,
description: action.Description,
inputs: action.ImportantInput || []
})
})
}
ElMessage.success('任务流程分支创建成功')
}
const response = await api.branchTaskProcess({
branch_Number: 1,
Modification_Requirement: branchContent,
Existing_Steps: existingSteps,
Baseline_Completion: baselineCompletion,
stepTaskExisting: currentTask.value,
goal: generalGoal
})
// 使用通用函数解析 API 响应
const newAgentActions = parseBranchResponse(response)
ElMessage.success('任务流程分支创建成功')
// 创建新的 agent 节点
if (newAgentActions.length > 0) {
// 计算分支起始位置:在父 agent 节点右下方(固定位置)
const branchStartX = parentNode.position.x + 150
const branchStartY = parentNode.position.y + 200 // 固定位置父节点下方200px
const timestamp = Date.now()
const agentNodeIds: string[] = []
const branchStartY = parentNode.position.y + 200
// 为每个动作创建一个 agent 节点
newAgentActions.forEach((action, index) => {
const agentNodeId = `branch-agent-${parentNodeId}-${timestamp}-${index}`
agentNodeIds.push(agentNodeId)
const agentInfo = getAgentMapIcon(action.agent)
const actionTypeInfo = getActionTypeDisplay(action.type)
// 从 agentsStore 中获取 agent 的个人简介
const agentProfile =
agentsStore.agents.find((agent: any) => agent.Name === action.agent)?.Profile ||
'暂无个人简介'
// 计算位置:横向排列
const nodeX = branchStartX + index * 120
const nodeY = branchStartY
// 创建 agent 节点
const newAgentNode: Node = {
id: agentNodeId,
type: 'agent',
position: { x: nodeX, y: nodeY },
data: {
agentName: action.agent,
agentIcon: agentInfo.icon,
agentColor: agentInfo.color,
actionTypeColor: actionTypeInfo?.color || '#909399',
agentDescription: action.description || '暂无描述',
agentProfile: agentProfile,
actionTypeName: actionTypeInfo?.name || '未知职责',
actionTypeKey: actionTypeInfo?.key || 'unknown',
isRoot: false,
isBranchTask: true // 标记为分支任务
}
}
nodes.value.push(newAgentNode)
newBranchNodes.push(newAgentNode)
// 创建连接边
if (index === 0) {
// 第一个 agent 连接到父节点
const newEdge: Edge = {
id: `edge-${parentNodeId}-${agentNodeId}`,
source: parentNodeId,
target: agentNodeId,
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 {
// 后续 agent 连接到前一个 agent
const prevAgentNodeId = agentNodeIds[index - 1]
const newEdge: Edge = {
id: `edge-${prevAgentNodeId}-${agentNodeId}`,
source: prevAgentNodeId,
target: agentNodeId,
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)
}
})
// 使用通用函数创建分支节点和边
const result = createBranchNodesAndEdges(
newAgentActions,
branchStartX,
branchStartY,
parentNodeId,
'#409eff' // Agent 节点分支颜色
)
newBranchNodes.push(...result.nodes)
newBranchEdges.push(...result.edges)
// 保存分支数据到 store
if (newBranchNodes.length > 0) {
@@ -1105,22 +985,7 @@ const submitBranch = async () => {
ImportantInput: action.inputs || []
}))
// 使用任务过程分支存储
// 注意:需要对 nodes 和 edges 进行深拷贝,避免保存响应式引用
const taskStepId = currentTask.value?.Id || ''
const currentAgents = currentTask.value?.AgentSelection || []
isSyncing = true // 设置标志,避免 watch 触发重复同步
selectionStore.addTaskProcessBranch(taskStepId, currentAgents, {
parentNodeId: parentNodeId,
branchContent: branchContent,
branchType: 'task',
nodes: JSON.parse(JSON.stringify(newBranchNodes)),
edges: JSON.parse(JSON.stringify(newBranchEdges)),
tasks: JSON.parse(JSON.stringify(branchTasks))
})
setTimeout(() => {
isSyncing = false
}, 100)
saveBranchToStore('task', newBranchNodes, newBranchEdges, branchTasks)
}
}
}
@@ -1154,6 +1019,20 @@ const handleBranchKeydown = (event: KeyboardEvent) => {
}
onConnect(params => addEdges(params))
// 保存任务过程分支到数据库
const saveTaskProcessBranchesToDB = async () => {
const TaskID = (window as any).__CURRENT_TASK_ID__
if (TaskID) {
try {
await selectionStore.saveTaskProcessBranchesToDB(TaskID)
} catch (error) {
console.error('保存任务过程分支数据失败:', error)
}
} else {
console.warn('[saveTaskProcessBranchesToDB] 未找到 TaskID跳过保存')
}
}
</script>
<template>

View File

@@ -1,183 +0,0 @@
// 模拟后端原始返回格式的 Mock 数据 - fill_stepTask_TaskProcess 接口
// 后端返回格式: IRawStepTask { StepName, TaskContent, InputObject_List, OutputObject, AgentSelection, TaskProcess, Collaboration_Brief_frontEnd }
import type { IRawStepTask } from '@/stores'
// TaskProcess 项格式
interface RawTaskProcessItem {
ID: string
ActionType: string
AgentName: string
Description: string
ImportantInput: string[]
}
// Collaboration_Brief_frontEnd 数据项格式
interface RawBriefDataItem {
text: string
color: number[] // [h, s, l]
}
// 后端返回的完整数据格式
export interface RawAgentTaskProcessResponse {
StepName: string
TaskContent: string
InputObject_List: string[]
OutputObject: string
AgentSelection: string[]
TaskProcess: RawTaskProcessItem[]
Collaboration_Brief_frontEnd?: {
template: string
data: Record<string, RawBriefDataItem>
}
}
// 模拟后端返回的原始数据结构(与后端缓存数据格式一致)
// 使用与 AgentAssignmentBackendMock 相同的 agent 列表
export const mockBackendAgentTaskProcessData: RawAgentTaskProcessResponse = {
StepName: '腐蚀类型识别',
TaskContent: '分析船舶制造中常见的材料腐蚀类型及其成因。',
InputObject_List: [],
OutputObject: '腐蚀类型及成因列表',
AgentSelection: ['腐蚀机理研究员', '实验材料学家', '防护工程专家'],
TaskProcess: [
{
ID: 'action_101',
ActionType: 'Propose',
AgentName: '腐蚀机理研究员',
Description: '分析海洋环境下的腐蚀机理,确定关键防护要素',
ImportantInput: ['海洋环境参数', '防护性能指标'],
},
{
ID: 'action_102',
ActionType: 'Critique',
AgentName: '实验材料学家',
Description: '基于腐蚀机理分析结果,设计涂层材料的基础配方',
ImportantInput: ['腐蚀机理分析结果', '涂层材料配方'],
},
{
ID: 'action_103',
ActionType: 'Improve',
AgentName: '防护工程专家',
Description: '筛选适用于防护涂层的二维材料,评估其性能潜力',
ImportantInput: ['材料配方设计', '涂层材料配方'],
},
{
ID: 'action_104',
ActionType: 'Finalize',
AgentName: '实验材料学家',
Description: '制定涂层材料性能测试实验方案,包括测试指标和方法',
ImportantInput: ['二维材料筛选结果', '防护性能指标'],
},
{
ID: 'action_105',
ActionType: 'Critique',
AgentName: '防护工程专家',
Description: '模拟海洋流体环境对涂层材料的影响,优化涂层结构',
ImportantInput: ['实验方案', '海洋环境参数'],
},
{
ID: 'action_106',
ActionType: 'Improve',
AgentName: '腐蚀机理研究员',
Description: '综合评估涂层材料的防护性能,提出改进建议',
ImportantInput: ['流体力学模拟结果', '实验材料学测试结果', '二维材料性能数据'],
},
{
ID: 'action_107',
ActionType: 'Improve',
AgentName: '实验材料学家',
Description: '整理研发数据和测试结果,撰写完整的研发报告',
ImportantInput: ['综合性能评估', '所有研发数据'],
},
],
Collaboration_Brief_frontEnd: {
template: '基于!<0>!、!<1>!和!<2>!!<3>!、!<4>!、!<5>!和!<6>!执行!<7>!任务,以获得!<8>!。',
data: {
'0': {
text: '涂层材料配方',
color: [120, 60, 70], // hsl(120, 60%, 70%)
},
'1': {
text: '海洋环境参数',
color: [120, 60, 70], // hsl(120, 60%, 70%)
},
'2': {
text: '防护性能指标',
color: [120, 60, 70], // hsl(120, 60%, 70%)
},
'3': {
text: '腐蚀机理研究员',
color: [0, 0, 90], // hsl(0, 0%, 90%)
},
'4': {
text: '先进材料研发员',
color: [0, 0, 90], // hsl(0, 0%, 90%)
},
'5': {
text: '二维材料科学家',
color: [0, 0, 90], // hsl(0, 0%, 90%)
},
'6': {
text: '实验材料学家',
color: [0, 0, 90], // hsl(0, 0%, 90%)
},
'7': {
text: '研发适用于海洋环境的耐腐蚀防护涂层材料,并进行性能测试与评估',
color: [0, 0, 87], // hsl(0, 0%, 87%)
},
'8': {
text: '防护涂层材料研发报告',
color: [30, 100, 80], // hsl(30, 100%, 80%)
},
},
},
}
// 模拟后端API调用 - fill_stepTask_TaskProcess
export const mockBackendFillAgentTaskProcess = async (
goal: string,
stepTask: any,
agents: string[],
): Promise<RawAgentTaskProcessResponse> => {
// 模拟网络延迟 500ms
await new Promise((resolve) => setTimeout(resolve, 500))
// 在真实场景中,后端会根据传入的 goal、stepTask 和 agents 生成不同的 TaskProcess
// 这里我们直接返回预设的 Mock 数据
// 可以根据传入的 agents 动态修改 AgentSelection 和 TaskProcess
// 确保 agents 数组不为空
const safeAgents = agents.length > 0 ? agents : ['腐蚀机理研究员']
const responseData: RawAgentTaskProcessResponse = {
...mockBackendAgentTaskProcessData,
AgentSelection: agents,
TaskProcess: mockBackendAgentTaskProcessData.TaskProcess.map((action, index) => ({
...action,
AgentName: safeAgents[index % safeAgents.length],
})),
Collaboration_Brief_frontEnd: mockBackendAgentTaskProcessData.Collaboration_Brief_frontEnd
? {
template: mockBackendAgentTaskProcessData.Collaboration_Brief_frontEnd.template,
data: { ...mockBackendAgentTaskProcessData.Collaboration_Brief_frontEnd.data },
}
: undefined,
}
// 更新 Collaboration_Brief_frontEnd.data 中的 agent 引用
if (responseData.Collaboration_Brief_frontEnd?.data) {
const agentCount = Math.min(safeAgents.length, 4) // 最多4个agent
for (let i = 0; i < agentCount; i++) {
const key = String(i + 3) // agent从索引3开始
if (responseData.Collaboration_Brief_frontEnd.data[key]) {
responseData.Collaboration_Brief_frontEnd.data[key] = {
...responseData.Collaboration_Brief_frontEnd.data[key],
text: safeAgents[i]!,
}
}
}
}
return responseData
}

View File

@@ -14,9 +14,15 @@ import websocket from '@/utils/websocket'
import Notification from '@/components/Notification/Notification.vue'
import { useNotification } from '@/composables/useNotification'
// 定义组件 props
const props = defineProps<{
TaskID?: string // 任务唯一标识,用于写入数据库
}>()
// 定义组件事件
const emit = defineEmits<{
(e: 'refreshLine'): void
(el: 'setCurrentTask', task: IRawStepTask): void
(e: 'setCurrentTask', task: IRawStepTask): void
}>()
const agentsStore = useAgentsStore()
@@ -411,7 +417,10 @@ async function executeBatchSteps(readySteps: IRawStepTask[]) {
isStreaming.value = true
currentExecutionId.value = executionId
},
currentExecutionId.value || undefined
currentExecutionId.value || undefined,
undefined,
undefined,
props.TaskID || undefined
)
})
}
@@ -961,7 +970,8 @@ async function restartFromStep(stepIndex: number) {
},
newExecutionId, // 传入前端生成的 execution_id
stepIndex,
truncatedLog
truncatedLog,
props.TaskID || undefined // 传入 TaskID 用于更新数据库
)
success('重新执行', `正在从步骤 ${stepIndex + 1} 重新执行...`)

View File

@@ -1,155 +0,0 @@
// branch_TaskProcess 接口的 Mock 数据和 Mock API
export interface BranchAction {
ID: string
ActionType: string
AgentName: string
Description: string
ImportantInput: string[]
}
export type BranchTaskProcessResponse = BranchAction[][]
// Mock 数据模拟后端返回的原始任务流程数据2D 数组)
// 格式:[[action1, action2], [action3, action4]]
const mockBranchTaskProcessDataRaw: BranchAction[][] = [
// 第一个任务分支方案
[
{
ID: 'agent3',
ActionType: 'Critique',
AgentName: '实验材料学家',
Description: '详细分析用户需求文档',
ImportantInput: ['agent2'],
},
{
ID: 'agent4',
ActionType: 'Critique',
AgentName: '腐蚀机理研究员',
Description: '设计系统整体架构',
ImportantInput: ['agent3'],
},
{
ID: 'agent5',
ActionType: 'Improve',
AgentName: '防护工程专家',
Description: '实现系统核心功能',
ImportantInput: ['agent4'],
},
{
ID: 'agent6',
ActionType: 'Finalize',
AgentName: '实验材料学家',
Description: '进行系统集成测试',
ImportantInput: ['agent5'],
},
],
// 第二个任务分支方案
[
{
ID: 'agent7',
ActionType: 'Critique',
AgentName: '实验材料学家',
Description: '深入分析用户需求和技术约束',
ImportantInput: ['agent2'],
},
{
ID: 'agent8',
ActionType: 'Critique',
AgentName: '防护工程专家',
Description: '设计系统技术架构和数据流',
ImportantInput: ['agent8'],
},
{
ID: 'agent9',
ActionType: 'Improve',
AgentName: '腐蚀机理研究员',
Description: '评估系统安全性',
ImportantInput: ['agent4'],
},
{
ID: 'agent10',
ActionType: 'Finalize',
AgentName: '实验材料学家',
Description: '完成系统安全测试',
ImportantInput: ['agent9'],
},
],
//第三个任务分支方案
[
{
ID: 'agent12',
ActionType: 'Critique',
AgentName: '腐蚀机理研究员',
Description: '设计系统整体架构',
ImportantInput: ['agent11'],
},
{
ID: 'agent13',
ActionType: 'Improve',
AgentName: '防护工程专家',
Description: '实现系统核心功能',
ImportantInput: ['agent12'],
},
{
ID: 'agent14',
ActionType: 'Finalize',
AgentName: '实验材料学家',
Description: '进行系统集成测试',
ImportantInput: ['agent13'],
},
],
]
/**
* Mock API模拟后端 branch_TaskProcess 接口调用
*
* @param branch_Number - 分支数量
* @param Modification_Requirement - 修改需求
* @param Existing_Steps - 现有步骤列表(包含完整信息的对象数组)
* @param Baseline_Completion - 基线完成度
* @param stepTaskExisting - 现有任务
* @param General_Goal - 总体目标
* @returns Promise<BranchAction[][]> - 返回 2D 数组,与后端格式完全一致
*/
export const mockBranchTaskProcessAPI = async (params: {
branch_Number: number
Modification_Requirement: string
Existing_Steps: BranchAction[]
Baseline_Completion: number
stepTaskExisting: any
General_Goal: string
}): Promise<BranchAction[][]> => {
// 模拟网络延迟 800ms
await new Promise((resolve) => setTimeout(resolve, 800))
console.log('[Mock API] branch_TaskProcess 调用参数:', params)
// 🆕 使用轮询方式选择分支方案(依次循环使用所有分支方案)
const totalBranches = mockBranchTaskProcessDataRaw.length
const sessionKey = `branch-task-process-index-${params.stepTaskExisting?.Id || 'default'}`
// 获取上一次的选择索引
let lastIndex = parseInt(sessionStorage.getItem(sessionKey) || '0')
// 计算本次的选择索引(轮询到下一个分支)
const selectedBranchIndex = (lastIndex + 1) % totalBranches
// 保存本次的选择索引
sessionStorage.setItem(sessionKey, selectedBranchIndex.toString())
const rawBranchData = mockBranchTaskProcessDataRaw[selectedBranchIndex] || []
console.log(
'[Mock API] branch_TaskProcess 选择分支方案:',
selectedBranchIndex + 1,
'/',
totalBranches,
)
console.log('[Mock API] branch_TaskProcess 返回数据:', rawBranchData)
// 直接返回 2D 数组,与后端格式完全一致
return [rawBranchData]
}
export default mockBranchTaskProcessAPI

View File

@@ -83,8 +83,23 @@ const handleSubmit = async () => {
}
try {
isAddingDimension.value = true
// 获取大任务ID和小任务信息
const dbTaskId = (window as any).__CURRENT_TASK_ID__
const stepTask = currentTask.value
? {
Id: currentTask.value.Id,
StepName: currentTask.value.StepName,
TaskContent: currentTask.value.TaskContent,
InputObject_List: currentTask.value.InputObject_List,
OutputObject: currentTask.value.OutputObject
}
: undefined
const response = await api.agentSelectModifyAddAspect({
aspectList: [...scoreDimensions.value, newDimension]
aspectList: [...scoreDimensions.value, newDimension],
stepTask: stepTask,
TaskID: dbTaskId
})
scoreDimensions.value.push(response.aspectName)
@@ -103,9 +118,10 @@ const handleSubmit = async () => {
})
//异步更新 store等前端显示完成后再更新避免触发重新初始化
await nextTick()
// 使用 Id 作为 key
const taskId = currentTask.value?.Id
//更新按任务ID的存储
//更新按任务Id的存储
if (taskId) {
const existingTaskData = agentsStore.getTaskScoreData(taskId)
if (existingTaskData) {
@@ -172,7 +188,28 @@ const confirmAgentSelection = async () => {
selectedAgents.value = new Set(existingGroup)
return
}
// 获取大任务ID
const dbTaskId = (window as any).__CURRENT_TASK_ID__
// 添加到内存 store
agentsStore.addConfirmedAgentGroup(currentTask.value.Id, agentArray)
// 🆕 保存 confirmed_groups 到数据库
if (dbTaskId && currentTask.value.Id) {
try {
const updatedGroups = agentsStore.getConfirmedAgentGroups(currentTask.value.Id)
await api.updateAssignedAgents({
task_id: dbTaskId,
step_id: currentTask.value.Id,
agents: agentArray,
confirmed_groups: updatedGroups
})
} catch (error) {
console.error('❌ 保存 confirmed_groups 失败:', error)
}
}
try {
isLoadingConfirm.value = true
const stepTaskForApi = agentsStore.createStepTaskForApi(agentArray)
@@ -186,7 +223,8 @@ const confirmAgentSelection = async () => {
const filledTask = await api.fillStepTaskTaskProcess({
goal,
stepTask: stepTaskForApi,
agents: agentArray
agents: agentArray,
TaskID: dbTaskId
})
selectionStore.setAgentTaskProcess(currentTask.value.Id, agentArray, filledTask)
} catch (error) {
@@ -212,6 +250,7 @@ const selectAgentGroup = async (agentNames: string[]) => {
if (currentTask.value?.Id && agentNames.length > 0) {
let taskProcessData = selectionStore.getAgentTaskProcess(currentTask.value.Id, agentNames)
// 如果 store 中没有数据,调用 API 生成
if (!taskProcessData) {
try {
isLoadingSelectGroup.value = true
@@ -225,9 +264,17 @@ const selectAgentGroup = async (agentNames: string[]) => {
const filledTask = await api.fillStepTaskTaskProcess({
goal,
stepTask: stepTaskForApi,
agents: agentNames
agents: agentNames,
TaskID: (window as any).__CURRENT_TASK_ID__
})
// 🆕 先存储到 store确保 getAgentCombinations 能获取到
// 注意:需要转换为后端期望的格式 { process, brief }
const groupKey = selectionStore.getAgentGroupKey(agentNames)
selectionStore.setAgentTaskProcess(currentTask.value.Id, agentNames, {
process: filledTask.process || [],
brief: filledTask.brief || {}
})
selectionStore.setAgentTaskProcess(currentTask.value.Id, agentNames, filledTask)
taskProcessData = filledTask
} catch (error) {
console.error('❌ 加载 TaskProcess 数据失败:', error)
@@ -239,10 +286,16 @@ const selectAgentGroup = async (agentNames: string[]) => {
if (taskProcessData) {
const convertedTaskProcess = convertToTaskProcess(taskProcessData.process || [])
agentsStore.updateCurrentAgentSelection(
// 🆕 获取所有 agent combinations包含新存储的数据
// getAgentCombinations 现在使用 stepId 作为第一层 key
const agentCombinations = selectionStore.getAgentCombinations(currentTask.value.Id)
await agentsStore.updateCurrentAgentSelection(
[...agentNames],
convertedTaskProcess,
taskProcessData.brief || currentTask.value.Collaboration_Brief_frontEnd
taskProcessData.brief || currentTask.value.Collaboration_Brief_frontEnd,
agentCombinations
)
}
@@ -345,9 +398,9 @@ const calculateAgentAverage = (agentData: AgentHeatmapData, selectedDimensions?:
// API调用 - 获取智能体评分数据
const fetchAgentScores = async () => {
// 使用 Id 作为 key
const taskId = currentTask.value?.Id
if (!taskId) {
console.warn('⚠️ fetchAgentScores: 当前任务没有 Id')
return null
}
//先检查 store 中是否有该任务的评分数据
@@ -369,14 +422,15 @@ const fetchAgentScores = async () => {
// 检查是否已停止
if (agentsStore.isStopping || agentsStore.hasStoppedFilling) {
console.log('检测到停止信号,跳过获取智能体评分')
return null
}
// 调用 API 获取评分(如果没有缓存或预加载)
const dbTaskId = (window as any).__CURRENT_TASK_ID__
const agentScores = await api.agentSelectModifyInit({
goal: agentsStore.agentRawPlan.data?.['General Goal'] || '',
stepTask: currentTask.value
stepTask: currentTask.value,
TaskID: dbTaskId
})
// 再次检查是否已停止API 调用后)
@@ -388,9 +442,7 @@ const fetchAgentScores = async () => {
const firstAgent = Object.keys(agentScores)[0]
const aspectList = firstAgent ? Object.keys(agentScores[firstAgent] || {}) : []
console.log('✅ 获取到的维度列表:', aspectList)
// 🆕 保存到 store按任务ID存储
// 🆕 保存到 store按 Id 存储)
agentsStore.setTaskScoreData(taskId, {
aspectList,
agentScores
@@ -438,7 +490,6 @@ const initHeatmapData = async () => {
// 获取接口数据
const scoreData = await fetchAgentScores()
if (!scoreData) {
console.warn('⚠️ initHeatmapData: 没有获取到评分数据')
return
}

View File

@@ -1,116 +0,0 @@
// Mock数据 - 用于agentSelectModifyAddAspect接口
// 模拟用户输入新维度后所有agent在该维度上的评分数据
import { vueAgentList } from './AgentAssignmentMock'
// 类型定义
export interface NewDimensionScore {
score: number
reason: string
}
export type NewDimensionScoreData = Record<string, NewDimensionScore>
// 模拟接口返回的数据结构
export interface AgentAddAspectResponse {
aspectName: string // 新添加的维度名称
agentScores: NewDimensionScoreData // 所有agent在该维度上的评分
}
// 生成指定维度名称的mock评分数据
export const generateMockDimensionScores = (dimensionName: string): AgentAddAspectResponse => {
const agentScores: NewDimensionScoreData = {}
vueAgentList.forEach((agent) => {
// 随机生成1-5的评分
const score = Math.floor(Math.random() * 5) + 1
// 根据评分生成不同的原因描述
let reason = ''
switch (score) {
case 5:
reason = `在"${dimensionName}"方面表现卓越,展现出杰出的能力和深刻的理解`
break
case 4:
reason = `在"${dimensionName}"方面表现优秀,具有良好的专业能力和执行力`
break
case 3:
reason = `在"${dimensionName}"方面表现合格,能够完成相关任务`
break
case 2:
reason = `在"${dimensionName}"方面表现一般,仍有提升空间`
break
case 1:
reason = `在"${dimensionName}"方面需要加强,建议进一步提升相关能力`
break
}
agentScores[agent] = { score, reason }
})
return {
aspectName: dimensionName,
agentScores,
}
}
// 预设的一些常用维度及其评分数据
export const presetDimensionScores: Record<string, AgentAddAspectResponse> = {
创新性: generateMockDimensionScores('创新性'),
技术能力: generateMockDimensionScores('技术能力'),
沟通技巧: generateMockDimensionScores('沟通技巧'),
问题解决: generateMockDimensionScores('问题解决'),
团队协作: generateMockDimensionScores('团队协作'),
学习能力: generateMockDimensionScores('学习能力'),
执行力: generateMockDimensionScores('执行力'),
责任心: generateMockDimensionScores('责任心'),
适应性: generateMockDimensionScores('适应性'),
领导力: generateMockDimensionScores('领导力'),
}
// 模拟API调用函数用于前端测试
export const mockAgentAddAspectApi = async (
aspectList: string[],
): Promise<AgentAddAspectResponse[]> => {
// 获取新增的维度(最后一个)
const newAspect = aspectList[aspectList.length - 1]
// 模拟网络延迟 500ms
await new Promise((resolve) => setTimeout(resolve, 20000))
// 如果是预设维度,返回预设数据
if (presetDimensionScores[newAspect]) {
return [presetDimensionScores[newAspect]]
}
// 否则动态生成新的评分数据
return [generateMockDimensionScores(newAspect)]
}
// Vue Composition API 兼容的hook
export const useAgentAddAspectMock = () => {
const addNewDimension = async (dimensionName: string) => {
const response = await mockAgentAddAspectApi([dimensionName])
return response[0]
}
const getMultipleDimensions = async (dimensionNames: string[]) => {
const responses: AgentAddAspectResponse[] = []
for (const dimension of dimensionNames) {
if (presetDimensionScores[dimension]) {
responses.push(presetDimensionScores[dimension])
} else {
responses.push(generateMockDimensionScores(dimension))
}
}
return responses
}
return {
addNewDimension,
getMultipleDimensions,
generateMockDimensionScores,
}
}

View File

@@ -1,192 +0,0 @@
// 模拟后端原始返回格式的Mock数据 - 维度 -> agent -> { Reason, Score }
import { vueAgentList, vueAspectList } from './AgentAssignmentMock'
// 后端返回的评分项格式
export interface BackendScoreItem {
Reason: string
Score: number
}
// 后端返回的完整数据格式
export type BackendAgentScoreResponse = Record<string, Record<string, BackendScoreItem>>
// 模拟后端返回的原始数据结构(维度 -> agent -> { Reason, Score }
export const mockBackendAgentScoreData: BackendAgentScoreResponse = {
: {
: { Reason: '展现出卓越的创造力和创新思维', Score: 4 },
: { Reason: '展现出杰出的创造性问题解决能力', Score: 5 },
: { Reason: '具有中等创造技能,有待提升', Score: 3 },
: { Reason: '在大多数情况下展现较强的创造性思维', Score: 4 },
: { Reason: '展现出胜任的创造能力', Score: 3 },
: { Reason: '具有较强的创造性表达能力', Score: 4 },
: { Reason: '擅长创新性思维方法', Score: 5 },
: { Reason: '展现出卓越的创造性思维和创新能力', Score: 5 },
: { Reason: '展现出良好的创造性问题解决能力', Score: 4 },
: { Reason: '展现出卓越的创造性问题解决能力', Score: 5 },
: { Reason: '展现出平衡的创造能力', Score: 4 },
: { Reason: '展现出卓越的创造天赋', Score: 5 },
: { Reason: '展现出胜任的创造性思维', Score: 3 },
: { Reason: '展现出较强的创造性主动性', Score: 4 },
: { Reason: '具有发展中的创造技能', Score: 3 },
: { Reason: '展现出卓越的创造愿景', Score: 5 },
: { Reason: '展现出卓越的创造性执行力', Score: 4 },
: { Reason: '具有较强的创造性问题解决能力', Score: 4 },
: { Reason: '展现出胜任的创造能力', Score: 3 },
},
: {
: { Reason: '展现出卓越的共情能力和社会意识', Score: 5 },
: { Reason: '具有较强的情绪调节和人际交往技能', Score: 4 },
: { Reason: '展现出卓越的情感智力', Score: 5 },
: { Reason: '在大多数情况下展现平均的情感智力', Score: 3 },
: { Reason: '具有良好的情绪意识和沟通能力', Score: 4 },
: { Reason: '在情绪意识方面偶尔表现不足', Score: 3 },
: { Reason: '具有较强的情绪理解能力', Score: 4 },
: { Reason: '展现出卓越的共情能力和社交技能', Score: 5 },
: { Reason: '具有良好的情绪调节能力', Score: 4 },
: { Reason: '展现出卓越的情感智力和社会意识', Score: 5 },
: { Reason: '具有发展中的情绪意识', Score: 3 },
: { Reason: '擅长人际交往和建立关系', Score: 5 },
: { Reason: '展现出平衡的情感智力', Score: 4 },
: { Reason: '具有基本的情绪理解能力', Score: 3 },
: { Reason: '展现出良好的情绪调节能力', Score: 4 },
: { Reason: '展现出卓越的社会意识', Score: 5 },
: { Reason: '在情感智力方面需要提升', Score: 3 },
: { Reason: '具有较强的共情能力', Score: 4 },
: { Reason: '具有良好的情绪沟通技能', Score: 4 },
},
: {
: { Reason: '展现出胜任的哲学推理技能', Score: 3 },
: { Reason: '展现出卓越的逻辑推理和分析能力', Score: 5 },
: { Reason: '展现出深刻的哲学洞察力和批判性思维', Score: 2 },
: { Reason: '展现出良好的哲学理解能力', Score: 1 },
: { Reason: '具有基础哲学推理能力,存在一些局限', Score: 3 },
: { Reason: '展现出较强的分析思维能力', Score: 4 },
: { Reason: '展现出卓越的哲学深度', Score: 5 },
: { Reason: '展现出卓越的专业分析和推理能力', Score: 5 },
: { Reason: '具有良好的批判性思维能力', Score: 4 },
: { Reason: '具有较强的专业性分析和推理能力', Score: 4 },
: { Reason: '展现出卓越的逻辑推理能力', Score: 5 },
: { Reason: '具有基础的哲学理解能力', Score: 3 },
: { Reason: '展现出平衡的哲学推理能力', Score: 4 },
: { Reason: '需要在哲学思维方面发展', Score: 3 },
: { Reason: '展现出良好的分析技能', Score: 4 },
: { Reason: '具有较强的哲学洞察力', Score: 4 },
: { Reason: '擅长批判性思维和分析', Score: 5 },
: { Reason: '具有基础哲学推理能力', Score: 3 },
: { Reason: '展现出卓越的哲学才能', Score: 5 },
},
: {
: { Reason: '在任务完成方面展现出卓越的效率', Score: 4 },
: { Reason: '擅长高效的工作流程管理', Score: 5 },
: { Reason: '展现出平均的效率,有提升空间', Score: 3 },
: { Reason: '具有良好的时间管理技能', Score: 4 },
: { Reason: '展现出胜任的效率', Score: 3 },
: { Reason: '展现出卓越的生产力', Score: 5 },
: { Reason: '具有强大的任务执行能力', Score: 4 },
: { Reason: '具有良好的工作效率和时间管理', Score: 4 },
: { Reason: '展现出良好的工作流程优化能力', Score: 4 },
: { Reason: '展现出卓越的效率和工作流程管理', Score: 5 },
: { Reason: '展现出足够的效率', Score: 3 },
: { Reason: '擅长快速完成任务', Score: 5 },
: { Reason: '展现出良好的生产力', Score: 4 },
: { Reason: '具有中等的效率水平', Score: 3 },
: { Reason: '具有较强的任务效率', Score: 4 },
: { Reason: '具有良好的执行速度', Score: 4 },
: { Reason: '展现出卓越的效率', Score: 5 },
: { Reason: '展现出平均的生产力', Score: 3 },
: { Reason: '在执行方面具有良好的效率', Score: 4 },
},
: {
: { Reason: '展现出卓越的细节关注度', Score: 5 },
: { Reason: '展现出卓越的准确性和精确度', Score: 5 },
: { Reason: '展现出卓越的精确度', Score: 5 },
: { Reason: '展现出良好的准确性', Score: 4 },
: { Reason: '展现出中等的准确性,有提升空间', Score: 3 },
: { Reason: '具有较强的细节关注度', Score: 4 },
: { Reason: '在精确度和准确性方面表现卓越', Score: 5 },
: { Reason: '展现出卓越的细节导向和精确度', Score: 5 },
: { Reason: '展现出平均的准确性', Score: 3 },
: { Reason: '展现出卓越的准确性和精确技能', Score: 5 },
: { Reason: '展现出卓越的准确性', Score: 5 },
: { Reason: '展现出较强的精确度', Score: 4 },
: { Reason: '展现出中等的准确性', Score: 3 },
: { Reason: '具有良好的细节导向能力', Score: 4 },
: { Reason: '在准确性和精确度方面表现卓越', Score: 5 },
: { Reason: '展现出较强的细节关注度', Score: 4 },
: { Reason: '展现出平均的准确性水平', Score: 3 },
: { Reason: '在工作中具有良好的精确度', Score: 4 },
: { Reason: '展现出卓越的准确性', Score: 5 },
},
: {
: { Reason: '展现出卓越的协作技能', Score: 4 },
: { Reason: '在团队合作和协作方面表现卓越', Score: 5 },
: { Reason: '具有较强的协作能力', Score: 4 },
: { Reason: '具有中等的协作技能', Score: 3 },
: { Reason: '展现出良好的团队合作精神', Score: 4 },
: { Reason: '具有较强的合作能力', Score: 4 },
: { Reason: '展现出平均的协作技能', Score: 3 },
: { Reason: '在团队协作方面表现卓越', Score: 5 },
: { Reason: '展现出良好的合作工作能力', Score: 4 },
: { Reason: '在团队协作和合作方面表现卓越', Score: 5 },
: { Reason: '具有中等的协作水平', Score: 3 },
: { Reason: '展现出良好的协作技能', Score: 4 },
: { Reason: '在协调和团队合作方面表现卓越', Score: 5 },
: { Reason: '具有较强的合作能力', Score: 4 },
: { Reason: '展现出平均的协作水平', Score: 3 },
: { Reason: '展现出良好的团队合作精神', Score: 4 },
: { Reason: '具有较强的协作技能', Score: 4 },
: { Reason: '在团队协作方面表现卓越', Score: 5 },
: { Reason: '具有中等的协作能力', Score: 3 },
},
}
// 模拟后端API调用 - agentSelectModifyInit
export const mockBackendAgentSelectModifyInit = async (): Promise<BackendAgentScoreResponse> => {
// 模拟网络延迟 300ms
await new Promise(resolve => setTimeout(resolve, 300))
return mockBackendAgentScoreData
}
// 模拟后端API调用 - agentSelectModifyAddAspect添加新维度
export const mockBackendAgentSelectModifyAddAspect = async (
aspectList: string[]
): Promise<BackendAgentScoreResponse> => {
// 模拟网络延迟 500ms
await new Promise(resolve => setTimeout(resolve, 500))
// 获取新添加的维度(最后一个)
const newAspect = aspectList[aspectList.length - 1]
if (!newAspect) {
return {}
}
// 生成该维度下所有agent的评分
const aspectData: Record<string, BackendScoreItem> = {}
vueAgentList.forEach(agent => {
const score = Math.floor(Math.random() * 5) + 1
let reason = ''
switch (score) {
case 5:
reason = `在"${newAspect}"方面表现卓越,展现出杰出的能力和深刻的理解`
break
case 4:
reason = `在"${newAspect}"方面表现优秀,具有良好的专业能力和执行力`
break
case 3:
reason = `在"${newAspect}"方面表现合格,能够完成相关任务`
break
case 2:
reason = `在"${newAspect}"方面表现一般,仍有提升空间`
break
case 1:
reason = `在"${newAspect}"方面需要加强,建议进一步提升相关能力`
break
}
aspectData[agent] = { Reason: reason, Score: score }
})
return {
[newAspect]: aspectData
}
}

View File

@@ -1,314 +0,0 @@
// Vue兼容的mock数据 - 6个维度19个智能体
export const vueAgentList = [
'船舶设计师',
'防护工程专家',
'病理生理学家',
'药物化学家',
'制剂工程师',
'监管事务专家',
'物理学家',
'实验材料学家',
'计算模拟专家',
'腐蚀机理研究员',
'先进材料研发员',
'肾脏病学家',
'临床研究协调员',
'中医药专家',
'药物安全专家',
'二维材料科学家',
'光电物理学家',
'机器学习专家',
'流体动力学专家',
]
export const vueAspectList = ['能力', '可用性', '专业性', '效率', '准确性', '协作性']
// 类型定义
export type AgentName = (typeof vueAgentList)[number]
export type AspectName = (typeof vueAspectList)[number]
export interface AgentScore {
score: number
reason: string
}
export type IAgentSelectModifyAddRequest = Record<AspectName, Record<AgentName, AgentScore>>
// Vue友好的数据结构 - agent -> 维度 -> 评分(与后端返回格式一致)
export const vueAgentScoreData: Record<AgentName, Record<AspectName, AgentScore>> = {
: {
: { score: 4, reason: '展现出卓越的创造力和创新思维' },
: { score: 5, reason: '展现出卓越的共情能力和社会意识' },
: { score: 3, reason: '展现出胜任的哲学推理技能' },
: { score: 4, reason: '在任务完成方面展现出卓越的效率' },
: { score: 5, reason: '展现出卓越的细节关注度' },
: { score: 4, reason: '展现出卓越的协作技能' },
},
: {
: { score: 5, reason: '展现出杰出的创造性问题解决能力' },
: { score: 4, reason: '具有较强的情绪调节和人际交往技能' },
: { score: 5, reason: '展现出卓越的逻辑推理和分析能力' },
: { score: 5, reason: '擅长高效的工作流程管理' },
: { score: 5, reason: '展现出卓越的准确性和精确度' },
: { score: 5, reason: '在团队合作和协作方面表现卓越' },
},
: {
: { score: 3, reason: '具有中等创造技能,有待提升' },
: { score: 5, reason: '展现出卓越的情感智力' },
: { score: 2, reason: '展现出深刻的哲学洞察力和批判性思维' },
: { score: 3, reason: '展现出平均的效率,有提升空间' },
: { score: 5, reason: '展现出卓越的精确度' },
: { score: 4, reason: '具有较强的协作能力' },
},
: {
: { score: 4, reason: '在大多数情况下展现较强的创造性思维' },
: { score: 3, reason: '在大多数情况下展现平均的情感智力' },
: { score: 1, reason: '展现出良好的哲学理解能力' },
: { score: 4, reason: '具有良好的时间管理技能' },
: { score: 4, reason: '展现出良好的准确性' },
: { score: 3, reason: '具有中等的协作技能' },
},
: {
: { score: 3, reason: '展现出胜任的创造能力' },
: { score: 4, reason: '具有良好的情绪意识和沟通能力' },
: { score: 3, reason: '具有基础哲学推理能力,存在一些局限' },
: { score: 3, reason: '展现出胜任的效率' },
: { score: 3, reason: '展现出中等的准确性,有提升空间' },
: { score: 4, reason: '展现出良好的团队合作精神' },
},
: {
: { score: 4, reason: '具有较强的创造性表达能力' },
: { score: 3, reason: '在情绪意识方面偶尔表现不足' },
: { score: 4, reason: '展现出较强的分析思维能力' },
: { score: 5, reason: '展现出卓越的生产力' },
: { score: 4, reason: '具有较强的细节关注度' },
: { score: 4, reason: '具有较强的合作能力' },
},
: {
: { score: 5, reason: '擅长创新性思维方法' },
: { score: 4, reason: '具有较强的情绪理解能力' },
: { score: 5, reason: '展现出卓越的哲学深度' },
: { score: 4, reason: '具有强大的任务执行能力' },
: { score: 5, reason: '在精确度和准确性方面表现卓越' },
: { score: 3, reason: '展现出平均的协作技能' },
},
: {
: { score: 5, reason: '展现出卓越的创造性思维和创新能力' },
: { score: 5, reason: '展现出卓越的共情能力和社交技能' },
: { score: 5, reason: '展现出卓越的专业分析和推理能力' },
: { score: 4, reason: '具有良好的工作效率和时间管理' },
: { score: 5, reason: '展现出卓越的细节导向和精确度' },
: { score: 5, reason: '在团队协作方面表现卓越' },
},
: {
: { score: 4, reason: '展现出良好的创造性问题解决能力' },
: { score: 4, reason: '具有良好的情绪调节能力' },
: { score: 4, reason: '具有良好的批判性思维能力' },
: { score: 4, reason: '展现出良好的工作流程优化能力' },
: { score: 3, reason: '展现出平均的准确性' },
: { score: 4, reason: '展现出良好的合作工作能力' },
},
: {
: { score: 5, reason: '展现出卓越的创造性问题解决能力' },
: { score: 5, reason: '展现出卓越的情感智力和社会意识' },
: { score: 4, reason: '具有较强的专业性分析和推理能力' },
: { score: 5, reason: '展现出卓越的效率和工作流程管理' },
: { score: 5, reason: '展现出卓越的准确性和精确技能' },
: { score: 5, reason: '在团队协作和合作方面表现卓越' },
},
: {
: { score: 4, reason: '展现出平衡的创造能力' },
: { score: 3, reason: '具有发展中的情绪意识' },
: { score: 5, reason: '展现出卓越的逻辑推理能力' },
: { score: 3, reason: '展现出足够的效率' },
: { score: 5, reason: '展现出卓越的准确性' },
: { score: 3, reason: '具有中等的协作水平' },
},
: {
: { score: 5, reason: '展现出卓越的创造天赋' },
: { score: 5, reason: '擅长人际交往和建立关系' },
: { score: 3, reason: '具有基础的哲学理解能力' },
: { score: 5, reason: '擅长快速完成任务' },
: { score: 4, reason: '展现出较强的精确度' },
: { score: 4, reason: '展现出良好的协作技能' },
},
: {
: { score: 3, reason: '展现出胜任的创造性思维' },
: { score: 4, reason: '展现出平衡的情感智力' },
: { score: 4, reason: '展现出平衡的哲学推理能力' },
: { score: 4, reason: '展现出良好的生产力' },
: { score: 3, reason: '展现出中等的准确性' },
: { score: 5, reason: '在协调和团队合作方面表现卓越' },
},
: {
: { score: 4, reason: '展现出较强的创造性主动性' },
: { score: 3, reason: '具有基本的情绪理解能力' },
: { score: 3, reason: '需要在哲学思维方面发展' },
: { score: 3, reason: '具有中等的效率水平' },
: { score: 4, reason: '具有良好的细节导向能力' },
: { score: 4, reason: '具有较强的合作能力' },
},
: {
: { score: 3, reason: '具有发展中的创造技能' },
: { score: 4, reason: '展现出良好的情绪调节能力' },
: { score: 4, reason: '展现出良好的分析技能' },
: { score: 4, reason: '具有较强的任务效率' },
: { score: 5, reason: '在准确性和精确度方面表现卓越' },
: { score: 3, reason: '展现出平均的协作水平' },
},
: {
: { score: 5, reason: '展现出卓越的创造愿景' },
: { score: 5, reason: '展现出卓越的社会意识' },
: { score: 4, reason: '具有较强的哲学洞察力' },
: { score: 4, reason: '具有良好的执行速度' },
: { score: 4, reason: '展现出较强的细节关注度' },
: { score: 4, reason: '展现出良好的团队合作精神' },
},
: {
: { score: 4, reason: '展现出卓越的创造性执行力' },
: { score: 3, reason: '在情感智力方面需要提升' },
: { score: 5, reason: '擅长批判性思维和分析' },
: { score: 5, reason: '展现出卓越的效率' },
: { score: 3, reason: '展现出平均的准确性水平' },
: { score: 4, reason: '具有较强的协作技能' },
},
: {
: { score: 4, reason: '具有较强的创造性问题解决能力' },
: { score: 4, reason: '具有较强的共情能力' },
: { score: 3, reason: '具有基础哲学推理能力' },
: { score: 3, reason: '展现出平均的生产力' },
: { score: 4, reason: '在工作中具有良好的精确度' },
: { score: 5, reason: '在团队协作方面表现卓越' },
},
: {
: { score: 3, reason: '展现出胜任的创造能力' },
: { score: 4, reason: '具有良好的情绪沟通技能' },
: { score: 5, reason: '展现出卓越的哲学才能' },
: { score: 4, reason: '在执行方面具有良好的效率' },
: { score: 5, reason: '展现出卓越的准确性' },
: { score: 3, reason: '具有中等的协作能力' },
},
}
// Vue友好的智能体选择配置
export const vueAgentSelections = {
balanced: { agents: ['船舶设计师', '防护工程专家', '病理生理学家'] },
creative: { agents: ['防护工程专家', '物理学家', '二维材料科学家'] },
emotional: { agents: ['船舶设计师', '病理生理学家', '实验材料学家'] },
philosophical: { agents: ['病理生理学家', '物理学家', '光电物理学家'] },
mixed: { agents: ['药物化学家', '先进材料研发员', '肾脏病学家', '机器学习专家'] },
}
export const vueCurrentAgentSelection = 'balanced'
// Vue兼容的工具函数
export const vueCalculateAgentAverages = () => {
const averages: Record<string, number> = {}
vueAgentList.forEach((agent) => {
let total = 0
let count = 0
vueAspectList.forEach((aspect) => {
// 数据结构agentScores[agent][aspect]
const scoreData = vueAgentScoreData[agent]?.[aspect]
if (scoreData) {
total += scoreData.score
count++
}
})
averages[agent] = count > 0 ? Number((total / count).toFixed(2)) : 0
})
return averages
}
// 获取按平均分排序的智能体列表
export const vueGetSortedAgentsByAverage = () => {
const averages = vueCalculateAgentAverages()
return [...vueAgentList].sort((a, b) => {
return averages[b] - averages[a]
})
}
// Vue Composition API 兼容的hook
export const useAgentMockData = () => {
// 在Vue中可以使用ref或reactive包装数据
const agentScores = vueAgentScoreData
const agentSelections = vueAgentSelections
const currentSelection = vueCurrentAgentSelection
// 计算平均分的响应式函数
const calculateAverages = () => {
return vueCalculateAgentAverages()
}
// 获取特定维度的评分(返回该维度下所有智能体的评分)
const getScoresByAspect = (aspect: AspectName) => {
const result: Record<AgentName, AgentScore> = {}
// 数据结构agentScores[agent][aspect]需要遍历所有agent提取指定aspect
vueAgentList.forEach((agent) => {
if (agentScores[agent]?.[aspect]) {
result[agent] = agentScores[agent][aspect]!
}
})
return result
}
// 获取特定智能体的所有维度评分
const getScoresByAgent = (agent: AgentName) => {
// 数据结构agentScores[agent][aspect]直接返回agent的所有维度评分
return agentScores[agent] || {}
}
return {
agentScores,
agentSelections,
currentSelection,
calculateAverages,
getScoresByAspect,
getScoresByAgent,
getSortedAgents: vueGetSortedAgentsByAverage,
}
}
// Vue 2.x 兼容的选项式API版本
export const vueMockMixin = {
data() {
return {
vueAgentScores: vueAgentScoreData,
vueAgentSelections: vueAgentSelections,
vueCurrentSelection: vueCurrentAgentSelection,
vueAgentList: vueAgentList,
vueAspectList: vueAspectList,
}
},
computed: {
vueAgentAverages() {
return vueCalculateAgentAverages()
},
vueSortedAgents() {
return vueGetSortedAgentsByAverage()
},
},
methods: {
vueGetScoresByAspect(aspect: AspectName) {
const agentScores = (this as any).vueAgentScores
const agentList = (this as any).vueAgentList
// 数据结构agentScores[agent][aspect]遍历所有agent提取指定aspect
const result: Record<string, { score: number; reason: string }> = {}
agentList.forEach((agent: string) => {
if (agentScores[agent]?.[aspect]) {
result[agent] = agentScores[agent][aspect]
}
})
return result
},
vueGetScoresByAgent(agent: AgentName) {
const agentScores = (this as any).vueAgentScores
// 数据结构agentScores[agent][aspect]直接返回agent的所有维度评分
return agentScores[agent] || {}
},
},
}

View File

@@ -3,6 +3,7 @@ import SvgIcon from '@/components/SvgIcon/index.vue'
import { getAgentMapIcon } from '@/layout/components/config.ts'
import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/utils.ts'
import { type IRawStepTask, useAgentsStore } from '@/stores'
// import api from '@/api'
import { computed, nextTick, watch, onMounted } from 'vue'
import { AnchorLocations } from '@jsplumb/browser-ui'
import { Loading } from '@element-plus/icons-vue'
@@ -109,7 +110,7 @@ watch(
)
// 保存编辑内容
const handleContentSave = (taskId: string, content: string) => {
const handleContentSave = async (taskId: string, content: string) => {
const taskToUpdate = collaborationProcess.value.find(item => item.Id === taskId)
if (taskToUpdate && content !== taskToUpdate.TaskContent) {
taskToUpdate.TaskContent = content
@@ -118,6 +119,18 @@ const handleContentSave = (taskId: string, content: string) => {
if (stepIndex >= 0) {
agentsStore.addModifiedStep(stepIndex)
}
//暂时注释掉直接保存 task_outline 的操作,避免覆盖整个主流程
// 如果需要持久化,应该只更新变化的字段或使用专门的 API
// const dbTaskId = (window as any).__CURRENT_TASK_ID__
// if (dbTaskId && agentsStore.agentRawPlan.data) {
// try {
// await api.updateTaskOutline(dbTaskId, agentsStore.agentRawPlan.data)
// console.log('[handleContentSave] 主流程已保存到数据库')
// } catch (error) {
// console.error('[handleContentSave] 保存主流程失败:', error)
// }
// }
}
}

View File

@@ -6,6 +6,12 @@ import { Jsplumb } from './utils.ts'
import { type IRawStepTask, useAgentsStore } from '@/stores'
import { BezierConnector } from '@jsplumb/browser-ui'
import { ref } from 'vue'
// 定义组件 props
const props = defineProps<{
TaskID?: string // 任务唯一标识,用于写入数据库
}>()
const agentsStore = useAgentsStore()
// 智能体库
@@ -100,6 +106,7 @@ defineExpose({
<div class="flex-1 h-full">
<TaskResult
ref="taskResultRef"
:TaskID="props.TaskID"
@refresh-line="taskResultJsplumb.repaintEverything"
@set-current-task="handleTaskResultCurrentTask"
/>

View File

@@ -3,6 +3,7 @@ import Task from './Task.vue'
import TaskTemplate from './TaskTemplate/index.vue'
import { nextTick, ref } from 'vue'
const taskRef = ref<{ currentTaskID: string }>()
const taskTemplateRef = ref<{ changeTask: () => void; clear: () => void }>()
function handleSearch() {
@@ -10,12 +11,21 @@ function handleSearch() {
taskTemplateRef.value?.changeTask()
})
}
// 获取当前任务的 TaskID
function getTaskID(): string {
return taskRef.value?.currentTaskID || ''
}
defineExpose({
getTaskID
})
</script>
<template>
<div class="p-[24px] h-[calc(100%-60px)]">
<Task @search="handleSearch" @search-start="taskTemplateRef?.clear" />
<TaskTemplate ref="taskTemplateRef" />
<Task ref="taskRef" @search="handleSearch" @search-start="taskTemplateRef?.clear" />
<TaskTemplate ref="taskTemplateRef" :TaskID="taskRef?.currentTaskID" />
</div>
</template>

View File

@@ -1,14 +1,38 @@
<script setup lang="ts">
import Header from './components/Header.vue'
import Main from './components/Main/index.vue'
import { ref, onMounted } from 'vue'
import { ref, onMounted, computed } from 'vue'
import FloatWindow from './components/Main/TaskTemplate/TaskSyllabus/components/FloatWindow.vue'
import PlanModification from './components/Main/TaskTemplate/TaskSyllabus/Branch/PlanModification.vue'
import { useAgentsStore } from '@/stores/modules/agents'
import PlanTask from './components/Main/TaskTemplate/TaskProcess/components/PlanTask.vue'
import AgentAllocation from './components/Main/TaskTemplate/TaskSyllabus/components/AgentAllocation.vue'
const isDarkMode = ref(false)
const agentsStore = useAgentsStore()
const mainRef = ref<{ currentTaskID: string } | null>(null)
// 获取当前的 taskId
const currentTaskId = computed(() => {
return mainRef.value?.currentTaskID || ''
})
onMounted(() => {
// 等待 Main 组件挂载后获取 taskId
setTimeout(() => {
const mainEl = document.querySelector('.main-container')
if (mainEl) {
// 通过 parent 获取
const mainParent = mainEl.parentElement
if (mainParent) {
const mainComponent = mainParent.querySelector('[class*="main"]')
if (mainComponent) {
// 尝试从 Vue 组件实例获取
}
}
}
}, 100)
})
// 初始化主题
const initTheme = () => {
const savedTheme = localStorage.getItem('theme')

View File

@@ -5,6 +5,7 @@ import { getAgentMapIcon } from '@/layout/components/config'
import { store } from '../index'
import { useStorage } from '@vueuse/core'
import type { IExecuteRawResponse } from '@/api'
import api from '@/api'
import { useConfigStore } from '@/stores/modules/config.ts'
export interface Agent {
Name: string
@@ -102,14 +103,14 @@ export interface IRawPlanResponse {
* @returns TaskProcess 数组
*/
export function convertToTaskProcess(
actions: { id: string; type: string; agent: string; description: string; inputs?: string[] }[]
actions: { id: string; type: string; agent: string; description: string; inputs?: string[] }[],
): TaskProcess[] {
return actions.map(action => ({
return actions.map((action) => ({
ID: action.id,
ActionType: action.type,
AgentName: action.agent,
Description: action.description,
ImportantInput: action.inputs || []
ImportantInput: action.inputs || [],
}))
}
@@ -129,16 +130,16 @@ export const useAgentsStore = defineStore('agents', () => {
agents.value = agent
}
// 🆕 新的按任务ID存储的评分数据
// 新的按任务ID存储的评分数据
const taskScoreDataMap = useStorage<Record<string, ITaskScoreData>>(
`${storageKey}-task-score-data`,
{},
)
// 🆕 预加载状态追踪(用于避免重复预加载)
// 预加载状态追踪(用于避免重复预加载)
const preloadingTaskIds = ref<Set<string>>(new Set())
// 🆕 获取指定任务的评分数据按任务ID获取
// 获取指定任务的评分数据按任务ID获取
function getTaskScoreData(taskId: string): IAgentSelectModifyAddRequest | null {
if (!taskId) {
console.warn('⚠️ getTaskScoreData: taskId 为空')
@@ -147,7 +148,9 @@ export const useAgentsStore = defineStore('agents', () => {
const taskScoreData = taskScoreDataMap.value[taskId]
if (taskScoreData) {
console.log(`✅ 使用任务 ${taskId} 的缓存评分数据,维度数: ${taskScoreData.aspectList.length}`)
console.log(
`✅ 使用任务 ${taskId} 的缓存评分数据,维度数: ${taskScoreData.aspectList.length}`,
)
return {
aspectList: taskScoreData.aspectList,
agentScores: taskScoreData.agentScores,
@@ -284,6 +287,11 @@ export const useAgentsStore = defineStore('agents', () => {
confirmedAgentGroupsMap.value.clear()
}
// 设置指定任务的确认的agent组合列表用于从数据库恢复
function setConfirmedAgentGroups(taskId: string, groups: string[][]) {
confirmedAgentGroupsMap.value.set(taskId, groups)
}
const planModificationWindow = ref(false)
const planTaskWindow = ref(false)
const agentAllocationDialog = ref(false)
@@ -350,7 +358,7 @@ export const useAgentsStore = defineStore('agents', () => {
function setCurrentTask(task: IRawStepTask) {
const existingTask = currentTask.value
// 🆕 智能判断:如果是同一个任务,保留用户修改过的数据AgentSelection、TaskProcess、Collaboration_Brief_frontEnd
// 智能判断:如果是同一个任务,保留用户修改过的数据
if (existingTask && existingTask.Id === task.Id) {
currentTask.value = {
...task,
@@ -360,7 +368,7 @@ export const useAgentsStore = defineStore('agents', () => {
existingTask.Collaboration_Brief_frontEnd || task.Collaboration_Brief_frontEnd,
}
// 🆕 同步更新主流程数据(让执行结果卡片和任务大纲都能联动)
// 同步更新主流程数据(让执行结果卡片和任务大纲都能联动)
syncCurrentTaskToMainProcess(currentTask.value)
console.log('🔄 setCurrentTask: 保留同一任务的分支数据', {
@@ -431,10 +439,11 @@ export const useAgentsStore = defineStore('agents', () => {
// 🆕 更新当前任务的 AgentSelection 和 TaskProcess用于在 AgentAllocation 中切换 agent 组合)
// 此函数专门用于强制更新,不会被 setCurrentTask 的"智能保留"逻辑阻止
function updateCurrentAgentSelection(
async function updateCurrentAgentSelection(
agentSelection: string[],
taskProcess: TaskProcess[],
collaborationBrief: any,
agentCombinations?: Record<string, { process: any; brief: any }>,
) {
if (currentTask.value) {
// 直接更新 currentTask不保留旧数据
@@ -453,7 +462,40 @@ export const useAgentsStore = defineStore('agents', () => {
taskName: currentTask.value.StepName,
newAgentSelection: agentSelection,
taskProcessLength: taskProcess.length,
agentCombinationsKeys: agentCombinations ? Object.keys(agentCombinations) : undefined,
})
// 🆕 保存 assigned_agents 到数据库
const dbTaskId = (window as any).__CURRENT_TASK_ID__
const stepId = currentTask.value?.Id
// 获取已确认的 agent 组合列表
const confirmedGroups = stepId ? getConfirmedAgentGroups(stepId) : []
console.log('[updateCurrentAgentSelection] 保存到数据库:', {
dbTaskId,
stepId,
agents: agentSelection,
confirmedGroupsCount: confirmedGroups?.length,
agentCombinationsKeys: agentCombinations ? Object.keys(agentCombinations) : undefined,
})
if (dbTaskId && stepId) {
try {
console.log('[updateCurrentAgentSelection] 调用 APIconfirmedGroups:', confirmedGroups)
await api.updateAssignedAgents({
task_id: dbTaskId,
step_id: stepId,
agents: agentSelection,
confirmed_groups: confirmedGroups,
agent_combinations: agentCombinations,
})
} catch (error) {
console.error('❌ 保存 assigned_agents 到数据库失败:', error)
}
} else {
console.warn('[updateCurrentAgentSelection] dbTaskId 或 stepId 为空,跳过保存', {
dbTaskId,
stepId,
})
}
}
}
@@ -542,9 +584,9 @@ export const useAgentsStore = defineStore('agents', () => {
agents,
brief: currentTask.value?.Collaboration_Brief_frontEnd || {
template: '',
data: {}
data: {},
},
process: []
process: [],
}
}
@@ -602,6 +644,7 @@ export const useAgentsStore = defineStore('agents', () => {
addConfirmedAgentGroup,
clearConfirmedAgentGroups,
clearAllConfirmedAgentGroups,
setConfirmedAgentGroups,
// 停止填充状态
hasStoppedFilling,
setHasStoppedFilling,

View File

@@ -4,6 +4,7 @@ import { v4 as uuidv4 } from 'uuid'
import { store } from '../index'
import type { IRawStepTask, IApiStepTask } from './agents'
import type { Node, Edge } from '@vue-flow/core'
import { useAgentsStoreHook } from './agents'
/**
* 分支数据接口
@@ -243,6 +244,107 @@ export const useSelectionStore = defineStore('selection', () => {
}
}
// ==================== 数据库持久化方法 ====================
/**
* 从数据库恢复分支数据
* @param dbBranches 从数据库读取的分支数据数组
*/
function restoreBranchesFromDB(dbBranches: IBranchData[]) {
// 清除现有分支
clearFlowBranches()
// 恢复分支数据到 store
dbBranches.forEach((branch) => {
if (branch && branch.id && branch.tasks) {
addFlowBranch({
parentNodeId: branch.parentNodeId,
branchContent: branch.branchContent,
branchType: branch.branchType,
nodes: branch.nodes || [],
edges: branch.edges || [],
tasks: branch.tasks,
})
}
})
}
/**
* 保存分支数据到数据库
* @param taskId 任务ID
* @returns Promise<boolean> 是否保存成功
*/
async function saveBranchesToDB(taskId: string): Promise<boolean> {
// 导入 api避免循环导入问题
const { default: api } = await import('@/api')
const branches = getAllFlowBranches()
const result = await api.saveBranches(taskId, branches)
return result
}
/**
* 保存任务过程分支数据到数据库
* @param TaskID 大任务ID数据库主键
* @returns Promise<boolean> 是否保存成功
* @description 存储结构: Map<taskStepId, Map<agentGroupKey, IBranchData[]>>
*/
async function saveTaskProcessBranchesToDB(TaskID: string): Promise<boolean> {
// 导入 api避免循环导入问题
const { default: api } = await import('@/api')
// 将 Map 转换为普通对象,便于序列化
const branchesMap = getAllTaskProcessBranches()
const branchesObj: Record<string, Record<string, any[]>> = {}
for (const [taskStepId, agentMap] of branchesMap.entries()) {
branchesObj[taskStepId] = {}
for (const [agentGroupKey, branches] of agentMap.entries()) {
branchesObj[taskStepId][agentGroupKey] = branches
}
}
const result = await api.saveTaskProcessBranches(TaskID, branchesObj)
return result
}
/**
* 从数据库恢复任务过程分支数据
* @param dbBranches 从数据库读取的任务过程分支数据
* @param TaskID 大任务ID用于获取全局 __CURRENT_TASK_ID__
* @description 数据格式: { stepId小任务UUID: { agentGroupKey: [IBranchData...] } }
*/
function restoreTaskProcessBranchesFromDB(dbBranches: Record<string, Record<string, any[]>>) {
// 清除现有数据
taskProcessBranchesMap.value.clear()
if (!dbBranches) {
return
}
// 恢复数据
for (const [taskStepId, agentMap] of Object.entries(dbBranches)) {
if (typeof agentMap !== 'object' || agentMap === null) {
continue
}
// 获取或创建该步骤的 Map
if (!taskProcessBranchesMap.value.has(taskStepId)) {
taskProcessBranchesMap.value.set(taskStepId, new Map())
}
const stepMap = taskProcessBranchesMap.value.get(taskStepId)!
// 恢复 agent combinations
for (const [agentGroupKey, branches] of Object.entries(agentMap)) {
if (Array.isArray(branches)) {
stepMap.set(agentGroupKey, branches)
}
}
}
}
// ==================== Agent 组合 TaskProcess 数据存储 ====================
/**
* Agent 组合 TaskProcess 数据映射
@@ -270,20 +372,34 @@ export const useSelectionStore = defineStore('selection', () => {
/**
* 存储 agent 组合的 TaskProcess 数据
* @param taskId 任务 ID
* @param stepId 步骤 ID小任务 UUID用于作为 agentTaskProcessMap 的第一层主键)
* @param agents Agent 列表
* @param taskProcess TaskProcess 数据
* @param taskProcess TaskProcess 数据(支持完整格式或简化格式)
* @description 存储结构: Map<stepId, Map<agentGroupKey, { process, brief }>>
* agentTaskProcessMap = { stepId: { agentGroupKey: { process, brief } } }
* 注意:简化格式 { process, brief } 与后端数据库格式一致
*/
function setAgentTaskProcess(taskId: string, agents: string[], taskProcess: IApiStepTask) {
function setAgentTaskProcess(
stepId: string,
agents: string[],
taskProcess: IApiStepTask | { process: any; brief: any },
) {
const groupKey = getAgentGroupKey(agents)
// 获取或创建该任务的 Map
if (!agentTaskProcessMap.value.has(taskId)) {
agentTaskProcessMap.value.set(taskId, new Map())
// 获取或创建该步骤的 Map
if (!agentTaskProcessMap.value.has(stepId)) {
agentTaskProcessMap.value.set(stepId, new Map())
}
const stepMap = agentTaskProcessMap.value.get(stepId)!
// 统一转换为简化格式 { process, brief },与后端期望格式一致
const simplifiedData = {
process: (taskProcess as IApiStepTask).process || taskProcess.process || [],
brief: (taskProcess as IApiStepTask).brief || taskProcess.brief || {},
}
// 存储该 agent 组合的 TaskProcess 数据
agentTaskProcessMap.value.get(taskId)!.set(groupKey, taskProcess)
stepMap.set(groupKey, simplifiedData)
}
/**
@@ -321,6 +437,97 @@ export const useSelectionStore = defineStore('selection', () => {
agentTaskProcessMap.value.clear()
}
/**
* 获取指定步骤的所有 agent 组合的 TaskProcess 数据
* @param stepId 步骤 ID小任务 UUID
* @returns agent_combinations 对象格式 { "[\"AgentA\",\"AgentB\"]": { process, brief }, ... }
* @description 存储结构: Map<stepId, Map<agentGroupKey, { process, brief }>>
* 注意:简化设计后,第一层 key 就是 stepId
*/
function getAgentCombinations(
stepId: string,
): Record<string, { process: any; brief: any }> | undefined {
// 直接使用 stepId 作为第一层 key
const stepMap = agentTaskProcessMap.value.get(stepId)
if (!stepMap) {
return undefined
}
// 将 Map 转换为普通对象
const result: Record<string, { process: any; brief: any }> = {}
for (const [key, value] of stepMap.entries()) {
result[key] = value
}
return result
}
// ==================== 数据库持久化恢复方法 ====================
/**
* 从数据库 assigned_agents 字段恢复 agent 组合数据
* @param assignedAgents 从数据库读取的 assigned_agents 数据
* @param taskId 大任务 ID此参数已不再使用因为简化后直接用 stepId 作为 key
* @description 数据格式: { step_id小任务UUID: { current: [...], confirmed_groups: [...], agent_combinations: {...} } }
* 存储结构: Map<stepId, Map<agentGroupKey, IApiStepTask>>
* agentTaskProcessMap = { stepId: { agentGroupKey: data } }
*/
function restoreAgentCombinationsFromDB(assignedAgents: Record<string, any>, taskId: string) {
// 获取 agents store 实例
const agentsStore = useAgentsStoreHook()
// 清除现有数据
clearAllAgentTaskProcess()
agentsStore.clearAllConfirmedAgentGroups()
if (!assignedAgents) {
return
}
// 🆕 简化版本:直接使用 stepId 作为第一层 key
// 遍历每个步骤的数据
for (const [stepId, stepData] of Object.entries(assignedAgents)) {
if (typeof stepData !== 'object' || stepData === null) {
continue
}
// 获取或创建该步骤的 Map
if (!agentTaskProcessMap.value.has(stepId)) {
agentTaskProcessMap.value.set(stepId, new Map<string, IApiStepTask>())
}
const stepMap = agentTaskProcessMap.value.get(stepId)!
// 恢复 agent_combinations 到 stepMap
// 格式: { agentGroupKey: { process, brief } }
if (stepData.agent_combinations) {
for (const [agentGroupKey, combinationData] of Object.entries(
stepData.agent_combinations,
)) {
stepMap.set(agentGroupKey, combinationData as IApiStepTask)
}
}
// 恢复 confirmed_groups
if (stepData.confirmed_groups) {
agentsStore.setConfirmedAgentGroups(stepId, stepData.confirmed_groups)
}
// 恢复 current当前选中的 agent 组合)
if (stepData.current) {
agentsStore.setSelectedAgentGroup(stepId, stepData.current)
// 同步更新 agentRawPlan 中对应步骤的 AgentSelection
const planData = agentsStore.agentRawPlan.data
if (planData && planData['Collaboration Process']) {
const process = planData['Collaboration Process']
const step = process.find((s: any) => s.Id === stepId)
if (step) {
step.AgentSelection = stepData.current
}
}
}
}
}
// ==================== 当前生效的任务过程分支 ====================
/**
* 当前生效的任务过程分支映射
@@ -450,7 +657,15 @@ export const useSelectionStore = defineStore('selection', () => {
getAgentTaskProcess,
hasAgentTaskProcess,
clearAgentTaskProcess,
getAgentCombinations,
clearAllAgentTaskProcess,
// ==================== 数据库持久化方法 ====================
restoreBranchesFromDB,
saveBranchesToDB,
restoreAgentCombinationsFromDB,
saveTaskProcessBranchesToDB,
restoreTaskProcessBranchesFromDB,
}
})