feat:三个浮动窗口功能新增
This commit is contained in:
@@ -1,5 +1,14 @@
|
||||
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 {
|
||||
ID: string
|
||||
@@ -19,8 +28,13 @@ export type IExecuteRawResponse = {
|
||||
ActionHistory: ActionHistory[]
|
||||
}
|
||||
|
||||
export interface IFillAgentSelectionRequest {
|
||||
goal: string
|
||||
stepTask: IApiStepTask
|
||||
agents: string[]
|
||||
}
|
||||
|
||||
class Api {
|
||||
// 智能体信息
|
||||
setAgents = (data: Pick<Agent, 'Name' | 'Profile' | 'apiUrl' | 'apiKey' | 'apiModel'>[]) => {
|
||||
return request({
|
||||
url: '/setAgents',
|
||||
@@ -65,7 +79,7 @@ class Api {
|
||||
InputObject_List: step.InputObject_List,
|
||||
OutputObject: step.OutputObject,
|
||||
AgentSelection: step.AgentSelection,
|
||||
Collaboration_Brief_frontEnd: step.Collaboration_Brief_FrontEnd,
|
||||
Collaboration_Brief_frontEnd: step.Collaboration_Brief_frontEnd,
|
||||
TaskProcess: step.TaskProcess.map((action) => ({
|
||||
ActionType: action.ActionType,
|
||||
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()
|
||||
|
||||
@@ -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">
|
||||
import { ref, onMounted, computed, reactive } from 'vue'
|
||||
|
||||
import { ref, onMounted, computed, reactive, nextTick } from 'vue'
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||
import { useAgentsStore, useConfigStore } from '@/stores'
|
||||
import api from '@/api'
|
||||
import { changeBriefs } from '@/utils/collaboration_Brief_FrontEnd.ts'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { log } from '@jsplumb/browser-ui'
|
||||
import ProcessCard from './TaskTemplate/TaskProcess/ProcessCard.vue'
|
||||
import AgentAllocation from './TaskTemplate/TaskSyllabus/components/AgentAllocation.vue'
|
||||
|
||||
import AssignmentButton from './TaskTemplate/TaskSyllabus/components/AssignmentButton.vue'
|
||||
const emit = defineEmits<{
|
||||
(e: 'search-start'): void
|
||||
(e: 'search', value: string): void
|
||||
@@ -21,7 +17,6 @@ const searchValue = ref('')
|
||||
const triggerOnFocus = ref(true)
|
||||
const isFocus = ref(false)
|
||||
const hasAutoSearched = ref(false) // 防止重复自动搜索
|
||||
const agentAllocationVisible = ref(false) // 智能体分配弹窗是否可见
|
||||
const isExpanded = ref(false) // 控制搜索框是否展开
|
||||
|
||||
// 解析URL参数
|
||||
@@ -30,6 +25,13 @@ function getUrlParam(param: string): string | null {
|
||||
return urlParams.get(param)
|
||||
}
|
||||
|
||||
const planReady = computed(() => {
|
||||
return agentsStore.agentRawPlan.data !== undefined
|
||||
})
|
||||
const openAgentAllocationDialog = () => {
|
||||
console.log('打开智能体分配弹窗')
|
||||
agentsStore.openAgentAllocationDialog()
|
||||
}
|
||||
// 自动搜索函数
|
||||
async function autoSearchFromUrl() {
|
||||
const query = getUrlParam('q')
|
||||
@@ -46,32 +48,42 @@ async function autoSearchFromUrl() {
|
||||
}
|
||||
}
|
||||
|
||||
// 智能体分配
|
||||
// 处理智能体分配点击事件
|
||||
function handleAgentAllocation() {
|
||||
agentAllocationVisible.value = true
|
||||
}
|
||||
|
||||
// 关闭智能体分配弹窗
|
||||
function handleAgentAllocationClose() {
|
||||
agentAllocationVisible.value = false
|
||||
}
|
||||
|
||||
// 处理获取焦点事件
|
||||
function handleFocus() {
|
||||
isFocus.value = true
|
||||
isExpanded.value = true // 搜索框展开
|
||||
}
|
||||
|
||||
const taskContainerRef = ref<HTMLDivElement | null>(null)
|
||||
|
||||
// 处理失去焦点事件
|
||||
function handleBlur() {
|
||||
isFocus.value = false
|
||||
// 延迟收起搜索框,以便点击按钮等操作
|
||||
setTimeout(() => {
|
||||
isExpanded.value = false
|
||||
// 强制重置文本区域高度到最小行数
|
||||
resetTextareaHeight()
|
||||
}, 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() {
|
||||
try {
|
||||
triggerOnFocus.value = false
|
||||
@@ -109,8 +121,6 @@ const createFilter = (queryString: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
const taskContainerRef = ref<HTMLDivElement | null>(null)
|
||||
|
||||
// 组件挂载时检查URL参数
|
||||
onMounted(() => {
|
||||
autoSearchFromUrl()
|
||||
@@ -133,10 +143,12 @@ onMounted(() => {
|
||||
>
|
||||
<span class="text-[var(--color-text-task)] font-bold task-title">任务</span>
|
||||
<el-autocomplete
|
||||
ref="autocompleteRef"
|
||||
v-model.trim="searchValue"
|
||||
class="task-input"
|
||||
size="large"
|
||||
:rows="isFocus ? 3 : 1"
|
||||
:rows="1"
|
||||
:autosize="{ minRows: 1, maxRows: 10 }"
|
||||
placeholder="请输入您的任务"
|
||||
type="textarea"
|
||||
:append-to="taskContainerRef"
|
||||
@@ -169,23 +181,7 @@ onMounted(() => {
|
||||
/>
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 智能体分配 -->
|
||||
<!-- <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>
|
||||
<AssignmentButton v-dev-only v-if="planReady" @click="openAgentAllocationDialog" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
@@ -216,6 +212,21 @@ onMounted(() => {
|
||||
/* 搜索框展开时的样式 */
|
||||
&.expanded {
|
||||
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) {
|
||||
@@ -267,6 +278,11 @@ onMounted(() => {
|
||||
resize: none;
|
||||
color: var(--color-text-taskbar);
|
||||
|
||||
/* 聚焦时的样式 */
|
||||
.expanded & {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
line-height: 1.2;
|
||||
font-size: 18px;
|
||||
@@ -363,67 +379,4 @@ onMounted(() => {
|
||||
border-color: var(--el-border-color);
|
||||
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>
|
||||
|
||||
@@ -28,12 +28,14 @@ const agentsStore = useAgentsStore()
|
||||
:class="agentsStore.currentTask?.AgentSelection?.includes(item.Name) ? 'active-card' : ''"
|
||||
>
|
||||
<div class="flex items-center justify-between relative h-[43px]">
|
||||
<!-- 图标区域 -->
|
||||
<div
|
||||
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 }"
|
||||
>
|
||||
<svg-icon :icon-class="getAgentMapIcon(item.Name).icon" color="#fff" size="24px" />
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<span
|
||||
@@ -96,39 +98,9 @@ const agentsStore = useAgentsStore()
|
||||
v-if="index1 !== taskProcess.filter(i => i.AgentName === item.Name).length - 1"
|
||||
class="h-[1px] w-full bg-[var(--color-border-default)] my-[8px]"
|
||||
></div>
|
||||
|
||||
<AssignmentButton />
|
||||
</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>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
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<{
|
||||
step: {
|
||||
@@ -27,6 +30,11 @@ const editValue = ref('')
|
||||
// 鼠标悬停的process ID
|
||||
const hoverProcessId = ref<string | null>(null)
|
||||
|
||||
// 检测当前是否是深色模式
|
||||
function isDarkMode(): boolean {
|
||||
return document.documentElement.classList.contains('dark')
|
||||
}
|
||||
|
||||
// 获取颜色浅两号的函数
|
||||
function getLightColor(color: string, level: number = 2): string {
|
||||
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')}`
|
||||
}
|
||||
|
||||
// 获取颜色深两号的函数
|
||||
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) {
|
||||
hoverProcessId.value = processId
|
||||
@@ -107,30 +143,32 @@ function handleCancel() {
|
||||
<!-- 编辑模式 - 修改为卡片样式 -->
|
||||
<div v-if="editingProcessId === process.ID" class="edit-container">
|
||||
<div class="edit-card">
|
||||
<el-input
|
||||
v-model="editValue"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 3, maxRows: 6 }"
|
||||
placeholder="请输入描述内容"
|
||||
autofocus
|
||||
/>
|
||||
<div class="edit-buttons">
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
:icon="Select"
|
||||
@click="handleSave(process.ID)"
|
||||
title="保存"
|
||||
>
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
:icon="CloseBold"
|
||||
@click="handleCancel"
|
||||
title="取消"
|
||||
>
|
||||
</el-button>
|
||||
<div class="flex flex-col gap-3">
|
||||
<el-input
|
||||
v-model="editValue"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 3, maxRows: 6 }"
|
||||
placeholder="请输入描述内容"
|
||||
autofocus
|
||||
/>
|
||||
<div class="flex justify-end">
|
||||
<svg-icon
|
||||
icon-class="Check"
|
||||
size="20px"
|
||||
color="#328621"
|
||||
class="cursor-pointer mr-4"
|
||||
@click="handleSave(process.ID)"
|
||||
title="保存"
|
||||
/>
|
||||
<svg-icon
|
||||
icon-class="Cancel"
|
||||
size="20px"
|
||||
color="#8e0707"
|
||||
class="cursor-pointer mr-1"
|
||||
@click="handleCancel"
|
||||
title="取消"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -144,7 +182,7 @@ function handleCancel() {
|
||||
border: `1px solid ${getActionTypeDisplay(process.ActionType)?.border}`,
|
||||
backgroundColor:
|
||||
hoverProcessId === process.ID
|
||||
? getLightColor(getActionTypeDisplay(process.ActionType)?.color || '#909399')
|
||||
? getAdjustedColor(getActionTypeDisplay(process.ActionType)?.color || '#909399')
|
||||
: 'transparent'
|
||||
}"
|
||||
@dblclick="handleDblClick(process.ID, process.Description)"
|
||||
@@ -156,11 +194,13 @@ function handleCancel() {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<BranchButton :step="step" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.process-card {
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
@@ -194,7 +234,6 @@ function handleCancel() {
|
||||
margin-bottom: 8px;
|
||||
|
||||
.edit-card {
|
||||
position: relative;
|
||||
//background: #f0f2f5;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
@@ -217,36 +256,10 @@ function handleCancel() {
|
||||
}
|
||||
|
||||
.edit-buttons {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
bottom: 8px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
.el-button {
|
||||
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);
|
||||
}
|
||||
}
|
||||
justify-content: flex-end;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
</el-input>
|
||||
</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>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,11 @@ const emit = defineEmits<{
|
||||
const agentsStore = useAgentsStore()
|
||||
const drawerVisible = ref(false)
|
||||
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 }"
|
||||
>
|
||||
<!-- 标题与执行按钮 -->
|
||||
<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>
|
||||
<div
|
||||
class="flex items-center justify-end gap-[14px] task-button-group min-w-[175px]"
|
||||
@@ -332,6 +336,7 @@ defineExpose({
|
||||
:title="processBtnTitle"
|
||||
@mouseenter="handleProcessMouseEnter"
|
||||
@click="handleTaskProcess"
|
||||
style="order: 1"
|
||||
>
|
||||
<svg-icon icon-class="process" />
|
||||
<span v-if="showProcessText" class="btn-text">任务过程</span>
|
||||
@@ -343,6 +348,7 @@ defineExpose({
|
||||
title="请先输入要执行的任务"
|
||||
:visible="showPopover"
|
||||
@hide="showPopover = false"
|
||||
style="order: 2"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
@@ -393,7 +399,7 @@ defineExpose({
|
||||
<!-- 内容 -->
|
||||
<div
|
||||
v-loading="agentsStore.agentRawPlan.loading"
|
||||
class="flex-1 overflow-auto relative"
|
||||
class="flex-1 overflow-auto relative ml-[20px] mr-[20px]"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<div id="task-results-main" class="px-[40px] relative">
|
||||
@@ -580,7 +586,7 @@ defineExpose({
|
||||
|
||||
.card-item {
|
||||
background: var(--color-bg-detail);
|
||||
padding: 25px;
|
||||
padding: 5px;
|
||||
padding-top: 10px;
|
||||
border-radius: 7px;
|
||||
}
|
||||
@@ -668,6 +674,8 @@ defineExpose({
|
||||
|
||||
// ========== 新增:按钮交互样式 ==========
|
||||
.task-button-group {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
.el-button {
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
@@ -679,6 +687,10 @@ defineExpose({
|
||||
color: var(--color-text-primary) !important;
|
||||
position: relative;
|
||||
background-color: var(--color-bg-tertiary);
|
||||
gap: 0px !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
-webkit-tap-highlight-color: transparent !important;
|
||||
&:hover {
|
||||
transform: translateY(-2px) !important;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
|
||||
@@ -717,25 +729,58 @@ defineExpose({
|
||||
padding: 0 16px !important;
|
||||
gap: 8px;
|
||||
|
||||
.btn-text {
|
||||
display: inline-block !important;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-left: 4px;
|
||||
// 任务过程按钮 - 左边固定,向右展开
|
||||
&:nth-child(1) {
|
||||
justify-content: flex-start !important;
|
||||
|
||||
.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;
|
||||
animation: fadeIn 0.3s ease forwards;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-left: 4px;
|
||||
opacity: 0;
|
||||
animation: fadeIn 0.3s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
@keyframes fadeInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
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 MultiLineTooltip from '@/components/MultiLineTooltip/index.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<{
|
||||
(el: 'resetAgentRepoLine'): void
|
||||
@@ -15,8 +26,6 @@ const emit = defineEmits<{
|
||||
(el: 'click-branch'): void
|
||||
}>()
|
||||
|
||||
// 分支数量
|
||||
|
||||
const jsplumb = new Jsplumb('task-syllabus')
|
||||
|
||||
const handleScroll = () => {
|
||||
@@ -305,11 +314,23 @@ defineExpose({
|
||||
class="task-content-editor"
|
||||
size="small"
|
||||
/>
|
||||
<div class="flex justify-end gap-2">
|
||||
<el-button @click="saveEditing" type="primary" size="small" class="px-3">
|
||||
√
|
||||
</el-button>
|
||||
<el-button @click="cancelEditing" size="small" class="px-3"> × </el-button>
|
||||
<div class="flex justify-end">
|
||||
<svg-icon
|
||||
icon-class="Check"
|
||||
size="20px"
|
||||
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>
|
||||
@@ -369,13 +390,8 @@ defineExpose({
|
||||
</el-card>
|
||||
</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>
|
||||
<BranchButton v-dev-only v-if="planReady" @click="openPlanModification" />
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
import Header from './components/Header.vue'
|
||||
import Main from './components/Main/index.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 agentsStore = useAgentsStore()
|
||||
// 初始化主题
|
||||
const initTheme = () => {
|
||||
const savedTheme = localStorage.getItem('theme')
|
||||
@@ -80,5 +84,27 @@ onMounted(() => {
|
||||
|
||||
<Header />
|
||||
<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>
|
||||
</template>
|
||||
|
||||
@@ -9,11 +9,14 @@ import 'virtual:svg-icons-register'
|
||||
import { initService } from '@/utils/request.ts'
|
||||
import { setupStore, useConfigStore } from '@/stores'
|
||||
import { setupDirective } from '@/ directive'
|
||||
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
async function init() {
|
||||
const configStore = useConfigStore()
|
||||
await configStore.initConfig()
|
||||
const app = createApp(App)
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component)
|
||||
}
|
||||
setupStore(app)
|
||||
setupDirective(app)
|
||||
initService()
|
||||
|
||||
@@ -10,3 +10,4 @@ export function setupStore(app: App<Element>) {
|
||||
|
||||
export * from './modules/agents.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 { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { getAgentMapIcon } from '@/layout/components/config'
|
||||
import { store } from '../index'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import type { IExecuteRawResponse } from '@/api'
|
||||
@@ -45,10 +45,44 @@ export interface IRawStepTask {
|
||||
InputObject_List?: string[]
|
||||
OutputObject?: string
|
||||
AgentSelection?: string[]
|
||||
Collaboration_Brief_FrontEnd: IRichText
|
||||
Collaboration_Brief_frontEnd: IRichText
|
||||
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 {
|
||||
'Initial Input Object'?: string[] | string
|
||||
'General Goal'?: string
|
||||
@@ -71,6 +105,95 @@ export const useAgentsStore = defineStore('agents', () => {
|
||||
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`, '')
|
||||
function setSearchValue(value: string) {
|
||||
@@ -134,6 +257,30 @@ export const useAgentsStore = defineStore('agents', () => {
|
||||
// 额外的产物列表
|
||||
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) {
|
||||
if (!outputObject.trim()) return false
|
||||
@@ -177,6 +324,32 @@ export const useAgentsStore = defineStore('agents', () => {
|
||||
addNewOutput,
|
||||
removeAdditionalOutput,
|
||||
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-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-border-separate: rgba(255,255,255,0.18);
|
||||
// url颜色
|
||||
--color-text-url: #2cd235;
|
||||
// model颜色
|
||||
--color-text-model: #00c8d2;
|
||||
|
||||
--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%
|
||||
// 结果卡片运行前颜色
|
||||
$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;
|
||||
agent-list-selected-bg: $agent-list-selected-bg;
|
||||
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) => {
|
||||
const record = {
|
||||
...item,
|
||||
Collaboration_Brief_FrontEnd: changeBrief(item),
|
||||
Collaboration_Brief_frontEnd: changeBrief(item),
|
||||
}
|
||||
return record
|
||||
})
|
||||
@@ -30,10 +30,10 @@ function changeBrief(task: IRawStepTask): IRichText {
|
||||
// 如果不存在AgentSelection直接返回
|
||||
const agents = task.AgentSelection ?? []
|
||||
if (agents.length === 0) {
|
||||
return task.Collaboration_Brief_FrontEnd
|
||||
return task.Collaboration_Brief_frontEnd
|
||||
}
|
||||
const data: IRichText['data'] = {};
|
||||
let indexOffset = 0;
|
||||
const data: IRichText['data'] = {}
|
||||
let indexOffset = 0
|
||||
|
||||
// 根据InputObject_List修改
|
||||
const inputs = task.InputObject_List ?? []
|
||||
@@ -41,63 +41,62 @@ function changeBrief(task: IRawStepTask): IRichText {
|
||||
data[(index + indexOffset).toString()] = {
|
||||
text,
|
||||
style: { background: '#ACDBA0' },
|
||||
};
|
||||
return `!<${index + indexOffset}>!`;
|
||||
});
|
||||
const inputSentence = nameJoin(inputPlaceHolders);
|
||||
indexOffset += inputs.length;
|
||||
}
|
||||
return `!<${index + indexOffset}>!`
|
||||
})
|
||||
const inputSentence = nameJoin(inputPlaceHolders)
|
||||
indexOffset += inputs.length
|
||||
|
||||
// 根据AgentSelection修改
|
||||
// 根据AgentSelection修改
|
||||
const namePlaceholders = agents.map((text, index) => {
|
||||
data[(index + indexOffset).toString()] = {
|
||||
text,
|
||||
style: { background: '#E5E5E5', boxShadow: '1px 1px 4px 1px #0003' },
|
||||
};
|
||||
return `!<${index + indexOffset}>!`;
|
||||
});
|
||||
const nameSentence = nameJoin(namePlaceholders);
|
||||
indexOffset += agents.length;
|
||||
}
|
||||
return `!<${index + indexOffset}>!`
|
||||
})
|
||||
const nameSentence = nameJoin(namePlaceholders)
|
||||
indexOffset += agents.length
|
||||
|
||||
|
||||
let actionSentence = task.TaskContent ?? '';
|
||||
let actionSentence = task.TaskContent ?? ''
|
||||
|
||||
// delete the last '.' of actionSentence
|
||||
if (actionSentence[actionSentence.length - 1] === '.') {
|
||||
actionSentence = actionSentence.slice(0, -1);
|
||||
actionSentence = actionSentence.slice(0, -1)
|
||||
}
|
||||
const actionIndex = indexOffset++;
|
||||
const actionIndex = indexOffset++
|
||||
|
||||
data[actionIndex.toString()] = {
|
||||
text: actionSentence,
|
||||
style: { background: '#DDD', border: '1.5px solid #ddd' },
|
||||
};
|
||||
}
|
||||
|
||||
let outputSentence = '';
|
||||
const output = task.OutputObject ?? '';
|
||||
let outputSentence = ''
|
||||
const output = task.OutputObject ?? ''
|
||||
if (output) {
|
||||
data[indexOffset.toString()] = {
|
||||
text: output,
|
||||
style: { background: '#FFCA8C' },
|
||||
};
|
||||
outputSentence = `得到 !<${indexOffset}>!`;
|
||||
}
|
||||
outputSentence = `得到 !<${indexOffset}>!`
|
||||
}
|
||||
|
||||
// Join them togeter
|
||||
let content = inputSentence;
|
||||
let content = inputSentence
|
||||
if (content) {
|
||||
content = `基于${content}, ${nameSentence} 执行任务 !<${actionIndex}>!`;
|
||||
content = `基于${content}, ${nameSentence} 执行任务 !<${actionIndex}>!`
|
||||
} else {
|
||||
content = `${nameSentence} 执行任务 !<${actionIndex}>!`;
|
||||
content = `${nameSentence} 执行任务 !<${actionIndex}>!`
|
||||
}
|
||||
if (outputSentence) {
|
||||
content = `${content}, ${outputSentence}.`;
|
||||
content = `${content}, ${outputSentence}.`
|
||||
} else {
|
||||
content = `${content}.`;
|
||||
content = `${content}.`
|
||||
}
|
||||
content = content.trim();
|
||||
content = content.trim()
|
||||
|
||||
return {
|
||||
template: content,
|
||||
data,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user