zhaoweijie 77530c49f8 feat(agent): 支持自定义API配置并优化UI交互
- 为agent.json添加apiUrl、apiKey、apiModel字段支持
- 更新API接口类型定义,支持传递自定义API配置
- 优化AgentRepoList组件UI样式和交互效果
- 增强JSON文件上传校验逻辑,支持API配置验证
- 改进任务结果页面布局和视觉呈现
- 添加任务过程查看抽屉功能
- 实现执行按钮动态样式和悬停效果
- 优化节点连接线渲染逻辑和性能
2025-12-15 20:46:54 +08:00

534 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import SvgIcon from '@/components/SvgIcon/index.vue'
import { getAgentMapIcon } from '@/layout/components/config.ts'
import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/utils.ts'
import { type IRawStepTask, useAgentsStore } from '@/stores'
import { computed, ref } from 'vue'
import { AnchorLocations } from '@jsplumb/browser-ui'
import MultiLineTooltip from '@/components/MultiLineTooltip/index.vue'
import Bg from './Bg.vue'
const emit = defineEmits<{
(el: 'resetAgentRepoLine'): void
(el: 'setCurrentTask', task: IRawStepTask): void
}>()
const jsplumb = new Jsplumb('task-syllabus')
const handleScroll = () => {
emit('resetAgentRepoLine')
}
const agentsStore = useAgentsStore()
const collaborationProcess = computed(() => {
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
})
// 编辑状态管理
const editingTaskId = ref<string | null>(null)
const editingContent = ref('')
// 添加新产物状态管理
const showAddOutputForm = ref(false)
const newOutputName = ref('')
// 开始编辑
const startEditing = (task: IRawStepTask) => {
if (!task.Id) {
console.warn('Task ID is missing, cannot start editing')
return
}
editingTaskId.value = task.Id
editingContent.value = task.TaskContent || ''
}
// 保存编辑
const saveEditing = () => {
if (editingTaskId.value && editingContent.value.trim()) {
const taskToUpdate = collaborationProcess.value.find(item => item.Id === editingTaskId.value)
if (taskToUpdate) {
taskToUpdate.TaskContent = editingContent.value.trim()
}
}
editingTaskId.value = null
editingContent.value = ''
}
// 取消编辑
const cancelEditing = () => {
editingTaskId.value = null
editingContent.value = ''
}
// 处理键盘事件
const handleKeydown = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
event.preventDefault()
saveEditing()
} else if (event.key === 'Escape') {
cancelEditing()
}
}
// 显示添加产物表单
const showAddOutputDialog = () => {
showAddOutputForm.value = true
newOutputName.value = ''
}
// 添加新产物
const addNewOutput = () => {
if (newOutputName.value.trim()) {
const success = agentsStore.addNewOutput(newOutputName.value.trim())
if (success) {
showAddOutputForm.value = false
newOutputName.value = ''
}
}
}
// 取消添加产物
const cancelAddOutput = () => {
showAddOutputForm.value = false
newOutputName.value = ''
}
// 处理添加产物的键盘事件
const handleAddOutputKeydown = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
event.preventDefault()
addNewOutput()
} else if (event.key === 'Escape') {
cancelAddOutput()
}
}
// 删除额外产物
const removeAdditionalOutput = (output: string) => {
agentsStore.removeAdditionalOutput(output)
}
function handleCurrentTask(task: IRawStepTask, transparent: boolean): ConnectArg[] {
// 创建当前流程与产出的连线
const arr: ConnectArg[] = [
{
sourceId: `task-syllabus-flow-${task.Id}`,
targetId: `task-syllabus-output-object-${task.Id}`,
anchor: [AnchorLocations.Right, AnchorLocations.Left],
config: {
transparent
}
}
]
// 创建当前产出与流程的连线
task.InputObject_List?.forEach(item => {
const id = collaborationProcess.value.find(i => i.OutputObject === item)?.Id
if (id) {
arr.push({
sourceId: `task-syllabus-output-object-${id}`,
targetId: `task-syllabus-flow-${task.Id}`,
anchor: [AnchorLocations.Left, AnchorLocations.Right],
config: {
type: 'output',
transparent
}
})
}
})
return arr
}
function changeTask(task?: IRawStepTask, isEmit?: boolean) {
jsplumb.reset()
const arr: ConnectArg[] = []
agentsStore.agentRawPlan.data?.['Collaboration Process']?.forEach(item => {
arr.push(...handleCurrentTask(item, item.Id !== task?.Id))
})
jsplumb.connects(arr)
if (isEmit && task) {
emit('setCurrentTask', task)
}
}
function clear() {
jsplumb.reset()
}
defineExpose({
changeTask,
clear
})
</script>
<template>
<div class="h-full flex flex-col">
<div class="text-[18px] font-bold mb-[18px] text-[var(--color-text-title-header)]">
任务大纲
</div>
<div
v-loading="agentsStore.agentRawPlan.loading"
class="flex-1 w-full overflow-y-auto relative"
@scroll="handleScroll"
>
<div
v-show="collaborationProcess.length > 0"
class="w-full relative min-h-full"
id="task-syllabus"
>
<Bg />
<div class="w-full flex items-center gap-[14%] mb-[35px]">
<div class="flex-1 flex justify-center">
<div
class="card-item w-[168px] h-[41px] flex justify-center relative z-99 items-center rounded-[20px] bg-[var(--color-bg-flow)]"
>
流程
</div>
</div>
<div class="flex-1 flex justify-center">
<div
class="card-item w-[168px] h-[41px] flex justify-center relative z-99 items-center rounded-[20px] bg-[var(--color-bg-flow)]"
>
产物
</div>
</div>
</div>
<div
v-for="item in collaborationProcess"
:key="item.Id"
class="card-it w-full flex items-center gap-[14%] bg-[var(--color-card-bg)]"
>
<!-- 流程卡片 -->
<el-card
class="w-[43%] overflow-y-auto relative z-99 task-syllabus-flow-card"
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
:shadow="true"
:id="`task-syllabus-flow-${item.Id}`"
@click="changeTask(item, true)"
>
<MultiLineTooltip placement="right" :text="item.StepName" :lines="2">
<div class="text-[18px] font-bold text-center">{{ item.StepName }}</div>
</MultiLineTooltip>
<div class="h-[1px] w-full bg-[#d6d6d6] my-[8px]"></div>
<!-- 任务内容区域 - 支持双击编辑 -->
<div v-if="editingTaskId === item.Id" class="w-full">
<div class="flex flex-col gap-3">
<el-input
v-model="editingContent"
type="textarea"
:autosize="{ minRows: 2, maxRows: 4 }"
placeholder="请输入任务内容"
@keydown="handleKeydown"
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>
</div>
</div>
<div v-else @dblclick="startEditing(item)" class="w-full cursor-pointer">
<MultiLineTooltip placement="right" :text="item.TaskContent" :lines="3">
<div class="text-[14px] text-[var(--color-text-secondary)] task-content-display">
{{ item.TaskContent }}
</div>
</MultiLineTooltip>
</div>
<div class="h-[1px] w-full bg-[#d6d6d6] my-[8px]"></div>
<div
class="flex items-center gap-2 overflow-y-auto flex-wrap relative w-full max-h-[72px]"
>
<!-- 连接到智能体库的连接点 -->
<div
class="absolute left-[-10px] top-1/2 transform -translate-y-1/2"
:id="`task-syllabus-flow-agents-${item.Id}`"
></div>
<el-tooltip
v-for="agentSelection in item.AgentSelection"
:key="agentSelection"
effect="light"
placement="right"
>
<template #content>
<div class="w-[150px]">
<div class="text-[18px] font-bold">{{ agentSelection }}</div>
<div class="h-[1px] w-full bg-[#494B51] my-[8px]"></div>
<div>
{{ item.TaskProcess.find(i => i.AgentName === agentSelection)?.Description }}
</div>
</div>
</template>
<div
class="w-[31px] h-[31px] rounded-full flex items-center justify-center"
:style="{ background: getAgentMapIcon(agentSelection).color }"
>
<svg-icon
:icon-class="getAgentMapIcon(agentSelection).icon"
color="#fff"
size="24px"
/>
</div>
</el-tooltip>
</div>
</el-card>
<!-- 产物卡片 -->
<el-card
class="w-[43%] relative task-syllabus-output-object-card"
:shadow="true"
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
:id="`task-syllabus-output-object-${item.Id}`"
>
<div class="text-[18px] font-bold text-center">{{ item.OutputObject }}</div>
</el-card>
</div>
<!-- 额外的产物列表 -->
<!-- <div
v-for="(output, index) in agentsStore.additionalOutputs"
:key="`additional-${index}`"
class="card-item w-full flex items-center gap-[14%]"
> -->
<!-- 空的流程卡片位置 -->
<!-- <div class="w-[43%]"></div> -->
<!-- 额外产物卡片 -->
<!-- <el-card class="w-[43%] relative additional-output-card group" :shadow="false">
<div class="flex items-center justify-between">
<div class="text-[18px] font-bold text-center flex-1">{{ output }}</div>
<button
@click="removeAdditionalOutput(output)"
class="opacity-0 group-hover:opacity-100 text-red-500 hover:text-red-700 transition-all duration-200 ml-2 text-lg font-bold w-5 h-5 flex items-center justify-center rounded hover:bg-red-50"
>
×
</button>
</div>
</el-card>
</div> -->
<!-- 添加新产物按钮 -->
<!-- <div class="card-item w-full flex items-center gap-[14%] mt-[20px]"> -->
<!-- 空的流程卡片位置 -->
<!-- <div class="w-[43%]"></div> -->
<!-- 添加新产物按钮 -->
<!-- <div class="w-[43%] relative"> -->
<!-- <div v-if="!showAddOutputForm" class="add-output-btn">
<button
@click="showAddOutputDialog"
class="w-full h-[50px] border-2 border-dashed border-gray-300 rounded-lg flex items-center justify-center text-gray-500 hover:border-blue-500 hover:text-blue-500 transition-all duration-200 group"
>
<svg-icon icon-class="plus" size="20px" class="mr-2" />
<span class="text-sm font-medium"></span>
</button>
</div> -->
<!-- 添加产物表单 -->
<!-- <div v-else class="add-output-form">
<el-card class="w-full" shadow="hover">
<div class="p-3">
<el-input
v-model="newOutputName"
placeholder="输入产物名称"
size="small"
@keydown="handleAddOutputKeydown"
@blur="addNewOutput"
class="w-full mb-3"
/>
<div class="flex gap-2">
<el-button @click="addNewOutput" type="primary" size="small" class="flex-1">
确定
</el-button>
<el-button @click="cancelAddOutput" size="small" class="flex-1">
取消
</el-button>
</div>
</div>
</el-card>
</div> -->
<!-- </div> -->
<!-- </div> -->
<!-- <div>
<el-button type="info" size="medium" class="flex-1"> branch </el-button>
</div> -->
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.task-syllabus-flow-card {
background-color: var(--color-card-bg-task);
border: 1px solid var(--color-card-border-task);
box-sizing: border-box;
transition: border-color 0.2s ease;
// box-shadow: var(--color-card-shadow);
margin-bottom: 100px;
&:hover {
background-color: var(--color-card-bg-task-hover);
border-color: var(--color-card-border-hover);
box-shadow: var(--color-card-shadow-hover);
}
:deep(.el-card__body) {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: auto;
}
}
.task-syllabus-output-object-card {
background-color: var(--color-card-bg-task);
border: 1px solid var(--color-card-border-task);
box-sizing: border-box;
transition: border-color 0.2s ease;
// box-shadow: var(--color-card-shadow-hover);
&:hover {
background-color: var(--color-card-bg-task-hover);
border-color: var(--color-card-border-hover);
box-shadow: var(--color-card-shadow-hover);
}
:deep(.el-card__body) {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: auto;
}
}
.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-content-display {
min-height: 40px;
word-break: break-word;
white-space: pre-wrap;
}
.add-output-btn {
opacity: 0.8;
transition: opacity 0.2s ease;
&:hover {
opacity: 1;
}
button {
background: transparent;
cursor: pointer;
&:hover {
background-color: rgba(59, 130, 246, 0.05);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
&:active {
transform: translateY(0);
}
}
}
.add-output-form {
animation: slideDown 0.3s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
// 添加产物表单样式
:deep(.el-card__body) {
padding: 16px;
}
// 输入框样式
:deep(.el-input__wrapper) {
background: transparent;
border: 1px solid #dcdfe6;
border-radius: 4px;
box-shadow: none;
transition: all 0.2s ease;
&:hover {
border-color: #c0c4cc;
}
&.is-focus {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
}
:deep(.el-input__inner) {
color: var(--color-text-primary);
font-size: 14px;
background: transparent;
}
// 任务内容编辑按钮样式 - 匹配执行结果编辑按钮样式
.task-content-editor {
.el-button {
font-weight: bold;
font-size: 16px;
border-radius: 4px;
&.el-button--small {
padding: 4px 12px;
}
}
// 在深色模式下,按钮背景会自动适配为深色
html.dark & {
.el-button {
&.el-button--primary {
background-color: var(--color-bg-detail);
border-color: var(--color-border);
color: var(--color-text);
&:hover {
background-color: var(--color-bg-hover);
border-color: var(--color-text-hover);
}
}
&:not(.el-button--primary) {
background-color: var(--color-bg-detail);
border-color: var(--color-border);
color: var(--color-text);
&:hover {
background-color: var(--color-bg-hover);
border-color: var(--color-text-hover);
}
}
}
}
}
</style>