feat:三个浮动窗口功能新增
This commit is contained in:
@@ -1,5 +1,14 @@
|
|||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
import type { Agent, IRawPlanResponse } from '@/stores'
|
import type { Agent, IApiStepTask, IRawPlanResponse } from '@/stores'
|
||||||
|
import {
|
||||||
|
mockBackendAgentSelectModifyInit,
|
||||||
|
mockBackendAgentSelectModifyAddAspect,
|
||||||
|
type BackendAgentScoreResponse,
|
||||||
|
} from '@/layout/components/Main/TaskTemplate/TaskSyllabus/components/mock/AgentAssignmentBackendMock'
|
||||||
|
import {
|
||||||
|
mockBackendFillAgentTaskProcess,
|
||||||
|
type RawAgentTaskProcessResponse,
|
||||||
|
} from '@/layout/components/Main/TaskTemplate/TaskProcess/components/mock/AgentTaskProcessBackendMock'
|
||||||
|
|
||||||
export interface ActionHistory {
|
export interface ActionHistory {
|
||||||
ID: string
|
ID: string
|
||||||
@@ -19,8 +28,13 @@ export type IExecuteRawResponse = {
|
|||||||
ActionHistory: ActionHistory[]
|
ActionHistory: ActionHistory[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IFillAgentSelectionRequest {
|
||||||
|
goal: string
|
||||||
|
stepTask: IApiStepTask
|
||||||
|
agents: string[]
|
||||||
|
}
|
||||||
|
|
||||||
class Api {
|
class Api {
|
||||||
// 智能体信息
|
|
||||||
setAgents = (data: Pick<Agent, 'Name' | 'Profile' | 'apiUrl' | 'apiKey' | 'apiModel'>[]) => {
|
setAgents = (data: Pick<Agent, 'Name' | 'Profile' | 'apiUrl' | 'apiKey' | 'apiModel'>[]) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/setAgents',
|
url: '/setAgents',
|
||||||
@@ -65,7 +79,7 @@ class Api {
|
|||||||
InputObject_List: step.InputObject_List,
|
InputObject_List: step.InputObject_List,
|
||||||
OutputObject: step.OutputObject,
|
OutputObject: step.OutputObject,
|
||||||
AgentSelection: step.AgentSelection,
|
AgentSelection: step.AgentSelection,
|
||||||
Collaboration_Brief_frontEnd: step.Collaboration_Brief_FrontEnd,
|
Collaboration_Brief_frontEnd: step.Collaboration_Brief_frontEnd,
|
||||||
TaskProcess: step.TaskProcess.map((action) => ({
|
TaskProcess: step.TaskProcess.map((action) => ({
|
||||||
ActionType: action.ActionType,
|
ActionType: action.ActionType,
|
||||||
AgentName: action.AgentName,
|
AgentName: action.AgentName,
|
||||||
@@ -78,6 +92,378 @@ class Api {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 分支任务大纲(根节点级别)
|
||||||
|
branchPlanOutline = (data: {
|
||||||
|
branch_Number: number
|
||||||
|
Modification_Requirement: string
|
||||||
|
Existing_Steps: string[]
|
||||||
|
Baseline_Completion: number
|
||||||
|
initialInputs: string[]
|
||||||
|
goal: string
|
||||||
|
}) => {
|
||||||
|
return request<unknown, IRawPlanResponse>({
|
||||||
|
url: '/branch_PlanOutline',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分支任务流程(任务节点级别)
|
||||||
|
branchTaskProcess = (data: {
|
||||||
|
branch_Number: number
|
||||||
|
Modification_Requirement: string
|
||||||
|
Existing_Steps: string[]
|
||||||
|
Baseline_Completion: number
|
||||||
|
stepTaskExisting: any
|
||||||
|
goal: string
|
||||||
|
}) => {
|
||||||
|
return request<unknown, IRawPlanResponse>({
|
||||||
|
url: '/branch_TaskProcess',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fillStepTask = (data: { goal: string; stepTask: any }) => {
|
||||||
|
return request<unknown, any>({
|
||||||
|
url: '/fill_stepTask',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
'General Goal': data.goal,
|
||||||
|
stepTask: data.stepTask,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fillStepTaskTaskProcess = async (data: {
|
||||||
|
goal: string
|
||||||
|
stepTask: IApiStepTask
|
||||||
|
agents: string[]
|
||||||
|
}): Promise<IApiStepTask> => {
|
||||||
|
// 后端返回格式: IRawStepTask
|
||||||
|
const response = await request<
|
||||||
|
{
|
||||||
|
'General Goal': string
|
||||||
|
stepTask_lackTaskProcess: {
|
||||||
|
StepName: string
|
||||||
|
TaskContent: string
|
||||||
|
InputObject_List: string[]
|
||||||
|
OutputObject: string
|
||||||
|
AgentSelection: string[]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 数据转换:后端格式 (IRawStepTask) → 前端格式 (IApiStepTask)
|
||||||
|
// 注意:此转换逻辑与Mock API完全一致
|
||||||
|
|
||||||
|
// 1. 转换颜色格式:[h, s, l] → "hsl(h, s%, l%)"
|
||||||
|
const vec2Hsl = (color: number[]): string => {
|
||||||
|
const [h, s, l] = color
|
||||||
|
return `hsl(${h}, ${s}%, ${l}%)`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 转换 brief.data: { "0": { text, color: [h,s,l] } } → { "0": { text, style: { background } } }
|
||||||
|
const briefData: Record<string, { text: string; style: { background: string } }> = {}
|
||||||
|
if (response.Collaboration_Brief_FrontEnd?.data) {
|
||||||
|
for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) {
|
||||||
|
briefData[key] = {
|
||||||
|
text: value.text,
|
||||||
|
style: {
|
||||||
|
background: vec2Hsl(value.color),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 转换 process: { ID, ActionType, AgentName, Description, ImportantInput } → { id, type, agent, description, inputs }
|
||||||
|
const process = (response.TaskProcess || []).map((action) => ({
|
||||||
|
id: action.ID,
|
||||||
|
type: action.ActionType,
|
||||||
|
agent: action.AgentName,
|
||||||
|
description: action.Description,
|
||||||
|
inputs: action.ImportantInput,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// 4. 构建前端格式的 IApiStepTask
|
||||||
|
return {
|
||||||
|
name: response.StepName || '',
|
||||||
|
content: response.TaskContent || '',
|
||||||
|
inputs: response.InputObject_List || [],
|
||||||
|
output: response.OutputObject || '',
|
||||||
|
agents: response.AgentSelection || [],
|
||||||
|
brief: {
|
||||||
|
template: response.Collaboration_Brief_FrontEnd?.template || '',
|
||||||
|
data: briefData,
|
||||||
|
},
|
||||||
|
process,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为每个智能体评分
|
||||||
|
agentSelectModifyInit = async (data: {
|
||||||
|
goal: string
|
||||||
|
stepTask: any
|
||||||
|
}): Promise<Record<string, Record<string, { reason: string; score: number }>>> => {
|
||||||
|
// 后端返回:维度 -> agent -> { Reason, Score }
|
||||||
|
const response = await request<
|
||||||
|
{
|
||||||
|
'General Goal': string
|
||||||
|
stepTask: any
|
||||||
|
},
|
||||||
|
Record<string, Record<string, { Reason: string; Score: number }>>
|
||||||
|
>({
|
||||||
|
url: '/agentSelectModify_init',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
'General Goal': data.goal,
|
||||||
|
stepTask: {
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 数据转换:后端格式 (维度->agent) → 前端格式 (agent->维度)
|
||||||
|
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
|
||||||
|
|
||||||
|
for (const [aspect, agents] of Object.entries(response)) {
|
||||||
|
// aspect: 维度名称, agents: { agentName: { Reason, Score } }
|
||||||
|
for (const [agentName, scoreInfo] of Object.entries(agents)) {
|
||||||
|
if (!transformedData[agentName]) {
|
||||||
|
transformedData[agentName] = {}
|
||||||
|
}
|
||||||
|
transformedData[agentName][aspect] = {
|
||||||
|
reason: scoreInfo.Reason,
|
||||||
|
score: scoreInfo.Score,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformedData
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加新的评估维度
|
||||||
|
// 定义返回类型(与 Mock 数据格式一致)
|
||||||
|
agentSelectModifyAddAspect = async (data: {
|
||||||
|
aspectList: string[]
|
||||||
|
}): Promise<{
|
||||||
|
aspectName: string
|
||||||
|
agentScores: Record<string, { score: number; reason: string }>
|
||||||
|
}> => {
|
||||||
|
// 后端返回:维度 -> agent -> { Reason, Score }
|
||||||
|
const response = await request<
|
||||||
|
{
|
||||||
|
aspectList: string[]
|
||||||
|
},
|
||||||
|
Record<string, Record<string, { Reason: string; Score: number }>>
|
||||||
|
>({
|
||||||
|
url: '/agentSelectModify_addAspect',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
aspectList: data.aspectList,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取新添加的维度(最后一个)
|
||||||
|
const newAspect = data.aspectList[data.aspectList.length - 1]
|
||||||
|
if (!newAspect) {
|
||||||
|
throw new Error('aspectList is empty')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取该维度的数据:维度 -> agent -> { Reason, Score } → agent -> { score, reason }
|
||||||
|
const newAspectAgents = response[newAspect]
|
||||||
|
const agentScores: Record<string, { score: number; reason: string }> = {}
|
||||||
|
|
||||||
|
if (newAspectAgents) {
|
||||||
|
for (const [agentName, scoreInfo] of Object.entries(newAspectAgents)) {
|
||||||
|
agentScores[agentName] = {
|
||||||
|
score: scoreInfo.Score,
|
||||||
|
reason: scoreInfo.Reason,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
aspectName: newAspect,
|
||||||
|
agentScores,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Mock API(开发阶段使用)====================
|
||||||
|
// Mock API 使用与真实 API 相同的数据转换逻辑,确保将来切换无缝
|
||||||
|
|
||||||
|
// Mock: 为每个智能体评分
|
||||||
|
mockAgentSelectModifyInit = async (): Promise<
|
||||||
|
Record<string, Record<string, { reason: string; score: number }>>
|
||||||
|
> => {
|
||||||
|
// 调用Mock后端数据(维度 -> agent -> { Reason, Score })
|
||||||
|
const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyInit()
|
||||||
|
|
||||||
|
// 数据转换:后端格式 (维度->agent) → 前端格式 (agent->维度)
|
||||||
|
// 注意:此转换逻辑与真实API完全一致
|
||||||
|
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
|
||||||
|
|
||||||
|
for (const [aspect, agents] of Object.entries(response)) {
|
||||||
|
// aspect: 维度名称, agents: { agentName: { Reason, Score } }
|
||||||
|
for (const [agentName, scoreInfo] of Object.entries(agents)) {
|
||||||
|
if (!transformedData[agentName]) {
|
||||||
|
transformedData[agentName] = {}
|
||||||
|
}
|
||||||
|
transformedData[agentName][aspect] = {
|
||||||
|
reason: scoreInfo.Reason,
|
||||||
|
score: scoreInfo.Score,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformedData
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock: 添加新的评估维度
|
||||||
|
mockAgentSelectModifyAddAspect = async (data: {
|
||||||
|
aspectList: string[]
|
||||||
|
}): Promise<{
|
||||||
|
aspectName: string
|
||||||
|
agentScores: Record<string, { score: number; reason: string }>
|
||||||
|
}> => {
|
||||||
|
// 调用Mock后端数据(维度 -> agent -> { Reason, Score })
|
||||||
|
const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyAddAspect(
|
||||||
|
data.aspectList,
|
||||||
|
)
|
||||||
|
|
||||||
|
// 获取新添加的维度(最后一个)
|
||||||
|
const newAspect = data.aspectList[data.aspectList.length - 1]
|
||||||
|
if (!newAspect) {
|
||||||
|
throw new Error('aspectList is empty')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取该维度的数据:维度 -> agent -> { Reason, Score } → agent -> { score, reason }
|
||||||
|
// 注意:此转换逻辑与真实API完全一致
|
||||||
|
const newAspectAgents = response[newAspect]
|
||||||
|
const agentScores: Record<string, { score: number; reason: string }> = {}
|
||||||
|
|
||||||
|
if (newAspectAgents) {
|
||||||
|
for (const [agentName, scoreInfo] of Object.entries(newAspectAgents)) {
|
||||||
|
agentScores[agentName] = {
|
||||||
|
score: scoreInfo.Score,
|
||||||
|
reason: scoreInfo.Reason,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
aspectName: newAspect,
|
||||||
|
agentScores,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock: 填充智能体任务流程
|
||||||
|
mockFillStepTaskTaskProcess = async (data: {
|
||||||
|
goal: string
|
||||||
|
stepTask: IApiStepTask
|
||||||
|
agents: string[]
|
||||||
|
}): Promise<IApiStepTask> => {
|
||||||
|
// 调用Mock后端数据(后端原始格式)
|
||||||
|
const response: RawAgentTaskProcessResponse = await mockBackendFillAgentTaskProcess(
|
||||||
|
data.goal,
|
||||||
|
data.stepTask,
|
||||||
|
data.agents,
|
||||||
|
)
|
||||||
|
|
||||||
|
// 数据转换:后端格式 → 前端格式
|
||||||
|
// 注意:此转换逻辑与真实API完全一致
|
||||||
|
|
||||||
|
// 1. 转换颜色格式:[h, s, l] → "hsl(h, s%, l%)"
|
||||||
|
const vec2Hsl = (color: number[]): string => {
|
||||||
|
const [h, s, l] = color
|
||||||
|
return `hsl(${h}, ${s}%, ${l}%)`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 转换 brief.data: { "0": { text, color: [h,s,l] } } → { "0": { text, style: { background } } }
|
||||||
|
const briefData: Record<string, { text: string; style: { background: string } }> = {}
|
||||||
|
if (response.Collaboration_Brief_frontEnd?.data) {
|
||||||
|
for (const [key, value] of Object.entries(response.Collaboration_Brief_frontEnd.data)) {
|
||||||
|
briefData[key] = {
|
||||||
|
text: value.text,
|
||||||
|
style: {
|
||||||
|
background: vec2Hsl(value.color),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 转换 process: { ID, ActionType, AgentName, Description, ImportantInput } → { id, type, agent, description, inputs }
|
||||||
|
const process = (response.TaskProcess || []).map((action) => ({
|
||||||
|
id: action.ID,
|
||||||
|
type: action.ActionType,
|
||||||
|
agent: action.AgentName,
|
||||||
|
description: action.Description,
|
||||||
|
inputs: action.ImportantInput,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// 4. 构建前端格式的 IApiStepTask
|
||||||
|
return {
|
||||||
|
name: response.StepName || '',
|
||||||
|
content: response.TaskContent || '',
|
||||||
|
inputs: response.InputObject_List || [],
|
||||||
|
output: response.OutputObject || '',
|
||||||
|
agents: response.AgentSelection || [],
|
||||||
|
brief: {
|
||||||
|
template: response.Collaboration_Brief_frontEnd?.template || '',
|
||||||
|
data: briefData,
|
||||||
|
},
|
||||||
|
process,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new Api()
|
export default new Api()
|
||||||
|
|||||||
@@ -1 +1,7 @@
|
|||||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1766247454540" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9049" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M512 1024C230.4 1024 0 793.6 0 512S230.4 0 512 0s512 230.4 512 512-230.4 512-512 512z m0-938.666667C277.333333 85.333333 85.333333 277.333333 85.333333 512s192 426.666667 426.666667 426.666667 426.666667-192 426.666667-426.666667S746.666667 85.333333 512 85.333333z" p-id="9050"></path><path d="M708.266667 375.466667c-17.066667-17.066667-44.8-17.066667-59.733334 0l-181.333333 181.333333-91.733333-91.733333c-14.933333-14.933333-40.533333-14.933333-55.466667 0l-6.4 4.266666c-14.933333 14.933333-14.933333 40.533333 0 55.466667l125.866667 125.866667c14.933333 14.933333 40.533333 14.933333 55.466666 0l4.266667-4.266667 209.066667-209.066667c17.066667-17.066667 17.066667-44.8 0-61.866666z" p-id="9051"></path></svg>
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg t="1766247454540" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9049"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
|
||||||
|
<path d="M512 1024C230.4 1024 0 793.6 0 512S230.4 0 512 0s512 230.4 512 512-230.4 512-512 512z m0-938.666667C277.333333 85.333333 85.333333 277.333333 85.333333 512s192 426.666667 426.666667 426.666667 426.666667-192 426.666667-426.666667S746.666667 85.333333 512 85.333333z" p-id="9050">
|
||||||
|
</path>
|
||||||
|
<path d="M708.266667 375.466667c-17.066667-17.066667-44.8-17.066667-59.733334 0l-181.333333 181.333333-91.733333-91.733333c-14.933333-14.933333-40.533333-14.933333-55.466667 0l-6.4 4.266666c-14.933333 14.933333-14.933333 40.533333 0 55.466667l125.866667 125.866667c14.933333 14.933333 40.533333 14.933333 55.466666 0l4.266667-4.266667 209.066667-209.066667c17.066667-17.066667 17.066667-44.8 0-61.866666z" p-id="9051">
|
||||||
|
</path></svg>
|
||||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
@@ -1 +1 @@
|
|||||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764640542560" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1335" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M838.611638 631.662714l67.309037-44.87798 87.97131 131.929203a40.41239 40.41239 0 0 1-33.646587 62.843448H555.939064v-80.82478h328.851364l-46.17879-69.069891zM718.135918 979.106157l-67.237652 44.862116-88.050627-131.921271a40.41239 40.41239 0 0 1 33.583133-62.779994h404.37772v80.761326h-328.772047l46.107404 69.149209v-0.071386zM701.510919 407.10625a294.54644 294.54644 0 0 0-84.695487-59.139309c44.34655-35.891279 72.670917-90.69984 72.670917-152.218682 0-108.300447-90.461886-197.31875-199.008219-195.621351A196.089325 196.089325 0 0 0 318.390354 107.673837a7.828661 7.828661 0 0 0 6.202648 11.278983c18.655533 2.014671 36.882751 6.194716 54.126428 12.214932a7.804866 7.804866 0 0 0 9.07395-3.140982 125.203058 125.203058 0 0 1 104.11247-57.632273 124.608175 124.608175 0 0 1 91.262995 37.898018 124.679561 124.679561 0 0 1 35.462963 83.759537 124.846128 124.846128 0 0 1-73.979659 117.953417 7.884184 7.884184 0 0 0-4.386271 8.582179 250.96134 250.96134 0 0 1 2.879234 71.409765l0.13484 0.063454a7.828661 7.828661 0 0 0 5.821923 8.526657 220.661962 220.661962 0 0 1 102.478524 58.369928 221.201323 221.201323 0 0 1 65.278503 150.338851 7.773139 7.773139 0 0 0 7.828661 7.51139h55.189286a7.844525 7.844525 0 0 0 7.574845-8.074546 291.05646 291.05646 0 0 0-85.940775-199.626897z" fill="#17A29E" p-id="1336"></path><path d="M458.386171 627.942712h89.145212a291.072323 291.072323 0 0 0-41.649747-52.38937 293.443924 293.443924 0 0 0-84.568578-59.13931A195.875168 195.875168 0 0 0 493.761885 367.550491c1.808445-108.173539-84.425806-197.310819-192.591413-199.111331l-0.824905-0.007932c-108.768422-0.650405-197.469454 86.987769-198.119859 195.764123a195.296148 195.296148 0 0 0 72.655053 152.21075c-31.441554 14.602397-59.401058 35.288464-84.560647 59.139309C4.371408 657.03646 6.187784 763.131873 4.371408 838.277504a7.836593 7.836593 0 0 0 7.828661 8.011092h54.816492a7.804866 7.804866 0 0 0 7.828662-7.511391c1.840172-56.601142 25.207179-173.483769 65.334025-213.420252 42.157381-42.157381 98.227094-65.334025 157.850241-65.334025a221.827933 221.827933 0 0 1 157.858173 65.334025c0.864563 0.832836 1.657741 1.729127 2.498509 2.585759zM298.037421 489.549113l-0.063454-0.071386a124.663697 124.663697 0 0 1-88.637578-36.636866c-23.668415-23.747732-36.70032-55.141695-36.70032-88.64551 0-33.829018 13.27779-65.643364 37.580747-89.454551a126.05969 126.05969 0 0 1 89.081757-35.764371 125.512397 125.512397 0 0 1 86.686362 36.414776c49.169069 48.820071 49.454613 128.264723 0.642474 177.449656a124.354358 124.354358 0 0 1-88.589988 36.708252z" fill="#17A29E" p-id="1337"></path></svg>
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1767084779395" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="21382" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M304.79872 108.70784c-118.75328 2.56-194.5856 51.74784-259.92192 144.73216-69.6832 99.1232-37.41696 233.31328-37.41696 233.31328s31.7696-100.06528 88.81664-147.74784c50.53952-42.22976 99.60448-78.11072 208.52224-82.97984v54.68672l141.69088-126.94016-141.69088-129.024v53.95968zM719.19616 915.25632c118.75328-2.56512 194.5856-51.712 259.96288-144.6912 69.64224-99.1232 37.37088-233.31328 37.37088-233.31328s-31.7696 100.06528-88.81152 147.74784c-50.51904 42.20928-99.6096 78.08512-208.52736 82.95936v-54.66624l-141.66528 126.93504 141.66528 129.024v-53.99552zM794.82368 304.37376h-88.64256c-72.77056 0-131.712 58.96192-131.712 131.712v110.96064c54.29248 22.21056 113.75616 34.49856 176.06656 34.49856 62.26944 0 121.728-12.288 176.02048-34.49856V436.08064c-0.00512-72.75008-58.96192-131.70688-131.73248-131.70688zM863.34464 167.58272c0 62.336-50.49856 112.87552-112.80896 112.87552-62.37696 0-112.87552-50.53952-112.87552-112.87552s50.49856-112.83456 112.87552-112.83456c62.30528 0 112.80896 50.49856 112.80896 112.83456z" fill="#ffffff" p-id="21383"></path><path d="M333.27616 692.08576H244.6336c-72.77056 0-131.73248 58.9824-131.73248 131.73248v110.94016c54.30784 22.23104 113.75104 34.49856 176.06656 34.49856 62.28992 0 121.74848-12.26752 176.02048-34.49856v-110.94016c0-72.75008-58.96192-131.73248-131.712-131.73248zM401.80224 555.31008c0 62.31552-50.47808 112.88064-112.83456 112.88064-62.37696 0-112.88064-50.56512-112.88064-112.88064 0-62.35136 50.50368-112.85504 112.88064-112.85504 62.35648 0 112.83456 50.5088 112.83456 112.85504z" fill="#ffffff" p-id="21384"></path></svg>
|
||||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.9 KiB |
1
frontend/src/assets/icons/branch.svg
Normal file
1
frontend/src/assets/icons/branch.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1766289101374" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2562" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M801.796646 348.507817c-6.599924 7.099918-9.599889 16.799806-8.299904 26.399694 1.499983 10.899874 2.399972 21.799748 2.699969 32.299627 1.399984 51.499404-11.099872 96.098888-37.09957 132.698464-45.099478 63.199269-117.398641 83.499034-170.098032 98.298863-35.799586 9.999884-61.599287 12.599854-82.399047 14.599831-22.799736 2.299973-34.299603 3.399961-50.599414 12.799851-3.199963 1.899978-6.399926 3.899955-9.49989 6.09993-29.499659 21.199755-46.399463 55.699355-46.399463 91.998935v28.599669c0 10.699876 5.499936 20.599762 14.399833 26.599692 30.299649 20.399764 50.199419 55.199361 49.599426 94.598906-0.799991 60.299302-49.999421 109.598732-110.398722 110.398722C291.002557 1024.89999 240.003148 974.400574 240.003148 912.001296c0-38.89955 19.799771-73.199153 49.899422-93.198921 8.799898-5.899932 14.099837-15.899816 14.099837-26.499694V231.60917c0-10.699876-5.499936-20.599762-14.399833-26.599692-30.299649-20.399764-50.199419-55.199361-49.599426-94.598906C240.803138 50.11127 290.002569 0.911839 350.40187 0.01185 413.001146-0.88814 464.000555 49.611276 464.000555 112.010554c0 38.89955-19.799771 73.199153-49.899422 93.198921-8.799898 5.899932-14.099837 15.899816-14.099837 26.499694v346.095994c0 4.099953 4.399949 6.599924 7.999908 4.599947h0.099998c34.299603-19.699772 62.099281-22.399741 88.99897-25.099709 18.799782-1.799979 38.299557-3.799956 65.899238-11.499867 43.299499-12.09986 92.398931-25.8997 117.898635-61.599287 16.999803-23.799725 20.599762-53.399382 19.099779-79.599079-0.699992-12.599854-8.699899-23.499728-20.399763-28.099675-42.099513-16.299811-71.899168-57.299337-71.599172-105.298781 0.399995-61.699286 51.299406-111.698707 112.998692-111.198714 61.399289 0.499994 110.998715 50.499416 110.998716 111.998704 0 29.599657-11.499867 56.499346-30.199651 76.499115z" p-id="2563" fill="#ffffff"></path></svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -1,15 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed, reactive } from 'vue'
|
import { ref, onMounted, computed, reactive, nextTick } from 'vue'
|
||||||
|
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||||
import { useAgentsStore, useConfigStore } from '@/stores'
|
import { useAgentsStore, useConfigStore } from '@/stores'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { changeBriefs } from '@/utils/collaboration_Brief_FrontEnd.ts'
|
import { changeBriefs } from '@/utils/collaboration_Brief_FrontEnd.ts'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { log } from '@jsplumb/browser-ui'
|
import AssignmentButton from './TaskTemplate/TaskSyllabus/components/AssignmentButton.vue'
|
||||||
import ProcessCard from './TaskTemplate/TaskProcess/ProcessCard.vue'
|
|
||||||
import AgentAllocation from './TaskTemplate/TaskSyllabus/components/AgentAllocation.vue'
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'search-start'): void
|
(e: 'search-start'): void
|
||||||
(e: 'search', value: string): void
|
(e: 'search', value: string): void
|
||||||
@@ -21,7 +17,6 @@ const searchValue = ref('')
|
|||||||
const triggerOnFocus = ref(true)
|
const triggerOnFocus = ref(true)
|
||||||
const isFocus = ref(false)
|
const isFocus = ref(false)
|
||||||
const hasAutoSearched = ref(false) // 防止重复自动搜索
|
const hasAutoSearched = ref(false) // 防止重复自动搜索
|
||||||
const agentAllocationVisible = ref(false) // 智能体分配弹窗是否可见
|
|
||||||
const isExpanded = ref(false) // 控制搜索框是否展开
|
const isExpanded = ref(false) // 控制搜索框是否展开
|
||||||
|
|
||||||
// 解析URL参数
|
// 解析URL参数
|
||||||
@@ -30,6 +25,13 @@ function getUrlParam(param: string): string | null {
|
|||||||
return urlParams.get(param)
|
return urlParams.get(param)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const planReady = computed(() => {
|
||||||
|
return agentsStore.agentRawPlan.data !== undefined
|
||||||
|
})
|
||||||
|
const openAgentAllocationDialog = () => {
|
||||||
|
console.log('打开智能体分配弹窗')
|
||||||
|
agentsStore.openAgentAllocationDialog()
|
||||||
|
}
|
||||||
// 自动搜索函数
|
// 自动搜索函数
|
||||||
async function autoSearchFromUrl() {
|
async function autoSearchFromUrl() {
|
||||||
const query = getUrlParam('q')
|
const query = getUrlParam('q')
|
||||||
@@ -46,32 +48,42 @@ async function autoSearchFromUrl() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 智能体分配
|
|
||||||
// 处理智能体分配点击事件
|
|
||||||
function handleAgentAllocation() {
|
|
||||||
agentAllocationVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭智能体分配弹窗
|
|
||||||
function handleAgentAllocationClose() {
|
|
||||||
agentAllocationVisible.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理获取焦点事件
|
// 处理获取焦点事件
|
||||||
function handleFocus() {
|
function handleFocus() {
|
||||||
isFocus.value = true
|
isFocus.value = true
|
||||||
isExpanded.value = true // 搜索框展开
|
isExpanded.value = true // 搜索框展开
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const taskContainerRef = ref<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
// 处理失去焦点事件
|
// 处理失去焦点事件
|
||||||
function handleBlur() {
|
function handleBlur() {
|
||||||
isFocus.value = false
|
isFocus.value = false
|
||||||
// 延迟收起搜索框,以便点击按钮等操作
|
// 延迟收起搜索框,以便点击按钮等操作
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isExpanded.value = false
|
isExpanded.value = false
|
||||||
|
// 强制重置文本区域高度到最小行数
|
||||||
|
resetTextareaHeight()
|
||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重置文本区域高度到最小行数
|
||||||
|
function resetTextareaHeight() {
|
||||||
|
nextTick(() => {
|
||||||
|
// 修复:使用更可靠的方式获取textarea元素
|
||||||
|
const textarea =
|
||||||
|
document.querySelector('#task-container .el-textarea__inner') ||
|
||||||
|
document.querySelector('#task-container textarea')
|
||||||
|
|
||||||
|
if (textarea) {
|
||||||
|
// 强制设置最小高度
|
||||||
|
textarea.style.height = 'auto'
|
||||||
|
textarea.style.minHeight = '56px'
|
||||||
|
textarea.style.overflowY = 'hidden'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSearch() {
|
async function handleSearch() {
|
||||||
try {
|
try {
|
||||||
triggerOnFocus.value = false
|
triggerOnFocus.value = false
|
||||||
@@ -109,8 +121,6 @@ const createFilter = (queryString: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const taskContainerRef = ref<HTMLDivElement | null>(null)
|
|
||||||
|
|
||||||
// 组件挂载时检查URL参数
|
// 组件挂载时检查URL参数
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
autoSearchFromUrl()
|
autoSearchFromUrl()
|
||||||
@@ -133,10 +143,12 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
<span class="text-[var(--color-text-task)] font-bold task-title">任务</span>
|
<span class="text-[var(--color-text-task)] font-bold task-title">任务</span>
|
||||||
<el-autocomplete
|
<el-autocomplete
|
||||||
|
ref="autocompleteRef"
|
||||||
v-model.trim="searchValue"
|
v-model.trim="searchValue"
|
||||||
class="task-input"
|
class="task-input"
|
||||||
size="large"
|
size="large"
|
||||||
:rows="isFocus ? 3 : 1"
|
:rows="1"
|
||||||
|
:autosize="{ minRows: 1, maxRows: 10 }"
|
||||||
placeholder="请输入您的任务"
|
placeholder="请输入您的任务"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:append-to="taskContainerRef"
|
:append-to="taskContainerRef"
|
||||||
@@ -169,23 +181,7 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<!-- 智能体分配 -->
|
<AssignmentButton v-dev-only v-if="planReady" @click="openAgentAllocationDialog" />
|
||||||
<!-- <div class="agent-allocation-entry" @click="handleAgentAllocation" title="智能体分配">
|
|
||||||
<SvgIcon icon-class="agent-change" size="20px" color="var(--color-bg)" />
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<!-- 智能体分配弹窗 -->
|
|
||||||
<el-dialog
|
|
||||||
v-model="agentAllocationVisible"
|
|
||||||
width="70%"
|
|
||||||
top="10vh"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
:close-on-press-escape="true"
|
|
||||||
:show-close="false"
|
|
||||||
custom-class="agent-allocation-dialog"
|
|
||||||
>
|
|
||||||
<AgentAllocation @close="handleAgentAllocationClose" />
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
@@ -216,6 +212,21 @@ onMounted(() => {
|
|||||||
/* 搜索框展开时的样式 */
|
/* 搜索框展开时的样式 */
|
||||||
&.expanded {
|
&.expanded {
|
||||||
box-shadow: var(--color-task-shadow);
|
box-shadow: var(--color-task-shadow);
|
||||||
|
:deep(.el-autocomplete .el-textarea .el-textarea__inner) {
|
||||||
|
overflow-y: auto !important;
|
||||||
|
min-height: 56px !important;
|
||||||
|
// overflow-y: hidden;
|
||||||
|
// background-color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 非展开状态时,确保文本区域高度固定 */
|
||||||
|
&:not(.expanded) {
|
||||||
|
:deep(.el-textarea__inner) {
|
||||||
|
height: 56px !important;
|
||||||
|
overflow-y: hidden !important;
|
||||||
|
min-height: 56px !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-popper) {
|
:deep(.el-popper) {
|
||||||
@@ -267,6 +278,11 @@ onMounted(() => {
|
|||||||
resize: none;
|
resize: none;
|
||||||
color: var(--color-text-taskbar);
|
color: var(--color-text-taskbar);
|
||||||
|
|
||||||
|
/* 聚焦时的样式 */
|
||||||
|
.expanded & {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
@@ -363,67 +379,4 @@ onMounted(() => {
|
|||||||
border-color: var(--el-border-color);
|
border-color: var(--el-border-color);
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.agent-allocation-entry {
|
|
||||||
position: absolute;
|
|
||||||
right: 0; /* 顶头 */
|
|
||||||
top: 0;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #00aaff; /* 纯蓝色背景 */
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 2px 6px rgba(0, 170, 255, 0.35);
|
|
||||||
transition: transform 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: scale(1.08);
|
|
||||||
}
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.96);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 智能体分配弹窗样式
|
|
||||||
:deep(.agent-allocation-dialog) {
|
|
||||||
.el-dialog {
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
padding: 16px 20px;
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
margin-right: 0;
|
|
||||||
|
|
||||||
.el-dialog__title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--color-text-title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__body {
|
|
||||||
padding: 0;
|
|
||||||
max-height: 70vh;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__headerbtn {
|
|
||||||
top: 16px;
|
|
||||||
right: 20px;
|
|
||||||
|
|
||||||
.el-dialog__close {
|
|
||||||
color: var(--color-text);
|
|
||||||
font-size: 16px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -28,12 +28,14 @@ const agentsStore = useAgentsStore()
|
|||||||
:class="agentsStore.currentTask?.AgentSelection?.includes(item.Name) ? 'active-card' : ''"
|
:class="agentsStore.currentTask?.AgentSelection?.includes(item.Name) ? 'active-card' : ''"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between relative h-[43px]">
|
<div class="flex items-center justify-between relative h-[43px]">
|
||||||
|
<!-- 图标区域 -->
|
||||||
<div
|
<div
|
||||||
class="w-[44px] h-[44px] rounded-full flex items-center justify-center flex-shrink-0 relative right-[2px] icon-container"
|
class="w-[44px] h-[44px] rounded-full flex items-center justify-center flex-shrink-0 relative right-[2px] icon-container"
|
||||||
:style="{ background: getAgentMapIcon(item.Name).color }"
|
:style="{ background: getAgentMapIcon(item.Name).color }"
|
||||||
>
|
>
|
||||||
<svg-icon :icon-class="getAgentMapIcon(item.Name).icon" color="#fff" size="24px" />
|
<svg-icon :icon-class="getAgentMapIcon(item.Name).icon" color="#fff" size="24px" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-1 text-[14px] textClass flex flex-col items-end justify-start truncate ml-1">
|
<div class="flex-1 text-[14px] textClass flex flex-col items-end justify-start truncate ml-1">
|
||||||
<div class="flex items-center justify-start gap-2 w-full">
|
<div class="flex items-center justify-start gap-2 w-full">
|
||||||
<span
|
<span
|
||||||
@@ -96,39 +98,9 @@ const agentsStore = useAgentsStore()
|
|||||||
v-if="index1 !== taskProcess.filter(i => i.AgentName === item.Name).length - 1"
|
v-if="index1 !== taskProcess.filter(i => i.AgentName === item.Name).length - 1"
|
||||||
class="h-[1px] w-full bg-[var(--color-border-default)] my-[8px]"
|
class="h-[1px] w-full bg-[var(--color-border-default)] my-[8px]"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
|
<AssignmentButton />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="item.apiUrl"
|
|
||||||
class="h-[1px] w-full bg-[var(--color-border-default)] my-[8px]"
|
|
||||||
></div>
|
|
||||||
<!-- 使用 v-if 判断 item 中是否存在 apiUrl -->
|
|
||||||
<div v-if="item.apiUrl" class="text-[12px] break-all">
|
|
||||||
<span class="text-[12px] text-[red]">apiUrl:</span>
|
|
||||||
<span class="text-[12px] text-[var(--color-text-quaternary)] break-all ml-2">{{
|
|
||||||
item.apiUrl
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 只有当 item.apiUrl 存在时才显示上面的分隔线 -->
|
|
||||||
<div
|
|
||||||
v-if="item.apiUrl"
|
|
||||||
class="h-[1px] w-full bg-[var(--color-border-default)] my-[8px]"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<!-- 使用 v-if 判断 item 中是否存在 apiModel -->
|
|
||||||
<div v-if="item.apiModel" class="text-[12px]">
|
|
||||||
<span class="text-[12px] text-[blue]">apiModel:</span>
|
|
||||||
<span class="text-[12px] text-[var(--color-text-quaternary)] ml-2">{{
|
|
||||||
item.apiModel
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 只有当 item.apiModel 存在时才显示下面的分隔线 -->
|
|
||||||
<div
|
|
||||||
v-if="item.apiModel"
|
|
||||||
class="h-[1px] w-full bg-[var(--color-border-default)] my-[8px]"
|
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { getActionTypeDisplay } from '@/layout/components/config.ts'
|
import { getActionTypeDisplay } from '@/layout/components/config.ts'
|
||||||
import { CloseBold, Select } from '@element-plus/icons-vue'
|
import { useAgentsStore } from '@/stores'
|
||||||
|
import BranchButton from './components/TaskButton.vue'
|
||||||
|
|
||||||
|
const agentsStore = useAgentsStore()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
step: {
|
step: {
|
||||||
@@ -27,6 +30,11 @@ const editValue = ref('')
|
|||||||
// 鼠标悬停的process ID
|
// 鼠标悬停的process ID
|
||||||
const hoverProcessId = ref<string | null>(null)
|
const hoverProcessId = ref<string | null>(null)
|
||||||
|
|
||||||
|
// 检测当前是否是深色模式
|
||||||
|
function isDarkMode(): boolean {
|
||||||
|
return document.documentElement.classList.contains('dark')
|
||||||
|
}
|
||||||
|
|
||||||
// 获取颜色浅两号的函数
|
// 获取颜色浅两号的函数
|
||||||
function getLightColor(color: string, level: number = 2): string {
|
function getLightColor(color: string, level: number = 2): string {
|
||||||
if (!color || color.length !== 7 || color[0] !== '#') return color
|
if (!color || color.length !== 7 || color[0] !== '#') return color
|
||||||
@@ -46,6 +54,34 @@ function getLightColor(color: string, level: number = 2): string {
|
|||||||
.padStart(2, '0')}${Math.round(newB).toString(16).padStart(2, '0')}`
|
.padStart(2, '0')}${Math.round(newB).toString(16).padStart(2, '0')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取颜色深两号的函数
|
||||||
|
function getDarkColor(color: string, level: number = 2): string {
|
||||||
|
if (!color || color.length !== 7 || color[0] !== '#') return color
|
||||||
|
|
||||||
|
const r = parseInt(color.substr(1, 2), 16)
|
||||||
|
const g = parseInt(color.substr(3, 2), 16)
|
||||||
|
const b = parseInt(color.substr(5, 2), 16)
|
||||||
|
|
||||||
|
// 降低亮度(深两号)
|
||||||
|
const darkenAmount = level * 20
|
||||||
|
const newR = Math.max(0, r - darkenAmount)
|
||||||
|
const newG = Math.max(0, g - darkenAmount)
|
||||||
|
const newB = Math.max(0, b - darkenAmount)
|
||||||
|
|
||||||
|
return `#${Math.round(newR).toString(16).padStart(2, '0')}${Math.round(newG)
|
||||||
|
.toString(16)
|
||||||
|
.padStart(2, '0')}${Math.round(newB).toString(16).padStart(2, '0')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据主题模式获取调整后的颜色
|
||||||
|
function getAdjustedColor(color: string, level: number = 2): string {
|
||||||
|
if (isDarkMode()) {
|
||||||
|
return getDarkColor(color, level)
|
||||||
|
} else {
|
||||||
|
return getLightColor(color, level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 处理鼠标进入
|
// 处理鼠标进入
|
||||||
function handleMouseEnter(processId: string) {
|
function handleMouseEnter(processId: string) {
|
||||||
hoverProcessId.value = processId
|
hoverProcessId.value = processId
|
||||||
@@ -107,30 +143,32 @@ function handleCancel() {
|
|||||||
<!-- 编辑模式 - 修改为卡片样式 -->
|
<!-- 编辑模式 - 修改为卡片样式 -->
|
||||||
<div v-if="editingProcessId === process.ID" class="edit-container">
|
<div v-if="editingProcessId === process.ID" class="edit-container">
|
||||||
<div class="edit-card">
|
<div class="edit-card">
|
||||||
<el-input
|
<div class="flex flex-col gap-3">
|
||||||
v-model="editValue"
|
<el-input
|
||||||
type="textarea"
|
v-model="editValue"
|
||||||
:autosize="{ minRows: 3, maxRows: 6 }"
|
type="textarea"
|
||||||
placeholder="请输入描述内容"
|
:autosize="{ minRows: 3, maxRows: 6 }"
|
||||||
autofocus
|
placeholder="请输入描述内容"
|
||||||
/>
|
autofocus
|
||||||
<div class="edit-buttons">
|
/>
|
||||||
<el-button
|
<div class="flex justify-end">
|
||||||
type="success"
|
<svg-icon
|
||||||
size="small"
|
icon-class="Check"
|
||||||
:icon="Select"
|
size="20px"
|
||||||
@click="handleSave(process.ID)"
|
color="#328621"
|
||||||
title="保存"
|
class="cursor-pointer mr-4"
|
||||||
>
|
@click="handleSave(process.ID)"
|
||||||
</el-button>
|
title="保存"
|
||||||
<el-button
|
/>
|
||||||
type="danger"
|
<svg-icon
|
||||||
size="small"
|
icon-class="Cancel"
|
||||||
:icon="CloseBold"
|
size="20px"
|
||||||
@click="handleCancel"
|
color="#8e0707"
|
||||||
title="取消"
|
class="cursor-pointer mr-1"
|
||||||
>
|
@click="handleCancel"
|
||||||
</el-button>
|
title="取消"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -144,7 +182,7 @@ function handleCancel() {
|
|||||||
border: `1px solid ${getActionTypeDisplay(process.ActionType)?.border}`,
|
border: `1px solid ${getActionTypeDisplay(process.ActionType)?.border}`,
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
hoverProcessId === process.ID
|
hoverProcessId === process.ID
|
||||||
? getLightColor(getActionTypeDisplay(process.ActionType)?.color || '#909399')
|
? getAdjustedColor(getActionTypeDisplay(process.ActionType)?.color || '#909399')
|
||||||
: 'transparent'
|
: 'transparent'
|
||||||
}"
|
}"
|
||||||
@dblclick="handleDblClick(process.ID, process.Description)"
|
@dblclick="handleDblClick(process.ID, process.Description)"
|
||||||
@@ -156,11 +194,13 @@ function handleCancel() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<BranchButton :step="step" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.process-card {
|
.process-card {
|
||||||
|
position: relative;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -194,7 +234,6 @@ function handleCancel() {
|
|||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
|
||||||
.edit-card {
|
.edit-card {
|
||||||
position: relative;
|
|
||||||
//background: #f0f2f5;
|
//background: #f0f2f5;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
@@ -217,36 +256,10 @@ function handleCancel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.edit-buttons {
|
.edit-buttons {
|
||||||
position: absolute;
|
|
||||||
right: 12px;
|
|
||||||
bottom: 8px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
justify-content: flex-end;
|
||||||
.el-button {
|
margin-top: 8px;
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
background-color: #67c23a;
|
|
||||||
border-color: #67c23a;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
background-color: #f56c6c;
|
|
||||||
border-color: #f56c6c;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,113 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useAgentsStore, useSelectionStore } from '@/stores'
|
||||||
|
|
||||||
|
const agentsStore = useAgentsStore()
|
||||||
|
const selectionStore = useSelectionStore()
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'click'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
step?: any
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 获取分支数量 - 主分支(1) + 额外分支数量
|
||||||
|
const branchCount = computed(() => {
|
||||||
|
if (!props.step?.Id) return 1
|
||||||
|
|
||||||
|
// 获取该任务步骤的分支数据
|
||||||
|
const taskStepId = props.step.Id
|
||||||
|
const branches = selectionStore.getTaskProcessBranches(taskStepId)
|
||||||
|
|
||||||
|
// 主分支(1) + 额外分支数量
|
||||||
|
return 1 + branches.length
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleClick = (event: MouseEvent) => {
|
||||||
|
event.stopPropagation() // 阻止冒泡,避免触发卡片点击
|
||||||
|
emit('click')
|
||||||
|
// 设置当前任务
|
||||||
|
if (props.step) {
|
||||||
|
agentsStore.setCurrentTask(props.step)
|
||||||
|
}
|
||||||
|
// 触发打开任务过程探索窗口
|
||||||
|
agentsStore.openPlanTask()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="task-button"
|
||||||
|
:class="{ 'has-branches': branchCount > 1 }"
|
||||||
|
@click="handleClick"
|
||||||
|
:title="`${branchCount} 个分支`"
|
||||||
|
>
|
||||||
|
<!-- 流程图标 -->
|
||||||
|
<svg-icon icon-class="branch" size="20px" class="task-icon" />
|
||||||
|
|
||||||
|
<!-- 分支数量显示 -->
|
||||||
|
<span class="branch-count">
|
||||||
|
{{ branchCount }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.task-button {
|
||||||
|
/* 定位 - 右下角 */
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
/* 尺寸 */
|
||||||
|
width: 36px;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
/* 样式 */
|
||||||
|
background-color: #43a8aa;
|
||||||
|
border-radius: 10px 0 0 0;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
/* 布局 */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
/* 交互 */
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
filter: brightness(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-branches::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -2px;
|
||||||
|
right: -2px;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: #ff6b6b;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-icon {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.branch-count {
|
||||||
|
position: absolute;
|
||||||
|
right: 4px;
|
||||||
|
bottom: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: white;
|
||||||
|
font-weight: 800;
|
||||||
|
text-align: right;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
// 模拟后端原始返回格式的 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
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
// /api/fill_stepTask 接口的Vue适用mock数据
|
||||||
|
import type { IApiStepTask, IRawStepTask } from '@/stores/modules/agents'
|
||||||
|
|
||||||
|
// 模拟接口响应数据
|
||||||
|
export const mockFillStepTaskResponse: IApiStepTask = {
|
||||||
|
name: '需求分析与原型设计',
|
||||||
|
content: '分析用户需求并创建产品原型',
|
||||||
|
inputs: ['用户调研报告', '竞品分析文档'],
|
||||||
|
output: '产品原型设计稿',
|
||||||
|
agents: ['实验材料学家', '腐蚀机理研究员', '防护工程专家'],
|
||||||
|
brief: {
|
||||||
|
template: '基于!<0>!和!<1>!,!<2>!、!<3>!和!<4>!执行!<5>!任务,以获得!<6>!。',
|
||||||
|
data: {
|
||||||
|
'0': {
|
||||||
|
text: '用户调研报告',
|
||||||
|
style: {
|
||||||
|
background: 'hsl(120, 60%, 70%)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'1': {
|
||||||
|
text: '竞品分析文档',
|
||||||
|
style: {
|
||||||
|
background: 'hsl(120, 60%, 70%)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'2': {
|
||||||
|
text: '实验材料学家',
|
||||||
|
style: {
|
||||||
|
background: 'hsl(0, 0%, 90%)',
|
||||||
|
boxShadow: '1px 1px 4px 1px rgba(0,0,0,0.2)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'3': {
|
||||||
|
text: '腐蚀机理研究员',
|
||||||
|
style: {
|
||||||
|
background: 'hsl(0, 0%, 90%)',
|
||||||
|
boxShadow: '1px 1px 4px 1px rgba(0,0,0,0.2)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'4': {
|
||||||
|
text: '防护工程专家',
|
||||||
|
style: {
|
||||||
|
background: 'hsl(0, 0%, 90%)',
|
||||||
|
boxShadow: '1px 1px 4px 1px rgba(0,0,0,0.2)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'5': {
|
||||||
|
text: '分析用户需求并创建产品原型',
|
||||||
|
style: {
|
||||||
|
background: 'hsl(0, 0%, 87%)',
|
||||||
|
border: '1.5px solid #ddd',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'6': {
|
||||||
|
text: '产品原型设计稿',
|
||||||
|
style: {
|
||||||
|
background: 'hsl(30, 100%, 80%)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
process: [
|
||||||
|
{
|
||||||
|
id: 'action_001',
|
||||||
|
type: '需求分析',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '分析用户调研报告,识别核心需求点',
|
||||||
|
inputs: ['用户调研报告'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'action_002',
|
||||||
|
type: '竞品分析',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '对比竞品功能,确定产品差异化优势',
|
||||||
|
inputs: ['竞品分析文档'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'action_003',
|
||||||
|
type: '信息架构设计',
|
||||||
|
agent: '防护工程专家',
|
||||||
|
description: '设计产品信息结构和用户流程',
|
||||||
|
inputs: ['需求分析结果'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'action_004',
|
||||||
|
type: '界面原型设计',
|
||||||
|
agent: '腐蚀机理研究员',
|
||||||
|
description: '创建高保真界面原型',
|
||||||
|
inputs: ['信息架构设计'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'action_005',
|
||||||
|
type: '原型评审',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '组织团队评审原型设计',
|
||||||
|
inputs: ['界面原型设计'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求参数类型
|
||||||
|
export interface IFillStepTaskRequest {
|
||||||
|
goal: string
|
||||||
|
stepTask: IApiStepTask
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vue composable
|
||||||
|
export const useFillStepTaskMock = () => {
|
||||||
|
const fillStepTask = async (
|
||||||
|
goal: string,
|
||||||
|
stepTask: IApiStepTask,
|
||||||
|
): Promise<{ data: IApiStepTask }> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({
|
||||||
|
data: mockFillStepTaskResponse,
|
||||||
|
})
|
||||||
|
}, 500)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
fillStepTask,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vue组件使用示例
|
||||||
|
export const fillStepTaskExampleRequest: IFillStepTaskRequest = {
|
||||||
|
goal: '开发一个智能协作平台',
|
||||||
|
stepTask: {
|
||||||
|
name: '需求分析与原型设计',
|
||||||
|
content: '分析用户需求并创建产品原型',
|
||||||
|
inputs: ['用户调研报告', '竞品分析文档'],
|
||||||
|
output: '产品原型设计稿',
|
||||||
|
agents: [],
|
||||||
|
brief: {
|
||||||
|
template: '',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
process: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
// /api/fill_stepTask_TaskProcess 接口的Vue适用mock数据
|
||||||
|
import type { IApiStepTask } from '@/stores'
|
||||||
|
|
||||||
|
// 模拟接口响应数据
|
||||||
|
export const mockFillAgentSelectionResponse: IApiStepTask = {
|
||||||
|
name: '技术方案设计与开发',
|
||||||
|
content: '设计技术架构并完成核心功能开发',
|
||||||
|
inputs: ['产品需求文档', '技术选型指南'],
|
||||||
|
output: '可运行的产品版本',
|
||||||
|
agents: ['架构师', '后端工程师', '前端工程师', '测试工程师'],
|
||||||
|
brief: {
|
||||||
|
template: '基于!<0>!和!<1>!,!<2>!、!<3>!、!<4>!和!<5>!执行!<6>!任务,以获得!<7>!。',
|
||||||
|
data: {
|
||||||
|
'0': {
|
||||||
|
text: '产品需求文档',
|
||||||
|
style: {
|
||||||
|
background: 'hsl(120, 60%, 70%)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'1': {
|
||||||
|
text: '技术选型指南',
|
||||||
|
style: {
|
||||||
|
background: 'hsl(120, 60%, 70%)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'2': {
|
||||||
|
text: '架构师',
|
||||||
|
style: {
|
||||||
|
background: 'hsl(0, 0%, 90%)',
|
||||||
|
boxShadow: '1px 1px 4px 1px rgba(0,0,0,0.2)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'3': {
|
||||||
|
text: '后端工程师',
|
||||||
|
style: {
|
||||||
|
background: 'hsl(0, 0%, 90%)',
|
||||||
|
boxShadow: '1px 1px 4px 1px rgba(0,0,0,0.2)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'4': {
|
||||||
|
text: '前端工程师',
|
||||||
|
style: {
|
||||||
|
background: 'hsl(0, 0%, 90%)',
|
||||||
|
boxShadow: '1px 1px 4px 1px rgba(0,0,0,0.2)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'5': {
|
||||||
|
text: '测试工程师',
|
||||||
|
style: {
|
||||||
|
background: 'hsl(0, 0%, 90%)',
|
||||||
|
boxShadow: '1px 1px 4px 1px rgba(0,0,0,0.2)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'6': {
|
||||||
|
text: '设计技术架构并完成核心功能开发',
|
||||||
|
style: {
|
||||||
|
background: 'hsl(0, 0%, 87%)',
|
||||||
|
border: '1.5px solid #ddd',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'7': {
|
||||||
|
text: '可运行的产品版本',
|
||||||
|
style: {
|
||||||
|
background: 'hsl(30, 100%, 80%)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
process: [
|
||||||
|
{
|
||||||
|
id: 'action_101',
|
||||||
|
type: '技术架构设计',
|
||||||
|
agent: '架构师',
|
||||||
|
description: '设计系统架构和技术栈选型',
|
||||||
|
inputs: ['产品需求文档', '技术选型指南'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'action_102',
|
||||||
|
type: '数据库设计',
|
||||||
|
agent: '后端工程师',
|
||||||
|
description: '设计数据库表结构和关系',
|
||||||
|
inputs: ['技术架构设计'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'action_103',
|
||||||
|
type: '后端API开发',
|
||||||
|
agent: '后端工程师',
|
||||||
|
description: '实现RESTful API接口',
|
||||||
|
inputs: ['数据库设计'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'action_104',
|
||||||
|
type: '前端界面开发',
|
||||||
|
agent: '前端工程师',
|
||||||
|
description: '开发用户界面和交互功能',
|
||||||
|
inputs: ['后端API开发'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'action_105',
|
||||||
|
type: '单元测试',
|
||||||
|
agent: '测试工程师',
|
||||||
|
description: '编写和执行单元测试用例',
|
||||||
|
inputs: ['前端界面开发'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'action_106',
|
||||||
|
type: '集成测试',
|
||||||
|
agent: '测试工程师',
|
||||||
|
description: '进行系统集成测试',
|
||||||
|
inputs: ['单元测试'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求参数类型
|
||||||
|
export interface IFillAgentSelectionRequest {
|
||||||
|
goal: string
|
||||||
|
stepTask: IApiStepTask
|
||||||
|
agents: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vue composable
|
||||||
|
export const useFillAgentSelectionMock = () => {
|
||||||
|
const fillAgentSelection = async (
|
||||||
|
goal: string,
|
||||||
|
stepTask: IApiStepTask,
|
||||||
|
agents: string[],
|
||||||
|
): Promise<{ data: IApiStepTask }> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({
|
||||||
|
data: mockFillAgentSelectionResponse,
|
||||||
|
})
|
||||||
|
}, 500)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
fillAgentSelection,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vue组件使用示例
|
||||||
|
export const fillAgentSelectionExampleRequest: IFillAgentSelectionRequest = {
|
||||||
|
goal: '开发一个智能协作平台',
|
||||||
|
stepTask: {
|
||||||
|
name: '技术方案设计与开发',
|
||||||
|
content: '设计技术架构并完成核心功能开发',
|
||||||
|
inputs: ['产品需求文档', '技术选型指南'],
|
||||||
|
output: '可运行的产品版本',
|
||||||
|
agents: [],
|
||||||
|
brief: {
|
||||||
|
template: '',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
process: [],
|
||||||
|
},
|
||||||
|
agents: ['架构师', '后端工程师', '前端工程师', '测试工程师'],
|
||||||
|
}
|
||||||
@@ -109,6 +109,26 @@ const handleBlur = () => {
|
|||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 编辑状态下的提示 -->
|
||||||
|
<div v-if="isEditing" class="mt-2 text-end text-xs text-gray-500">
|
||||||
|
<svg-icon
|
||||||
|
icon-class="Check"
|
||||||
|
size="20px"
|
||||||
|
color="#328621"
|
||||||
|
class="cursor-pointer mr-4"
|
||||||
|
@click="handleSave"
|
||||||
|
title="保存"
|
||||||
|
/>
|
||||||
|
<svg-icon
|
||||||
|
icon-class="Cancel"
|
||||||
|
size="20px"
|
||||||
|
color="#8e0707"
|
||||||
|
class="cursor-pointer mr-4"
|
||||||
|
@click="handleCancel"
|
||||||
|
title="取消"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ const emit = defineEmits<{
|
|||||||
const agentsStore = useAgentsStore()
|
const agentsStore = useAgentsStore()
|
||||||
const drawerVisible = ref(false)
|
const drawerVisible = ref(false)
|
||||||
const collaborationProcess = computed(() => {
|
const collaborationProcess = computed(() => {
|
||||||
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
|
const data = agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
|
||||||
|
// console.log('data:', data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
// return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
|
||||||
})
|
})
|
||||||
|
|
||||||
// 监听额外产物变化
|
// 监听额外产物变化
|
||||||
@@ -319,7 +323,7 @@ defineExpose({
|
|||||||
:class="{ 'is-running': agentsStore.executePlan.length > 0 }"
|
:class="{ 'is-running': agentsStore.executePlan.length > 0 }"
|
||||||
>
|
>
|
||||||
<!-- 标题与执行按钮 -->
|
<!-- 标题与执行按钮 -->
|
||||||
<div class="text-[18px] font-bold mb-[18px] flex justify-between items-center px-[20px]">
|
<div class="text-[18px] font-bold mb-[7px] flex justify-between items-center px-[20px]">
|
||||||
<span class="text-[var(--color-text-title-header)]">执行结果</span>
|
<span class="text-[var(--color-text-title-header)]">执行结果</span>
|
||||||
<div
|
<div
|
||||||
class="flex items-center justify-end gap-[14px] task-button-group min-w-[175px]"
|
class="flex items-center justify-end gap-[14px] task-button-group min-w-[175px]"
|
||||||
@@ -332,6 +336,7 @@ defineExpose({
|
|||||||
:title="processBtnTitle"
|
:title="processBtnTitle"
|
||||||
@mouseenter="handleProcessMouseEnter"
|
@mouseenter="handleProcessMouseEnter"
|
||||||
@click="handleTaskProcess"
|
@click="handleTaskProcess"
|
||||||
|
style="order: 1"
|
||||||
>
|
>
|
||||||
<svg-icon icon-class="process" />
|
<svg-icon icon-class="process" />
|
||||||
<span v-if="showProcessText" class="btn-text">任务过程</span>
|
<span v-if="showProcessText" class="btn-text">任务过程</span>
|
||||||
@@ -343,6 +348,7 @@ defineExpose({
|
|||||||
title="请先输入要执行的任务"
|
title="请先输入要执行的任务"
|
||||||
:visible="showPopover"
|
:visible="showPopover"
|
||||||
@hide="showPopover = false"
|
@hide="showPopover = false"
|
||||||
|
style="order: 2"
|
||||||
>
|
>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-button
|
<el-button
|
||||||
@@ -393,7 +399,7 @@ defineExpose({
|
|||||||
<!-- 内容 -->
|
<!-- 内容 -->
|
||||||
<div
|
<div
|
||||||
v-loading="agentsStore.agentRawPlan.loading"
|
v-loading="agentsStore.agentRawPlan.loading"
|
||||||
class="flex-1 overflow-auto relative"
|
class="flex-1 overflow-auto relative ml-[20px] mr-[20px]"
|
||||||
@scroll="handleScroll"
|
@scroll="handleScroll"
|
||||||
>
|
>
|
||||||
<div id="task-results-main" class="px-[40px] relative">
|
<div id="task-results-main" class="px-[40px] relative">
|
||||||
@@ -580,7 +586,7 @@ defineExpose({
|
|||||||
|
|
||||||
.card-item {
|
.card-item {
|
||||||
background: var(--color-bg-detail);
|
background: var(--color-bg-detail);
|
||||||
padding: 25px;
|
padding: 5px;
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
}
|
}
|
||||||
@@ -668,6 +674,8 @@ defineExpose({
|
|||||||
|
|
||||||
// ========== 新增:按钮交互样式 ==========
|
// ========== 新增:按钮交互样式 ==========
|
||||||
.task-button-group {
|
.task-button-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
.el-button {
|
.el-button {
|
||||||
display: inline-flex !important;
|
display: inline-flex !important;
|
||||||
align-items: center !important;
|
align-items: center !important;
|
||||||
@@ -679,6 +687,10 @@ defineExpose({
|
|||||||
color: var(--color-text-primary) !important;
|
color: var(--color-text-primary) !important;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: var(--color-bg-tertiary);
|
background-color: var(--color-bg-tertiary);
|
||||||
|
gap: 0px !important;
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
-webkit-tap-highlight-color: transparent !important;
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateY(-2px) !important;
|
transform: translateY(-2px) !important;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
|
||||||
@@ -717,25 +729,58 @@ defineExpose({
|
|||||||
padding: 0 16px !important;
|
padding: 0 16px !important;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
.btn-text {
|
// 任务过程按钮 - 左边固定,向右展开
|
||||||
display: inline-block !important;
|
&:nth-child(1) {
|
||||||
font-size: 14px;
|
justify-content: flex-start !important;
|
||||||
font-weight: 500;
|
|
||||||
margin-left: 4px;
|
.btn-text {
|
||||||
|
display: inline-block !important;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-left: 0;
|
||||||
|
opacity: 1;
|
||||||
|
animation: fadeInLeft 0.3s ease forwards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务执行按钮 - 右边固定,向左展开
|
||||||
|
&:nth-child(2) {
|
||||||
|
justify-content: flex-end !important;
|
||||||
|
|
||||||
|
.btn-text {
|
||||||
|
display: inline-block !important;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-left: 8px;
|
||||||
|
margin-right: 0;
|
||||||
|
opacity: 1;
|
||||||
|
animation: fadeInRight 0.3s ease forwards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// .btn-text {
|
||||||
|
// display: inline-block !important;
|
||||||
|
// font-size: 14px;
|
||||||
|
// font-weight: 500;
|
||||||
|
// margin-left: 4px;
|
||||||
|
// opacity: 1;
|
||||||
|
// animation: fadeIn 0.3s ease forwards;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInLeft {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(5px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
animation: fadeIn 0.3s ease forwards;
|
transform: translateX(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-text {
|
@keyframes fadeInRight {
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-left: 4px;
|
|
||||||
opacity: 0;
|
|
||||||
animation: fadeIn 0.3s ease forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateX(-5px);
|
transform: translateX(-5px);
|
||||||
|
|||||||
@@ -0,0 +1,861 @@
|
|||||||
|
<template>
|
||||||
|
<div class="plan-modification">
|
||||||
|
<div
|
||||||
|
v-loading="agentsStore.agentRawPlan.loading || branchLoading"
|
||||||
|
class="flow-wrapper"
|
||||||
|
v-if="collaborationProcess.length > 0"
|
||||||
|
>
|
||||||
|
<VueFlow
|
||||||
|
v-model:nodes="nodes"
|
||||||
|
v-model:edges="edges"
|
||||||
|
:default-zoom="0.5"
|
||||||
|
:min-zoom="0.5"
|
||||||
|
:max-zoom="2"
|
||||||
|
class="flow-container"
|
||||||
|
@node-click="onNodeClick"
|
||||||
|
>
|
||||||
|
<!-- 控制面板 -->
|
||||||
|
<Controls />
|
||||||
|
|
||||||
|
<!-- 小地图 -->
|
||||||
|
<MiniMap />
|
||||||
|
|
||||||
|
<!-- 自定义根节点(初始目标) -->
|
||||||
|
<template #node-root="nodeProps">
|
||||||
|
<RootNode
|
||||||
|
v-bind="nodeProps"
|
||||||
|
:is-adding-branch="currentAddingBranchNodeId === nodeProps.id"
|
||||||
|
@add-branch="handleAddBranch"
|
||||||
|
@start-add-branch="handleStartAddBranch"
|
||||||
|
@cancel-add-branch="handleCancelAddBranch"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 自定义任务节点 -->
|
||||||
|
<template #node-task="nodeProps">
|
||||||
|
<TaskNode
|
||||||
|
v-bind="nodeProps"
|
||||||
|
:is-adding-branch="currentAddingBranchNodeId === nodeProps.id"
|
||||||
|
@edit-task="handleEditTask"
|
||||||
|
@save-task="handleSaveTask"
|
||||||
|
@cancel-edit="handleCancelEdit"
|
||||||
|
@add-branch="handleAddBranch"
|
||||||
|
@start-add-branch="handleStartAddBranch"
|
||||||
|
@cancel-add-branch="handleCancelAddBranch"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</VueFlow>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch, nextTick, onMounted } from 'vue'
|
||||||
|
import { VueFlow, useVueFlow } from '@vue-flow/core'
|
||||||
|
import { Controls } from '@vue-flow/controls'
|
||||||
|
import { MiniMap } from '@vue-flow/minimap'
|
||||||
|
import type { Node, Edge } from '@vue-flow/core'
|
||||||
|
import {
|
||||||
|
useAgentsStore,
|
||||||
|
useSelectionStore,
|
||||||
|
type IRawStepTask,
|
||||||
|
type IApiStepTask,
|
||||||
|
type IApiAgentAction,
|
||||||
|
type TaskProcess
|
||||||
|
} from '@/stores'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import api from '@/api'
|
||||||
|
import branchPlanOutlineMock from './mock/branchPlanOutlineMock'
|
||||||
|
import { mockFillStepTaskForVue } from './mock/fill-step-task-mock'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
|
// 导入样式
|
||||||
|
import '@vue-flow/core/dist/style.css'
|
||||||
|
import '@vue-flow/core/dist/theme-default.css'
|
||||||
|
import '@vue-flow/minimap/dist/style.css'
|
||||||
|
import '@vue-flow/controls/dist/style.css'
|
||||||
|
|
||||||
|
// 自定义节点组件
|
||||||
|
import RootNode from './components/RootNode.vue'
|
||||||
|
import TaskNode from './components/TaskNode.vue'
|
||||||
|
|
||||||
|
const agentsStore = useAgentsStore()
|
||||||
|
const selectionStore = useSelectionStore()
|
||||||
|
|
||||||
|
// Mock 数据配置
|
||||||
|
// 开关:控制是否使用 mock 数据(开发时设置为 true,生产时设置为 false)
|
||||||
|
const USE_MOCK_DATA = true
|
||||||
|
|
||||||
|
// 数据转换函数
|
||||||
|
//将 IApiAgentAction 转换为 TaskProcess
|
||||||
|
const convertToTaskProcess = (apiAction: IApiAgentAction): TaskProcess => {
|
||||||
|
return {
|
||||||
|
ActionType: apiAction.type,
|
||||||
|
AgentName: apiAction.agent,
|
||||||
|
Description: apiAction.description,
|
||||||
|
ID: apiAction.id,
|
||||||
|
ImportantInput: apiAction.inputs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//将 IApiStepTask 转换为 IRawStepTask
|
||||||
|
const convertToIRawStepTask = (apiTask: IApiStepTask): IRawStepTask => {
|
||||||
|
return {
|
||||||
|
Id: uuidv4(),
|
||||||
|
StepName: apiTask.name,
|
||||||
|
TaskContent: apiTask.content,
|
||||||
|
InputObject_List: apiTask.inputs,
|
||||||
|
OutputObject: apiTask.output,
|
||||||
|
AgentSelection: apiTask.agents,
|
||||||
|
Collaboration_Brief_frontEnd: apiTask.brief,
|
||||||
|
TaskProcess: apiTask.process.map(convertToTaskProcess)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取协作流程数据
|
||||||
|
const collaborationProcess = computed(() => {
|
||||||
|
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
|
||||||
|
})
|
||||||
|
|
||||||
|
// 节点和边数据
|
||||||
|
const nodes = ref<Node[]>([])
|
||||||
|
const edges = ref<Edge[]>([])
|
||||||
|
|
||||||
|
// Vue Flow 实例方法
|
||||||
|
const { fitView: fit, setTransform } = useVueFlow()
|
||||||
|
|
||||||
|
// 编辑状态管理
|
||||||
|
const editingTasks = ref<Record<string, string>>({})
|
||||||
|
|
||||||
|
// 当前正在添加分支的节点 ID(确保同一时间只有一个输入框)
|
||||||
|
const currentAddingBranchNodeId = ref<string | null>(null)
|
||||||
|
|
||||||
|
// 分支操作加载状态
|
||||||
|
const branchLoading = ref(false)
|
||||||
|
|
||||||
|
// 初始化标记,避免重复适应视图(使用 sessionStorage 持久化)
|
||||||
|
const INIT_KEY = 'plan-modification-initialized'
|
||||||
|
const isInitialized = ref(sessionStorage.getItem(INIT_KEY) === 'true')
|
||||||
|
|
||||||
|
// 标记是否已经挂载
|
||||||
|
const isMounted = ref(false)
|
||||||
|
|
||||||
|
// 节点尺寸配置
|
||||||
|
const NODE_WIDTH = 120 // 任务节点宽度
|
||||||
|
const ROOT_WIDTH = 200 // 根节点宽度
|
||||||
|
const NODE_GAP = 80 // 节点间距
|
||||||
|
const START_X = 40 // 起始 X 坐标
|
||||||
|
const START_Y = 50 // 起始 Y 坐标
|
||||||
|
|
||||||
|
// 初始化流程图 - 横向布局(根节点为初始目标,只展示流程卡片)
|
||||||
|
const initializeFlow = () => {
|
||||||
|
const newNodes: Node[] = []
|
||||||
|
const newEdges: Edge[] = []
|
||||||
|
|
||||||
|
// 获取初始目标作为根节点
|
||||||
|
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal']
|
||||||
|
const initialInput = agentsStore.agentRawPlan.data?.['Initial Input Object']
|
||||||
|
|
||||||
|
// 创建根节点(初始目标)
|
||||||
|
if (generalGoal) {
|
||||||
|
newNodes.push({
|
||||||
|
id: 'root-goal',
|
||||||
|
type: 'root',
|
||||||
|
position: { x: START_X, y: START_Y - 2 }, // 向下偏移与任务卡片中心对齐
|
||||||
|
data: {
|
||||||
|
goal: generalGoal,
|
||||||
|
initialInput: initialInput,
|
||||||
|
isRoot: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历协作流程,只创建任务节点
|
||||||
|
collaborationProcess.value.forEach((task, index) => {
|
||||||
|
const taskId = task.Id || `task-${index}`
|
||||||
|
|
||||||
|
// 计算任务节点位置(横向排列)
|
||||||
|
const taskX = START_X + ROOT_WIDTH + NODE_GAP + index * (NODE_WIDTH + NODE_GAP)
|
||||||
|
|
||||||
|
// 创建任务节点
|
||||||
|
newNodes.push({
|
||||||
|
id: `task-${taskId}`,
|
||||||
|
type: 'task',
|
||||||
|
position: { x: taskX, y: START_Y },
|
||||||
|
data: {
|
||||||
|
task,
|
||||||
|
isEditing: false,
|
||||||
|
editingContent: task.TaskContent || ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 如果是第一个任务,创建从根节点到第一个任务的连接
|
||||||
|
if (index === 0 && generalGoal) {
|
||||||
|
newEdges.push({
|
||||||
|
id: 'edge-root-task',
|
||||||
|
source: 'root-goal',
|
||||||
|
target: `task-${taskId}`,
|
||||||
|
sourceHandle: 'right',
|
||||||
|
targetHandle: 'left',
|
||||||
|
animated: true,
|
||||||
|
type: 'smoothstep',
|
||||||
|
style: { stroke: '#e6a23c', strokeWidth: 2 },
|
||||||
|
label: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果不是第一个任务,创建从前一个任务到当前任务的连接
|
||||||
|
if (index > 0) {
|
||||||
|
const prevTaskId = collaborationProcess.value[index - 1].Id || `task-${index - 1}`
|
||||||
|
newEdges.push({
|
||||||
|
id: `edge-task-${prevTaskId}-${taskId}`,
|
||||||
|
source: `task-${prevTaskId}`,
|
||||||
|
target: `task-${taskId}`,
|
||||||
|
sourceHandle: 'right',
|
||||||
|
targetHandle: 'left',
|
||||||
|
animated: true,
|
||||||
|
type: 'smoothstep',
|
||||||
|
style: { stroke: '#409eff', strokeWidth: 2 },
|
||||||
|
label: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
nodes.value = newNodes
|
||||||
|
edges.value = newEdges
|
||||||
|
|
||||||
|
// 📂 从 store 恢复已保存的分支数据(新增)
|
||||||
|
const savedBranches = selectionStore.getAllFlowBranches()
|
||||||
|
console.log('📂 从 store 恢复分支数据:', savedBranches)
|
||||||
|
|
||||||
|
if (savedBranches.length > 0) {
|
||||||
|
savedBranches.forEach(branch => {
|
||||||
|
// 恢复节点
|
||||||
|
branch.nodes.forEach(node => {
|
||||||
|
nodes.value.push(node)
|
||||||
|
})
|
||||||
|
// 恢复边
|
||||||
|
branch.edges.forEach(edge => {
|
||||||
|
edges.value.push(edge)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
console.log('📂 已恢复', savedBranches.length, '个分支')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只在首次挂载后初始化时适应视图,后续数据变化不适应
|
||||||
|
if (isMounted.value && !isInitialized.value) {
|
||||||
|
nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
fit({ padding: 0.15, duration: 300 })
|
||||||
|
isInitialized.value = true
|
||||||
|
sessionStorage.setItem(INIT_KEY, 'true')
|
||||||
|
}, 100)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听数据变化,重新初始化流程图(不使用 immediate,手动初始化)
|
||||||
|
watch(
|
||||||
|
() => collaborationProcess.value,
|
||||||
|
() => {
|
||||||
|
initializeFlow()
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// 组件挂载后初始化
|
||||||
|
onMounted(() => {
|
||||||
|
isMounted.value = true
|
||||||
|
initializeFlow()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 节点点击事件
|
||||||
|
const onNodeClick = (event: any) => {
|
||||||
|
console.log('点击节点:', event.node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑任务
|
||||||
|
const handleEditTask = (taskId: string) => {
|
||||||
|
const node = nodes.value.find(n => n.id === taskId)
|
||||||
|
if (node && node.data.task) {
|
||||||
|
editingTasks.value[taskId] = node.data.task.TaskContent || ''
|
||||||
|
node.data.isEditing = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存编辑
|
||||||
|
const handleSaveTask = (taskId: string, content: string) => {
|
||||||
|
const node = nodes.value.find(n => n.id === taskId)
|
||||||
|
if (node && node.data.task) {
|
||||||
|
// 更新 store 中的数据
|
||||||
|
const task = collaborationProcess.value.find(t => t.Id === node.data.task.Id)
|
||||||
|
if (task) {
|
||||||
|
task.TaskContent = content
|
||||||
|
}
|
||||||
|
node.data.task.TaskContent = content
|
||||||
|
node.data.isEditing = false
|
||||||
|
}
|
||||||
|
delete editingTasks.value[taskId]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消编辑
|
||||||
|
const handleCancelEdit = (taskId: string) => {
|
||||||
|
const node = nodes.value.find(n => n.id === taskId)
|
||||||
|
if (node) {
|
||||||
|
node.data.isEditing = false
|
||||||
|
}
|
||||||
|
delete editingTasks.value[taskId]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始添加分支(确保同一时间只有一个输入框)
|
||||||
|
const handleStartAddBranch = (nodeId: string) => {
|
||||||
|
currentAddingBranchNodeId.value = nodeId
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消添加分支
|
||||||
|
const handleCancelAddBranch = () => {
|
||||||
|
currentAddingBranchNodeId.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加分支
|
||||||
|
const handleAddBranch = async (taskId: string, branchContent: string) => {
|
||||||
|
console.log('添加分支:', { taskId, branchContent })
|
||||||
|
|
||||||
|
// 获取父节点
|
||||||
|
const parentNode = nodes.value.find(n => n.id === taskId)
|
||||||
|
if (!parentNode) return
|
||||||
|
|
||||||
|
// ==================== 移动已有分支 ====================
|
||||||
|
// 查找所有已有的分支任务节点(标记为 isBranchTask)
|
||||||
|
const allExistingBranchNodes = nodes.value.filter(n => n.data.isBranchTask)
|
||||||
|
|
||||||
|
// 将所有已有分支向下移动150px
|
||||||
|
allExistingBranchNodes.forEach(node => {
|
||||||
|
node.position.y += 200
|
||||||
|
})
|
||||||
|
|
||||||
|
// 开始加载
|
||||||
|
branchLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 创建分支节点ID
|
||||||
|
const branchId = `branch-${Date.now()}`
|
||||||
|
|
||||||
|
// 计算分支节点位置
|
||||||
|
// 根节点分支:向下分叉
|
||||||
|
// 任务节点分支:向右下方分叉
|
||||||
|
let branchX: number
|
||||||
|
let branchY: number
|
||||||
|
|
||||||
|
if (taskId === 'root-goal') {
|
||||||
|
// 根节点分支:在根节点下方
|
||||||
|
branchX = parentNode.position.x
|
||||||
|
branchY = parentNode.position.y + 200
|
||||||
|
} else {
|
||||||
|
// 任务节点分支:在任务节点右下方
|
||||||
|
branchX = parentNode.position.x + 200
|
||||||
|
branchY = parentNode.position.y + 150
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是根节点还是任务节点
|
||||||
|
if (taskId === 'root-goal') {
|
||||||
|
// ========== 根节点级别分支 ==========
|
||||||
|
let newTasks: IRawStepTask[] = []
|
||||||
|
|
||||||
|
// 用于存储新创建的分支节点和边
|
||||||
|
const newBranchNodes: Node[] = []
|
||||||
|
const newBranchEdges: Edge[] = []
|
||||||
|
|
||||||
|
if (USE_MOCK_DATA) {
|
||||||
|
// 使用 mock 数据
|
||||||
|
console.log('[Mock] 使用 branchPlanOutlineMock 数据')
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 800)) // 模拟网络延迟
|
||||||
|
|
||||||
|
// 从 mock 数据中随机选择一个分支方案(这里默认选择第一个)
|
||||||
|
const mockBranchTasks = branchPlanOutlineMock[0]
|
||||||
|
|
||||||
|
// 转换为 IRawStepTask 格式
|
||||||
|
newTasks = mockBranchTasks.map(convertToIRawStepTask)
|
||||||
|
|
||||||
|
ElMessage.success('[Mock] 任务大纲分支创建成功')
|
||||||
|
} else {
|
||||||
|
// 调用真实 API
|
||||||
|
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
|
||||||
|
const initialInput = agentsStore.agentRawPlan.data?.['Initial Input Object'] || []
|
||||||
|
|
||||||
|
// 提取现有步骤名称
|
||||||
|
const existingSteps = collaborationProcess.value
|
||||||
|
.map(step => step.StepName)
|
||||||
|
.filter(Boolean) as string[]
|
||||||
|
|
||||||
|
// 计算基线完成度
|
||||||
|
const baselineCompletion =
|
||||||
|
collaborationProcess.value.length > 0
|
||||||
|
? Math.round(
|
||||||
|
(collaborationProcess.value.length / collaborationProcess.value.length) * 100
|
||||||
|
)
|
||||||
|
: 0
|
||||||
|
|
||||||
|
const response = await api.branchPlanOutline({
|
||||||
|
branch_Number: 1,
|
||||||
|
Modification_Requirement: branchContent,
|
||||||
|
Existing_Steps: existingSteps,
|
||||||
|
Baseline_Completion: baselineCompletion,
|
||||||
|
initialInputs: Array.isArray(initialInput) ? initialInput : [initialInput],
|
||||||
|
goal: generalGoal
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response && response['Collaboration Process']) {
|
||||||
|
newTasks = response['Collaboration Process']
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessage.success('任务大纲分支创建成功')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建多个任务节点,每个任务显示为单独的流程卡片
|
||||||
|
if (newTasks.length > 0) {
|
||||||
|
// 分支任务与主流程任务对齐:从第一个主流程任务的位置开始
|
||||||
|
const branchBaseX = START_X + ROOT_WIDTH + NODE_GAP
|
||||||
|
const branchBaseY = START_Y + 300 // 向下偏移,与主流程垂直排列
|
||||||
|
const timestamp = Date.now()
|
||||||
|
const taskNodeIds: string[] = []
|
||||||
|
|
||||||
|
// 为每个任务创建节点
|
||||||
|
newTasks.forEach((task, index) => {
|
||||||
|
const taskNodeId = `branch-task-${taskId}-${timestamp}-${index}`
|
||||||
|
taskNodeIds.push(taskNodeId)
|
||||||
|
|
||||||
|
// 计算位置:横向排列,与主流程任务对齐
|
||||||
|
const taskX = branchBaseX + index * (NODE_WIDTH + NODE_GAP)
|
||||||
|
const taskY = branchBaseY
|
||||||
|
|
||||||
|
// 创建任务节点(只显示 StepName)
|
||||||
|
const newTaskNode: Node = {
|
||||||
|
id: taskNodeId,
|
||||||
|
type: 'task',
|
||||||
|
position: { x: taskX, y: taskY },
|
||||||
|
data: {
|
||||||
|
task,
|
||||||
|
isEditing: false,
|
||||||
|
editingContent: '',
|
||||||
|
isBranchTask: true // 标记为分支任务,可用于特殊样式
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes.value.push(newTaskNode)
|
||||||
|
newBranchNodes.push(newTaskNode)
|
||||||
|
|
||||||
|
// 创建连接边
|
||||||
|
if (index === 0) {
|
||||||
|
// 第一个任务连接到父节点(从根节点底部到第一个任务左边)
|
||||||
|
const newEdge: Edge = {
|
||||||
|
id: `edge-${taskId}-${taskNodeId}`,
|
||||||
|
source: taskId,
|
||||||
|
target: taskNodeId,
|
||||||
|
sourceHandle: 'bottom',
|
||||||
|
targetHandle: 'left',
|
||||||
|
type: 'smoothstep',
|
||||||
|
animated: true,
|
||||||
|
style: { stroke: '#409eff', strokeWidth: 2, strokeDasharray: '5,5' },
|
||||||
|
label: ''
|
||||||
|
}
|
||||||
|
edges.value.push(newEdge)
|
||||||
|
newBranchEdges.push(newEdge)
|
||||||
|
} else {
|
||||||
|
// 后续任务连接到前一个任务(左右连接)
|
||||||
|
const prevTaskNodeId = taskNodeIds[index - 1]
|
||||||
|
const newEdge: Edge = {
|
||||||
|
id: `edge-${prevTaskNodeId}-${taskNodeId}`,
|
||||||
|
source: prevTaskNodeId,
|
||||||
|
target: taskNodeId,
|
||||||
|
sourceHandle: 'right',
|
||||||
|
targetHandle: 'left',
|
||||||
|
type: 'smoothstep',
|
||||||
|
animated: true,
|
||||||
|
style: { stroke: '#409eff', strokeWidth: 2 }
|
||||||
|
}
|
||||||
|
edges.value.push(newEdge)
|
||||||
|
newBranchEdges.push(newEdge)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔄 填充 TaskProcess
|
||||||
|
// 为每个新创建的任务节点填充详细的 TaskProcess
|
||||||
|
if (newTasks.length > 0) {
|
||||||
|
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
|
||||||
|
|
||||||
|
// 遍历每个任务,填充 TaskProcess
|
||||||
|
for (let i = 0; i < newTasks.length; i++) {
|
||||||
|
const task = newTasks[i]
|
||||||
|
|
||||||
|
if (USE_MOCK_DATA) {
|
||||||
|
// 使用 mock 数据
|
||||||
|
console.log('[Mock] 使用 fill-step-task-mock 数据填充 TaskProcess')
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 300)) // 模拟网络延迟
|
||||||
|
|
||||||
|
// 从 mock 数据中获取一个完整的任务流程
|
||||||
|
const mockTasks = mockFillStepTaskForVue.getAllMockTasks()
|
||||||
|
const mockTask = mockTasks[i % mockTasks.length] // 循环使用 mock 数据
|
||||||
|
// 填充 TaskProcess
|
||||||
|
task.TaskProcess = mockTask.process.map((action: IApiAgentAction) => ({
|
||||||
|
ActionType: action.type,
|
||||||
|
AgentName: action.agent,
|
||||||
|
Description: action.description,
|
||||||
|
ID: action.id,
|
||||||
|
ImportantInput: action.inputs
|
||||||
|
}))
|
||||||
|
// 更新节点数据
|
||||||
|
const nodeIndex = newBranchNodes.findIndex(n => n.data.task?.Id === task.Id)
|
||||||
|
if (nodeIndex >= 0) {
|
||||||
|
newBranchNodes[nodeIndex].data.task = task
|
||||||
|
nodes.value.find(n => n.id === newBranchNodes[nodeIndex].id)!.data.task = task
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 调用真实 API 填充 TaskProcess
|
||||||
|
try {
|
||||||
|
const response = await api.fillStepTask({
|
||||||
|
goal: generalGoal,
|
||||||
|
stepTask: task
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
// 将返回的 TaskProcess 数据更新到任务中
|
||||||
|
const apiTask = response as IApiStepTask
|
||||||
|
const filledTask = convertToIRawStepTask(apiTask)
|
||||||
|
task.TaskProcess = filledTask.TaskProcess || []
|
||||||
|
|
||||||
|
// 更新节点数据
|
||||||
|
const nodeIndex = newBranchNodes.findIndex(n => n.data.task?.Id === task.Id)
|
||||||
|
if (nodeIndex >= 0) {
|
||||||
|
newBranchNodes[nodeIndex].data.task = task
|
||||||
|
nodes.value.find(n => n.id === newBranchNodes[nodeIndex].id)!.data.task = task
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('填充 TaskProcess 失败:', error)
|
||||||
|
// 继续处理下一个任务,不中断整个流程
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔄 已填充', newTasks.length, '个任务的 TaskProcess')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 📂 保存分支数据到 store(新增)
|
||||||
|
if (newBranchNodes.length > 0) {
|
||||||
|
selectionStore.addFlowBranch({
|
||||||
|
parentNodeId: taskId,
|
||||||
|
branchContent: branchContent,
|
||||||
|
branchType: 'root',
|
||||||
|
nodes: newBranchNodes,
|
||||||
|
edges: newBranchEdges,
|
||||||
|
tasks: newTasks
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ========== 任务节点级别分支 ==========
|
||||||
|
let newTasks: IRawStepTask[] = []
|
||||||
|
|
||||||
|
// 用于存储新创建的分支节点和边
|
||||||
|
const newBranchNodes: Node[] = []
|
||||||
|
const newBranchEdges: Edge[] = []
|
||||||
|
|
||||||
|
if (USE_MOCK_DATA) {
|
||||||
|
// 使用 mock 数据
|
||||||
|
console.log('[Mock] 使用 branchPlanOutlineMock 数据')
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 800)) // 模拟网络延迟
|
||||||
|
|
||||||
|
// 从 mock 数据中随机选择一个分支方案(这里默认选择第一个)
|
||||||
|
const mockBranchTasks = branchPlanOutlineMock[0]
|
||||||
|
|
||||||
|
// 转换为 IRawStepTask 格式
|
||||||
|
newTasks = mockBranchTasks.map(convertToIRawStepTask)
|
||||||
|
|
||||||
|
ElMessage.success('[Mock] 任务大纲分支创建成功')
|
||||||
|
} else {
|
||||||
|
// 调用真实 API
|
||||||
|
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
|
||||||
|
const initialInput = agentsStore.agentRawPlan.data?.['Initial Input Object'] || []
|
||||||
|
|
||||||
|
// 提取现有步骤名称
|
||||||
|
const existingSteps = collaborationProcess.value
|
||||||
|
.map(step => step.StepName)
|
||||||
|
.filter(Boolean) as string[]
|
||||||
|
|
||||||
|
// 计算基线完成度
|
||||||
|
const baselineCompletion =
|
||||||
|
collaborationProcess.value.length > 0
|
||||||
|
? Math.round(
|
||||||
|
(collaborationProcess.value.length / collaborationProcess.value.length) * 100
|
||||||
|
)
|
||||||
|
: 0
|
||||||
|
|
||||||
|
const response = await api.branchPlanOutline({
|
||||||
|
branch_Number: 1,
|
||||||
|
Modification_Requirement: branchContent,
|
||||||
|
Existing_Steps: existingSteps,
|
||||||
|
Baseline_Completion: baselineCompletion,
|
||||||
|
initialInputs: Array.isArray(initialInput) ? initialInput : [initialInput],
|
||||||
|
goal: generalGoal
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response && response['Collaboration Process']) {
|
||||||
|
newTasks = response['Collaboration Process']
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessage.success('任务大纲分支创建成功')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建多个任务节点,每个任务显示为单独的流程卡片
|
||||||
|
if (newTasks.length > 0) {
|
||||||
|
// 计算分支起始位置:在父任务节点的右下方
|
||||||
|
const branchBaseX = parentNode.position.x + NODE_WIDTH + NODE_GAP
|
||||||
|
const branchBaseY = parentNode.position.y + 300 // 向下偏移,增加距离避免重叠
|
||||||
|
const timestamp = Date.now()
|
||||||
|
const taskNodeIds: string[] = []
|
||||||
|
|
||||||
|
// 为每个任务创建节点
|
||||||
|
newTasks.forEach((task, index) => {
|
||||||
|
const taskNodeId = `branch-task-${taskId}-${timestamp}-${index}`
|
||||||
|
taskNodeIds.push(taskNodeId)
|
||||||
|
|
||||||
|
// 计算位置:横向排列
|
||||||
|
const taskX = branchBaseX + index * (NODE_WIDTH + NODE_GAP)
|
||||||
|
const taskY = branchBaseY
|
||||||
|
|
||||||
|
// 创建任务节点(只显示 StepName)
|
||||||
|
const newTaskNode: Node = {
|
||||||
|
id: taskNodeId,
|
||||||
|
type: 'task',
|
||||||
|
position: { x: taskX, y: taskY },
|
||||||
|
data: {
|
||||||
|
task,
|
||||||
|
isEditing: false,
|
||||||
|
editingContent: '',
|
||||||
|
isBranchTask: true // 标记为分支任务,可用于特殊样式
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes.value.push(newTaskNode)
|
||||||
|
newBranchNodes.push(newTaskNode)
|
||||||
|
|
||||||
|
// 创建连接边
|
||||||
|
if (index === 0) {
|
||||||
|
// 第一个任务连接到父节点(从父任务底部到第一个分支任务左边)
|
||||||
|
const newEdge: Edge = {
|
||||||
|
id: `edge-${taskId}-${taskNodeId}`,
|
||||||
|
source: taskId,
|
||||||
|
target: taskNodeId,
|
||||||
|
sourceHandle: 'bottom',
|
||||||
|
targetHandle: 'left',
|
||||||
|
type: 'smoothstep',
|
||||||
|
animated: true,
|
||||||
|
style: { stroke: '#67c23a', strokeWidth: 2, strokeDasharray: '5,5' },
|
||||||
|
label: ''
|
||||||
|
}
|
||||||
|
edges.value.push(newEdge)
|
||||||
|
newBranchEdges.push(newEdge)
|
||||||
|
} else {
|
||||||
|
// 后续任务连接到前一个任务(左右连接)
|
||||||
|
const prevTaskNodeId = taskNodeIds[index - 1]
|
||||||
|
const newEdge: Edge = {
|
||||||
|
id: `edge-${prevTaskNodeId}-${taskNodeId}`,
|
||||||
|
source: prevTaskNodeId,
|
||||||
|
target: taskNodeId,
|
||||||
|
sourceHandle: 'right',
|
||||||
|
targetHandle: 'left',
|
||||||
|
type: 'smoothstep',
|
||||||
|
animated: true,
|
||||||
|
style: { stroke: '#67c23a', strokeWidth: 2 }
|
||||||
|
}
|
||||||
|
edges.value.push(newEdge)
|
||||||
|
newBranchEdges.push(newEdge)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔄 填充 TaskProcess
|
||||||
|
// 为每个新创建的任务节点填充详细的 TaskProcess
|
||||||
|
if (newTasks.length > 0) {
|
||||||
|
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
|
||||||
|
|
||||||
|
// 遍历每个任务,填充 TaskProcess
|
||||||
|
for (let i = 0; i < newTasks.length; i++) {
|
||||||
|
const task = newTasks[i]
|
||||||
|
|
||||||
|
if (USE_MOCK_DATA) {
|
||||||
|
// 使用 mock 数据
|
||||||
|
console.log('[Mock] 使用 fill-step-task-mock 数据填充 TaskProcess')
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 300)) // 模拟网络延迟
|
||||||
|
|
||||||
|
// 从 mock 数据中获取一个完整的任务流程
|
||||||
|
const mockTasks = mockFillStepTaskForVue.getAllMockTasks()
|
||||||
|
const mockTask = mockTasks[i % mockTasks.length] // 循环使用 mock 数据
|
||||||
|
// 填充 TaskProcess
|
||||||
|
task.TaskProcess = mockTask.process.map((action: IApiAgentAction) => ({
|
||||||
|
ActionType: action.type,
|
||||||
|
AgentName: action.agent,
|
||||||
|
Description: action.description,
|
||||||
|
ID: action.id,
|
||||||
|
ImportantInput: action.inputs
|
||||||
|
}))
|
||||||
|
// 更新节点数据
|
||||||
|
const nodeIndex = newBranchNodes.findIndex(n => n.data.task?.Id === task.Id)
|
||||||
|
if (nodeIndex >= 0) {
|
||||||
|
newBranchNodes[nodeIndex].data.task = task
|
||||||
|
nodes.value.find(n => n.id === newBranchNodes[nodeIndex].id)!.data.task = task
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 调用真实 API 填充 TaskProcess
|
||||||
|
try {
|
||||||
|
const response = await api.fillStepTask({
|
||||||
|
goal: generalGoal,
|
||||||
|
stepTask: task
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
// 将返回的 TaskProcess 数据更新到任务中
|
||||||
|
const apiTask = response as IApiStepTask
|
||||||
|
const filledTask = convertToIRawStepTask(apiTask)
|
||||||
|
task.TaskProcess = filledTask.TaskProcess || []
|
||||||
|
|
||||||
|
// 更新节点数据
|
||||||
|
const nodeIndex = newBranchNodes.findIndex(n => n.data.task?.Id === task.Id)
|
||||||
|
if (nodeIndex >= 0) {
|
||||||
|
newBranchNodes[nodeIndex].data.task = task
|
||||||
|
nodes.value.find(n => n.id === newBranchNodes[nodeIndex].id)!.data.task = task
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('填充 TaskProcess 失败:', error)
|
||||||
|
// 继续处理下一个任务,不中断整个流程
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔄 已填充', newTasks.length, '个任务的 TaskProcess')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 📂 保存分支数据到 store(新增)
|
||||||
|
if (newBranchNodes.length > 0) {
|
||||||
|
selectionStore.addFlowBranch({
|
||||||
|
parentNodeId: taskId,
|
||||||
|
branchContent: branchContent,
|
||||||
|
branchType: 'task',
|
||||||
|
nodes: newBranchNodes,
|
||||||
|
edges: newBranchEdges,
|
||||||
|
tasks: newTasks
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置当前添加分支的节点 ID
|
||||||
|
currentAddingBranchNodeId.value = null
|
||||||
|
|
||||||
|
// 适应视图以显示新节点
|
||||||
|
nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
fit({ padding: 100, duration: 300 })
|
||||||
|
}, 100)
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('添加分支失败:', error)
|
||||||
|
ElMessage.error('添加分支失败,请重试')
|
||||||
|
// 重置当前添加分支的节点 ID
|
||||||
|
currentAddingBranchNodeId.value = null
|
||||||
|
} finally {
|
||||||
|
branchLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除分支
|
||||||
|
const handleDeleteBranch = (branchId: string) => {
|
||||||
|
console.log('删除分支:', branchId)
|
||||||
|
|
||||||
|
// 删除分支节点
|
||||||
|
nodes.value = nodes.value.filter(n => n.id !== branchId)
|
||||||
|
|
||||||
|
// 删除相关的边
|
||||||
|
edges.value = edges.value.filter(e => e.source !== branchId && e.target !== branchId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置视图
|
||||||
|
const resetView = () => {
|
||||||
|
setTransform({ x: 0, y: 0, zoom: 0.5 })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置初始化状态并重新适应视图
|
||||||
|
const refitView = () => {
|
||||||
|
isInitialized.value = false
|
||||||
|
sessionStorage.removeItem(INIT_KEY)
|
||||||
|
nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
fit({ padding: 50, duration: 300 })
|
||||||
|
isInitialized.value = true
|
||||||
|
sessionStorage.setItem(INIT_KEY, 'true')
|
||||||
|
}, 100)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
initializeFlow,
|
||||||
|
resetView,
|
||||||
|
refitView
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.plan-modification {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: var(--color-bg-detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-modification-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 20px;
|
||||||
|
background: var(--color-card-bg);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--color-text-title-header);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: var(--color-bg-detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vue Flow 节点样式
|
||||||
|
:deep(.vue-flow__node) {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.vue-flow__edge-path) {
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.vue-flow__edge.selected .vue-flow__edge-path) {
|
||||||
|
stroke: #409eff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,365 @@
|
|||||||
|
<template>
|
||||||
|
<div class="root-node-wrapper">
|
||||||
|
<!-- 左侧连接点(输入) -->
|
||||||
|
<Handle type="target" :position="Position.Left" id="left" />
|
||||||
|
<!-- 右侧连接点(输出) -->
|
||||||
|
<Handle type="source" :position="Position.Right" id="right" />
|
||||||
|
<!-- 底部连接点(用于分支) -->
|
||||||
|
<Handle type="source" :position="Position.Bottom" id="bottom" />
|
||||||
|
|
||||||
|
<el-card class="root-node-card" :shadow="true">
|
||||||
|
<!-- 目标内容 -->
|
||||||
|
<div class="goal-content">
|
||||||
|
<div class="goal-label">初始目标</div>
|
||||||
|
<div class="goal-text">{{ goal }}</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 底部添加按钮 -->
|
||||||
|
<div v-if="!isAddingBranch" class="external-add-btn" @click="startAddBranch">
|
||||||
|
<el-icon :size="20" color="#409eff">
|
||||||
|
<CirclePlus />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 取消按钮(输入框显示时) -->
|
||||||
|
<div v-else class="external-add-btn cancel-btn" @click="cancelAddBranch">
|
||||||
|
<el-icon :size="20" color="#f56c6c">
|
||||||
|
<Remove />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 外部输入框 -->
|
||||||
|
<div v-if="isAddingBranch" class="external-input-container">
|
||||||
|
<el-input
|
||||||
|
v-model="branchInput"
|
||||||
|
placeholder="输入分支需求..."
|
||||||
|
size="small"
|
||||||
|
@keydown="handleBranchKeydown"
|
||||||
|
ref="branchInputRef"
|
||||||
|
class="branch-input"
|
||||||
|
>
|
||||||
|
<template #suffix>
|
||||||
|
<svg-icon
|
||||||
|
icon-class="paper-plane"
|
||||||
|
size="16px"
|
||||||
|
color="#409eff"
|
||||||
|
class="submit-icon"
|
||||||
|
:class="{ 'is-disabled': !branchInput.trim() }"
|
||||||
|
@click="submitBranch"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, nextTick } from 'vue'
|
||||||
|
import { CirclePlus, Remove } from '@element-plus/icons-vue'
|
||||||
|
import { Handle, Position } from '@vue-flow/core'
|
||||||
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||||
|
interface RootNodeData {
|
||||||
|
goal: string
|
||||||
|
initialInput?: string[] | string
|
||||||
|
isRoot?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
id: string
|
||||||
|
data: RootNodeData
|
||||||
|
isAddingBranch?: boolean
|
||||||
|
[key: string]: any
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'add-branch', nodeId: string, branchContent: string): void
|
||||||
|
(e: 'start-add-branch', nodeId: string): void
|
||||||
|
(e: 'cancel-add-branch'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const goal = computed(() => props.data.goal || '')
|
||||||
|
|
||||||
|
const initialInput = computed(() => props.data.initialInput)
|
||||||
|
|
||||||
|
const displayInput = computed(() => {
|
||||||
|
if (!initialInput.value) return false
|
||||||
|
if (Array.isArray(initialInput.value)) {
|
||||||
|
return initialInput.value.length > 0
|
||||||
|
}
|
||||||
|
return initialInput.value.trim().length > 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 分支添加相关状态(使用父组件传入的 prop)
|
||||||
|
const branchInput = ref('')
|
||||||
|
const branchInputRef = ref<InstanceType<typeof HTMLInputElement>>()
|
||||||
|
|
||||||
|
// 计算属性,使用父组件传入的状态
|
||||||
|
const isAddingBranch = computed(() => props.isAddingBranch || false)
|
||||||
|
|
||||||
|
// 分支添加相关方法
|
||||||
|
const startAddBranch = () => {
|
||||||
|
emit('start-add-branch', props.id)
|
||||||
|
branchInput.value = ''
|
||||||
|
nextTick(() => {
|
||||||
|
branchInputRef.value?.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelAddBranch = () => {
|
||||||
|
emit('cancel-add-branch')
|
||||||
|
branchInput.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitBranch = () => {
|
||||||
|
if (branchInput.value.trim()) {
|
||||||
|
emit('add-branch', props.id, branchInput.value.trim())
|
||||||
|
branchInput.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBranchKeydown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault()
|
||||||
|
submitBranch()
|
||||||
|
} else if (event.key === 'Escape') {
|
||||||
|
cancelAddBranch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.root-node-card {
|
||||||
|
width: 200px;
|
||||||
|
min-height: 100px;
|
||||||
|
background: var(--color-bg-three);
|
||||||
|
border: 2px solid #409eff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 6px 16px rgba(64, 158, 255, 0.3);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.root-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: -10px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goal-icon {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goal-content {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goal-label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #409eff;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goal-text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-title-header);
|
||||||
|
line-height: 1.6;
|
||||||
|
word-break: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.initial-input {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-divider {
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(to right, transparent, #409eff, transparent);
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
word-break: break-word;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节点包装器
|
||||||
|
.root-node-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 外部添加按钮
|
||||||
|
.external-add-btn {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #409eff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
z-index: 10;
|
||||||
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #409eff;
|
||||||
|
transform: translateX(-50%) scale(1.1);
|
||||||
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateX(-50%) scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消按钮样式
|
||||||
|
&.cancel-btn {
|
||||||
|
border-color: #f56c6c;
|
||||||
|
box-shadow: 0 2px 8px rgba(245, 108, 108, 0.2);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f56c6c;
|
||||||
|
box-shadow: 0 4px 12px rgba(245, 108, 108, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 外部输入容器
|
||||||
|
.external-input-container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -80px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 260px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 100;
|
||||||
|
animation: slideDown 0.2s ease-out;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -6px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background: #fff;
|
||||||
|
border-left: 1px solid #dcdfe6;
|
||||||
|
border-top: 1px solid #dcdfe6;
|
||||||
|
transform: translateX(-50%) rotate(45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50%) translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.branch-input {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
:deep(.el-input__wrapper) {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #c0c4cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-focus {
|
||||||
|
border-color: #409eff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__inner {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
padding-right: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交图标样式
|
||||||
|
.submit-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:hover:not(.is-disabled) {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(.is-disabled) {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,538 @@
|
|||||||
|
<template>
|
||||||
|
<div class="task-node-wrapper">
|
||||||
|
<!-- 左侧连接点(输入) -->
|
||||||
|
<Handle type="target" :position="Position.Left" id="left" />
|
||||||
|
<!-- 右侧连接点(输出) -->
|
||||||
|
<Handle type="source" :position="Position.Right" id="right" />
|
||||||
|
<!-- 底部连接点(用于分支) -->
|
||||||
|
<Handle type="source" :position="Position.Bottom" id="bottom" />
|
||||||
|
|
||||||
|
<el-card
|
||||||
|
class="task-node-card"
|
||||||
|
:class="{ 'is-editing': isEditing, 'is-active': isActive }"
|
||||||
|
:shadow="true"
|
||||||
|
>
|
||||||
|
<!-- 任务名称 -->
|
||||||
|
<div class="task-name">{{ task.StepName }}</div>
|
||||||
|
|
||||||
|
<!-- <div class="divider"></div> -->
|
||||||
|
|
||||||
|
<!-- 任务内容 -->
|
||||||
|
<!-- <div v-if="isEditing" class="task-content-editing">
|
||||||
|
<el-input
|
||||||
|
v-model="editingContent"
|
||||||
|
type="textarea"
|
||||||
|
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||||
|
placeholder="请输入任务内容"
|
||||||
|
@keydown="handleKeydown"
|
||||||
|
class="task-content-editor"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<div class="edit-actions">
|
||||||
|
<svg-icon
|
||||||
|
icon-class="Check"
|
||||||
|
size="18px"
|
||||||
|
color="#328621"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="saveEdit"
|
||||||
|
title="保存"
|
||||||
|
/>
|
||||||
|
<svg-icon
|
||||||
|
icon-class="Cancel"
|
||||||
|
size="18px"
|
||||||
|
color="#8e0707"
|
||||||
|
class="cursor-pointer ml-2"
|
||||||
|
@click="cancelEdit"
|
||||||
|
title="取消"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="task-content" @dblclick="startEdit">
|
||||||
|
{{ task.TaskContent || '暂无内容' }}
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<!-- <div class="divider"></div> -->
|
||||||
|
|
||||||
|
<!-- 智能体列表 -->
|
||||||
|
<div class="agents-container">
|
||||||
|
<el-tooltip
|
||||||
|
v-for="agentSelection in task.AgentSelection"
|
||||||
|
:key="agentSelection"
|
||||||
|
effect="light"
|
||||||
|
placement="right"
|
||||||
|
:show-after="500"
|
||||||
|
popper-class="task-syllabus-tooltip-popper"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div class="agent-tooltip">
|
||||||
|
<div class="text-[16px] font-bold">{{ agentSelection }}</div>
|
||||||
|
<div class="h-[1px] w-full bg-[#494B51] my-[4px]"></div>
|
||||||
|
<div class="text-[12px]">
|
||||||
|
{{ task.TaskProcess.find(i => i.AgentName === agentSelection)?.Description }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="agent-icon" :style="{ background: getAgentIcon(agentSelection).color }">
|
||||||
|
<svg-icon :icon-class="getAgentIcon(agentSelection).icon" color="#fff" size="20px" />
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 底部添加按钮(卡片外部) -->
|
||||||
|
<div v-if="!isAddingBranch" class="external-add-btn" @click="startAddBranch">
|
||||||
|
<el-icon :size="20" color="#409eff">
|
||||||
|
<CirclePlus />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 取消按钮(输入框显示时) -->
|
||||||
|
<div v-else class="external-add-btn cancel-btn" @click="cancelAddBranch">
|
||||||
|
<el-icon :size="20" color="#f56c6c">
|
||||||
|
<Remove />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 外部输入框 -->
|
||||||
|
<div v-if="isAddingBranch" class="external-input-container">
|
||||||
|
<el-input
|
||||||
|
v-model="branchInput"
|
||||||
|
placeholder="输入分支需求..."
|
||||||
|
size="small"
|
||||||
|
@keydown="handleBranchKeydown"
|
||||||
|
ref="branchInputRef"
|
||||||
|
class="branch-input"
|
||||||
|
>
|
||||||
|
<template #suffix>
|
||||||
|
<svg-icon
|
||||||
|
icon-class="paper-plane"
|
||||||
|
size="16px"
|
||||||
|
color="#409eff"
|
||||||
|
class="submit-icon"
|
||||||
|
:class="{ 'is-disabled': !branchInput.trim() }"
|
||||||
|
@click="submitBranch"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, nextTick } from 'vue'
|
||||||
|
import { CirclePlus, Remove } from '@element-plus/icons-vue'
|
||||||
|
import { Handle, Position } from '@vue-flow/core'
|
||||||
|
import { type IRawStepTask } from '@/stores'
|
||||||
|
import { getAgentMapIcon } from '@/layout/components/config'
|
||||||
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||||
|
|
||||||
|
interface TaskNodeData {
|
||||||
|
task: IRawStepTask
|
||||||
|
isEditing: boolean
|
||||||
|
editingContent: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用更宽松的类型定义来避免类型错误
|
||||||
|
const props = defineProps<{
|
||||||
|
id: string
|
||||||
|
data: TaskNodeData
|
||||||
|
isAddingBranch?: boolean
|
||||||
|
[key: string]: any
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'edit-task', nodeId: string): void
|
||||||
|
(e: 'save-task', nodeId: string, content: string): void
|
||||||
|
(e: 'cancel-edit', nodeId: string): void
|
||||||
|
(e: 'add-branch', nodeId: string, branchContent: string): void
|
||||||
|
(e: 'start-add-branch', nodeId: string): void
|
||||||
|
(e: 'cancel-add-branch'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const editingContent = ref(props.data.task.TaskContent || '')
|
||||||
|
|
||||||
|
// 分支添加相关状态(使用父组件传入的 prop)
|
||||||
|
const branchInput = ref('')
|
||||||
|
const branchInputRef = ref<InstanceType<typeof HTMLInputElement>>()
|
||||||
|
|
||||||
|
// 计算属性,使用父组件传入的状态
|
||||||
|
const isAddingBranch = computed(() => props.isAddingBranch || false)
|
||||||
|
|
||||||
|
const isEditing = computed({
|
||||||
|
get: () => props.data.isEditing,
|
||||||
|
set: value => {
|
||||||
|
if (!value) {
|
||||||
|
emit('cancel-edit', props.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const isActive = computed(() => {
|
||||||
|
return props.data.task.StepName === props.data.task.StepName
|
||||||
|
})
|
||||||
|
|
||||||
|
const task = computed(() => props.data.task)
|
||||||
|
|
||||||
|
const getAgentIcon = (agentName: string) => {
|
||||||
|
return getAgentMapIcon(agentName)
|
||||||
|
}
|
||||||
|
|
||||||
|
const startEdit = () => {
|
||||||
|
emit('edit-task', props.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveEdit = () => {
|
||||||
|
emit('save-task', props.id, editingContent.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelEdit = () => {
|
||||||
|
emit('cancel-edit', props.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeydown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault()
|
||||||
|
saveEdit()
|
||||||
|
} else if (event.key === 'Escape') {
|
||||||
|
cancelEdit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分支添加相关方法
|
||||||
|
const startAddBranch = () => {
|
||||||
|
emit('start-add-branch', props.id)
|
||||||
|
branchInput.value = ''
|
||||||
|
nextTick(() => {
|
||||||
|
branchInputRef.value?.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelAddBranch = () => {
|
||||||
|
emit('cancel-add-branch')
|
||||||
|
branchInput.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitBranch = () => {
|
||||||
|
if (branchInput.value.trim()) {
|
||||||
|
emit('add-branch', props.id, branchInput.value.trim())
|
||||||
|
branchInput.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBranchKeydown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault()
|
||||||
|
submitBranch()
|
||||||
|
} else if (event.key === 'Escape') {
|
||||||
|
cancelAddBranch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.task-node-card {
|
||||||
|
width: 150px;
|
||||||
|
min-height: 100px;
|
||||||
|
background-color: var(--color-card-bg-task);
|
||||||
|
border: 1px solid var(--color-card-border-task);
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-card-bg-task-hover);
|
||||||
|
border-color: var(--color-card-border-hover);
|
||||||
|
box-shadow: var(--color-card-shadow-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-editing {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
padding: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--color-text-title-header);
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--color-border-separate);
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-content {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
min-height: 40px;
|
||||||
|
word-break: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-content-editing {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agents-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-icon {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-outputs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-tooltip {
|
||||||
|
padding: 8px;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑器样式
|
||||||
|
.task-content-editor {
|
||||||
|
:deep(.el-textarea__inner) {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 4px;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节点包装器(用于容纳外部按钮)
|
||||||
|
.task-node-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 外部添加按钮
|
||||||
|
.external-add-btn {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #409eff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
z-index: 10;
|
||||||
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #409eff;
|
||||||
|
transform: translateX(-50%) scale(1.1);
|
||||||
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateX(-50%) scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消按钮样式
|
||||||
|
&.cancel-btn {
|
||||||
|
border-color: #f56c6c;
|
||||||
|
box-shadow: 0 2px 8px rgba(245, 108, 108, 0.2);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f56c6c;
|
||||||
|
box-shadow: 0 4px 12px rgba(245, 108, 108, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 外部输入容器
|
||||||
|
.external-input-container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -80px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 260px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 100;
|
||||||
|
animation: slideDown 0.2s ease-out;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -6px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background: #fff;
|
||||||
|
border-left: 1px solid #dcdfe6;
|
||||||
|
border-top: 1px solid #dcdfe6;
|
||||||
|
transform: translateX(-50%) rotate(45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50%) translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-branch-section {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px dashed var(--color-border-separate);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-branch-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.branch-input-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.branch-input {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
:deep(.el-input__wrapper) {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #c0c4cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-focus {
|
||||||
|
border-color: #409eff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__inner {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
padding-right: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交图标样式
|
||||||
|
.submit-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:hover:not(.is-disabled) {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(.is-disabled) {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-adding-branch {
|
||||||
|
.task-node-card {
|
||||||
|
border-color: #409eff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.task-syllabus-tooltip-popper {
|
||||||
|
z-index: 4000 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# Mock 数据说明
|
||||||
|
|
||||||
|
本目录包含用于分支功能的 mock 数据,支持在开发环境中测试分支逻辑,无需调用真实后端 API。
|
||||||
|
|
||||||
|
## 文件说明
|
||||||
|
|
||||||
|
### 1. `branchPlanOutlineMock.ts`
|
||||||
|
**用途**: 根节点级别的分支(任务大纲分支)
|
||||||
|
|
||||||
|
**类型**: `IApiStepTask[][]`
|
||||||
|
|
||||||
|
**说明**: 返回多个分支方案,每个方案是一个完整的任务流程(IApiStepTask[])
|
||||||
|
|
||||||
|
**示例结构**:
|
||||||
|
```typescript
|
||||||
|
[
|
||||||
|
// 第一个分支方案(瀑布流开发)
|
||||||
|
[task1, task2, ...],
|
||||||
|
// 第二个分支方案(敏捷开发)
|
||||||
|
[task1, task2, ...],
|
||||||
|
// 第三个分支方案(快速原型)
|
||||||
|
[task1, task2, ...]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `branchTaskProcessMock.ts`
|
||||||
|
**用途**: 任务节点级别的分支(任务流程分支)
|
||||||
|
|
||||||
|
**类型**: `IApiAgentAction[][]`
|
||||||
|
|
||||||
|
**说明**: 返回多个分支方案,每个方案是一系列动作(IApiAgentAction[]),这些动作会追加到现有任务的 TaskProcess 中
|
||||||
|
|
||||||
|
**示例结构**:
|
||||||
|
```typescript
|
||||||
|
[
|
||||||
|
// 第一个分支方案(标准开发流程)
|
||||||
|
[action1, action2, action3, ...],
|
||||||
|
// 第二个分支方案(快速原型流程)
|
||||||
|
[action1, action2, ...],
|
||||||
|
// ... 更多方案
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 如何使用
|
||||||
|
|
||||||
|
### 切换 Mock 数据开关
|
||||||
|
|
||||||
|
在 `PlanModification.vue` 文件中,找到以下配置:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 开关:控制是否使用 mock 数据(开发时设置为 true,生产时设置为 false)
|
||||||
|
const USE_MOCK_DATA = true
|
||||||
|
```
|
||||||
|
|
||||||
|
- **开发阶段**: 设置为 `true`,使用 mock 数据
|
||||||
|
- **生产环境**: 设置为 `false`,调用真实 API
|
||||||
|
|
||||||
|
### 数据转换流程
|
||||||
|
|
||||||
|
```
|
||||||
|
IApiStepTask (API 格式)
|
||||||
|
↓ convertToIRawStepTask()
|
||||||
|
IRawStepTask (内部格式)
|
||||||
|
↓
|
||||||
|
更新到 agentsStore.agentRawPlan.data
|
||||||
|
↓
|
||||||
|
Vue Flow 流程图自动更新
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mock 数据特点
|
||||||
|
|
||||||
|
1. **模拟网络延迟**: Mock 数据调用会模拟 800ms 的网络延迟
|
||||||
|
2. **多方案选择**: 每个分支接口提供多个备选方案(目前默认使用第一个)
|
||||||
|
3. **完整数据结构**: Mock 数据包含完整的字段,与真实 API 返回格式一致
|
||||||
|
4. **类型安全**: 使用 TypeScript 类型定义,确保类型正确
|
||||||
|
|
||||||
|
## 扩展 Mock 数据
|
||||||
|
|
||||||
|
### 添加新的分支方案
|
||||||
|
|
||||||
|
在对应的 mock 文件中添加新的数组元素:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// branchPlanOutlineMock.ts
|
||||||
|
const mockPlanBranchData: IApiStepTask[][] = [
|
||||||
|
// 现有方案...
|
||||||
|
[
|
||||||
|
// 新增方案
|
||||||
|
{
|
||||||
|
name: '新方案步骤1',
|
||||||
|
content: '...',
|
||||||
|
// ... 其他字段
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '新方案步骤2',
|
||||||
|
content: '...',
|
||||||
|
// ... 其他字段
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 随机选择方案
|
||||||
|
|
||||||
|
修改 `PlanModification.vue` 中的方案选择逻辑:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 当前:固定选择第一个
|
||||||
|
const mockBranchTasks = branchPlanOutlineMock[0]
|
||||||
|
|
||||||
|
// 改为:随机选择一个方案
|
||||||
|
const randomIndex = Math.floor(Math.random() * branchPlanOutlineMock.length)
|
||||||
|
const mockBranchTasks = branchPlanOutlineMock[randomIndex]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. ⚠️ **生产环境**: 发布前务必将 `USE_MOCK_DATA` 设置为 `false`
|
||||||
|
2. 🔍 **调试**: 查看控制台日志,以 `[Mock]` 开头的是 mock 数据相关日志
|
||||||
|
3. 📝 **数据一致性**: 确保 mock 数据结构与真实 API 返回格式一致
|
||||||
|
4. 🔄 **数据持久化**: Mock 数据仅存在于前端,刷新页面后会丢失
|
||||||
|
|
||||||
|
## 相关文件
|
||||||
|
|
||||||
|
- `PlanModification.vue`: 主要逻辑文件,包含分支添加和 mock 数据集成
|
||||||
|
- `api/index.ts`: 真实 API 接口定义
|
||||||
|
- `stores/modules/agents.ts`: 类型定义和数据存储
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
// branch_PlanOutline 接口返回的 mock 数据
|
||||||
|
// 类型: IApiStepTask[][] (二维数组)
|
||||||
|
|
||||||
|
import type { IApiStepTask } from '@/stores/modules/agents'
|
||||||
|
|
||||||
|
const mockPlanBranchData: IApiStepTask[][] = [
|
||||||
|
// 第一个分支方案
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: '需求分析与规划',
|
||||||
|
content: '分析用户需求,制定项目开发计划',
|
||||||
|
inputs: ['用户需求文档', '技术规范'],
|
||||||
|
output: '项目开发计划书',
|
||||||
|
agents: ['腐蚀机理研究员', '实验材料学家'],
|
||||||
|
brief: {
|
||||||
|
template: '!<项目经理>!负责!<需求分析>!,!<产品经理>!负责!<规划制定>!',
|
||||||
|
data: {
|
||||||
|
项目经理: { text: '项目经理', style: { background: 'hsl(210, 70%, 50%)' } },
|
||||||
|
需求分析: { text: '需求分析', style: { background: 'hsl(120, 70%, 50%)' } },
|
||||||
|
产品经理: { text: '产品经理', style: { background: 'hsl(30, 70%, 50%)' } },
|
||||||
|
规划制定: { text: '规划制定', style: { background: 'hsl(300, 70%, 50%)' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
process: [
|
||||||
|
{
|
||||||
|
id: 'action-1',
|
||||||
|
type: '分析',
|
||||||
|
agent: '腐蚀机理研究员',
|
||||||
|
description: '详细分析用户需求文档',
|
||||||
|
inputs: ['用户需求文档'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'action-2',
|
||||||
|
type: '规划',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '制定项目开发计划',
|
||||||
|
inputs: ['技术规范'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '系统设计与架构',
|
||||||
|
content: '设计系统架构和数据库结构',
|
||||||
|
inputs: ['项目开发计划书'],
|
||||||
|
output: '系统设计文档',
|
||||||
|
agents: ['腐蚀机理研究员', '防护工程专家'],
|
||||||
|
brief: {
|
||||||
|
template: '!<架构师>!负责!<系统架构设计>!,!<数据库工程师>!负责!<数据库设计>!',
|
||||||
|
data: {
|
||||||
|
架构师: { text: '架构师', style: { background: 'hsl(180, 70%, 50%)' } },
|
||||||
|
系统架构设计: { text: '系统架构设计', style: { background: 'hsl(240, 70%, 50%)' } },
|
||||||
|
数据库工程师: { text: '数据库工程师', style: { background: 'hsl(60, 70%, 50%)' } },
|
||||||
|
数据库设计: { text: '数据库设计', style: { background: 'hsl(0, 70%, 50%)' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
process: [
|
||||||
|
{
|
||||||
|
id: 'action-3',
|
||||||
|
type: '设计',
|
||||||
|
agent: '腐蚀机理研究员',
|
||||||
|
description: '设计系统整体架构',
|
||||||
|
inputs: ['项目开发计划书'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'action-4',
|
||||||
|
type: '设计',
|
||||||
|
agent: '防护工程专家',
|
||||||
|
description: '设计数据库表结构',
|
||||||
|
inputs: ['项目开发计划书'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
// 第二个分支方案(替代方案)
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: '敏捷开发规划',
|
||||||
|
content: '采用敏捷开发方法制定迭代计划',
|
||||||
|
inputs: ['用户需求文档', '敏捷开发指南'],
|
||||||
|
output: '敏捷开发迭代计划',
|
||||||
|
agents: ['敏捷教练', '开发团队负责人'],
|
||||||
|
brief: {
|
||||||
|
template: '!<敏捷教练>!指导!<敏捷流程>!,!<开发团队负责人>!制定!<迭代计划>!',
|
||||||
|
data: {
|
||||||
|
敏捷教练: { text: '敏捷教练', style: { background: 'hsl(270, 70%, 50%)' } },
|
||||||
|
敏捷流程: { text: '敏捷流程', style: { background: 'hsl(90, 70%, 50%)' } },
|
||||||
|
开发团队负责人: { text: '开发团队负责人', style: { background: 'hsl(150, 70%, 50%)' } },
|
||||||
|
迭代计划: { text: '迭代计划', style: { background: 'hsl(330, 70%, 50%)' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
process: [
|
||||||
|
{
|
||||||
|
id: 'action-5',
|
||||||
|
type: '指导',
|
||||||
|
agent: '敏捷教练',
|
||||||
|
description: '指导敏捷开发流程',
|
||||||
|
inputs: ['敏捷开发指南'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'action-6',
|
||||||
|
type: '规划',
|
||||||
|
agent: '开发团队负责人',
|
||||||
|
description: '制定迭代开发计划',
|
||||||
|
inputs: ['用户需求文档'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '微服务架构设计',
|
||||||
|
content: '设计基于微服务的系统架构',
|
||||||
|
inputs: ['敏捷开发迭代计划'],
|
||||||
|
output: '微服务架构设计文档',
|
||||||
|
agents: ['微服务架构师', 'DevOps工程师'],
|
||||||
|
brief: {
|
||||||
|
template: '!<微服务架构师>!设计!<微服务架构>!,!<DevOps工程师>!规划!<部署流程>!',
|
||||||
|
data: {
|
||||||
|
微服务架构师: { text: '微服务架构师', style: { background: 'hsl(210, 70%, 50%)' } },
|
||||||
|
微服务架构: { text: '微服务架构', style: { background: 'hsl(120, 70%, 50%)' } },
|
||||||
|
DevOps工程师: { text: 'DevOps工程师', style: { background: 'hsl(30, 70%, 50%)' } },
|
||||||
|
部署流程: { text: '部署流程', style: { background: 'hsl(300, 70%, 50%)' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
process: [
|
||||||
|
{
|
||||||
|
id: 'action-7',
|
||||||
|
type: '设计',
|
||||||
|
agent: '微服务架构师',
|
||||||
|
description: '设计微服务拆分方案',
|
||||||
|
inputs: ['敏捷开发迭代计划'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'action-8',
|
||||||
|
type: '规划',
|
||||||
|
agent: 'DevOps工程师',
|
||||||
|
description: '规划CI/CD部署流程',
|
||||||
|
inputs: ['敏捷开发迭代计划'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
// 第三个分支方案(简化方案)
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: '快速原型开发',
|
||||||
|
content: '快速开发系统原型验证需求',
|
||||||
|
inputs: ['用户需求文档'],
|
||||||
|
output: '系统原型',
|
||||||
|
agents: ['全栈开发工程师'],
|
||||||
|
brief: {
|
||||||
|
template: '!<全栈开发工程师>!负责!<快速原型开发>!',
|
||||||
|
data: {
|
||||||
|
全栈开发工程师: { text: '全栈开发工程师', style: { background: 'hsl(180, 70%, 50%)' } },
|
||||||
|
快速原型开发: { text: '快速原型开发', style: { background: 'hsl(240, 70%, 50%)' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
process: [
|
||||||
|
{
|
||||||
|
id: 'action-9',
|
||||||
|
type: '开发',
|
||||||
|
agent: '全栈开发工程师',
|
||||||
|
description: '快速开发系统原型',
|
||||||
|
inputs: ['用户需求文档'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '用户反馈收集',
|
||||||
|
content: '收集用户对原型的反馈意见',
|
||||||
|
inputs: ['系统原型'],
|
||||||
|
output: '用户反馈报告',
|
||||||
|
agents: ['产品经理', '用户体验设计师'],
|
||||||
|
brief: {
|
||||||
|
template: '!<产品经理>!收集!<用户反馈>!,!<用户体验设计师>!分析!<用户体验>!',
|
||||||
|
data: {
|
||||||
|
产品经理: { text: '产品经理', style: { background: 'hsl(60, 70%, 50%)' } },
|
||||||
|
用户反馈: { text: '用户反馈', style: { background: 'hsl(0, 70%, 50%)' } },
|
||||||
|
用户体验设计师: { text: '用户体验设计师', style: { background: 'hsl(270, 70%, 50%)' } },
|
||||||
|
用户体验: { text: '用户体验', style: { background: 'hsl(90, 70%, 50%)' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
process: [
|
||||||
|
{
|
||||||
|
id: 'action-10',
|
||||||
|
type: '收集',
|
||||||
|
agent: '产品经理',
|
||||||
|
description: '收集用户对原型的反馈',
|
||||||
|
inputs: ['系统原型'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'action-11',
|
||||||
|
type: '分析',
|
||||||
|
agent: '用户体验设计师',
|
||||||
|
description: '分析用户体验问题',
|
||||||
|
inputs: ['系统原型'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
export default mockPlanBranchData
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
// branch_TaskProcess 接口返回的 mock 数据
|
||||||
|
// 类型: IApiAgentAction[][] (二维数组)
|
||||||
|
|
||||||
|
import type { IApiAgentAction } from '@/stores/modules/agents'
|
||||||
|
|
||||||
|
const mockTaskProcessData: IApiAgentAction[][] = [
|
||||||
|
// 第一个任务分支方案
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: 'analyze-requirements-1',
|
||||||
|
type: '分析',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '详细分析用户需求文档',
|
||||||
|
inputs: ['用户需求文档'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'design-architecture-1',
|
||||||
|
type: '设计',
|
||||||
|
agent: '腐蚀机理研究员',
|
||||||
|
description: '设计系统整体架构',
|
||||||
|
inputs: ['需求分析结果'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'implement-core-1',
|
||||||
|
type: '实现',
|
||||||
|
agent: '防护工程专家',
|
||||||
|
description: '实现系统核心功能',
|
||||||
|
inputs: ['系统架构设计'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'test-system-1',
|
||||||
|
type: '测试',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '进行系统集成测试',
|
||||||
|
inputs: ['系统核心功能'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
// 第二个任务分支方案(替代方案)
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: 'quick-prototype-2',
|
||||||
|
type: '原型',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '快速开发系统原型',
|
||||||
|
inputs: ['用户需求文档'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'user-feedback-2',
|
||||||
|
type: '收集',
|
||||||
|
agent: '腐蚀机理研究员',
|
||||||
|
description: '收集用户对原型的反馈',
|
||||||
|
inputs: ['系统原型'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'iterate-design-2',
|
||||||
|
type: '迭代',
|
||||||
|
agent: '防护工程专家',
|
||||||
|
description: '根据反馈迭代设计',
|
||||||
|
inputs: ['用户反馈'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'final-implement-2',
|
||||||
|
type: '实现',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '实现最终用户界面',
|
||||||
|
inputs: ['迭代设计稿'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
// 第三个任务分支方案(敏捷开发方案)
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: 'sprint-planning-3',
|
||||||
|
type: '规划',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '制定冲刺计划',
|
||||||
|
inputs: ['用户故事', '技术债务'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'code-review-3',
|
||||||
|
type: '评审',
|
||||||
|
agent: '防护工程专家',
|
||||||
|
description: '进行代码审查',
|
||||||
|
inputs: ['开发代码'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'unit-test-3',
|
||||||
|
type: '测试',
|
||||||
|
agent: '腐蚀机理研究员',
|
||||||
|
description: '编写单元测试',
|
||||||
|
inputs: ['功能代码'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'deploy-staging-3',
|
||||||
|
type: '部署',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '部署到测试环境',
|
||||||
|
inputs: ['测试通过代码'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'acceptance-test-3',
|
||||||
|
type: '验收',
|
||||||
|
agent: '腐蚀机理研究员',
|
||||||
|
description: '进行验收测试',
|
||||||
|
inputs: ['测试环境系统'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
// 第四个任务分支方案(微服务方案)
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: 'service-split-4',
|
||||||
|
type: '拆分',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '拆分单体应用为微服务',
|
||||||
|
inputs: ['单体应用代码'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'api-design-4',
|
||||||
|
type: '设计',
|
||||||
|
agent: '防护工程专家',
|
||||||
|
description: '设计微服务API接口',
|
||||||
|
inputs: ['服务拆分方案'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'service-implement-4',
|
||||||
|
type: '实现',
|
||||||
|
agent: '防护工程专家',
|
||||||
|
description: '实现各个微服务',
|
||||||
|
inputs: ['API设计文档'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'orchestration-4',
|
||||||
|
type: '编排',
|
||||||
|
agent: '腐蚀机理研究员',
|
||||||
|
description: '配置服务编排和负载均衡',
|
||||||
|
inputs: ['微服务实现'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'monitoring-setup-4',
|
||||||
|
type: '监控',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '设置监控和日志系统',
|
||||||
|
inputs: ['运行中的微服务'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
// 第五个任务分支方案(数据驱动方案)
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: 'data-analysis-5',
|
||||||
|
type: '分析',
|
||||||
|
agent: '腐蚀机理研究员',
|
||||||
|
description: '分析业务数据和需求',
|
||||||
|
inputs: ['业务数据', '用户行为数据'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ml-model-5',
|
||||||
|
type: '建模',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '构建预测模型',
|
||||||
|
inputs: ['分析结果', '历史数据'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'data-pipeline-5',
|
||||||
|
type: '构建',
|
||||||
|
agent: '防护工程专家',
|
||||||
|
description: '构建数据处理流水线',
|
||||||
|
inputs: ['数据源', '模型需求'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'api-integration-5',
|
||||||
|
type: '集成',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '集成数据服务到应用',
|
||||||
|
inputs: ['数据处理流水线', '预测模型'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'performance-optimize-5',
|
||||||
|
type: '优化',
|
||||||
|
agent: '腐蚀机理研究员',
|
||||||
|
description: '优化系统性能',
|
||||||
|
inputs: ['运行数据', '性能指标'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
export default mockTaskProcessData
|
||||||
@@ -0,0 +1,423 @@
|
|||||||
|
export interface IApiAgentAction {
|
||||||
|
id: string
|
||||||
|
type: string
|
||||||
|
agent: string
|
||||||
|
description: string
|
||||||
|
inputs: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRichText {
|
||||||
|
template: string
|
||||||
|
data: Record<string, { text: string; style: Record<string, string> }>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IApiStepTask {
|
||||||
|
name: string
|
||||||
|
content: string
|
||||||
|
inputs: string[]
|
||||||
|
output: string
|
||||||
|
agents: string[]
|
||||||
|
brief: IRichText
|
||||||
|
process: IApiAgentAction[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 场景1: 软件开发任务 - 用户登录功能开发
|
||||||
|
export const mockSoftwareDevelopment: IApiStepTask = {
|
||||||
|
name: '用户登录功能开发',
|
||||||
|
content: '开发用户登录认证系统,包括前端界面和后端API',
|
||||||
|
inputs: ['用户需求文档', 'UI设计稿', '数据库表结构'],
|
||||||
|
output: '可用的用户登录系统',
|
||||||
|
agents: ['实验材料学家', '防护工程专家', '腐蚀机理研究员'],
|
||||||
|
brief: {
|
||||||
|
template: '!<前端开发>!负责实现登录界面,!<后端开发>!处理认证逻辑,!<测试>!进行功能验证',
|
||||||
|
data: {
|
||||||
|
前端开发: {
|
||||||
|
text: '实验材料学家',
|
||||||
|
style: {
|
||||||
|
backgroundColor: '#4a9cde',
|
||||||
|
color: 'white',
|
||||||
|
padding: '2px 6px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
后端开发: {
|
||||||
|
text: '防护工程专家',
|
||||||
|
style: {
|
||||||
|
backgroundColor: '#5cb85c',
|
||||||
|
color: 'white',
|
||||||
|
padding: '2px 6px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
测试: {
|
||||||
|
text: '腐蚀机理研究员',
|
||||||
|
style: {
|
||||||
|
backgroundColor: '#f0ad4e',
|
||||||
|
color: 'white',
|
||||||
|
padding: '2px 6px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
process: [
|
||||||
|
{
|
||||||
|
id: 'task-1',
|
||||||
|
type: '前端开发',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '创建登录页面UI组件',
|
||||||
|
inputs: ['UI设计稿'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'task-2',
|
||||||
|
type: '后端开发',
|
||||||
|
agent: '防护工程专家',
|
||||||
|
description: '实现用户认证API接口',
|
||||||
|
inputs: ['数据库表结构'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'task-3',
|
||||||
|
type: '前端开发',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '集成前端与后端API',
|
||||||
|
inputs: ['用户认证API'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'task-4',
|
||||||
|
type: '测试',
|
||||||
|
agent: '腐蚀机理研究员',
|
||||||
|
description: '进行端到端功能测试',
|
||||||
|
inputs: ['登录页面', '认证API'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
// 场景2: 数据分析任务 - 销售数据分析
|
||||||
|
export const mockDataAnalysis: IApiStepTask = {
|
||||||
|
name: '季度销售数据分析',
|
||||||
|
content: '分析Q3季度销售数据,生成可视化报告',
|
||||||
|
inputs: ['销售数据CSV', '产品分类表', '客户信息表'],
|
||||||
|
output: '销售分析报告',
|
||||||
|
agents: ['实验材料学家', '防护工程专家', '腐蚀机理研究员'],
|
||||||
|
brief: {
|
||||||
|
template: '!<实验材料学家>!负责数据清洗,!<防护工程专家>!进行统计分析,!<腐蚀机理研究员>!解读业务含义',
|
||||||
|
data: {
|
||||||
|
实验材料学家: {
|
||||||
|
text: '实验材料学家',
|
||||||
|
style: {
|
||||||
|
backgroundColor: '#4a9cde',
|
||||||
|
color: 'white',
|
||||||
|
padding: '2px 6px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
防护工程专家: {
|
||||||
|
text: '防护工程专家',
|
||||||
|
style: {
|
||||||
|
backgroundColor: '#5cb85c',
|
||||||
|
color: 'white',
|
||||||
|
padding: '2px 6px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
腐蚀机理研究员: {
|
||||||
|
text: '腐蚀机理研究员',
|
||||||
|
style: {
|
||||||
|
backgroundColor: '#f0ad4e',
|
||||||
|
color: 'white',
|
||||||
|
padding: '2px 6px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
process: [
|
||||||
|
{
|
||||||
|
id: 'task-1',
|
||||||
|
type: '数据清洗',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '清洗和预处理销售数据',
|
||||||
|
inputs: ['销售数据CSV'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'task-2',
|
||||||
|
type: '统计分析',
|
||||||
|
agent: '防护工程专家',
|
||||||
|
description: '计算销售指标和趋势',
|
||||||
|
inputs: ['清洗后的数据'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'task-3',
|
||||||
|
type: '可视化',
|
||||||
|
agent: '防护工程专家',
|
||||||
|
description: '创建数据可视化图表',
|
||||||
|
inputs: ['分析结果'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'task-4',
|
||||||
|
type: '报告编写',
|
||||||
|
agent: '腐蚀机理研究员',
|
||||||
|
description: '撰写业务分析报告',
|
||||||
|
inputs: ['可视化图表', '分析结果'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
// 场景3: 文档编写任务 - 产品需求文档
|
||||||
|
export const mockDocumentation: IApiStepTask = {
|
||||||
|
name: '产品需求文档编写',
|
||||||
|
content: '为新功能编写详细的产品需求文档',
|
||||||
|
inputs: ['产品原型', '用户故事', '技术约束'],
|
||||||
|
output: 'PRD文档',
|
||||||
|
agents: ['实验材料学家', '防护工程专家', '腐蚀机理研究员'],
|
||||||
|
brief: {
|
||||||
|
template: '!<实验材料学家>!主导文档编写,!<防护工程专家>!提供技术输入,!<腐蚀机理研究员>!补充用户体验细节',
|
||||||
|
data: {
|
||||||
|
实验材料学家: {
|
||||||
|
text: '实验材料学家',
|
||||||
|
style: {
|
||||||
|
backgroundColor: '#4a9cde',
|
||||||
|
color: 'white',
|
||||||
|
padding: '2px 6px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
防护工程专家: {
|
||||||
|
text: '防护工程专家',
|
||||||
|
style: {
|
||||||
|
backgroundColor: '#5cb85c',
|
||||||
|
color: 'white',
|
||||||
|
padding: '2px 6px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
腐蚀机理研究员: {
|
||||||
|
text: '腐蚀机理研究员',
|
||||||
|
style: {
|
||||||
|
backgroundColor: '#f0ad4e',
|
||||||
|
color: 'white',
|
||||||
|
padding: '2px 6px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
process: [
|
||||||
|
{
|
||||||
|
id: 'task-1',
|
||||||
|
type: '需求分析',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '分析用户需求和业务目标',
|
||||||
|
inputs: ['用户故事'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'task-2',
|
||||||
|
type: '技术评估',
|
||||||
|
agent: '防护工程专家',
|
||||||
|
description: '评估技术可行性和约束',
|
||||||
|
inputs: ['产品原型'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'task-3',
|
||||||
|
type: 'UX设计',
|
||||||
|
agent: '腐蚀机理研究员',
|
||||||
|
description: '完善用户体验设计',
|
||||||
|
inputs: ['需求分析结果'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'task-4',
|
||||||
|
type: '文档编写',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '编写完整的PRD文档',
|
||||||
|
inputs: ['技术评估', 'UX设计'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
// 场景4: 复杂协作任务 - 系统迁移项目
|
||||||
|
export const mockSystemMigration: IApiStepTask = {
|
||||||
|
name: '旧系统到新系统迁移',
|
||||||
|
content: '将现有系统迁移到新的技术架构',
|
||||||
|
inputs: ['旧系统代码', '新系统架构图', '迁移计划'],
|
||||||
|
output: '成功迁移的系统',
|
||||||
|
agents: ['实验材料学家', '防护工程专家', '腐蚀机理研究员'],
|
||||||
|
brief: {
|
||||||
|
template:
|
||||||
|
'!<防护工程专家>!制定迁移策略,!<实验材料学家>!执行代码迁移,!<腐蚀机理研究员>!负责部署,!<防护工程专家>!确保质量',
|
||||||
|
data: {
|
||||||
|
防护工程专家: {
|
||||||
|
text: '防护工程专家',
|
||||||
|
style: {
|
||||||
|
backgroundColor: '#5cb85c',
|
||||||
|
color: 'white',
|
||||||
|
padding: '2px 6px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
实验材料学家: {
|
||||||
|
text: '实验材料学家',
|
||||||
|
style: {
|
||||||
|
backgroundColor: '#4a9cde',
|
||||||
|
color: 'white',
|
||||||
|
padding: '2px 6px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
腐蚀机理研究员: {
|
||||||
|
text: '腐蚀机理研究员',
|
||||||
|
style: {
|
||||||
|
backgroundColor: '#f0ad4e',
|
||||||
|
color: 'white',
|
||||||
|
padding: '2px 6px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
process: [
|
||||||
|
{
|
||||||
|
id: 'task-1',
|
||||||
|
type: '架构设计',
|
||||||
|
agent: '防护工程专家',
|
||||||
|
description: '设计系统迁移架构',
|
||||||
|
inputs: ['新系统架构图'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'task-2',
|
||||||
|
type: '代码迁移',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '迁移业务逻辑代码',
|
||||||
|
inputs: ['旧系统代码', '迁移架构'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'task-3',
|
||||||
|
type: '数据迁移',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '迁移数据库和数据',
|
||||||
|
inputs: ['旧系统数据'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'task-4',
|
||||||
|
type: '部署',
|
||||||
|
agent: '腐蚀机理研究员',
|
||||||
|
description: '部署新系统到生产环境',
|
||||||
|
inputs: ['迁移后的代码'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'task-5',
|
||||||
|
type: '测试',
|
||||||
|
agent: '防护工程专家',
|
||||||
|
description: '进行系统集成测试',
|
||||||
|
inputs: ['部署的系统'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
// 场景5: 简单任务 - 日常报告生成
|
||||||
|
export const mockSimpleTask: IApiStepTask = {
|
||||||
|
name: '每日运营报告生成',
|
||||||
|
content: '自动生成每日业务运营报告',
|
||||||
|
inputs: ['业务数据', '报告模板'],
|
||||||
|
output: '每日运营报告',
|
||||||
|
agents: ['实验材料学家', '防护工程专家', '腐蚀机理研究员'],
|
||||||
|
brief: {
|
||||||
|
template: '!<实验材料学家>!自动处理数据并生成报告',
|
||||||
|
data: {
|
||||||
|
实验材料学家: {
|
||||||
|
text: '实验材料学家',
|
||||||
|
style: {
|
||||||
|
backgroundColor: '#4a9cde',
|
||||||
|
color: 'white',
|
||||||
|
padding: '2px 6px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
process: [
|
||||||
|
{
|
||||||
|
id: 'task-1',
|
||||||
|
type: '数据处理',
|
||||||
|
agent: '实验材料学家',
|
||||||
|
description: '提取和处理业务数据',
|
||||||
|
inputs: ['业务数据'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'task-2',
|
||||||
|
type: '报告生成',
|
||||||
|
agent: '防护工程专家',
|
||||||
|
description: '根据模板生成报告',
|
||||||
|
inputs: ['处理后的数据', '报告模板'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完整的 mock 数据集合
|
||||||
|
export const mockFillStepTaskData = {
|
||||||
|
softwareDevelopment: mockSoftwareDevelopment,
|
||||||
|
dataAnalysis: mockDataAnalysis,
|
||||||
|
documentation: mockDocumentation,
|
||||||
|
systemMigration: mockSystemMigration,
|
||||||
|
simpleTask: mockSimpleTask,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vue 组件使用示例
|
||||||
|
export const mockFillStepTaskForVue = {
|
||||||
|
// 返回所有 mock 数据的数组形式
|
||||||
|
getAllMockTasks(): IApiStepTask[] {
|
||||||
|
return [
|
||||||
|
mockSoftwareDevelopment,
|
||||||
|
mockDataAnalysis,
|
||||||
|
mockDocumentation,
|
||||||
|
mockSystemMigration,
|
||||||
|
mockSimpleTask,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// 根据任务类型获取 mock 数据
|
||||||
|
getMockTaskByType(type: string): IApiStepTask | null {
|
||||||
|
const tasks = {
|
||||||
|
软件开发: mockSoftwareDevelopment,
|
||||||
|
数据分析: mockDataAnalysis,
|
||||||
|
文档编写: mockDocumentation,
|
||||||
|
系统迁移: mockSystemMigration,
|
||||||
|
简单任务: mockSimpleTask,
|
||||||
|
}
|
||||||
|
return tasks[type as keyof typeof tasks] || null
|
||||||
|
},
|
||||||
|
|
||||||
|
// 模拟 API 响应
|
||||||
|
mockApiResponse(): Promise<IApiStepTask[]> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve(this.getAllMockTasks())
|
||||||
|
}, 500)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,115 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||||
|
import { useAgentsStore } from '@/stores'
|
||||||
|
|
||||||
|
const agentsStore = useAgentsStore()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'click'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 判断是否禁用 - 必须先点击任务大纲中的卡片
|
||||||
|
const isDisabled = computed(() => {
|
||||||
|
return !agentsStore.currentTask
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取agent组合卡片数量 - 当前任务agents(1) + 已确认的agent组合数量
|
||||||
|
const agentGroupCount = computed(() => {
|
||||||
|
if (!agentsStore.currentTask?.Id) return 1
|
||||||
|
|
||||||
|
// 获取该任务的已确认agent组合
|
||||||
|
const confirmedGroups = agentsStore.getConfirmedAgentGroups(agentsStore.currentTask.Id)
|
||||||
|
|
||||||
|
// 当前任务agents(1) + 已确认的agent组合数量
|
||||||
|
return 1 + confirmedGroups.length
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (isDisabled.value) return
|
||||||
|
emit('click')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="assignment-button"
|
||||||
|
:class="{ 'is-disabled': isDisabled, 'has-groups': agentGroupCount > 1 }"
|
||||||
|
@click="handleClick"
|
||||||
|
:title="isDisabled ? '请先点击任务大纲中的任务卡片' : `${agentGroupCount} 个agent组合`"
|
||||||
|
>
|
||||||
|
<!-- 智能体分配图标 -->
|
||||||
|
<SvgIcon icon-class="agent-change" size="24px" color="#fff" />
|
||||||
|
|
||||||
|
<!-- agent组合数量显示 -->
|
||||||
|
<span class="agent-group-count">
|
||||||
|
{{ agentGroupCount }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.assignment-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 40px;
|
||||||
|
height: 36px;
|
||||||
|
background-color: #43a8aa;
|
||||||
|
border-radius: 10px 0 0 0;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
filter: brightness(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
filter: brightness(0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 多个agent组合时显示红点指示器
|
||||||
|
&.has-groups::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -2px;
|
||||||
|
right: -2px;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: #ff6b6b;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 禁用状态
|
||||||
|
&.is-disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-group-count {
|
||||||
|
position: absolute;
|
||||||
|
right: 1px;
|
||||||
|
bottom: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: white;
|
||||||
|
font-weight: 800;
|
||||||
|
text-align: right;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useAgentsStore, useSelectionStore } from '@/stores'
|
||||||
|
|
||||||
|
const agentsStore = useAgentsStore()
|
||||||
|
const selectionStore = useSelectionStore()
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'click'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 获取分支数量 - 主分支(1) + 额外分支数量
|
||||||
|
const branchCount = computed(() => {
|
||||||
|
// flowBranches 包含所有通过 Branch 创建的分支
|
||||||
|
const extraBranches = selectionStore.flowBranches?.length || 0
|
||||||
|
// 始终至少有1个主分支
|
||||||
|
return 1 + extraBranches
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
// console.log('点击分支按钮')
|
||||||
|
emit('click')
|
||||||
|
// 触发打开分支窗口
|
||||||
|
agentsStore.openPlanModification()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="branch-button"
|
||||||
|
:class="{ 'has-branches': branchCount > 1 }"
|
||||||
|
@click="handleClick"
|
||||||
|
:title="`${branchCount} 个分支`"
|
||||||
|
>
|
||||||
|
<!-- 分支图标 -->
|
||||||
|
<svg-icon icon-class="branch" size="24px" class="branch-icon" />
|
||||||
|
|
||||||
|
<!-- 分支数量显示 -->
|
||||||
|
<span class="branch-count">
|
||||||
|
{{ branchCount }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.branch-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
/* 尺寸 */
|
||||||
|
width: 36px;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
/* 样式 */
|
||||||
|
background-color: #43a8aa;
|
||||||
|
border-radius: 10px 0 0 0;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
/* 布局 */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
/* 交互 */
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
filter: brightness(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-branches::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -2px;
|
||||||
|
right: -2px;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: #ff6b6b;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.branch-icon {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.branch-count {
|
||||||
|
position: absolute;
|
||||||
|
right: 4px;
|
||||||
|
bottom: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: white;
|
||||||
|
font-weight: 800;
|
||||||
|
text-align: right;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,446 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="rndContainer" class="rnd-container" :style="containerStyle">
|
||||||
|
<!-- 标题栏 -->
|
||||||
|
<div class="float-window-header" @mousedown="handleMouseDown">
|
||||||
|
<div v-if="typeof title === 'string'" class="header-title">
|
||||||
|
{{ title }}
|
||||||
|
</div>
|
||||||
|
<div v-else class="header-title-custom">
|
||||||
|
<slot name="title">
|
||||||
|
{{ title }}
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<div class="header-actions">
|
||||||
|
<button v-if="onClose" class="close-btn" @click="handleClose">
|
||||||
|
<svg-icon icon-class="close" size="20px" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 内容区域 -->
|
||||||
|
<div
|
||||||
|
ref="contentContainer"
|
||||||
|
class="float-window-content"
|
||||||
|
@pointerenter="setResizeable(false)"
|
||||||
|
@pointerleave="setResizeable(true)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 调整大小的手柄 -->
|
||||||
|
<div
|
||||||
|
v-for="handle in resizeHandles"
|
||||||
|
:key="handle"
|
||||||
|
:class="`resize-handle resize-handle-${handle}`"
|
||||||
|
@mousedown="e => startResize(e, handle)"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
|
import { debounce } from 'lodash-es'
|
||||||
|
export interface IFloatingWindowProps {
|
||||||
|
title?: string | any
|
||||||
|
onClose?: () => void
|
||||||
|
onResize?: () => void
|
||||||
|
minWidth?: number
|
||||||
|
minHeight?: number
|
||||||
|
defaultSize?: {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<IFloatingWindowProps>(), {
|
||||||
|
title: '',
|
||||||
|
minWidth: 150,
|
||||||
|
minHeight: 60
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:position': [value: { x: number; y: number }]
|
||||||
|
'update:size': [value: { width: number; height: number }]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 响应式状态
|
||||||
|
const position = ref({ x: 0, y: 0 })
|
||||||
|
const size = ref({ width: 400, height: 300 })
|
||||||
|
const isDragging = ref(false)
|
||||||
|
const isResizing = ref(false)
|
||||||
|
const resizeDirection = ref<string | null>(null)
|
||||||
|
const resizeable = ref(true)
|
||||||
|
const resizeStart = ref({ x: 0, y: 0, width: 0, height: 0, right: 0, bottom: 0 })
|
||||||
|
const dragStart = ref({ x: 0, y: 0 })
|
||||||
|
const containerRef = ref<HTMLElement>()
|
||||||
|
|
||||||
|
// 窗口管理
|
||||||
|
let windowsArrange: HTMLElement[] = []
|
||||||
|
|
||||||
|
const focusWindow = (element: HTMLElement) => {
|
||||||
|
if (!element) return
|
||||||
|
|
||||||
|
// 过滤掉已经不存在的元素
|
||||||
|
windowsArrange = windowsArrange.filter(ele => ele.isConnected && element !== ele)
|
||||||
|
|
||||||
|
// 将当前窗口移到最前面
|
||||||
|
windowsArrange.push(element)
|
||||||
|
|
||||||
|
// 更新所有窗口的z-index
|
||||||
|
windowsArrange.forEach((ele, index) => {
|
||||||
|
ele.style.zIndex = `${index + 3000}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算默认尺寸
|
||||||
|
const defaultSize = computed(() => {
|
||||||
|
if (props.defaultSize) return props.defaultSize
|
||||||
|
|
||||||
|
const width = Math.min(1280, window.innerWidth - 20)
|
||||||
|
const height = Math.min(600, window.innerHeight - 20)
|
||||||
|
const x = (window.innerWidth - width) / 2
|
||||||
|
const y = (window.innerHeight - height) / 2
|
||||||
|
return { x, y, width, height }
|
||||||
|
})
|
||||||
|
|
||||||
|
// 容器样式
|
||||||
|
const containerStyle = computed(() => ({
|
||||||
|
position: 'fixed' as const,
|
||||||
|
left: `${position.value.x}px`,
|
||||||
|
top: `${position.value.y}px`,
|
||||||
|
width: `${size.value.width}px`,
|
||||||
|
height: `${size.value.height}px`,
|
||||||
|
border: '3px solid #43A8AA',
|
||||||
|
boxShadow: '3px 3px 20px rgba(0, 0, 0, 0.3)',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column' as const,
|
||||||
|
zIndex: '3000',
|
||||||
|
backgroundColor: 'var(--color-bg-three)',
|
||||||
|
overflow: 'hidden',
|
||||||
|
userSelect: (isDragging.value || isResizing.value ? 'none' : 'auto') as any
|
||||||
|
}))
|
||||||
|
|
||||||
|
// 调整大小的手柄位置
|
||||||
|
const resizeHandles = ['n', 'e', 's', 'w', 'ne', 'nw', 'se', 'sw']
|
||||||
|
|
||||||
|
// 事件处理
|
||||||
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
|
if (!resizeable.value || isResizing.value) return
|
||||||
|
|
||||||
|
isDragging.value = true
|
||||||
|
dragStart.value = {
|
||||||
|
x: e.clientX - position.value.x,
|
||||||
|
y: e.clientY - position.value.y
|
||||||
|
}
|
||||||
|
|
||||||
|
if (containerRef.value) {
|
||||||
|
focusWindow(containerRef.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', handleDragMove)
|
||||||
|
document.addEventListener('mouseup', handleDragEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDragMove = (e: MouseEvent) => {
|
||||||
|
if (!isDragging.value) return
|
||||||
|
|
||||||
|
let newX = e.clientX - dragStart.value.x
|
||||||
|
let newY = e.clientY - dragStart.value.y
|
||||||
|
|
||||||
|
// 边界检查
|
||||||
|
newX = Math.max(0, Math.min(newX, window.innerWidth - size.value.width))
|
||||||
|
newY = Math.max(0, Math.min(newY, window.innerHeight - size.value.height))
|
||||||
|
|
||||||
|
position.value = { x: newX, y: newY }
|
||||||
|
emit('update:position', position.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDragEnd = () => {
|
||||||
|
isDragging.value = false
|
||||||
|
document.removeEventListener('mousemove', handleDragMove)
|
||||||
|
document.removeEventListener('mouseup', handleDragEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
const startResize = (e: MouseEvent, direction: string) => {
|
||||||
|
if (!resizeable.value) return
|
||||||
|
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
isResizing.value = true
|
||||||
|
resizeDirection.value = direction
|
||||||
|
resizeStart.value = {
|
||||||
|
x: e.clientX,
|
||||||
|
y: e.clientY,
|
||||||
|
width: size.value.width,
|
||||||
|
height: size.value.height,
|
||||||
|
right: position.value.x + size.value.width,
|
||||||
|
bottom: position.value.y + size.value.height
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', handleResizeMove)
|
||||||
|
document.addEventListener('mouseup', handleResizeEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResizeMove = (e: MouseEvent) => {
|
||||||
|
if (!isResizing.value || !resizeDirection.value) return
|
||||||
|
|
||||||
|
const deltaX = e.clientX - resizeStart.value.x
|
||||||
|
const deltaY = e.clientY - resizeStart.value.y
|
||||||
|
|
||||||
|
let newWidth = resizeStart.value.width
|
||||||
|
let newHeight = resizeStart.value.height
|
||||||
|
let newX = position.value.x
|
||||||
|
let newY = position.value.y
|
||||||
|
|
||||||
|
// 根据调整方向计算新尺寸和位置
|
||||||
|
switch (resizeDirection.value) {
|
||||||
|
// 右边调整 - 固定左边
|
||||||
|
case 'e':
|
||||||
|
newWidth = Math.max(props.minWidth, resizeStart.value.width + deltaX)
|
||||||
|
break
|
||||||
|
|
||||||
|
// 下边调整 - 固定上边
|
||||||
|
case 's':
|
||||||
|
newHeight = Math.max(props.minHeight, resizeStart.value.height + deltaY)
|
||||||
|
break
|
||||||
|
|
||||||
|
// 左边调整 - 固定右边
|
||||||
|
case 'w':
|
||||||
|
newWidth = Math.max(props.minWidth, resizeStart.value.width - deltaX)
|
||||||
|
newX = resizeStart.value.right - newWidth
|
||||||
|
break
|
||||||
|
|
||||||
|
// 上边调整 - 固定下边
|
||||||
|
case 'n':
|
||||||
|
newHeight = Math.max(props.minHeight, resizeStart.value.height - deltaY)
|
||||||
|
newY = resizeStart.value.bottom - newHeight
|
||||||
|
break
|
||||||
|
|
||||||
|
// 右上角调整 - 固定左下角
|
||||||
|
case 'ne':
|
||||||
|
newWidth = Math.max(props.minWidth, resizeStart.value.width + deltaX)
|
||||||
|
newHeight = Math.max(props.minHeight, resizeStart.value.height - deltaY)
|
||||||
|
newY = resizeStart.value.bottom - newHeight
|
||||||
|
break
|
||||||
|
|
||||||
|
// 左上角调整 - 固定右下角
|
||||||
|
case 'nw':
|
||||||
|
newWidth = Math.max(props.minWidth, resizeStart.value.width - deltaX)
|
||||||
|
newHeight = Math.max(props.minHeight, resizeStart.value.height - deltaY)
|
||||||
|
newX = resizeStart.value.right - newWidth
|
||||||
|
newY = resizeStart.value.bottom - newHeight
|
||||||
|
break
|
||||||
|
|
||||||
|
// 左下角调整 - 固定右上角
|
||||||
|
case 'sw':
|
||||||
|
newWidth = Math.max(props.minWidth, resizeStart.value.width - deltaX)
|
||||||
|
newHeight = Math.max(props.minHeight, resizeStart.value.height + deltaY)
|
||||||
|
newX = resizeStart.value.right - newWidth
|
||||||
|
break
|
||||||
|
|
||||||
|
// 右下角调整 - 固定左上角
|
||||||
|
case 'se':
|
||||||
|
default:
|
||||||
|
newWidth = Math.max(props.minWidth, resizeStart.value.width + deltaX)
|
||||||
|
newHeight = Math.max(props.minHeight, resizeStart.value.height + deltaY)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// 边界检查
|
||||||
|
newX = Math.max(0, Math.min(newX, window.innerWidth - newWidth))
|
||||||
|
newY = Math.max(0, Math.min(newY, window.innerHeight - newHeight))
|
||||||
|
newWidth = Math.min(newWidth, window.innerWidth - newX)
|
||||||
|
newHeight = Math.min(newHeight, window.innerHeight - newY)
|
||||||
|
|
||||||
|
size.value = { width: newWidth, height: newHeight }
|
||||||
|
position.value = { x: newX, y: newY }
|
||||||
|
|
||||||
|
emit('update:size', size.value)
|
||||||
|
emit('update:position', position.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResizeEnd = debounce(() => {
|
||||||
|
isResizing.value = false
|
||||||
|
resizeDirection.value = null
|
||||||
|
document.removeEventListener('mousemove', handleResizeMove)
|
||||||
|
document.removeEventListener('mouseup', handleResizeEnd)
|
||||||
|
|
||||||
|
props.onResize?.()
|
||||||
|
}, 50)
|
||||||
|
|
||||||
|
const setResizeable = (value: boolean) => {
|
||||||
|
resizeable.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
if (props.onClose) {
|
||||||
|
props.onClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
position.value = { x: defaultSize.value.x, y: defaultSize.value.y }
|
||||||
|
size.value = { width: defaultSize.value.width, height: defaultSize.value.height }
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
if (containerRef.value) {
|
||||||
|
focusWindow(containerRef.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 清理事件监听
|
||||||
|
document.removeEventListener('mousemove', handleDragMove)
|
||||||
|
document.removeEventListener('mouseup', handleDragEnd)
|
||||||
|
document.removeEventListener('mousemove', handleResizeMove)
|
||||||
|
document.removeEventListener('mouseup', handleResizeEnd)
|
||||||
|
|
||||||
|
// 从窗口管理数组中移除
|
||||||
|
if (containerRef.value) {
|
||||||
|
windowsArrange = windowsArrange.filter(ele => ele !== containerRef.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.rnd-container {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-window-header {
|
||||||
|
background-color: #1976d2;
|
||||||
|
color: white;
|
||||||
|
height: 36px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 5px;
|
||||||
|
user-select: none;
|
||||||
|
cursor: move;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 800;
|
||||||
|
flex-grow: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title-custom {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-window-content {
|
||||||
|
flex-grow: 1;
|
||||||
|
background: var(--color-card);
|
||||||
|
overflow: auto;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 调整大小的手柄样式 */
|
||||||
|
.resize-handle {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle-n,
|
||||||
|
.resize-handle-s {
|
||||||
|
width: 100%;
|
||||||
|
height: 6px;
|
||||||
|
cursor: ns-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle-e,
|
||||||
|
.resize-handle-w {
|
||||||
|
width: 6px;
|
||||||
|
height: 100%;
|
||||||
|
cursor: ew-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle-ne,
|
||||||
|
.resize-handle-nw,
|
||||||
|
.resize-handle-se,
|
||||||
|
.resize-handle-sw {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle-n {
|
||||||
|
top: -3px;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle-s {
|
||||||
|
bottom: -3px;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle-e {
|
||||||
|
top: 0;
|
||||||
|
right: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle-w {
|
||||||
|
top: 0;
|
||||||
|
left: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle-ne {
|
||||||
|
top: -6px;
|
||||||
|
right: -6px;
|
||||||
|
cursor: ne-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle-nw {
|
||||||
|
top: -6px;
|
||||||
|
left: -6px;
|
||||||
|
cursor: nw-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle-se {
|
||||||
|
bottom: -6px;
|
||||||
|
right: -6px;
|
||||||
|
cursor: se-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle-sw {
|
||||||
|
bottom: -6px;
|
||||||
|
left: -6px;
|
||||||
|
cursor: sw-resize;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -7,6 +7,17 @@ import { computed, ref, nextTick } from 'vue'
|
|||||||
import { AnchorLocations } from '@jsplumb/browser-ui'
|
import { AnchorLocations } from '@jsplumb/browser-ui'
|
||||||
import MultiLineTooltip from '@/components/MultiLineTooltip/index.vue'
|
import MultiLineTooltip from '@/components/MultiLineTooltip/index.vue'
|
||||||
import Bg from './Bg.vue'
|
import Bg from './Bg.vue'
|
||||||
|
import BranchButton from './components/BranchButton.vue'
|
||||||
|
|
||||||
|
// 判断计划是否就绪
|
||||||
|
const planReady = computed(() => {
|
||||||
|
return agentsStore.agentRawPlan.data !== undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const openPlanModification = () => {
|
||||||
|
console.log('打开分支修改窗口')
|
||||||
|
agentsStore.openPlanModification()
|
||||||
|
}
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(el: 'resetAgentRepoLine'): void
|
(el: 'resetAgentRepoLine'): void
|
||||||
@@ -15,8 +26,6 @@ const emit = defineEmits<{
|
|||||||
(el: 'click-branch'): void
|
(el: 'click-branch'): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// 分支数量
|
|
||||||
|
|
||||||
const jsplumb = new Jsplumb('task-syllabus')
|
const jsplumb = new Jsplumb('task-syllabus')
|
||||||
|
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
@@ -305,11 +314,23 @@ defineExpose({
|
|||||||
class="task-content-editor"
|
class="task-content-editor"
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
<div class="flex justify-end gap-2">
|
<div class="flex justify-end">
|
||||||
<el-button @click="saveEditing" type="primary" size="small" class="px-3">
|
<svg-icon
|
||||||
√
|
icon-class="Check"
|
||||||
</el-button>
|
size="20px"
|
||||||
<el-button @click="cancelEditing" size="small" class="px-3"> × </el-button>
|
color="#328621"
|
||||||
|
class="cursor-pointer mr-4"
|
||||||
|
@click="saveEditing"
|
||||||
|
title="保存"
|
||||||
|
/>
|
||||||
|
<svg-icon
|
||||||
|
icon-class="Cancel"
|
||||||
|
size="20px"
|
||||||
|
color="#8e0707"
|
||||||
|
class="cursor-pointer mr-1"
|
||||||
|
@click="cancelEditing"
|
||||||
|
title="取消"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -369,13 +390,8 @@ defineExpose({
|
|||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="branch-button" @click="handleBranchClick">
|
|
||||||
<div class="branch-icon">
|
|
||||||
<svg-icon icon-class="branch" color="#000" size="24px" />
|
|
||||||
</div>
|
|
||||||
<span>{{ branchCount }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<BranchButton v-dev-only v-if="planReady" @click="openPlanModification" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -2,9 +2,13 @@
|
|||||||
import Header from './components/Header.vue'
|
import Header from './components/Header.vue'
|
||||||
import Main from './components/Main/index.vue'
|
import Main from './components/Main/index.vue'
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } 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 isDarkMode = ref(false)
|
||||||
|
const agentsStore = useAgentsStore()
|
||||||
// 初始化主题
|
// 初始化主题
|
||||||
const initTheme = () => {
|
const initTheme = () => {
|
||||||
const savedTheme = localStorage.getItem('theme')
|
const savedTheme = localStorage.getItem('theme')
|
||||||
@@ -80,5 +84,27 @@ onMounted(() => {
|
|||||||
|
|
||||||
<Header />
|
<Header />
|
||||||
<Main />
|
<Main />
|
||||||
|
<FloatWindow
|
||||||
|
v-if="agentsStore.planModificationWindow"
|
||||||
|
title="任务大纲探索"
|
||||||
|
@close="agentsStore.closePlanModification"
|
||||||
|
>
|
||||||
|
<PlanModification />
|
||||||
|
</FloatWindow>
|
||||||
|
<FloatWindow
|
||||||
|
v-if="agentsStore.planTaskWindow"
|
||||||
|
title="任务过程探索"
|
||||||
|
@close="agentsStore.closePlanTask"
|
||||||
|
>
|
||||||
|
<PlanTask />
|
||||||
|
</FloatWindow>
|
||||||
|
|
||||||
|
<FloatWindow
|
||||||
|
v-if="agentsStore.agentAllocationDialog"
|
||||||
|
title="智能体探索"
|
||||||
|
@close="agentsStore.closeAgentAllocationDialog"
|
||||||
|
>
|
||||||
|
<AgentAllocation />
|
||||||
|
</FloatWindow>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -9,11 +9,14 @@ import 'virtual:svg-icons-register'
|
|||||||
import { initService } from '@/utils/request.ts'
|
import { initService } from '@/utils/request.ts'
|
||||||
import { setupStore, useConfigStore } from '@/stores'
|
import { setupStore, useConfigStore } from '@/stores'
|
||||||
import { setupDirective } from '@/ directive'
|
import { setupDirective } from '@/ directive'
|
||||||
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||||
async function init() {
|
async function init() {
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
await configStore.initConfig()
|
await configStore.initConfig()
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||||
|
app.component(key, component)
|
||||||
|
}
|
||||||
setupStore(app)
|
setupStore(app)
|
||||||
setupDirective(app)
|
setupDirective(app)
|
||||||
initService()
|
initService()
|
||||||
|
|||||||
@@ -10,3 +10,4 @@ export function setupStore(app: App<Element>) {
|
|||||||
|
|
||||||
export * from './modules/agents.ts'
|
export * from './modules/agents.ts'
|
||||||
export * from './modules/config.ts'
|
export * from './modules/config.ts'
|
||||||
|
export * from './modules/selection.ts'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ref, watch } from 'vue'
|
import { ref, watch, computed } from 'vue'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { getAgentMapIcon } from '@/layout/components/config'
|
||||||
import { store } from '../index'
|
import { store } from '../index'
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import type { IExecuteRawResponse } from '@/api'
|
import type { IExecuteRawResponse } from '@/api'
|
||||||
@@ -45,10 +45,44 @@ export interface IRawStepTask {
|
|||||||
InputObject_List?: string[]
|
InputObject_List?: string[]
|
||||||
OutputObject?: string
|
OutputObject?: string
|
||||||
AgentSelection?: string[]
|
AgentSelection?: string[]
|
||||||
Collaboration_Brief_FrontEnd: IRichText
|
Collaboration_Brief_frontEnd: IRichText
|
||||||
TaskProcess: TaskProcess[]
|
TaskProcess: TaskProcess[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IApiAgentAction {
|
||||||
|
id: string
|
||||||
|
type: string
|
||||||
|
agent: string
|
||||||
|
description: string
|
||||||
|
inputs: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IApiStepTask {
|
||||||
|
name: string
|
||||||
|
content: string
|
||||||
|
inputs: string[]
|
||||||
|
output: string
|
||||||
|
agents: string[]
|
||||||
|
brief: IRichText
|
||||||
|
process: IApiAgentAction[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 智能体评分数据类型
|
||||||
|
export interface IScoreItem {
|
||||||
|
score: number
|
||||||
|
reason: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAgentSelectModifyAddRequest {
|
||||||
|
aspectList: string[] // 维度列表
|
||||||
|
agentScores: Record<string, Record<string, IScoreItem>> // agent -> 维度 -> 评分(与后端返回格式一致)
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAgentSelectModifyInitRequest {
|
||||||
|
goal: string
|
||||||
|
stepTask: IApiStepTask
|
||||||
|
}
|
||||||
|
|
||||||
export interface IRawPlanResponse {
|
export interface IRawPlanResponse {
|
||||||
'Initial Input Object'?: string[] | string
|
'Initial Input Object'?: string[] | string
|
||||||
'General Goal'?: string
|
'General Goal'?: string
|
||||||
@@ -71,6 +105,95 @@ export const useAgentsStore = defineStore('agents', () => {
|
|||||||
agents.value = agent
|
agents.value = agent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 智能体评分数据存储
|
||||||
|
const IAgentSelectModifyAddRequest = useStorage<IAgentSelectModifyAddRequest | null>(
|
||||||
|
`${storageKey}-score-data`,
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
|
||||||
|
// 设置智能体评分数据
|
||||||
|
function setAgentScoreData(data: IAgentSelectModifyAddRequest) {
|
||||||
|
IAgentSelectModifyAddRequest.value = data
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加新维度的评分数据(追加模式)
|
||||||
|
function addAgentScoreAspect(aspectName: string, scores: Record<string, IScoreItem>) {
|
||||||
|
if (!IAgentSelectModifyAddRequest.value) {
|
||||||
|
IAgentSelectModifyAddRequest.value = {
|
||||||
|
aspectList: [],
|
||||||
|
agentScores: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查维度是否已存在
|
||||||
|
if (!IAgentSelectModifyAddRequest.value.aspectList.includes(aspectName)) {
|
||||||
|
IAgentSelectModifyAddRequest.value.aspectList.push(aspectName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加该维度的评分数据
|
||||||
|
// scores: { agentName: { score, reason } }
|
||||||
|
// 转换为 agentScores[agentName][aspectName] = { score, reason }
|
||||||
|
for (const [agentName, scoreItem] of Object.entries(scores)) {
|
||||||
|
if (!IAgentSelectModifyAddRequest.value.agentScores[agentName]) {
|
||||||
|
IAgentSelectModifyAddRequest.value.agentScores[agentName] = {}
|
||||||
|
}
|
||||||
|
IAgentSelectModifyAddRequest.value.agentScores[agentName][aspectName] = scoreItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除智能体评分数据
|
||||||
|
function clearAgentScoreData() {
|
||||||
|
IAgentSelectModifyAddRequest.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 智能体分配确认的组合列表存储 - 按任务ID分别存储(不持久化到localStorage)
|
||||||
|
const confirmedAgentGroupsMap = ref<Map<string, string[][]>>(new Map())
|
||||||
|
|
||||||
|
// 获取指定任务的确认的agent组合列表
|
||||||
|
function getConfirmedAgentGroups(taskId: string): string[][] {
|
||||||
|
return confirmedAgentGroupsMap.value.get(taskId) || []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加确认的agent组合到指定任务
|
||||||
|
function addConfirmedAgentGroup(taskId: string, group: string[]) {
|
||||||
|
const groups = confirmedAgentGroupsMap.value.get(taskId) || []
|
||||||
|
groups.push(group)
|
||||||
|
confirmedAgentGroupsMap.value.set(taskId, groups)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除指定任务的确认的agent组合列表
|
||||||
|
function clearConfirmedAgentGroups(taskId: string) {
|
||||||
|
confirmedAgentGroupsMap.value.delete(taskId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除所有任务的确认的agent组合列表
|
||||||
|
function clearAllConfirmedAgentGroups() {
|
||||||
|
confirmedAgentGroupsMap.value.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
const planModificationWindow = ref(false)
|
||||||
|
const planTaskWindow = ref(false)
|
||||||
|
const agentAllocationDialog = ref(false)
|
||||||
|
|
||||||
|
function openPlanModification() {
|
||||||
|
planModificationWindow.value = true
|
||||||
|
}
|
||||||
|
function closePlanModification() {
|
||||||
|
planModificationWindow.value = false
|
||||||
|
}
|
||||||
|
function openPlanTask() {
|
||||||
|
planTaskWindow.value = true
|
||||||
|
}
|
||||||
|
function closePlanTask() {
|
||||||
|
planTaskWindow.value = false
|
||||||
|
}
|
||||||
|
function openAgentAllocationDialog() {
|
||||||
|
agentAllocationDialog.value = true
|
||||||
|
}
|
||||||
|
function closeAgentAllocationDialog() {
|
||||||
|
agentAllocationDialog.value = false
|
||||||
|
}
|
||||||
|
|
||||||
// 任务搜索的内容
|
// 任务搜索的内容
|
||||||
const searchValue = useStorage<string>(`${storageKey}-search-value`, '')
|
const searchValue = useStorage<string>(`${storageKey}-search-value`, '')
|
||||||
function setSearchValue(value: string) {
|
function setSearchValue(value: string) {
|
||||||
@@ -134,6 +257,30 @@ export const useAgentsStore = defineStore('agents', () => {
|
|||||||
// 额外的产物列表
|
// 额外的产物列表
|
||||||
const additionalOutputs = ref<string[]>([])
|
const additionalOutputs = ref<string[]>([])
|
||||||
|
|
||||||
|
// 用户在 Assignment 中选择的 agent 组合(按任务ID分别存储)
|
||||||
|
const selectedAgentGroupMap = ref<Map<string, string[]>>(new Map())
|
||||||
|
|
||||||
|
// 设置指定任务的选择的 agent 组合
|
||||||
|
function setSelectedAgentGroup(taskId: string, agents: string[]) {
|
||||||
|
selectedAgentGroupMap.value.set(taskId, agents)
|
||||||
|
console.log('💾 保存任务选择的 agent 组合:', { taskId, agents })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取指定任务的选择的 agent 组合
|
||||||
|
function getSelectedAgentGroup(taskId: string): string[] | undefined {
|
||||||
|
return selectedAgentGroupMap.value.get(taskId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除指定任务的选择的 agent 组合
|
||||||
|
function clearSelectedAgentGroup(taskId: string) {
|
||||||
|
selectedAgentGroupMap.value.delete(taskId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除所有任务的选择的 agent 组合
|
||||||
|
function clearAllSelectedAgentGroups() {
|
||||||
|
selectedAgentGroupMap.value.clear()
|
||||||
|
}
|
||||||
|
|
||||||
// 添加新产物
|
// 添加新产物
|
||||||
function addNewOutput(outputObject: string) {
|
function addNewOutput(outputObject: string) {
|
||||||
if (!outputObject.trim()) return false
|
if (!outputObject.trim()) return false
|
||||||
@@ -177,6 +324,32 @@ export const useAgentsStore = defineStore('agents', () => {
|
|||||||
addNewOutput,
|
addNewOutput,
|
||||||
removeAdditionalOutput,
|
removeAdditionalOutput,
|
||||||
clearAdditionalOutputs,
|
clearAdditionalOutputs,
|
||||||
|
// 用户选择的 agent 组合
|
||||||
|
selectedAgentGroupMap,
|
||||||
|
setSelectedAgentGroup,
|
||||||
|
getSelectedAgentGroup,
|
||||||
|
clearSelectedAgentGroup,
|
||||||
|
clearAllSelectedAgentGroups,
|
||||||
|
planModificationWindow,
|
||||||
|
openPlanModification,
|
||||||
|
closePlanModification,
|
||||||
|
planTaskWindow,
|
||||||
|
openPlanTask,
|
||||||
|
closePlanTask,
|
||||||
|
agentAllocationDialog,
|
||||||
|
openAgentAllocationDialog,
|
||||||
|
closeAgentAllocationDialog,
|
||||||
|
// 智能体评分数据
|
||||||
|
IAgentSelectModifyAddRequest,
|
||||||
|
setAgentScoreData,
|
||||||
|
addAgentScoreAspect,
|
||||||
|
clearAgentScoreData,
|
||||||
|
// 确认的agent组合列表(按任务ID分别存储)
|
||||||
|
confirmedAgentGroupsMap,
|
||||||
|
getConfirmedAgentGroups,
|
||||||
|
addConfirmedAgentGroup,
|
||||||
|
clearConfirmedAgentGroups,
|
||||||
|
clearAllConfirmedAgentGroups,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
247
frontend/src/stores/modules/selection.ts
Normal file
247
frontend/src/stores/modules/selection.ts
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { store } from '../index'
|
||||||
|
import type { IRawStepTask, IApiStepTask } from './agents'
|
||||||
|
import type { Node, Edge } from '@vue-flow/core'
|
||||||
|
|
||||||
|
// 分支节点数据接口 - 用于存储流程图中的节点和边
|
||||||
|
export interface IBranchData {
|
||||||
|
id: string // 分支唯一ID
|
||||||
|
parentNodeId: string // 父节点ID(根节点或任务节点)
|
||||||
|
branchContent: string // 分支需求内容
|
||||||
|
branchType: 'root' | 'task' // 分支类型
|
||||||
|
nodes: Node[] // 分支包含的所有节点
|
||||||
|
edges: Edge[] // 分支包含的所有边
|
||||||
|
tasks: IRawStepTask[] // 分支的任务数据
|
||||||
|
createdAt: number // 创建时间
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSelectionStore = defineStore('selection', () => {
|
||||||
|
// ==================== 任务大纲探索分支数据存储 ====================
|
||||||
|
const flowBranches = ref<IBranchData[]>([])
|
||||||
|
|
||||||
|
// 任务大纲分支管理
|
||||||
|
|
||||||
|
// 添加流程图分支
|
||||||
|
function addFlowBranch(data: {
|
||||||
|
parentNodeId: string
|
||||||
|
branchContent: string
|
||||||
|
branchType: 'root' | 'task'
|
||||||
|
nodes: Node[]
|
||||||
|
edges: Edge[]
|
||||||
|
tasks: IRawStepTask[]
|
||||||
|
}): string {
|
||||||
|
const branchId = `flow-branch-${uuidv4()}`
|
||||||
|
const newBranch: IBranchData = {
|
||||||
|
id: branchId,
|
||||||
|
parentNodeId: data.parentNodeId,
|
||||||
|
branchContent: data.branchContent,
|
||||||
|
branchType: data.branchType,
|
||||||
|
nodes: data.nodes,
|
||||||
|
edges: data.edges,
|
||||||
|
tasks: data.tasks,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
}
|
||||||
|
flowBranches.value.push(newBranch)
|
||||||
|
console.log('📂 保存流程图分支到 store:', newBranch)
|
||||||
|
return branchId
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有流程图分支
|
||||||
|
function getAllFlowBranches(): IBranchData[] {
|
||||||
|
return flowBranches.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据父节点ID获取流程图分支
|
||||||
|
function getFlowBranchesByParent(parentNodeId: string): IBranchData[] {
|
||||||
|
return flowBranches.value.filter((branch) => branch.parentNodeId === parentNodeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除流程图分支
|
||||||
|
function removeFlowBranch(branchId: string): boolean {
|
||||||
|
const index = flowBranches.value.findIndex((branch) => branch.id === branchId)
|
||||||
|
if (index > -1) {
|
||||||
|
flowBranches.value.splice(index, 1)
|
||||||
|
console.log('🗑️ 删除流程图分支:', branchId)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除所有流程图分支
|
||||||
|
function clearFlowBranches() {
|
||||||
|
flowBranches.value = []
|
||||||
|
console.log('🗑️ 清除所有流程图分支')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据父节点ID清除流程图分支
|
||||||
|
function clearFlowBranchesByParent(parentNodeId: string) {
|
||||||
|
flowBranches.value = flowBranches.value.filter((branch) => branch.parentNodeId !== parentNodeId)
|
||||||
|
console.log('🗑️ 清除父节点为', parentNodeId, '的流程图分支')
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 任务过程探索分支数据存储 ====================
|
||||||
|
// 用于存储任务过程探索中的分支数据
|
||||||
|
// 结构: Map<taskStepId, IBranchData[]>
|
||||||
|
const taskProcessBranchesMap = ref<Map<string, IBranchData[]>>(new Map())
|
||||||
|
|
||||||
|
// 添加任务过程分支
|
||||||
|
function addTaskProcessBranch(
|
||||||
|
taskStepId: string,
|
||||||
|
data: {
|
||||||
|
parentNodeId: string
|
||||||
|
branchContent: string
|
||||||
|
branchType: 'root' | 'task'
|
||||||
|
nodes: Node[]
|
||||||
|
edges: Edge[]
|
||||||
|
tasks: IRawStepTask[]
|
||||||
|
},
|
||||||
|
): string {
|
||||||
|
const branchId = `task-process-branch-${uuidv4()}`
|
||||||
|
const newBranch: IBranchData = {
|
||||||
|
id: branchId,
|
||||||
|
parentNodeId: data.parentNodeId,
|
||||||
|
branchContent: data.branchContent,
|
||||||
|
branchType: data.branchType,
|
||||||
|
nodes: data.nodes,
|
||||||
|
edges: data.edges,
|
||||||
|
tasks: data.tasks,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取或创建该任务步骤的分支列表
|
||||||
|
if (!taskProcessBranchesMap.value.has(taskStepId)) {
|
||||||
|
taskProcessBranchesMap.value.set(taskStepId, [])
|
||||||
|
}
|
||||||
|
taskProcessBranchesMap.value.get(taskStepId)!.push(newBranch)
|
||||||
|
|
||||||
|
console.log('📂 保存任务过程分支到 store:', { taskStepId, branch: newBranch })
|
||||||
|
return branchId
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取指定任务步骤的所有分支
|
||||||
|
function getTaskProcessBranches(taskStepId: string): IBranchData[] {
|
||||||
|
return taskProcessBranchesMap.value.get(taskStepId) || []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有任务过程分支
|
||||||
|
function getAllTaskProcessBranches(): Map<string, IBranchData[]> {
|
||||||
|
return taskProcessBranchesMap.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据父节点ID获取任务过程分支
|
||||||
|
function getTaskProcessBranchesByParent(taskStepId: string, parentNodeId: string): IBranchData[] {
|
||||||
|
const branches = taskProcessBranchesMap.value.get(taskStepId) || []
|
||||||
|
return branches.filter((branch) => branch.parentNodeId === parentNodeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除任务过程分支
|
||||||
|
function removeTaskProcessBranch(taskStepId: string, branchId: string): boolean {
|
||||||
|
const branches = taskProcessBranchesMap.value.get(taskStepId)
|
||||||
|
if (branches) {
|
||||||
|
const index = branches.findIndex((branch) => branch.id === branchId)
|
||||||
|
if (index > -1) {
|
||||||
|
branches.splice(index, 1)
|
||||||
|
console.log('🗑️ 删除任务过程分支:', { taskStepId, branchId })
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除指定任务步骤的所有分支
|
||||||
|
function clearTaskProcessBranches(taskStepId: string) {
|
||||||
|
taskProcessBranchesMap.value.delete(taskStepId)
|
||||||
|
console.log('🗑️ 清除任务步骤的所有分支:', taskStepId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Agent 组合 TaskProcess 数据存储 ====================
|
||||||
|
// 用于存储 fill_stepTask_TaskProcess 接口返回的数据
|
||||||
|
// 结构: Map<taskId, Map<agentGroupKey, IApiStepTask>>
|
||||||
|
// - taskId: 任务ID
|
||||||
|
// - agentGroupKey: agent 组合的唯一标识(排序后的 JSON 字符串)
|
||||||
|
// - IApiStepTask: 该 agent 组合的完整 TaskProcess 数据
|
||||||
|
const agentTaskProcessMap = ref<Map<string, Map<string, IApiStepTask>>>(new Map())
|
||||||
|
|
||||||
|
// 生成 agent 组合的唯一 key(排序后保证一致性)
|
||||||
|
function getAgentGroupKey(agents: string[]): string {
|
||||||
|
return JSON.stringify(agents.sort())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存储 agent 组合的 TaskProcess 数据
|
||||||
|
function setAgentTaskProcess(taskId: string, agents: string[], taskProcess: IApiStepTask) {
|
||||||
|
const groupKey = getAgentGroupKey(agents)
|
||||||
|
|
||||||
|
// 获取或创建该任务的 Map
|
||||||
|
if (!agentTaskProcessMap.value.has(taskId)) {
|
||||||
|
agentTaskProcessMap.value.set(taskId, new Map())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存储该 agent 组合的 TaskProcess 数据
|
||||||
|
agentTaskProcessMap.value.get(taskId)!.set(groupKey, taskProcess)
|
||||||
|
console.log(`📦 存储 agent 组合 TaskProcess [任务: ${taskId}, agents: ${agents.join(', ')}]`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 agent 组合的 TaskProcess 数据
|
||||||
|
function getAgentTaskProcess(taskId: string, agents: string[]): IApiStepTask | undefined {
|
||||||
|
const groupKey = getAgentGroupKey(agents)
|
||||||
|
return agentTaskProcessMap.value.get(taskId)?.get(groupKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 agent 组合是否已有 TaskProcess 数据
|
||||||
|
function hasAgentTaskProcess(taskId: string, agents: string[]): boolean {
|
||||||
|
const groupKey = getAgentGroupKey(agents)
|
||||||
|
return agentTaskProcessMap.value.get(taskId)?.has(groupKey) || false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除指定任务的所有 agent 组合 TaskProcess 数据
|
||||||
|
function clearAgentTaskProcess(taskId: string) {
|
||||||
|
agentTaskProcessMap.value.delete(taskId)
|
||||||
|
console.log(`🗑️ 清除任务的 agent 组合 TaskProcess 数据: ${taskId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除所有任务的 agent 组合 TaskProcess 数据
|
||||||
|
function clearAllAgentTaskProcess() {
|
||||||
|
agentTaskProcessMap.value.clear()
|
||||||
|
console.log('🗑️ 清除所有任务的 agent 组合 TaskProcess 数据')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 状态
|
||||||
|
flowBranches,
|
||||||
|
taskProcessBranchesMap,
|
||||||
|
agentTaskProcessMap,
|
||||||
|
|
||||||
|
// 任务大纲分支管理方法
|
||||||
|
addFlowBranch,
|
||||||
|
getAllFlowBranches,
|
||||||
|
getFlowBranchesByParent,
|
||||||
|
removeFlowBranch,
|
||||||
|
clearFlowBranches,
|
||||||
|
clearFlowBranchesByParent,
|
||||||
|
|
||||||
|
// 任务过程分支管理方法
|
||||||
|
addTaskProcessBranch,
|
||||||
|
getTaskProcessBranches,
|
||||||
|
getAllTaskProcessBranches,
|
||||||
|
getTaskProcessBranchesByParent,
|
||||||
|
removeTaskProcessBranch,
|
||||||
|
clearTaskProcessBranches,
|
||||||
|
|
||||||
|
// Agent 组合 TaskProcess 数据管理方法
|
||||||
|
getAgentGroupKey,
|
||||||
|
setAgentTaskProcess,
|
||||||
|
getAgentTaskProcess,
|
||||||
|
hasAgentTaskProcess,
|
||||||
|
clearAgentTaskProcess,
|
||||||
|
clearAllAgentTaskProcess,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于在组件外部使用 Selection Store
|
||||||
|
*/
|
||||||
|
export function useSelectionStoreHook() {
|
||||||
|
return useSelectionStore(store)
|
||||||
|
}
|
||||||
@@ -102,6 +102,11 @@
|
|||||||
--color-text-result-detail-run: #000;
|
--color-text-result-detail-run: #000;
|
||||||
// 分割线
|
// 分割线
|
||||||
--color-border-separate: #D6D6D6;
|
--color-border-separate: #D6D6D6;
|
||||||
|
// url颜色
|
||||||
|
--color-text-url: #FF0000;
|
||||||
|
// model颜色
|
||||||
|
--color-text-model: #0580ff;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 深色模式
|
// 深色模式
|
||||||
@@ -208,6 +213,10 @@ html.dark {
|
|||||||
--color-text-result-detail-run: #fff;
|
--color-text-result-detail-run: #fff;
|
||||||
// 分割线
|
// 分割线
|
||||||
--color-border-separate: rgba(255,255,255,0.18);
|
--color-border-separate: rgba(255,255,255,0.18);
|
||||||
|
// url颜色
|
||||||
|
--color-text-url: #2cd235;
|
||||||
|
// model颜色
|
||||||
|
--color-text-model: #00c8d2;
|
||||||
|
|
||||||
--el-fill-color-blank: transparent !important;
|
--el-fill-color-blank: transparent !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,10 @@ $text-taskbar: var(--color-text-taskbar);
|
|||||||
$agent-list-selected-bg: var(--color-agent-list-selected-bg); //选中后背景色#171b22 100%
|
$agent-list-selected-bg: var(--color-agent-list-selected-bg); //选中后背景色#171b22 100%
|
||||||
// 结果卡片运行前颜色
|
// 结果卡片运行前颜色
|
||||||
$text-result-detail: var(--color-text-result-detail);
|
$text-result-detail: var(--color-text-result-detail);
|
||||||
|
// url和model颜色
|
||||||
|
$text-url: var(--color-text-url);
|
||||||
|
$text-model: var(--color-text-model);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -147,4 +151,6 @@ $text-result-detail: var(--color-text-result-detail);
|
|||||||
text-taskbar: $text-taskbar;
|
text-taskbar: $text-taskbar;
|
||||||
agent-list-selected-bg: $agent-list-selected-bg;
|
agent-list-selected-bg: $agent-list-selected-bg;
|
||||||
text-result-detail: $text-result-detail;
|
text-result-detail: $text-result-detail;
|
||||||
|
text-url: $text-url;
|
||||||
|
text-model: $text-model;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export function changeBriefs(task?: IRawStepTask[]): IRawStepTask[] {
|
|||||||
return task.map((item) => {
|
return task.map((item) => {
|
||||||
const record = {
|
const record = {
|
||||||
...item,
|
...item,
|
||||||
Collaboration_Brief_FrontEnd: changeBrief(item),
|
Collaboration_Brief_frontEnd: changeBrief(item),
|
||||||
}
|
}
|
||||||
return record
|
return record
|
||||||
})
|
})
|
||||||
@@ -30,10 +30,10 @@ function changeBrief(task: IRawStepTask): IRichText {
|
|||||||
// 如果不存在AgentSelection直接返回
|
// 如果不存在AgentSelection直接返回
|
||||||
const agents = task.AgentSelection ?? []
|
const agents = task.AgentSelection ?? []
|
||||||
if (agents.length === 0) {
|
if (agents.length === 0) {
|
||||||
return task.Collaboration_Brief_FrontEnd
|
return task.Collaboration_Brief_frontEnd
|
||||||
}
|
}
|
||||||
const data: IRichText['data'] = {};
|
const data: IRichText['data'] = {}
|
||||||
let indexOffset = 0;
|
let indexOffset = 0
|
||||||
|
|
||||||
// 根据InputObject_List修改
|
// 根据InputObject_List修改
|
||||||
const inputs = task.InputObject_List ?? []
|
const inputs = task.InputObject_List ?? []
|
||||||
@@ -41,63 +41,62 @@ function changeBrief(task: IRawStepTask): IRichText {
|
|||||||
data[(index + indexOffset).toString()] = {
|
data[(index + indexOffset).toString()] = {
|
||||||
text,
|
text,
|
||||||
style: { background: '#ACDBA0' },
|
style: { background: '#ACDBA0' },
|
||||||
};
|
}
|
||||||
return `!<${index + indexOffset}>!`;
|
return `!<${index + indexOffset}>!`
|
||||||
});
|
})
|
||||||
const inputSentence = nameJoin(inputPlaceHolders);
|
const inputSentence = nameJoin(inputPlaceHolders)
|
||||||
indexOffset += inputs.length;
|
indexOffset += inputs.length
|
||||||
|
|
||||||
// 根据AgentSelection修改
|
// 根据AgentSelection修改
|
||||||
const namePlaceholders = agents.map((text, index) => {
|
const namePlaceholders = agents.map((text, index) => {
|
||||||
data[(index + indexOffset).toString()] = {
|
data[(index + indexOffset).toString()] = {
|
||||||
text,
|
text,
|
||||||
style: { background: '#E5E5E5', boxShadow: '1px 1px 4px 1px #0003' },
|
style: { background: '#E5E5E5', boxShadow: '1px 1px 4px 1px #0003' },
|
||||||
};
|
}
|
||||||
return `!<${index + indexOffset}>!`;
|
return `!<${index + indexOffset}>!`
|
||||||
});
|
})
|
||||||
const nameSentence = nameJoin(namePlaceholders);
|
const nameSentence = nameJoin(namePlaceholders)
|
||||||
indexOffset += agents.length;
|
indexOffset += agents.length
|
||||||
|
|
||||||
|
let actionSentence = task.TaskContent ?? ''
|
||||||
let actionSentence = task.TaskContent ?? '';
|
|
||||||
|
|
||||||
// delete the last '.' of actionSentence
|
// delete the last '.' of actionSentence
|
||||||
if (actionSentence[actionSentence.length - 1] === '.') {
|
if (actionSentence[actionSentence.length - 1] === '.') {
|
||||||
actionSentence = actionSentence.slice(0, -1);
|
actionSentence = actionSentence.slice(0, -1)
|
||||||
}
|
}
|
||||||
const actionIndex = indexOffset++;
|
const actionIndex = indexOffset++
|
||||||
|
|
||||||
data[actionIndex.toString()] = {
|
data[actionIndex.toString()] = {
|
||||||
text: actionSentence,
|
text: actionSentence,
|
||||||
style: { background: '#DDD', border: '1.5px solid #ddd' },
|
style: { background: '#DDD', border: '1.5px solid #ddd' },
|
||||||
};
|
}
|
||||||
|
|
||||||
let outputSentence = '';
|
let outputSentence = ''
|
||||||
const output = task.OutputObject ?? '';
|
const output = task.OutputObject ?? ''
|
||||||
if (output) {
|
if (output) {
|
||||||
data[indexOffset.toString()] = {
|
data[indexOffset.toString()] = {
|
||||||
text: output,
|
text: output,
|
||||||
style: { background: '#FFCA8C' },
|
style: { background: '#FFCA8C' },
|
||||||
};
|
}
|
||||||
outputSentence = `得到 !<${indexOffset}>!`;
|
outputSentence = `得到 !<${indexOffset}>!`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Join them togeter
|
// Join them togeter
|
||||||
let content = inputSentence;
|
let content = inputSentence
|
||||||
if (content) {
|
if (content) {
|
||||||
content = `基于${content}, ${nameSentence} 执行任务 !<${actionIndex}>!`;
|
content = `基于${content}, ${nameSentence} 执行任务 !<${actionIndex}>!`
|
||||||
} else {
|
} else {
|
||||||
content = `${nameSentence} 执行任务 !<${actionIndex}>!`;
|
content = `${nameSentence} 执行任务 !<${actionIndex}>!`
|
||||||
}
|
}
|
||||||
if (outputSentence) {
|
if (outputSentence) {
|
||||||
content = `${content}, ${outputSentence}.`;
|
content = `${content}, ${outputSentence}.`
|
||||||
} else {
|
} else {
|
||||||
content = `${content}.`;
|
content = `${content}.`
|
||||||
}
|
}
|
||||||
content = content.trim();
|
content = content.trim()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
template: content,
|
template: content,
|
||||||
data,
|
data,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user