feat:rename subtree from frontend-vue to frontend

This commit is contained in:
zhaoweijie
2025-11-20 09:56:51 +08:00
parent 1aa9e280b0
commit ab8c9e294d
80 changed files with 0 additions and 0 deletions

101
frontend/src/App.vue Normal file
View File

@@ -0,0 +1,101 @@
<script setup lang="ts">
import { onMounted } from 'vue'
import Layout from './layout/index.vue'
onMounted(() => {
document.documentElement.classList.add('dark')
})
</script>
<template>
<layout />
</template>
<style lang="scss">
#app {
background-color: var(--color-bg);
color: var(--color-text);
}
.jtk-endpoint {
z-index: 100;
}
.card-item + .card-item {
margin-top: 35px;
}
.el-card {
border-radius: 8px;
background: var(--color-bg-tertiary);
border: 2px solid var(--color-bg-tertiary);
&:hover {
background: #171B22;
box-shadow: none !important;
transition: background-color 0.3s ease-in-out;
}
.el-card__body {
padding: 10px;
}
}
:root {
--gradient: linear-gradient(to right, #0093eb, #00d2d1);
}
#task-template {
.active-card {
border: 2px solid transparent;
$bg: var(--el-input-bg-color, var(--el-fill-color-blank));
background:
linear-gradient(var(--color-bg-tertiary), var(--color-bg-tertiary)) padding-box,
linear-gradient(to right, #00c8d2, #315ab4) border-box;
color: var(--color-text);
}
}
/* 1. 定义流动动画:让虚线沿路径移动 */
@keyframes flowAnimation {
to {
stroke-dashoffset: 8; /* 与stroke-dasharray总和一致 */
}
}
@keyframes flowAnimationReverse {
to {
stroke-dashoffset: -8; /* 与stroke-dasharray总和一致 */
}
}
/* 2. 为jsPlumb连线绑定动画作用于SVG的path元素 */
/* jtk-connector是jsPlumb连线的默认SVG类path是实际的线条元素 */
.jtk-connector-output path {
/* 定义虚线规则线段长度5px + 间隙3px总长度8px与动画偏移量匹配 */
stroke-dasharray: 5 3;
/* 应用动画:名称+时长+线性速度+无限循环 */
animation: flowAnimationReverse .5s linear infinite;
/* 可选:设置线条基础样式(颜色、宽度) */
stroke-width: 2;
}
/* 2. 为jsPlumb连线绑定动画作用于SVG的path元素 */
/* jtk-connector是jsPlumb连线的默认SVG类path是实际的线条元素 */
.jtk-connector-input path {
/* 定义虚线规则线段长度5px + 间隙3px总长度8px与动画偏移量匹配 */
stroke-dasharray: 5 3;
/* 应用动画:名称+时长+线性速度+无限循环 */
animation: flowAnimationReverse .5s linear infinite;
/* 可选:设置线条基础样式(颜色、宽度) */
stroke-width: 2;
}
/* 可选: hover时增强动画效果如加速、变色 */
.jtk-connector path:hover {
animation-duration: 0.5s; /* hover时流动加速 */
}
</style>

View File

@@ -0,0 +1,11 @@
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import App from '../App.vue'
describe('App', () => {
it('mounts renders properly', () => {
const wrapper = mount(App)
expect(wrapper.text()).toContain('You did it!')
})
})

74
frontend/src/api/index.ts Normal file
View File

@@ -0,0 +1,74 @@
import request from '@/utils/request'
import type { Agent, IRawPlanResponse } from '@/stores'
export interface ActionHistory {
ID: string
ActionType: string
AgentName: string
Description: string
ImportantInput: string[]
Action_Result: string
}
export type IExecuteRawResponse = {
LogNodeType: string
NodeId: string
InputName_List?: string[] | null
OutputName?: string
content?: string
ActionHistory: ActionHistory[]
}
class Api {
// 智能体信息
setAgents = (data: Pick<Agent, 'Name' | 'Profile'>[]) => {
return request({
url: '/setAgents',
data,
method: 'POST',
})
}
generateBasePlan = (data: { goal: string; inputs: string[] }) => {
return request<unknown, IRawPlanResponse>({
url: '/generate_basePlan',
method: 'POST',
data: {
'General Goal': data.goal,
'Initial Input Object': data.inputs,
},
})
}
executePlan = (plan: IRawPlanResponse) => {
return request<unknown, IExecuteRawResponse[]>({
url: '/executePlan',
method: 'POST',
data: {
RehearsalLog: [],
num_StepToRun: null,
plan: {
'Initial Input Object': plan['Initial Input Object'],
'General Goal': plan['General Goal'],
'Collaboration Process': plan['Collaboration Process']?.map(step => ({
StepName: step.StepName,
TaskContent: step.TaskContent,
InputObject_List: step.InputObject_List,
OutputObject: step.OutputObject,
AgentSelection: step.AgentSelection,
Collaboration_Brief_frontEnd: step.Collaboration_Brief_FrontEnd,
TaskProcess: step.TaskProcess.map(action => ({
ActionType: action.ActionType,
AgentName: action.AgentName,
Description: action.Description,
ID: action.ID,
ImportantInput: action.ImportantInput,
})),
})),
},
},
})
}
}
export default new Api()

View 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="1761736278335" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5885" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M226.592 896C167.616 896 128 850.48 128 782.736V241.264C128 173.52 167.616 128 226.592 128c20.176 0 41.136 5.536 62.288 16.464l542.864 280.432C887.648 453.792 896 491.872 896 512s-8.352 58.208-64.272 87.088L288.864 879.536C267.712 890.464 246.768 896 226.592 896z m0-704.304c-31.008 0-34.368 34.656-34.368 49.568v541.472c0 14.896 3.344 49.568 34.368 49.568 9.6 0 20.88-3.2 32.608-9.248l542.864-280.432c21.904-11.328 29.712-23.232 29.712-30.608s-7.808-19.28-29.712-30.592L259.2 200.96c-11.728-6.048-23.008-9.264-32.608-9.264z" p-id="5886"></path></svg>

After

Width:  |  Height:  |  Size: 886 B

View 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="1761212882014" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3052" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M988.8 939.2c0-134.4-78.4-252.8-193.6-308.8 4.8 12.8 8 25.6 8 40v11.2c54.4 11.2 96 60.8 96 118.4v65.6c8 6.4 14.4 17.6 14.4 30.4 0 22.4-17.6 38.4-38.4 38.4-22.4 0-38.4-17.6-38.4-38.4 0-12.8 6.4-24 16-32v-67.2c0-40-32-72-72-72s-72 32-72 72v68.8c9.6 6.4 14.4 17.6 14.4 30.4 0 22.4-17.6 38.4-38.4 38.4-22.4 0-38.4-17.6-38.4-38.4 0-11.2 4.8-22.4 14.4-30.4V800c0-59.2 43.2-108.8 100.8-120-1.6-28.8-14.4-56-32-76.8-19.2-4.8-38.4-8-57.6-8-9.6 216-75.2 384-156.8 384-80 0-147.2-168-156.8-384h-1.6c-19.2 22.4-32 52.8-32 84.8v99.2c27.2 9.6 48 36.8 48 68.8 0 40-32 73.6-73.6 73.6s-73.6-32-73.6-73.6c0-33.6 22.4-60.8 52.8-70.4v-118.4c0-19.2 6.4-38.4 16-54.4C145.6 644.8 35.2 779.2 35.2 939.2V953.6C35.2 992 249.6 1024 512 1024s476.8-32 476.8-72v-3.2-9.6z m-470.4-720C400 219.2 304 256 304 300.8v4.8l44.8 212.8c0 3.2 1.6 8 1.6 11.2 19.2 75.2 86.4 129.6 166.4 129.6 84.8 0 156.8-64 169.6-145.6l44.8-208v-4.8c0-44.8-96-81.6-212.8-81.6z m-1.6-16c105.6 0 193.6 35.2 225.6 83.2h9.6V83.2v-1.6C752 36.8 646.4 0 518.4 0S284.8 36.8 284.8 81.6V288h6.4c30.4-49.6 120-84.8 225.6-84.8z m-65.6-105.6c0-8 6.4-16 16-16h33.6V46.4c0-8 6.4-16 16-16 8 0 16 6.4 16 16V80h33.6c8 0 16 6.4 16 16s-6.4 16-16 16h-33.6v33.6c0 8-6.4 16-16 16-8 0-16-6.4-16-16v-32h-33.6c-8 0-16-8-16-16z" p-id="3053"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View 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="1761212791945" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2409" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M338.36032 362.9056c9.59488 59.4944 34.53952 115.1488 74.84416 161.20832l15.34976 13.43488c1.23904 2.47808 3.2768 4.15744 5.60128 6.07232 1.26976 1.04448 2.63168 2.16064 3.9936 3.52256 42.21952 38.37952 107.4688 38.37952 147.7632-1.92512 53.73952-47.9744 80.60928-113.22368 95.95904-182.31296H338.37056z m366.5408-63.32416h26.86976c7.68 0 13.43488 5.75488 9.59488 11.50976v15.36H275.02592v-15.36c0-7.68 5.75488-13.43488 13.43488-13.43488h28.78464c1.09568-7.63904 2.02752-14.97088 2.93888-22.12864 2.304-17.99168 4.4544-34.8672 8.58112-52.71552 5.75488-34.53952 21.10464-69.08928 42.21952-95.9488 21.10464-24.95488 49.88928-44.1344 82.51392-49.89952 40.30464-9.59488 82.5344-9.59488 120.90368 1.91488 47.98464 11.52 86.36416 46.05952 103.6288 92.11904 13.44512 36.46464 23.04 72.92928 24.95488 111.3088v7.68c1.91488 3.84 1.91488 5.75488 1.91488 9.59488z m-241.80736-86.36416c7.68-1.91488 13.43488-9.59488 13.43488-17.26464V132.608c0-7.68-7.68-15.36-15.34976-15.36-7.68 0-15.36 7.68-15.36 15.36v65.24928c0 7.68 7.68 15.36 15.36 15.36h1.91488z m99.79904 0c7.68-1.91488 13.43488-9.59488 13.43488-17.26464V132.608c0-7.68-7.68-15.36-15.36-15.36s-15.34976 7.68-15.34976 15.36v65.24928c0 7.68 7.68 15.36 15.36 15.36h1.91488z m351.16032 596.8384c5.8368 20.5824 11.66336 41.08288 17.3056 61.42976 4.1984 19.56864 9.41056 37.09952 14.8992 55.57248 2.048 6.88128 4.12672 13.89568 6.21568 21.1968 1.91488 3.82976 0 5.75488-3.84 5.75488H73.5232c-1.91488 0-3.84-3.84-1.91488-5.76512 3.82976-13.43488 7.68-25.9072 11.50976-38.37952 3.84-12.47232 7.68-24.94464 11.52-38.37952 12.1856-48.7424 26.27584-98.44736 40.26368-147.75296 5.8368-20.5824 11.65312-41.08288 17.3056-61.44 1.91488-7.66976 3.84-9.58464 11.50976-11.50976 18.2272-3.82976 35.98336-8.15104 53.73952-12.47232 17.75616-4.31104 35.50208-8.63232 53.73952-12.47232 3.82976-1.91488 5.75488 0 5.75488 3.84v163.1232c0 1.91488 1.91488 3.84 3.84 3.84h47.9744a4.12672 4.12672 0 0 0 3.84-3.84V612.4032c2.2528 0 3.1744-0.65536 3.95264-1.19808 0.54272-0.38912 1.00352-0.7168 1.80224-0.7168 23.02976-3.84 46.05952-17.27488 59.4944-38.37952 1.91488-3.84 5.75488-7.68 9.59488-11.52 1.91488-1.91488 5.75488-1.91488 7.68 0 15.34976 15.36 28.7744 30.69952 42.20928 46.05952 13.43488 17.27488 34.54976 26.86976 55.6544 24.94464 21.11488 1.92512 42.22976-7.68 55.6544-24.94464 13.43488-15.36 26.86976-30.70976 42.22976-46.05952 1.91488-1.91488 5.75488-1.91488 7.68 0 1.91488 3.84 5.74464 7.68 9.58464 11.52 13.43488 21.10464 34.54976 34.53952 59.4944 38.37952 1.92512 0 3.84 0 5.75488 1.91488v180.39808c0 1.91488 1.92512 3.84 3.84 3.84h47.9744a4.12672 4.12672 0 0 0 3.84-3.84v-163.1232c0-1.92512 3.84-3.84 5.76512-3.84 36.4544 9.59488 71.00416 17.27488 107.4688 24.94464a17.3056 17.3056 0 0 1 11.50976 11.52c12.1856 48.7424 26.28608 98.44736 40.26368 147.75296z" p-id="2410"></path></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View 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="1761626283461" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2759" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M511.882596 287.998081h-0.361244a31.998984 31.998984 0 0 1-31.659415-31.977309v-0.361244c0-0.104761 0.115598-11.722364 0.115598-63.658399V96.000564a31.998984 31.998984 0 1 1 64.001581 0V192.001129c0 52.586273-0.111986 63.88237-0.119211 64.337537a32.002596 32.002596 0 0 1-31.977309 31.659415zM511.998194 959.99842a31.998984 31.998984 0 0 1-31.998984-31.998984v-96.379871c0-51.610915-0.111986-63.174332-0.115598-63.286318s0-0.242033 0-0.361243a31.998984 31.998984 0 0 1 63.997968-0.314283c0 0.455167 0.11921 11.711527 0.11921 64.034093v96.307622a31.998984 31.998984 0 0 1-32.002596 31.998984zM330.899406 363.021212a31.897836 31.897836 0 0 1-22.866739-9.612699c-0.075861-0.075861-8.207461-8.370021-44.931515-45.094076L195.198137 240.429485a31.998984 31.998984 0 0 1 45.256635-45.253022L308.336112 263.057803c37.182834 37.182834 45.090463 45.253022 45.41197 45.578141A31.998984 31.998984 0 0 1 330.899406 363.021212zM806.137421 838.11473a31.901448 31.901448 0 0 1-22.628318-9.374279L715.624151 760.859111c-36.724054-36.724054-45.018214-44.859267-45.097687-44.93874a31.998984 31.998984 0 0 1 44.77618-45.729864c0.32512 0.317895 8.395308 8.229136 45.578142 45.411969l67.88134 67.88134a31.998984 31.998984 0 0 1-22.624705 54.630914zM224.000113 838.11473a31.901448 31.901448 0 0 0 22.628317-9.374279l67.88134-67.88134c36.724054-36.724054 45.021826-44.859267 45.097688-44.93874a31.998984 31.998984 0 0 0-44.776181-45.729864c-0.32512 0.317895-8.395308 8.229136-45.578142 45.411969l-67.88134 67.884953a31.998984 31.998984 0 0 0 22.628318 54.627301zM255.948523 544.058589h-0.361244c-0.104761 0-11.722364-0.115598-63.658399-0.115598H95.942765a31.998984 31.998984 0 1 1 0-64.00158h95.996952c52.586273 0 63.88237 0.111986 64.337538 0.11921a31.998984 31.998984 0 0 1 31.659414 31.97731v0.361244a32.002596 32.002596 0 0 1-31.988146 31.659414zM767.939492 544.058589a32.002596 32.002596 0 0 1-31.995372-31.666639v-0.361244a31.998984 31.998984 0 0 1 31.659415-31.970085c0.455167 0 11.754876-0.11921 64.34115-0.11921h96.000564a31.998984 31.998984 0 0 1 0 64.00158H831.944685c-51.936034 0-63.553638 0.111986-63.665624 0.115598h-0.335957zM692.999446 363.0176a31.998984 31.998984 0 0 1-22.863126-54.381656c0.317895-0.32512 8.229136-8.395308 45.41197-45.578141l67.88134-67.884953A31.998984 31.998984 0 1 1 828.693489 240.429485l-67.892177 67.88134c-31.020013 31.023625-41.644196 41.759794-44.241539 44.393262l-0.697201 0.722488a31.908673 31.908673 0 0 1-22.863126 9.591025z" fill="" p-id="2760"></path></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View 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="1761212815712" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2569" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M845.285053 1008.842105H520.421053v-52.224c45.999158-24.468211 96.538947-287.420632 134.03621-286.302316 145.960421 30.100211 249.317053 113.057684 249.317053 270.928843-1.684211 41.472-21.005474 67.597474-58.489263 67.597473z m0 0" p-id="2570"></path><path d="M719.764211 480.619789c-32.363789 107.344842-116.439579 183.457684-214.662737 183.457685-98.829474 0-182.905263-76.665263-214.703158-184.010106-14.767158-6.831158-32.943158-22.150737-39.760842-61.291789-8.528842-51.186526 22.703158-63.690105 22.703158-63.690105 2.290526 0 4.554105 1.751579 6.817684 5.133473a95.137684 95.137684 0 0 1 24.427789-36.917894c40.313263-39.774316 114.714947-60.779789 204.463158-61.359158 81.232842-0.565895 138.024421 5.133474 195.961263 60.240842 10.213053 9.633684 19.887158 24.939789 24.980211 40.326737 2.842947-4.554105 5.685895-7.424 8.528842-6.817685 0 0 31.797895 12.476632 23.282526 63.609264-7.949474 39.733895-26.691368 54.501053-42.037894 61.318736z m0 0" p-id="2571"></path><path d="M496.559158 15.454316c-126.059789 0-231.141053 64.794947-231.141053 64.794947v215.794526s69.308632-78.942316 237.406316-78.942315c166.979368 0 237.406316 79.508211 237.406316 79.50821V80.801684c-0.579368-0.552421-122.677895-65.347368-243.671579-65.347368z m71.545263 117.005473h-52.237474v52.237474h-27.823158V132.459789h-51.685052V104.084211h51.685052V51.846737h27.823158v52.237474h52.237474v28.375578zM497.125053 956.618105v52.224c-141.433263 0-240.249263-0.538947-324.877474 0-36.352 0-56.212211-26.125474-58.489263-67.058526 0-157.884632 103.356632-241.367579 249.330526-270.901895 38.642526-1.684211 88.616421 261.268211 134.036211 285.736421z" p-id="2572"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View 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="1761204835005" class="icon" viewBox="0 0 1171 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5692" xmlns:xlink="http://www.w3.org/1999/xlink" width="228.7109375" height="200"><path d="M502.237757 1024 644.426501 829.679301 502.237757 788.716444 502.237757 1024 502.237757 1024ZM0 566.713817 403.967637 689.088066 901.485385 266.66003 515.916344 721.68034 947.825442 855.099648 1170.285714 0 0 566.713817 0 566.713817Z" p-id="5693"></path></svg>

After

Width:  |  Height:  |  Size: 603 B

View 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="1761211358508" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6686" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M469.333333 469.333333V170.666667h85.333334v298.666666h298.666666v85.333334h-298.666666v298.666666h-85.333334v-298.666666H170.666667v-85.333334h298.666666z" p-id="6687"></path></svg>

After

Width:  |  Height:  |  Size: 516 B

View 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="1761368152518" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7696" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M835.516 709.837l154.351-225.894h-109.158c-14.541-221.047-198.11-396.151-422.844-396.151-234.291 0-423.731 189.918-423.731 424.277 0 234.155 189.918 424.141 424.209 424.141 105.062 0 200.977-38.434 275.115-101.786l-56.73-73.045c-58.368 51.063-134.69 82.398-218.385 82.398-183.296 0-331.844-148.617-331.844-331.708 0-183.364 148.617-331.913 331.844-331.913 173.739 0 315.665 133.734 329.933 303.787h-107.11l154.351 225.894z" p-id="7697"></path></svg>

After

Width:  |  Height:  |  Size: 783 B

View 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="1761545116983" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1453" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M856.98894 727.680806c-64.797424-20.50007-128.161277-40.856783-169.448131-61.356852-28.814784-14.335713-46.877782-28.671427-46.877782-43.00714v-34.405712c10.034999-8.171357 19.209856-17.202856 27.381212-27.237855 2.293714-2.867143 4.587428-5.734285 6.881142-8.888143 10.034999-13.905642 20.213356-32.972141 29.101498-51.895282 9.891642-21.073499 18.349713-41.716926 23.223856-54.47571 4.730785-5.590928 8.888142-11.181856 12.758785-16.629428 22.363713-32.111998 30.104998-62.647067 23.223855-91.175136-3.870643-16.48607-12.902142-31.395212-25.804284-42.433712 5.0175-40.570069 4.730785-86.444351-10.034999-126.727705-7.597928-21.216856-18.349713-41.143497-31.968641-59.063139-20.643427-26.807784-47.451211-47.307854-80.279994-61.070138-42.00364-17.632927-81.856923-19.639927-97.196136-19.639927h-10.608428c-15.195856 0-55.049139 1.863643-97.196136 19.639927-35.122498 14.765785-63.363853 37.129497-84.580708 66.947781-11.611928 16.48607-20.930141 34.405712-27.667927 53.328853-14.765785 40.283354-15.052499 86.300994-10.034999 126.727706-12.758785 11.038499-21.790284 25.947641-25.804284 42.433711-6.737785 27.954641 0.716786 57.916282 22.076998 89.454851 4.157357 6.164357 8.888142 12.185356 14.048999 18.49307 5.0175 12.902142 13.332213 33.545569 23.223856 54.762425 8.888142 18.923142 18.923142 37.846283 28.958141 51.608568 2.150357 3.0105 4.444071 5.877642 6.737785 8.744785 7.741285 9.461571 16.48607 18.206356 25.947641 25.947641v35.695926c0 14.47907-18.49307 29.101498-48.02464 43.580568-41.286854 20.213356-104.220636 40.570069-168.301273 60.783425C56.626067 762.51659 56.626067 946.157077 56.626067 946.157077s134.038919 48.884782 457.165897 48.884782S966.943861 946.157077 966.943861 946.157077s0.286714-183.783844-109.954921-218.476271zM351.224976 414.875542c-46.877782-51.321854-28.958141-72.251995-18.349714-76.265994 17.48957-6.594428 29.388212 19.49657 33.54557 31.108498-4.874143-15.052499-18.062999-59.636567-20.213356-106.51435 15.769285-22.076998 47.164497-58.919782 84.580708-64.797424 0 0 80.710066 96.47935 246.574269 94.47235-3.727285 26.090998-9.604928 51.895282-17.632928 76.982781 4.300714-11.611928 16.055999-37.702926 33.545569-31.108498 10.608428 4.014 28.528069 25.087498-18.349713 76.265995 0 0-17.346213 47.164497-35.552568 80.99678-3.870643 7.454571-8.171357 14.47907-12.902142 21.360212-12.041999 16.48607-27.667927 29.818284-45.874283 38.993141-20.50007 10.465071-43.293854 15.912642-66.230995 16.055998-0.430071 0-0.860143 0-1.146857-0.143357-0.430071 0-0.860143 0.143357-1.146857 0.143357-23.653927-0.143357-47.02114-6.021-68.094639-17.059498a129.42282 129.42282 0 0 1-44.010639-38.132998c-4.730785-6.737785-9.031499-13.762285-12.902142-21.073498-18.349713-33.832283-35.839283-81.283494-35.839283-81.283495z m170.881702 519.52625c-5.734285 3.727285-9.174857 5.447571-9.174857 5.447571s-3.440571-1.863643-9.174856-5.447571c-31.968641-20.069999-137.336133-94.615708-151.815204-208.584628 34.405712-15.48257 91.891922-46.447711 91.891922-102.50035v-2.293714c21.933641 7.454571 44.870783 11.468571 67.951281 11.755284h2.293714c22.50707-0.143357 44.870783-4.014 66.230996-11.181856v1.720286c0 57.056139 59.49321 88.164637 93.612208 103.360492-14.909142 113.252135-119.98992 187.654487-151.815204 207.724486z" p-id="1454"></path><path d="M574.432031 693.418452l-28.097998-30.53507h-66.804424L451.574969 693.418452s16.055999 34.118998 42.003639 47.451211l-42.003639 106.084278s30.821784 43.723926 61.356852 55.049139c30.678426-11.325213 61.50021-55.049139 61.50021-55.049139l-42.00364-106.084278c25.804284-13.332213 42.00364-47.451211 42.00364-47.451211z" p-id="1455"></path></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View 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="1761212863001" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2892" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M725.308 314.532c2.743 1.543 5.486 3.215 8.164 5.057l8.164 5.55 0.15 9.77c0.45 28.328-2.957 52.326-10.37 70.796-6.836 16.885-16.971 29.527-30.556 37.134-9.235 32.976-20.077 63.532-35.42 89.18-17.677 29.548-41.011 52.261-74.01 64.496-14.763 5.4-52.432 7.907-88.708 7.393-35.012-0.536-70.131-4.007-84.06-10.414-29.997-13.842-51.082-37.048-67.045-65.975-13.692-24.705-23.463-53.46-31.97-83.823-14.034-7.328-24.577-19.906-31.669-36.92-7.778-18.577-11.314-42.982-10.82-71.866l0.106-9.814 8.143-5.485c2.1-1.414 4.178-2.722 6.256-3.921-9.17-113.544-5.72-155.52 36.448-203.495 82.217-67.346 270.712-64.968 354-3.943 56.718 53.547 60.66 113.586 43.197 206.28m-172.66 328.33l1.2 26.013-15.407 25.434 21.47 141.12 88.045-189.224 134.07-4.585c69.189 65.503 113.63 219.758 102.701 320.38H137.623c1.843-88.366 18.106-239.278 106.108-316.03l121.107 1.135 113.414 187.124 21.32-139.92-15.427-25.434 1.178-26.013c29.355-1.607 37.99-1.607 67.325 0m100.3-368.656c-53.246 10.414-132.57 19.52-195.245-15.706-24.105-13.563-59.417 14.228-88.301 11.378a217.808 217.808 0 0 0-19.542 57.682l-3.214 17.035-17.142-1.671a24.02 24.02 0 0 0-9.942 1.264 38.098 38.098 0 0 0-4.65 1.843c0.45 18.877 3.107 34.54 7.971 46.261 4.285 10.307 10.307 17.035 18.106 19.477l10.007 3.107 2.742 10.071c8.4 31.134 17.806 60.468 31.027 84.36 12.256 22.22 27.984 39.833 49.711 49.84 9.107 4.156 38.226 6.556 68.653 7.006 32.334 0.471 64.603-1.243 75.253-5.164 23.934-8.871 41.204-25.97 54.618-48.34 14.399-24.127 24.62-54.661 33.62-88.173l2.549-9.578 9.535-3.321c7.542-2.7 13.392-9.536 17.549-19.97 4.714-11.614 7.264-27.02 7.692-45.534a35.355 35.355 0 0 0-4.178-1.67 25.413 25.413 0 0 0-9.706-1.48l-16.67 1.072-3.108-16.435a213.844 213.844 0 0 0-17.334-53.354m0 0" p-id="2893"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View 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="1761545158610" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1618" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 625.777778l85.333333 85.333333-56.888889 85.333333 56.888889 142.222223 79.644445-290.133334c142.222222 34.133333 290.133333 102.4 290.133333 204.8v113.777778H56.888889v-113.777778c0-108.088889 147.911111-176.355556 290.133333-204.8l79.644445 290.133334 56.888889-142.222223-56.888889-85.333333L512 625.777778z m196.266667-261.688889c0 110.933333-85.333333 204.8-196.266667 204.8-107.235556 0-190.577778-87.722667-195.982222-193.763556L315.733333 364.088889h392.533334zM521.159111 56.888889c12.970667 0.170667 28.444444 0.967111 41.415111 4.949333l9.159111 3.584v136.533334h34.133334v-119.466667c65.024 32.483556 114.574222 103.708444 119.125333 184.149333l0.341333 12.117334h42.666667v42.666666h-512V278.755556h42.666667c0-81.066667 38.513778-154.453333 100.864-190.805334L409.6 82.488889v119.466667h42.666667v-136.533334c14.222222-7.111111 34.360889-8.305778 50.574222-8.533333h18.318222z" p-id="1619"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View 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="1761212898988" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3212" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M105.9 959.3c5.5-79.7 16.6-187.3 41.1-286.1 5.1-25.5 23.2-46.9 23.8-47.7 2.3-15.1 187.9-15.2 187.9-15.2l30.7-2.9c-4.4 27.2-7.7 68.5 4 104 15.2 46.6 39.4 92.8 72.3 120.3l7-123.7H536l10.9 116c23-32.9 49.8-84.7 52.6-150.7 2.2-51.6 15.1-75.2 26.2-86.1 76.9 3.1 173.3 17.2 212 38.4 36.9 20.2 48.5 146.4 81.9 333.8H105.9v-0.1z" p-id="3213"></path><path d="M528.1 690.2h-39.7l-31.8-26.6 47.7-62 47.6 53.2-23.8 35.4z m210.7-526.8c-19-38-129.4-95-249.8-98.9-120.4-4-247.9 65.9-246.9 117 1 51 64.2 73 64.2 73-3.3 9.9-1.6 48.3-1.6 48.3s2.9 5.3 8.4 12.7C294.5 560.8 509 565 509 565c185.9-19.3 171.1-221.2 169.3-250.4-0.7-10.7-2-19.2-3.6-26.8 2.3-16.5-4.1-33.5-4.1-33.5 27.1-10.9 87.3-52.9 68.2-90.9z m-280.2-19.1c-0.2-1.6-0.5-3.2-0.5-4.9 0-20.7 18.2-37.5 40.7-37.5 22.5 0 40.7 16.8 40.7 37.5 0 2.6-0.3 5.2-0.9 7.7 12.1-1.1 25.2-2.1 37.1-2.3 0 0-33.9 15.8-53.2 25.1-2.9 1.9-6 3.4-9.4 4.6-0.2 0.1-0.5 0.3-0.8 0.4 0 0-1.1 0.3-2.9 0.6-3.4 0.9-7 1.5-10.7 1.5-0.7 0-1.4-0.2-2.1-0.2-9 0-20.4-1.8-29.6-9.5 0 0-13.9-9.5-45.3-22.6 0.1 0 16.8-2.1 36.9-0.4z m181.3 107.8H332v-36.4h307.9v36.4z" p-id="3214"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View 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="1761212837698" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2731" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M855.7 781.7c-11.8-25.2-29.1-47.3-48.4-67.2-26.3-27-56.5-50-87.9-70.7-32.1-20.9-65.6-39.7-100.4-55.6 28.1-18.7 49.6-45.9 64.9-75.7 20.9-40.7 31.9-85.6 38.1-130.7 6.3-47.9 7.5-96.5 3.7-144.7 32.3-12.9 64.5-25.8 96.8-38.7 12-5 24.3-9.4 36.2-14.7-114.9-39.9-229.9-79.6-344.8-119.4-2-1-4 0.5-5.9 1-108.1 39.5-216.3 78.9-324.5 118.4 39.4 18.4 79 36.5 118.5 54.9-8.8 55.8-7.2 113.4 7.1 168.2 9 34.8 23 68.2 41.4 99.1 18.3 30.4 40.2 58.9 67 82.3-48 15.6-94.7 36.5-136.1 65.7-28.4 20-54.1 43.9-74.7 71.9-23 31.1-39.5 67.3-46.7 105.4-3.7 19.5-5.3 39.5-3.2 59.2 112.7 48 235.5 71.4 357.9 69.7-37.9-32.3-75.9-64.6-113.9-96.9 30.3-73.3 60.5-146.6 90.8-219.9 0.4-1.4 1.8-2.9 0.8-4.2-8.6-14.2-17.4-28.2-25.8-42.5 14.6 7.7 30.8 12.6 47.4 12.7 14.9 0.3 29.7-4.3 42-12.7-9.1 13.7-18.3 27.4-27.5 41.1 17.8 40.6 35.7 81.2 53.5 121.9 14.7 33.9 29.9 67.6 44.4 101.5-37.3 32.9-74.3 66.1-111.4 99.2 90.1-1.2 180-15.1 266.2-41.2 26.5-8.2 52.7-17.2 78-28.6 6.5-15.2 10.3-31.6 10.5-48.1 0.2-21-5.2-41.8-14-60.7z" p-id="2732"></path><path d="M216.4 380.8h11V392h21.5v-11.2h11v-26.3h-11V223.3h-21.5c-0.1 43.7 0 87.5 0 131.2h-11v26.3z" p-id="2733"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View 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="1761212761688" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2247" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M636.478451 965.736274l10.285126-0.276102c-2.068945-0.25056-5.512321-0.662889-10.064973-1.188336a398.001259 398.001259 0 0 1-0.220153 1.464438zM319.897045 966.147387l15.81934-0.411113-0.214071-1.463222c-7.760064 0.893989-12.318797 1.463222-12.318797 1.463222l-3.286472 0.411113zM701.706773 232.246056c0-130.470023-177.671401-195.699562-177.671401-195.699562-25.16304-11.331153-50.139985-8.750141-75.875908 0 0 0-177.679915 57.274865-177.679915 195.699562 0 20.694314-37.558465 16.252347-49.98673 81.7142h531.217713c-22.614869-65.914321-50.003758-61.019886-50.003759-81.7142zM261.149228 691.016641C179.668559 732.292152 110.143011 786.116343 110.143011 839.449144v27.720942c0 57.176344 150.840799 106.349362 150.840799 106.349362l58.913235-7.372061-5.835859 0.152039s-48.965029-277.278751-52.911958-275.282785z" p-id="2248"></path><path d="M649.282557 868.865624c-18.205742 19.018238-43.489197 24.747062-67.840958 15.467826-25.402654-9.681835-41.173341-37.079238-36.441892-62.991526 2.217335-12.137567 7.629918-22.585677 16.273024-31.609489 36.338505-37.95255 72.662414-75.917262 108.994838-113.872244-20.740534-9.049353-41.343624-19.515709-62.035506-26.357458-5.480697-1.774598-39.908378-17.257019-18.379674-82.532777h-0.291915c50.639889-51.988778 86.252256-102.79895 86.252256-215.156888h-378.248369c0 113.53411 34.725677 163.877219 85.702483 215.795451 22.010362 57.542453-17.334863 78.908171-25.565639 81.90881-22.185511 8.003326-37.025721 17.671781-59.579774 27.987314-4.070993 1.864605 34.183202 264.833458 37.380883 286.769626 28.691558-3.307149 101.308969-11.070862 150.593888-11.070862 49.355464 0 121.929088 7.763713 150.601185 11.070862 1.212662-8.076305 7.314894-48.820288 14.275842-97.177161l-1.690672 1.768516zM780.85699 731.325185a466047.419516 466047.419516 0 0 1-98.887294 103.3694c-11.765376 66.245158-22.605138 130.427453-22.605138 130.427452l-12.599765 0.336918c1.455924 0.177581 2.246526 0.276103 2.246526 0.276103l62.66069 7.96562s150.381033-49.146259 150.381033-106.531808v-27.719726c-0.002433-37.857677-33.167582-75.24221-81.196052-108.123959z" p-id="2249"></path><path d="M843.295095 519.324624l-52.478951 54.703584 62.663122 57.647056 52.688157-55.292278c0.620319 1.188336 0.975481 1.74419 1.211445 2.343831 20.077644 51.086275-8.213748 109.894908-61.513708 127.951044-20.261307 6.862426-40.735469 7.679787-61.405457 2.17598-2.048268-0.54734-2.96415 0.256642-4.130592 1.474169A361833.588559 361833.588559 0 0 1 643.464941 853.40023c-13.557002 14.162724-32.385495 18.42711-50.519474 11.518465-18.916067-7.210291-30.659549-27.611474-27.138329-46.907031 1.652967-9.039623 5.682605-16.819147 12.118106-23.538049 45.458404-47.47748 90.870589-94.993883 136.371564-142.431225 1.828115-1.904743 2.042186-3.3631 1.162793-5.751934-19.576524-53.126028 12.26163-112.586604 68.353025-127.899958 18.585231-5.074449 37.137621-5.129183 55.717987-0.199475 1.046027 0.278535 2.072594 0.622751 3.764482 1.133601zM604.103904 841.024267c5.432045-0.211638 9.560204-4.471159 9.419112-9.706161-0.147174-5.362715-4.626847-9.473846-10.113626-9.281669-5.391906 0.189745-9.684268 4.732666-9.44587 10.004157 0.233532 5.146212 4.800779 9.190445 10.140384 8.983673z" p-id="2250"></path></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,93 @@
<template>
<el-tooltip :disabled="!isOverflow" effect="light" placement="top" :content="text">
<div
ref="containerRef"
class="multi-line-ellipsis"
:style="containerStyle"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
>
<slot>
{{ text }}
</slot>
</div>
</el-tooltip>
</template>
<script lang="ts" setup>
import { computed, type HTMLAttributes, nextTick, onMounted, ref } from 'vue'
import { ElTooltip } from 'element-plus'
// 定义组件 props 类型
interface Props {
text?: string
lines?: number
maxWidth?: string | number
}
const props = withDefaults(defineProps<Props>(), {
text: '',
lines: 3,
maxWidth: '100%',
})
const isOverflow = ref(false)
const containerRef = ref<HTMLElement | null>(null)
// 计算容器样式
const containerStyle = computed(
() =>
({
maxWidth: props.maxWidth,
display: '-webkit-box',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: props.lines,
overflow: 'hidden',
textOverflow: 'ellipsis',
lineHeight: '1.5',
wordBreak: 'break-all',
}) as HTMLAttributes['style'],
)
// 检查文字是否溢出
const checkOverflow = (element: HTMLElement): boolean => {
// 单行情况下使用宽度判断
if (props.lines === 1) {
return element.scrollWidth > element.clientWidth
}
// 多行情况下使用高度判断
else {
return element.scrollHeight > element.clientHeight
}
}
// 鼠标进入处理
const handleMouseEnter = (event: MouseEvent) => {
const element = event.target as HTMLElement
console.log(checkOverflow(element))
isOverflow.value = checkOverflow(element)
}
// 鼠标离开处理
const handleMouseLeave = () => {
isOverflow.value = false
}
// 初始化时检查溢出状态
onMounted(() => {
nextTick(() => {
if (containerRef.value) {
isOverflow.value = checkOverflow(containerRef.value)
}
})
})
</script>
<style scoped>
.multi-line-ellipsis {
cursor: default;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
}
</style>

View File

@@ -0,0 +1,35 @@
<script setup lang="ts">
const props = withDefaults(
defineProps<{
iconClass: string
prefix?: string
color?: string
size?: string
}>(),
{
prefix: 'icon',
color: '',
size: '1em',
},
)
const symbolId = computed(() => `#${props.prefix}-${props.iconClass}`)
</script>
<template>
<svg aria-hidden="true" class="svg-icon" :style="`color:${props.color}`">
<use :xlink:href="symbolId" />
</svg>
</template>
<style scoped>
.svg-icon {
display: inline-block;
width: v-bind('props.size');
height: v-bind('props.size');
overflow: hidden;
vertical-align: -0.15em; /* 因icon大小被设置为和字体大小一致而span等标签的下边缘会和字体的基线对齐故需设置一个往下的偏移比例来纠正视觉上的未对齐效果 */
outline: none;
fill: currentcolor; /* 定义元素的颜色currentColor是一个变量这个变量的值就表示当前元素的color值如果当前元素未设置color值则从父元素继承 */
}
</style>

View File

@@ -0,0 +1,21 @@
<script setup lang="ts">
import { useConfigStore } from '@/stores'
defineOptions({
name: 'AppHeader'
})
const configStore = useConfigStore()
</script>
<template>
<div class="bg-[var(--color-bg-secondary)] h-[60px] relative pl-[27px] font-[900] text-[24px]">
<div class="absolute left-0 h-full flex items-center">
<img class="w-[36.8px] h-[36.8px] rounded-full mr-[12px]" src="/logo.jpg" alt="logo" />
<span class="text-[#9E0000]">{{ configStore.config.title }}</span>{{ configStore.config.subTitle }}
</div>
<div class="text-center h-full w-full tracking-[8.5px] flex items-center justify-center">
{{ configStore.config.centerTitle }}
</div>
</div>
</template>

View File

@@ -0,0 +1,224 @@
<script setup lang="ts">
import { ref } 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'
const emit = defineEmits<{
(e: 'search-start'): void
(e: 'search', value: string): void
}>()
const agentsStore = useAgentsStore()
const configStore = useConfigStore()
const searchValue = ref('')
const triggerOnFocus = ref(true)
const isFocus = ref(false)
async function handleSearch() {
try {
triggerOnFocus.value = false
if (!searchValue.value) {
ElMessage.warning('请输入搜索内容')
return
}
emit('search-start')
agentsStore.resetAgent()
agentsStore.setAgentRawPlan({ loading: true })
const data = await api.generateBasePlan({
goal: searchValue.value,
inputs: [],
})
data['Collaboration Process'] = changeBriefs(data['Collaboration Process'])
agentsStore.setAgentRawPlan({ data })
emit('search', searchValue.value)
} finally {
triggerOnFocus.value = true
agentsStore.setAgentRawPlan({ loading: false })
}
}
const querySearch = (queryString: string, cb: (v: { value: string }[]) => void) => {
const results = queryString
? configStore.config.taskPromptWords.filter(createFilter(queryString))
: configStore.config.taskPromptWords
// call callback function to return suggestions
cb(results.map((item) => ({ value: item })))
}
const createFilter = (queryString: string) => {
return (restaurant: string) => {
return restaurant.toLowerCase().includes(queryString.toLowerCase())
}
}
const taskContainerRef = ref<HTMLDivElement | null>(null)
</script>
<template>
<el-tooltip
content="请先点击智能体库右侧的按钮上传智能体信息"
placement="top"
effect="light"
:disabled="agentsStore.agents.length > 0"
>
<div class="task-root-container">
<div class="task-container" ref="taskContainerRef" id="task-container">
<span class="text-[var(--color-text)] font-bold task-title">任务</span>
<el-autocomplete
v-model.trim="searchValue"
class="task-input"
size="large"
:rows="isFocus ? 3 : 1"
placeholder="请输入您的任务"
type="textarea"
:append-to="taskContainerRef"
:fetch-suggestions="querySearch"
@change="agentsStore.setSearchValue"
:disabled="!(agentsStore.agents.length > 0)"
:debounce="0"
:trigger-on-focus="triggerOnFocus"
@focus="isFocus = true"
@blur="isFocus = false"
@select="isFocus = false"
>
</el-autocomplete>
<el-button
class="task-button"
color="linear-gradient(to right, #00C7D2, #315AB4)"
size="large"
title="点击搜索任务"
circle
:loading="agentsStore.agentRawPlan.loading"
:disabled="!searchValue"
@click.stop="handleSearch"
>
<SvgIcon
v-if="!agentsStore.agentRawPlan.loading"
icon-class="paper-plane"
size="18px"
color="var(--color-text)"
/>
</el-button>
</div>
</div>
</el-tooltip>
</template>
<style scoped lang="scss">
.task-root-container {
height: 60px;
margin-bottom: 24px;
position: relative;
}
.task-container {
width: 40%;
margin: 0 auto;
border: 2px solid transparent;
$bg: var(--el-input-bg-color, var(--el-fill-color-blank));
background:
linear-gradient(var(--color-bg-tertiary), var(--color-bg-tertiary)) padding-box,
linear-gradient(to right, #00c8d2, #315ab4) border-box;
border-radius: 40px;
position: absolute;
left: 50%;
transform: translateX(-50%);
z-index: 998;
min-height: 100%;
overflow: hidden;
padding: 0 55px 0 47px;
:deep(.el-popper) {
position: static !important;
width: 100%;
min-width: 100%;
background: var(--color-bg-tertiary);
box-shadow: none;
border: none;
transition: height 0s ease-in-out;
border-top: 1px solid #494B51;
border-radius: 0;
li {
height: 45px;
box-sizing: border-box;
line-height: 45px;
font-size: 14px;
&:hover {
background: #1C1E25;
color: #00F3FF;
}
}
.el-popper__arrow {
display: none;
}
}
:deep(.el-autocomplete) {
min-height: 56px;
width: 100%;
.task-input {
height: 100%;
}
.el-textarea__inner {
border-radius: 0;
box-shadow: none;
font-size: 14px;
height: 100%;
line-height: 1.5;
padding: 18px 0;
resize: none;
&::placeholder {
line-height: 1.2;
font-size: 18px;
vertical-align: middle;
}
.el-icon.is-loading {
& + span {
display: none;
}
}
}
}
.task-title {
position: absolute;
top: 28px;
left: 10px;
z-index: 999;
transform: translateY(-50%);
}
.task-button {
background: linear-gradient(to right, #00c7d2, #315ab4);
border: none; // 如果需要移除边框
position: absolute;
top: 28px;
right: 10px;
transform: translateY(-50%);
z-index: 999;
display: flex;
justify-content: center;
align-items: center;
padding: 0;
}
.task-button.is-loading {
:deep(span) {
display: none !important;
}
}
}
</style>

View File

@@ -0,0 +1,141 @@
<script setup lang="ts">
import { getActionTypeDisplay, getAgentMapIcon } from '@/layout/components/config.ts'
import SvgIcon from '@/components/SvgIcon/index.vue'
import { type Agent, useAgentsStore } from '@/stores'
import { v4 as uuidv4 } from 'uuid'
const porps = defineProps<{
agentList: Agent[]
}>()
const taskProcess = computed(() => {
const list = agentsStore.currentTask?.TaskProcess ?? []
return list.map((item) => ({
...item,
key: uuidv4(),
}))
})
const agentsStore = useAgentsStore()
</script>
<template>
<div
v-for="item in porps.agentList"
:key="item.Name"
class="user-item"
:class="agentsStore.currentTask?.AgentSelection?.includes(item.Name) ? 'active-card' : ''"
>
<div class="flex items-center justify-between relative h-[41px]">
<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="var(--color-text)"
size="24px"
/>
</div>
<div class="flex-1 text-[14px] flex flex-col items-end justify-end truncate ml-1">
<span
class="w-full truncate text-right"
:style="
agentsStore.currentTask?.AgentSelection?.includes(item.Name) ? 'color:#00F3FF' : ''
"
>{{ item.Name }}</span
>
<div
v-if="agentsStore.currentTask?.AgentSelection?.includes(item.Name)"
class="flex items-center gap-[7px] h-[8px] mr-1"
>
<!-- 小圆点 -->
<div
v-for="item1 in taskProcess.filter((i) => i.AgentName === item.Name)"
:key="item1.key"
class="w-[6px] h-[6px] rounded-full"
:style="{ background: getActionTypeDisplay(item1.ActionType)?.color }"
></div>
</div>
</div>
</div>
<!-- 职责信息只有当执行流程中有当前智能体并且鼠标移入时才显示 -->
<div class="duty-info">
<div class="w-full flex justify-center">
<div
class="rounded-[9px] bg-[var(--color-bg-quaternary)] text-[12px] py-0.5 px-5 text-center my-2"
>
当前职责
</div>
</div>
<div class="p-[8px] pt-0">
<div
v-for="(item1, index1) in taskProcess.filter((i) => i.AgentName === item.Name)"
:key="item1.key"
class="text-[12px]"
>
<div>
<div class="mx-1 inline-block h-[14px]">
<div
:style="{ background: getActionTypeDisplay(item1.ActionType)?.color }"
class="w-[6px] h-[6px] rounded-full mt-[7px]"
></div>
</div>
<span :style="{ color: getActionTypeDisplay(item1.ActionType)?.color }"
>{{ getActionTypeDisplay(item1.ActionType)?.name }}</span
>
<span>{{ item1.Description }}</span>
</div>
<!-- 分割线 -->
<div
v-if="index1 !== taskProcess.filter((i) => i.AgentName === item.Name).length - 1"
class="h-[1px] w-full bg-[#494B51] my-[8px]"
></div>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.user-item {
background: #1d222b;
border-radius: 40px;
padding-right: 12px;
cursor: pointer;
transition: all 0.25s ease;
color: #969696;
border: 2px solid transparent;
.duty-info {
transition: height 0.25s ease;
height: 0;
overflow: hidden;
}
& + .user-item {
margin-top: 8px;
}
&:hover {
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
color: #b8b8b8;
}
}
.active-card {
background:
linear-gradient(#171B22, #171B22) padding-box,
linear-gradient(to right, #00c8d2, #315ab4) border-box;
&:hover {
border-radius: 20px;
.duty-info {
height: auto;
}
.icon-container {
bottom: 2px;
}
}
}
</style>

View File

@@ -0,0 +1,190 @@
<script setup lang="ts">
import { ElNotification } from 'element-plus'
import { pick } from 'lodash'
import api from '@/api/index.ts'
import SvgIcon from '@/components/SvgIcon/index.vue'
import { agentMapDuty } from '@/layout/components/config.ts'
import { type Agent, useAgentsStore } from '@/stores'
import { onMounted } from 'vue'
import { readConfig } from '@/utils/readJson.ts'
import AgentRepoList from './AgentRepoList.vue'
const agentsStore = useAgentsStore()
// 如果agentsStore.agents不存在就读取默认配置的json文件
onMounted(async () => {
if (!agentsStore.agents.length) {
const res = await readConfig<Agent[]>('agent.json')
agentsStore.setAgents(res)
}
await api.setAgents(agentsStore.agents.map((item) => pick(item, ['Name', 'Profile'])))
})
// 上传agent文件
const fileInput = ref<HTMLInputElement>()
const triggerFileSelect = () => {
fileInput.value?.click()
}
const handleFileSelect = (event: Event) => {
const input = event.target as HTMLInputElement
if (input.files && input.files[0]) {
const file = input.files[0]
readFileContent(file)
}
}
const readFileContent = async (file: File) => {
const reader = new FileReader()
reader.onload = async (e) => {
if (!e.target?.result) {
return
}
try {
const json = JSON.parse(e.target.result?.toString?.() ?? '{}')
// 处理 JSON 数据
if (Array.isArray(json)) {
const isValid = json.every(
(item) =>
typeof item.Name === 'string' &&
typeof item.Icon === 'string' &&
typeof item.Profile === 'string',
)
if (isValid) {
// 处理有效的 JSON 数据
agentsStore.setAgents(
json.map((item) => ({
Name: item.Name,
Icon: item.Icon.replace(/\.png$/, ''),
Profile: item.Profile,
Classification: item.Classification,
})),
)
await api.setAgents(json.map((item) => pick(item, ['Name', 'Profile'])))
} else {
ElNotification.error({
title: '错误',
message: 'JSON 格式错误',
})
}
} else {
console.error('JSON is not an array')
ElNotification.error({
title: '错误',
message: 'JSON 格式错误',
})
}
} catch (e) {
console.error(e)
}
}
reader.readAsText(file)
}
// 根据currentTask排序agent列表
const agentList = computed(() => {
const selected: Agent[] = []
const unselected: {
title: string
data: Agent[]
}[] = []
const obj: Record<string, Agent[]> = {}
if (!agentsStore.agents.length) {
return {
selected,
unselected,
}
}
for (const agent of agentsStore.agents) {
// if (agentsStore.currentTask?.AgentSelection?.includes(agent.Name)) {
// selected.push(agent)
// continue
// }
if (obj[agent.Classification]) {
obj[agent.Classification]!.push(agent)
} else {
const arr = [agent]
obj[agent.Classification] = arr
unselected.push({
title: agent.Classification,
data: arr,
})
}
}
return {
selected,
unselected: unselected,
}
})
</script>
<template>
<div class="agent-repo h-full flex flex-col" id="agent-repo">
<!-- 头部 -->
<div class="flex items-center justify-between">
<span class="text-[18px] font-bold">智能体库</span>
<!-- 上传文件 -->
<input type="file" accept=".json" @change="handleFileSelect" class="hidden" ref="fileInput" />
<div class="plus-button" @click="triggerFileSelect">
<svg-icon icon-class="plus" color="var(--color-text)" size="18px" />
</div>
</div>
<!-- 智能体列表 -->
<div class="pt-[18px] flex-1 overflow-y-auto relative">
<!-- 已选中的智能体 -->
<AgentRepoList :agent-list="agentList.selected" />
<!-- 为选择的智能体 -->
<div v-for="agent in agentList.unselected" :key="agent.title">
<p class="text-[12px] font-bold py-[8px]">{{ agent.title }}</p>
<AgentRepoList :agent-list="agent.data" />
</div>
</div>
<!-- 底部提示栏 -->
<div class="w-full grid grid-cols-3 gap-x-[10px] bg-[#1d222b] rounded-[20px] p-[8px] mt-[10px]">
<div
v-for="item in Object.values(agentMapDuty)"
:key="item.key"
class="flex items-center justify-center gap-x-1"
>
<span class="text-[12px]">{{ item.name }}</span>
<div class="w-[8px] h-[8px] rounded-full" :style="{ background: item.color }"></div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.agent-repo {
padding: 0 8px;
.plus-button {
background: #1d2128;
width: 24px;
height: 24px;
padding: 0;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: #374151;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
}
}
}
#agent-repo {
:deep(.agent-repo-item-popover) {
padding: 0;
border-radius: 20px;
background: var(--color-bg-secondary);
}
}
</style>

View File

@@ -0,0 +1,105 @@
<script setup lang="ts">
import type { IExecuteRawResponse } from '@/api'
import { computed } from 'vue'
import MarkdownIt from 'markdown-it'
import DOMPurify from 'dompurify'
import Iod from './Iod.vue'
const props = defineProps<{
executePlans: IExecuteRawResponse[]
nodeId?: string
actionId?: string
}>()
const md = new MarkdownIt({
html: true,
linkify: true,
typographer: true,
breaks: true,
})
function sanitize(str?: string) {
if (!str) {
return ''
}
const cleanStr = str
.replace(/\\n/g, '\n')
.replace(/\n\s*\d+\./g, '\n$&')
const html = md.render(cleanStr)
return html
// return DOMPurify.sanitize(html)
}
interface Data {
Description: string
Content: string
LogNodeType: string
}
const data = computed<Data | null>(() => {
for (const result of props.executePlans) {
if (result.NodeId === props.nodeId) {
// LogNodeType 为 object直接渲染Content
if (result.LogNodeType === 'object') {
return {
Description: props.nodeId,
Content: sanitize(result.content),
LogNodeType: result.LogNodeType,
}
}
if (!result.ActionHistory) {
return null
}
for (const action of result.ActionHistory) {
if (action.ID === props.actionId) {
return {
Description: action.Description,
Content: sanitize(action.Action_Result),
LogNodeType: result.LogNodeType,
}
}
}
}
}
return null
})
</script>
<template>
<div v-if="data" class="card-item w-full pl-[56px] pr-[41px]">
<!-- 分割线 -->
<div v-if="data.LogNodeType !== 'object'" class="h-[1px] w-full bg-[#494B51] my-[8px]"></div>
<div
v-if="data.Description"
class="text-[16px] flex items-center gap-1 text-[var(--color-text-secondary)] mb-1"
>
{{ data.Description }}
<Iod v-if="data.LogNodeType !== 'object'"/>
</div>
<div class="rounded-[8px] p-[15px] text-[14px] bg-[var(--color-bg-quaternary)]">
<div
class="markdown-content max-h-[240px] overflow-y-auto max-w-full"
v-html="data.Content"
></div>
</div>
</div>
</template>
<style scoped lang="scss">
.card-item + .card-item {
margin-top: 10px;
}
.markdown-content {
:deep(code) {
display: block;
width: 100px;
max-width: 100%;
}
:deep(pre) {
overflow-x: auto;
max-width: 100%;
}
}
</style>

View File

@@ -0,0 +1,76 @@
<script setup lang="ts">
import { readConfig } from '@/utils/readJson.ts'
import { onMounted } from 'vue'
interface Iod {
name: string
data_space: string
doId: string
fromRepo: string
}
const data = ref<Iod[]>([])
const displayIndex = ref(0)
const displayIod = computed(() => {
return data.value[displayIndex.value]!
})
onMounted(async () => {
const res = await readConfig<{ data: Iod[] }>('iodConfig.json')
data.value = res.data
})
function handleNext() {
if (displayIndex.value === data.value.length - 1) {
displayIndex.value = 0
} else {
displayIndex.value++
}
}
</script>
<template>
<el-popover trigger="hover" width="440">
<template #reference>
<div
class="rounded-full w-[20px] h-[20px] bg-[var(--color-bg-quaternary)] flex justify-center items-center cursor-pointer"
>
{{ data.length }}
</div>
</template>
<template #default v-if="data.length">
<div>
<div class="flex justify-between items-center p-2 pb-0 rounded-[8px] text-[16px] font-bold">
<span>数联网搜索结果</span>
<div class="flex items-center gap-3">
<div>{{ `${displayIndex + 1}/${data.length}` }}</div>
<el-button type="primary" size="small" @click="handleNext">下一个</el-button>
</div>
</div>
<!-- 分割线 -->
<div class="h-[1px] w-full bg-[#494B51] my-[8px]"></div>
<div class="p-2 pt-0">
<div class="flex items-center w-full gap-3">
<div class="font-bold w-[75px] text-right flex-shrink-0">名称:</div>
<div class="text-[var(--color-text-secondary)] flex-1 break-words">{{ displayIod.name }}</div>
</div>
<div class="flex items-center w-full gap-3">
<div class="font-bold w-[75px] text-right flex-shrink-0">数据空间:</div>
<div class="text-[var(--color-text-secondary)] lex-1 break-words">{{ displayIod.data_space }}</div>
</div>
<div class="flex items-center w-full gap-3">
<div class="font-bold w-[75px] text-right flex-shrink-0">DOID:</div>
<div class="text-[var(--color-text-secondary)] lex-1 break-words">{{ displayIod.doId }}</div>
</div>
<div class="flex items-center w-full gap-3">
<div class="font-bold w-[75px] text-right flex-shrink-0">来源仓库:</div>
<div class="text-[var(--color-text-secondary)] flex-1 break-words break-al">{{ displayIod.fromRepo }}</div>
</div>
</div>
</div>
</template>
</el-popover>
</template>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,400 @@
<script setup lang="ts">
import { computed, onUnmounted, ref } from 'vue'
import { throttle } from 'lodash'
import { AnchorLocations, BezierConnector } from '@jsplumb/browser-ui'
import SvgIcon from '@/components/SvgIcon/index.vue'
import { getActionTypeDisplay, getAgentMapIcon } from '@/layout/components/config.ts'
import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/utils.ts'
import variables from '@/styles/variables.module.scss'
import { type IRawStepTask, useAgentsStore } from '@/stores'
import api from '@/api'
import ExecutePlan from './ExecutePlan.vue'
const emit = defineEmits<{
(e: 'refreshLine'): void
(el: 'setCurrentTask', task: IRawStepTask): void
}>()
const agentsStore = useAgentsStore()
const collaborationProcess = computed(() => {
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
})
const jsplumb = new Jsplumb('task-results-main', {
connector: {
type: BezierConnector.type,
options: { curviness: 30, stub: 10 },
},
})
// 操作折叠面板时要实时的刷新连线
let timer: number
function handleCollapse() {
if (timer) {
clearInterval(timer)
}
timer = setInterval(() => {
jsplumb.repaintEverything()
emit('refreshLine')
}, 1)
// 默认三秒后已经完全打开
const timer1 = setTimeout(() => {
clearInterval(timer)
}, 3000)
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
if (timer1) {
clearInterval(timer1)
}
})
}
// 创建内部连线
function createInternalLine(id?: string) {
const arr: ConnectArg[] = []
jsplumb.reset()
collaborationProcess.value.forEach((item) => {
// 创建左侧流程与产出的连线
arr.push({
sourceId: `task-results-${item.Id}-0`,
targetId: `task-results-${item.Id}-1`,
anchor: [AnchorLocations.Left, AnchorLocations.Left],
})
collaborationProcess.value.forEach((jitem) => {
// 创建左侧产出与上一步流程的连线
if (item.InputObject_List!.includes(jitem.OutputObject ?? '')) {
arr.push({
sourceId: `task-results-${jitem.Id}-1`,
targetId: `task-results-${item.Id}-0`,
anchor: [AnchorLocations.Left, AnchorLocations.Left],
config: {
type: 'output',
},
})
}
// 创建右侧任务程序与InputObject字段的连线
jitem.TaskProcess.forEach((i) => {
if (i.ImportantInput?.includes(`InputObject:${item.OutputObject}`)) {
const color = getActionTypeDisplay(i.ActionType)?.color ?? ''
const sourceId = `task-results-${item.Id}-1`
const targetId = `task-results-${jitem.Id}-0-${i.ID}`
arr.push({
sourceId,
targetId,
anchor: [AnchorLocations.Right, AnchorLocations.Right],
config: {
stops: [
[0, color],
[1, color],
],
transparent: targetId !== id,
},
})
}
})
})
// 创建右侧TaskProcess内部连线
item.TaskProcess?.forEach((i) => {
if (!i.ImportantInput?.length) {
return
}
item.TaskProcess?.forEach((i2) => {
if (i.ImportantInput.includes(`ActionResult:${i2.ID}`)) {
const color = getActionTypeDisplay(i.ActionType)?.color ?? ''
const sourceId = `task-results-${item.Id}-0-${i2.ID}`
const targetId = `task-results-${item.Id}-0-${i.ID}`
arr.push({
sourceId,
targetId,
anchor: [AnchorLocations.Right, AnchorLocations.Right],
config: {
stops: [
[0, color],
[1, color],
],
transparent: targetId !== id,
},
})
}
})
})
})
jsplumb.connects(arr)
jsplumb.repaintEverything()
}
const loading = ref(false)
async function handleRun() {
try {
loading.value = true
const d = await api.executePlan(agentsStore.agentRawPlan.data!)
agentsStore.setExecutePlan(d)
} finally {
loading.value = false
}
}
// 添加滚动状态标识
const isScrolling = ref(false)
let scrollTimer: number
// 修改滚动处理函数
function handleScroll() {
isScrolling.value = true
emit('refreshLine')
// 清除之前的定时器
if (scrollTimer) {
clearTimeout(scrollTimer)
}
jsplumb.repaintEverything()
// 设置滚动结束检测
scrollTimer = setTimeout(() => {
isScrolling.value = false
}, 300)
}
// 修改鼠标事件处理函数
const handleMouseEnter = throttle((id) => {
if (!isScrolling.value) {
createInternalLine(id)
}
}, 100)
const handleMouseLeave = throttle(() => {
if (!isScrolling.value) {
createInternalLine()
}
}, 100)
function clear() {
jsplumb.reset()
}
defineExpose({
createInternalLine,
clear,
})
</script>
<template>
<div class="h-full flex flex-col relative" id="task-results">
<!-- 标题与执行按钮 -->
<div class="text-[18px] font-bold mb-[18px] flex justify-between items-center px-[20px]">
<span>执行结果</span>
<div class="flex items-center gap-[14px]">
<el-button circle :color="variables.tertiary" disabled title="点击刷新">
<svg-icon icon-class="refresh" />
</el-button>
<el-popover :disabled="Boolean(agentsStore.agentRawPlan.data)" title="请先输入要执行的任务">
<template #reference>
<el-button
circle
:color="variables.tertiary"
title="点击运行"
:disabled="!agentsStore.agentRawPlan.data"
@click="handleRun"
>
<svg-icon icon-class="action" />
</el-button>
</template>
</el-popover>
</div>
</div>
<!-- 内容 -->
<div
v-loading="agentsStore.agentRawPlan.loading"
class="flex-1 overflow-auto relative"
@scroll="handleScroll"
>
<div id="task-results-main" class="px-[40px] relative">
<div v-for="item in collaborationProcess" :key="item.Id" class="card-item">
<el-card
class="card-item w-full relative"
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
shadow="hover"
:id="`task-results-${item.Id}-0`"
@click="emit('setCurrentTask', item)"
>
<div class="text-[18px] mb-[15px]">{{ item.StepName }}</div>
<!-- 折叠面板 -->
<el-collapse @change="handleCollapse">
<el-collapse-item
v-for="item1 in item.TaskProcess"
:key="`task-results-${item.Id}-${item1.ID}`"
:name="`task-results-${item.Id}-${item1.ID}`"
:disabled="Boolean(!agentsStore.executePlan.length || loading)"
@mouseenter="() => handleMouseEnter(`task-results-${item.Id}-0-${item1.ID}`)"
@mouseleave="handleMouseLeave"
>
<template v-if="loading" #icon>
<SvgIcon icon-class="loading" size="20px" class="animate-spin" />
</template>
<template v-else-if="!agentsStore.executePlan.length" #icon>
<span></span>
</template>
<template #title>
<div class="flex items-center gap-[15px]">
<!-- 右侧链接点 -->
<div
class="absolute right-0 top-1/2 transform -translate-y-1/2"
:id="`task-results-${item.Id}-0-${item1.ID}`"
></div>
<div
class="w-[41px] h-[41px] rounded-full flex items-center justify-center"
:style="{ background: getAgentMapIcon(item1.AgentName).color }"
>
<svg-icon
:icon-class="getAgentMapIcon(item1.AgentName).icon"
color="var(--color-text)"
size="24px"
/>
</div>
<div class="text-[16px]">
<span>{{ item1.AgentName }}:&nbsp; &nbsp;</span>
<span :style="{ color: getActionTypeDisplay(item1.ActionType)?.color }">
{{ getActionTypeDisplay(item1.ActionType)?.name }}
</span>
</div>
</div>
</template>
<ExecutePlan
:action-id="item1.ID"
:node-id="item.StepName"
:execute-plans="agentsStore.executePlan"
/>
</el-collapse-item>
</el-collapse>
</el-card>
<el-card
class="card-item w-full relative output-object-card"
shadow="hover"
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
:id="`task-results-${item.Id}-1`"
@click="emit('setCurrentTask', item)"
>
<!-- <div class="text-[18px]">{{ item.OutputObject }}</div>-->
<el-collapse @change="handleCollapse">
<el-collapse-item
class="output-object"
:disabled="Boolean(!agentsStore.executePlan.length || loading)"
>
<template v-if="loading" #icon>
<SvgIcon icon-class="loading" size="20px" class="animate-spin" />
</template>
<template v-else-if="!agentsStore.executePlan.length" #icon>
<span></span>
</template>
<template #title>
<div class="text-[18px]">{{ item.OutputObject }}</div>
</template>
<ExecutePlan :node-id="item.OutputObject" :execute-plans="agentsStore.executePlan" />
</el-collapse-item>
</el-collapse>
</el-card>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
#task-results {
:deep(.el-collapse) {
border: none;
border-radius: 20px;
.el-collapse-item + .el-collapse-item {
margin-top: 10px;
}
.el-collapse-item__header {
border: none;
background: var(--color-bg-secondary);
min-height: 41px;
line-height: 41px;
border-radius: 20px;
transition: border-radius 1ms;
position: relative;
.el-collapse-item__title {
background: var(--color-bg-secondary);
border-radius: 20px;
}
.el-icon {
font-size: 20px;
font-weight: bold;
}
&.is-active {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
.output-object {
.el-collapse-item__header {
background: none;
.el-collapse-item__title {
background: none;
}
}
.el-collapse-item__wrap {
background: none;
.card-item {
background: var(--color-bg-secondary);
padding: 25px;
padding-top: 10px;
border-radius: 7px;
}
}
}
.el-collapse-item__wrap {
border: none;
background: var(--color-bg-secondary);
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
}
}
:deep(.el-card) {
.el-card__body {
padding-right: 40px;
}
}
.output-object-card {
:deep(.el-card__body) {
padding-top: 0;
padding-bottom: 0;
padding-right: 0;
}
}
.active-card {
background:
linear-gradient(var(--color-bg-tertiary), var(--color-bg-tertiary)) padding-box,
linear-gradient(to right, #00c8d2, #315ab4) border-box;
}
.card-item + .card-item {
margin-top: 10px;
}
}
</style>

View File

@@ -0,0 +1,31 @@
<template>
<div class="absolute inset-0 flex items-start gap-[14%]">
<!-- 左侧元素 -->
<div class="flex-1 relative h-full flex justify-center">
<!-- 背景那一根线 -->
<div
class="h-full bg-[var(--color-bg-tertiary)] w-[5px]"
>
<!-- 线底部的小圆球 -->
<div
class="absolute bottom-0 left-1/2 transform -translate-x-1/2 bg-[var(--color-bg-tertiary)] w-[15px] h-[15px] rounded-full"
></div>
</div>
</div>
<!-- 右侧元素 -->
<div class="flex-1 relative h-full flex justify-center">
<!-- 背景那一根线 -->
<div
class="h-full bg-[var(--color-bg-tertiary)] w-[5px]"
>
<!-- 线底部的小圆球 -->
<div
class="absolute bottom-0 left-1/2 transform -translate-x-1/2 bg-[var(--color-bg-tertiary)] w-[15px] h-[15px] rounded-full"
></div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
</script>

View File

@@ -0,0 +1,204 @@
<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 } 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'] ?? []
})
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]">任务大纲</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-[45%] h-[41px] flex justify-center relative z-99 items-center rounded-[20px] bg-[var(--color-bg-tertiary)]"
>
流程
</div>
</div>
<div class="flex-1 flex justify-center">
<div
class="card-item w-[45%] h-[41px] flex justify-center relative z-99 items-center rounded-[20px] bg-[var(--color-bg-tertiary)]"
>
产物
</div>
</div>
</div>
<div
v-for="item in collaborationProcess"
:key="item.Id"
class="card-item w-full flex items-center gap-[14%]"
>
<!-- 流程卡片 -->
<el-card
class="w-[43%] overflow-y-auto relative z-99 task-syllabus-flow-card"
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
shadow="hover"
: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-[#494B51] my-[8px]"></div>
<MultiLineTooltip placement="right" :text="item.StepName" :lines="3">
<div
class="text-[14px] text-[var(--color-text-secondary)]"
:title="item.TaskContent"
>
{{ item.TaskContent }}
</div>
</MultiLineTooltip>
<div class="h-[1px] w-full bg-[#494B51] 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="var(--color-text)"
size="24px"
/>
</div>
</el-tooltip>
</div>
</el-card>
<!-- 产物卡片 -->
<el-card
class="w-[43%] relative"
shadow="hover"
: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>
</div>
</div>
</template>
<style lang="scss" scoped>
.task-syllabus-flow-card {
:deep(.el-card__body) {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: auto;
}
}
</style>

View File

@@ -0,0 +1,122 @@
<script setup lang="ts">
import AgentRepo from './AgentRepo/index.vue'
import TaskSyllabus from './TaskSyllabus/index.vue'
import TaskResult from './TaskResult/index.vue'
import { Jsplumb } from './utils.ts'
import { type IRawStepTask, useAgentsStore } from '@/stores'
import { BezierConnector } from '@jsplumb/browser-ui'
const agentsStore = useAgentsStore()
// 智能体库
const agentRepoJsplumb = new Jsplumb('task-template', {
connector: {
type: BezierConnector.type,
options: {
curviness: 30, // 曲线弯曲程度
stub: 20, // 添加连接点与端点的距离
alwaysRespectStubs: true,
},
},
})
// 任务流程
const taskSyllabusRef = ref<{
changeTask: (task?: IRawStepTask, isEmit?: boolean) => void
clear: () => void
}>()
// 执行结果
const taskResultRef = ref<{
createInternalLine: () => void
clear: () => void
}>()
const taskResultJsplumb = new Jsplumb('task-template')
function scrollToElementTop(elementId: string) {
const element = document.getElementById(elementId);
if (element) {
element.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
}
function handleTaskSyllabusCurrentTask(task: IRawStepTask) {
scrollToElementTop(`task-results-${task.Id}-0`)
agentsStore.setCurrentTask(task)
}
function handleTaskResultCurrentTask(task: IRawStepTask) {
scrollToElementTop(`task-syllabus-flow-${task.Id}`)
agentsStore.setCurrentTask(task)
// 更新任务大纲内部的线
taskSyllabusRef.value?.changeTask(task, false)
}
function changeTask() {
taskResultRef.value?.createInternalLine()
taskSyllabusRef.value?.changeTask()
}
function resetAgentRepoLine() {
agentRepoJsplumb.repaintEverything()
taskResultJsplumb.repaintEverything()
}
function clear() {
taskSyllabusRef.value?.clear()
taskResultRef.value?.clear()
agentRepoJsplumb.repaintEverything()
taskResultJsplumb.repaintEverything()
}
defineExpose({
changeTask,
resetAgentRepoLine,
clear,
})
</script>
<template>
<div
class="task-template flex gap-6 items-center h-[calc(100%-84px)] relative overflow-hidden"
id="task-template"
>
<!-- 智能体库 -->
<div class="w-[9.5%] min-w-[179px] h-full relative flex-shrink-0">
<AgentRepo @resetAgentRepoLine="agentRepoJsplumb.repaintEverything" />
</div>
<!-- 任务大纲 -->
<div class="w-[35.5%] min-w-[600px] h-full px-[20px] flex-shrink-0">
<TaskSyllabus
ref="taskSyllabusRef"
@resetAgentRepoLine="resetAgentRepoLine"
@set-current-task="handleTaskSyllabusCurrentTask"
/>
</div>
<!-- 执行结果 -->
<div class="flex-1 h-full">
<TaskResult
ref="taskResultRef"
@refresh-line="taskResultJsplumb.repaintEverything"
@set-current-task="handleTaskResultCurrentTask"
/>
</div>
</div>
</template>
<style scoped lang="scss">
.task-template {
& > div {
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.8);
border-radius: 24px;
border: 1px solid #414752;
background: var(--color-bg-quinary);
padding-top: 20px;
padding-bottom: 20px;
}
}
</style>

View File

@@ -0,0 +1,169 @@
import type {
AnchorSpec,
ConnectorSpec,
ConnectParams,
EndpointOptions,
JsPlumbInstance,
} from '@jsplumb/browser-ui'
import { BezierConnector, DotEndpoint, newInstance } from '@jsplumb/browser-ui'
export interface JsplumbConfig {
connector?: ConnectorSpec
type?: 'input' | 'output'
stops?: [[number, string], [number, string]]
// 连接线条是否变透明一些
transparent?: boolean
}
export interface ConnectArg {
sourceId: string
targetId: string
anchor: AnchorSpec
config?: JsplumbConfig
}
const defaultConfig: JsplumbConfig = {
connector: {
type: BezierConnector.type,
options: {
curviness: 70,
stub: 10,
},
},
type: 'input',
}
export class Jsplumb {
instance!: JsPlumbInstance
containerId: string
config: JsplumbConfig
constructor(eleId: string, config = {} as JsplumbConfig) {
this.containerId = eleId
this.config = { ...defaultConfig, ...config }
onMounted(() => {
this.init()
})
}
init = () => {
if (this.instance) {
return
}
this.instance = newInstance({
container: document.querySelector(`#${this.containerId}`)!, // 或指定共同的父容器
})
}
getStops = (type?: 'input' | 'output'): [[number, string], [number, string]] => {
if (type === 'input') {
return [
[0, '#FF6161'],
[1, '#D76976'],
]
}
return [
[0, '#0093EB'],
[1, '#00D2D1'],
]
}
_connect = (
sourceId: string,
targetId: string,
anchor: AnchorSpec,
_config = {} as JsplumbConfig,
) => {
const config = {
...defaultConfig,
...this.config,
..._config,
}
this.init()
// 连接两个元素
const sourceElement = document.querySelector(`#${sourceId}`)
const targetElement = document.querySelector(`#${targetId}`)
const stops = _config.stops ?? this.getStops(config.type)
// 如果config.transparent为true则将stops都加一些透明度
if (config.transparent) {
stops[0][1] = stops[0][1] + '30'
stops[1][1] = stops[1][1] + '30'
}
if (targetElement && sourceElement) {
this.instance.connect({
source: sourceElement,
target: targetElement,
connector: config.connector,
anchor: anchor,
paintStyle: {
stroke: stops[0][1],
strokeWidth: 2.5,
dashstyle: '0',
zIndex: 100,
opacity: 0.9,
gradient: {
stops: stops,
type: 'linear',
},
},
sourceEndpointStyle: { fill: stops[0][1] },
endpoint: {
type: DotEndpoint.type,
options: { radius: 5 },
},
cssClass: `jtk-connector-${config.type}`
} as unknown as ConnectParams<unknown>)
// 为源元素添加端点
this.instance.addEndpoint(sourceElement, {
anchor: (anchor as [AnchorSpec, AnchorSpec])[0],
paintStyle: { fill: stops[0][1], zIndex: 100 }, // source端点颜色
} as unknown as EndpointOptions)
// 为目标元素添加端点
this.instance.addEndpoint(targetElement, {
anchor: (anchor as [AnchorSpec, AnchorSpec])[1],
paintStyle: { fill: stops[1][1], zIndex: 100 }, // target端点颜色
} as unknown as EndpointOptions)
}
}
connect = (
sourceId: string,
targetId: string,
anchor: AnchorSpec,
config = {} as JsplumbConfig,
) => {
this._connect(sourceId, targetId, anchor, config)
}
connects = (args: ConnectArg[]) => {
this.instance.batch(() => {
args.forEach(({ sourceId, targetId, anchor, config }) => {
this._connect(sourceId, targetId, anchor, config)
})
})
}
repaintEverything = () => {
// 重新验证元素位置
const container = document.querySelector(`#${this.containerId}`)
if (container) {
const elements = container.querySelectorAll('[id^="task-results-"]')
elements.forEach((element) => {
this.instance.revalidate(element)
})
}
this.instance.repaintEverything()
}
reset = () => {
this.instance.deleteEveryConnection()
const allEndpoints = this.instance.selectEndpoints()
allEndpoints.each((endpoint) => {
this.instance.deleteEndpoint(endpoint)
})
}
}

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
import Task from './Task.vue'
import TaskTemplate from './TaskTemplate/index.vue'
import { nextTick } from 'vue'
const taskTemplateRef = ref<{ changeTask: () => void, clear: () => void }>()
function handleSearch() {
nextTick(() => {
taskTemplateRef.value?.changeTask()
})
}
</script>
<template>
<div class="p-[24px] h-[calc(100%-60px)]">
<Task @search="handleSearch" @search-start="taskTemplateRef?.clear" />
<TaskTemplate ref="taskTemplateRef" />
</div>
</template>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,145 @@
export interface AgentMapIcon {
name: string
icon: string
color: string
}
// "肾脏病学家": {
// name: "肾脏病学家",
// icon: "doctor",
// color: "#00A2D2",
// },
export const agentMapIcon = new Map<string, AgentMapIcon>()
agentMapIcon.set("船舶设计师", {
name: '船舶设计师',
icon: 'shejishi',
color: '#65AE00',
})
agentMapIcon.set("防护工程专家", {
name: '防护工程专家',
icon: 'engineer',
color: '#B06CFE',
})
agentMapIcon.set("病理生理学家", {
name: '病理生理学家',
icon: 'doctor',
color: '#00A2D2',
})
agentMapIcon.set("药物化学家", {
name: '药物化学家',
icon: 'specialist',
color: '#FF7914',
})
agentMapIcon.set("制剂工程师", {
name: '制剂工程师',
icon: 'medical',
color: '#65AE00',
})
agentMapIcon.set("监管事务专家", {
name: '监管事务专家',
icon: 'researcher',
color: '#0064C4',
})
agentMapIcon.set("物理学家", {
name: '物理学家',
icon: 'specialist',
color: '#B06CFE',
})
agentMapIcon.set("实验材料学家", {
name: '实验材料学家',
icon: 'researcher',
color: '#C01E6A',
})
agentMapIcon.set("计算模拟专家", {
name: '计算模拟专家',
icon: 'researcher',
color: '#FF7914',
})
agentMapIcon.set("腐蚀机理研究员", {
name: '腐蚀机理研究员',
icon: 'specialist',
color: '#00C8D2',
})
agentMapIcon.set("先进材料研发员", {
name: '先进材料研发员',
icon: 'engineer',
color: '#00C8D2',
})
agentMapIcon.set("肾脏病学家", {
name: '肾脏病学家',
icon: 'doctor',
color: '#00A2D2',
})
agentMapIcon.set("临床研究协调员", {
name: '临床研究协调员',
icon: 'renyuan',
color: '#FF7914',
})
agentMapIcon.set("中医药专家", {
name: '中医药专家',
icon: 'medical',
color: '#00C8D2',
})
agentMapIcon.set("药物安全专家", {
name: '药物安全专家',
icon: 'medical',
color: '#65AE00',
})
agentMapIcon.set("二维材料科学家", {
name: '二维材料科学家',
icon: 'shejishi',
color: '#EB6363',
})
agentMapIcon.set("光电物理学家", {
name: '光电物理学家',
icon: 'specialist',
color: '#079EFF',
})
agentMapIcon.set("机器学习专家", {
name: '机器学习专家',
icon: 'researcher',
color: '#8700AE',
})
agentMapIcon.set("流体动力学专家", {
name: '流体动力学专家',
icon: 'specialist',
color: '#EB6363',
})
export function getAgentMapIcon(agentName: string):AgentMapIcon {
return agentMapIcon.get(agentName) ?? agentMapIcon.get('监管事务专家')!
}
export interface AgentMapDuty {
name: string
key: string
color: string
}
// 职责映射
// 提议 评审 改进 总结
export const agentMapDuty: Record<string, AgentMapDuty> = {
Propose: {
name: '提议',
key: 'propose',
color: '#06A3FF',
},
Critique: {
name: '评审',
key: 'review',
color: '#FFFC08',
},
Improve: {
name: '改进',
key: 'improve',
color: '#BF65FF',
},
Finalize: {
name: '总结',
key: 'summary',
color: '#FFA236',
},
}
export function getActionTypeDisplay(type: string) {
return agentMapDuty[type]
}

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
import Header from './components/Header.vue'
import Main from './components/Main/index.vue'
</script>
<template>
<div class="h-full">
<Header />
<Main />
</div>
</template>

23
frontend/src/main.ts Normal file
View File

@@ -0,0 +1,23 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import './styles/index.scss'
import './styles/tailwindcss.css'
import 'element-plus/theme-chalk/dark/css-vars.css'
import 'virtual:svg-icons-register'
import { initService } from '@/utils/request.ts'
import { setupStore, useConfigStore } from '@/stores'
async function init() {
const app = createApp(App)
setupStore(app)
initService()
const configStore = useConfigStore()
await configStore.initConfig()
document.title = configStore.config.centerTitle
app.use(router)
app.mount('#app')
}
void init()

View File

@@ -0,0 +1,8 @@
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [],
})
export default router

View File

@@ -0,0 +1,12 @@
import { createPinia } from 'pinia'
import type { App } from 'vue'
export const store = createPinia()
// 全局注册 store
export function setupStore(app: App<Element>) {
app.use(store)
}
export * from './modules/agents.ts'
export * from './modules/config.ts'

View File

@@ -0,0 +1,152 @@
import { ref } from 'vue'
import { defineStore } from 'pinia'
import { v4 as uuidv4 } from 'uuid'
import { store } from '../index'
import { useStorage } from '@vueuse/core'
import type { IExecuteRawResponse } from '@/api'
import { useConfigStore } from '@/stores/modules/config.ts'
export interface Agent {
Name: string
Profile: string
Icon: string
Classification: string
}
type HslColorVector = [number, number, number]
export interface IRichText {
template: string
data: Record<
string,
{
text: string
style?: Record<string, string>
color?: HslColorVector
}
>
}
export interface TaskProcess {
ActionType: string
AgentName: string
Description: string
ID: string
ImportantInput: string[]
}
export interface IRawStepTask {
Id?: string
StepName?: string
TaskContent?: string
InputObject_List?: string[]
OutputObject?: string
AgentSelection?: string[]
Collaboration_Brief_FrontEnd: IRichText
TaskProcess: TaskProcess[]
}
export interface IRawPlanResponse {
'Initial Input Object'?: string[] | string
'General Goal'?: string
'Collaboration Process'?: IRawStepTask[]
}
const storageKey = '$agents' as const
// 清除所有以 storageKey 开头的 localStorage
function clearStorageByVersion() {
Object.keys(localStorage)
.filter((key) => key.startsWith(storageKey))
.forEach((key) => localStorage.removeItem(key))
}
export const useAgentsStore = defineStore('agents', () => {
const configStore = useConfigStore()
const agents = useStorage<Agent[]>(`${storageKey}-repository`, [])
function setAgents(agent: Agent[]) {
agents.value = agent
}
// 任务搜索的内容
const searchValue = useStorage<string>(`${storageKey}-search-value`, '')
function setSearchValue(value: string) {
searchValue.value = value
}
const storageVersionIdentifier = useStorage<string>(`${storageKey}-storage-version-identifier`, '')
// 监听 configStore.config.agentRepository.storageVersionIdentifier 改变
watch(
() => configStore.config.agentRepository.storageVersionIdentifier,
(value) => {
// value与storageVersionIdentifier不一致清除所有storageKey开头的localStorage
if (value !== storageVersionIdentifier.value) {
clearStorageByVersion()
storageVersionIdentifier.value = value
}
},
{
immediate: true,
}
)
// 当前的展示的任务流程
const currentTask = ref<IRawStepTask>()
function setCurrentTask(task: IRawStepTask) {
currentTask.value = task
}
const agentRawPlan = ref<{ data?: IRawPlanResponse; loading?: boolean }>({ loading: false })
function setAgentRawPlan(plan: { data?: IRawPlanResponse; loading?: boolean }) {
if (plan.data) {
plan.data['Collaboration Process'] = plan.data['Collaboration Process']?.map((item) => ({
...item,
Id: uuidv4(),
}))
}
agentRawPlan.value = {
...agentRawPlan.value,
...plan,
}
}
// 执行完任务的结果
const executePlan = ref<IExecuteRawResponse[]>([])
function setExecutePlan(plan: IExecuteRawResponse[]) {
executePlan.value = plan
}
function resetAgent() {
agentRawPlan.value = {
loading: false,
}
currentTask.value = undefined
executePlan.value = []
}
return {
agents,
setAgents,
searchValue,
setSearchValue,
currentTask,
setCurrentTask,
agentRawPlan,
setAgentRawPlan,
executePlan,
setExecutePlan,
resetAgent,
}
})
/**
* 用于在组件外部如在Pinia Store 中)使用 Pinia 提供的 store 实例。
* 官方文档解释了如何在组件外部使用 Pinia Store
* https://pinia.vuejs.org/core-concepts/outside-component-usage.html#using-a-store-outside-of-a-component
*/
export function useAgentsStoreHook() {
return useAgentsStore(store)
}

View File

@@ -0,0 +1,39 @@
import { defineStore } from 'pinia'
import { readConfig } from '@/utils/readJson.ts'
import { store } from '@/stores'
export interface Config {
title: string
subTitle: string
centerTitle: string
taskPromptWords: string[]
agentRepository: {
storageVersionIdentifier: string
}
}
export const useConfigStore = defineStore('config', () => {
const config = ref<Config>({} as Config)
// 异步调用readConfig
async function initConfig() {
config.value = await readConfig<Config>('config.json')
}
return {
config,
initConfig
}
})
/**
* 用于在组件外部如在Pinia Store 中)使用 Pinia 提供的 store 实例。
* 官方文档解释了如何在组件外部使用 Pinia Store
* https://pinia.vuejs.org/core-concepts/outside-component-usage.html#using-a-store-outside-of-a-component
*/
export function useConfigStoreHook() {
return useConfigStore(store)
}

View File

@@ -0,0 +1,27 @@
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: (
'primary': (
'base': #165DFF,
),
'success': (
'base': #52C41A,
),
'warning': (
'base': #FAAD14,
),
'danger': (
'base': #FF4D4F,
),
),
$card: (
'border-radius': 0.75rem,
),
$text-color: (
'secondary': #86909c,
'regular': #1d2129,
)
);

View File

@@ -0,0 +1,3 @@
@use "reset";
@use "theme";
@use "element/index";

View File

@@ -0,0 +1,76 @@
*,
::before,
::after {
box-sizing: border-box;
border-color: currentcolor;
border-style: solid;
border-width: 0;
}
#app {
width: 100%;
height: 100%;
}
html {
box-sizing: border-box;
width: 100%;
height: 100%;
line-height: 1.5;
tab-size: 4;
text-size-adjust: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
"Microsoft YaHei", "微软雅黑", Arial, sans-serif;
line-height: inherit;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizelegibility;
}
a {
color: inherit;
text-decoration: inherit;
}
img,
svg {
display: inline-block;
}
svg {
// 因icon大小被设置为和字体大小一致而span等标签的下边缘会和字体的基线对齐故需设置一个往下的偏移比例来纠正视觉上的未对齐效果
vertical-align: -0.15em;
}
ul,
li {
padding: 0;
margin: 0;
list-style: none;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
a,
a:focus,
a:hover {
color: inherit;
text-decoration: none;
cursor: pointer;
}
a:focus,
a:active,
div:focus {
outline: none;
}

View File

@@ -0,0 +1 @@
@import 'tailwindcss';

View File

@@ -0,0 +1,16 @@
:root {
--color-bg: #fff;
--color-bg-secondary: #fafafa;
--color-bg-tertiary: #f5f5f5;
--color-text: #000;
}
html.dark {
--color-bg: #131A27;
--color-bg-secondary: #050505;
--color-bg-tertiary: #20222A;
--color-bg-quaternary: #24252A;
--color-bg-quinary: #29303c;
--color-text: #fff;
--color-text-secondary: #C9C9C9;
}

View File

@@ -0,0 +1,15 @@
$bg: var(--color-bg);
$bg-secondary: var(--color-bg-secondary);
$bg-tertiary: var(--color-bg-tertiary);
$bg-quaternary: var(--color-bg-quaternary);
$text: var(--color-text);
$text-secondary: var(--color-text-secondary);
:export {
bg: $bg;
bg-secondary: $bg-secondary;
bg-tertiary: $bg-tertiary;
bg-quaternary: $bg-quaternary;
text: $text;
text-secondary: $text-secondary;
}

View File

@@ -0,0 +1,103 @@
import type { IRawStepTask, IRichText } from '@/stores'
function nameJoin(names: string[]): string {
// join names with comma, and 'and' for the last one
const tmp = [...names]
const last = tmp.pop()!
let t = tmp.join(', ')
if (t.length > 0) {
t = `${t}${last}`
} else {
t = last
}
return t
}
export function changeBriefs(task?: IRawStepTask[]): IRawStepTask[] {
if (!task) {
return []
}
return task.map((item) => {
const record = {
...item,
Collaboration_Brief_FrontEnd: changeBrief(item),
}
return record
})
}
function changeBrief(task: IRawStepTask): IRichText {
// 如果不存在AgentSelection直接返回
const agents = task.AgentSelection ?? []
if (agents.length === 0) {
return task.Collaboration_Brief_FrontEnd
}
const data: IRichText['data'] = {};
let indexOffset = 0;
// 根据InputObject_List修改
const inputs = task.InputObject_List ?? []
const inputPlaceHolders = inputs.map((text, index) => {
data[(index + indexOffset).toString()] = {
text,
style: { background: '#ACDBA0' },
};
return `!<${index + indexOffset}>!`;
});
const inputSentence = nameJoin(inputPlaceHolders);
indexOffset += inputs.length;
// 根据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;
let actionSentence = task.TaskContent ?? '';
// delete the last '.' of actionSentence
if (actionSentence[actionSentence.length - 1] === '.') {
actionSentence = actionSentence.slice(0, -1);
}
const actionIndex = indexOffset++;
data[actionIndex.toString()] = {
text: actionSentence,
style: { background: '#DDD', border: '1.5px solid #ddd' },
};
let outputSentence = '';
const output = task.OutputObject ?? '';
if (output) {
data[indexOffset.toString()] = {
text: output,
style: { background: '#FFCA8C' },
};
outputSentence = `得到 !<${indexOffset}>!`;
}
// Join them togeter
let content = inputSentence;
if (content) {
content = `基于${content}, ${nameSentence} 执行任务 !<${actionIndex}>!`;
} else {
content = `${nameSentence} 执行任务 !<${actionIndex}>!`;
}
if (outputSentence) {
content = `${content}, ${outputSentence}.`;
} else {
content = `${content}.`;
}
content = content.trim();
return {
template: content,
data,
};
}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,4 @@
export async function readConfig<T>(fileName = 'config.json'): Promise<T> {
const url = `${location.protocol}//${location.host}${location.pathname}${fileName}`
return await fetch(url).then<T>((res) => res.json())
}

View File

@@ -0,0 +1,135 @@
import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
import axios from 'axios'
import qs from 'qs'
import type { Ref } from 'vue'
import { ElNotification } from 'element-plus'
import { ref } from 'vue'
// 创建 axios 实例
let service: AxiosInstance
export interface AxiosResponseData {
code: number
content: string
}
export function initService() {
service = axios.create({
baseURL: '/api',
timeout: 50000,
headers: { 'Content-Type': 'application/json;charset=utf-8' },
paramsSerializer: (params) => {
return qs.stringify(params)
},
})
// 请求拦截器
service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
return config
},
(error: Error) => {
return Promise.reject(error)
},
)
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse<AxiosResponseData, unknown>) => {
// 判断响应状态码是否为2xx
if (response.status < 200 || response.status >= 300) {
throw new Error(response.data?.content)
}
// 检查配置的响应类型是否为二进制类型('blob' 或 'arraybuffer', 如果是,直接返回响应对象
if (
response.config.responseType === 'blob' ||
response.config.responseType === 'arraybuffer'
) {
return response as unknown as AxiosResponse
}
return response.data as unknown as AxiosResponse
},
(error: AxiosError<AxiosResponseData | string>) => {
let message: string = ''
if (error.response && error.response.status === 500) {
message = '系统错误'
}
// if (error.config?.url === '/irs/data-do' && error.config?.method === 'get') {
// return Promise.reject(new Error(message))
// }
return Promise.reject(new Error(message))
},
)
}
export interface UseAxiosOption {
// 错误时不弹窗
hideErrorTip?: boolean
}
// 导出 axios 实例
export async function request<T = unknown, R = AxiosResponse<T>, D = unknown>(
config: AxiosRequestConfig<D>,
{ hideErrorTip = false } = {} as UseAxiosOption,
): Promise<R> {
try {
return await service<T, R, D>(config)
} catch (error) {
if (!hideErrorTip) {
ElNotification({
showClose: true,
message: (error as Error)?.message,
type: 'error',
})
}
return Promise.reject(error)
}
}
export default request
export interface UseRequestOption<R> extends UseAxiosOption {
defaultData?: R
}
export interface UseRequestResult<R> {
data: Ref<R>
error: Ref<Error | undefined>
loading: Ref<boolean>
refresh: () => Promise<void>
}
export function useRequest<T = unknown, R = AxiosResponse<T>, D = unknown>(
config: AxiosRequestConfig<D>,
{ defaultData = {} as R, ...rest } = {} as UseRequestOption<R>,
) {
const data = ref<R>(defaultData) as Ref<R>
const loading = ref<boolean>(false)
const error = ref<Error>()
const fetchResource = async (conf = config): Promise<void> => {
loading.value = true
try {
data.value = await request<T, R, D>(conf, rest)
} catch (e) {
error.value = e as Error
} finally {
loading.value = false
}
}
const promise = new Promise((resolve) => {
void fetchResource().finally(() => resolve({ data, error, loading, refresh: fetchResource }))
}) as unknown as UseRequestResult<R> & Promise<UseRequestResult<R>>
promise.data = data
promise.error = error
promise.loading = loading
promise.refresh = fetchResource
return promise
}