feat:三个浮动窗口功能新增

This commit is contained in:
liailing1026
2025-12-31 19:04:58 +08:00
parent d42554ce03
commit 5847365eee
34 changed files with 7471 additions and 647 deletions

View File

@@ -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;
}
}
}

View File

@@ -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>

View File

@@ -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
}

View File

@@ -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: [],
},
}

View File

@@ -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: ['架构师', '后端工程师', '前端工程师', '测试工程师'],
}