Compare commits

...

25 Commits

Author SHA1 Message Date
zhaoweijie
e0cc11647f feat(plan): 添加中文响应要求并本地化操作提示
- 在多个规划引擎文件中添加中文响应的语言要求说明
- 将操作相关的提示文本从英文翻译为中文
- 确保代理协作中的所有解释和推理使用中文输出
- 保持代理名称等标识符的原始格式不变
2026-01-13 09:49:53 +08:00
liailing1026
59fd94e783 feat:智能体探索窗口与任务大纲探索窗口联动 2026-01-12 17:27:37 +08:00
liailing1026
3ff70463ca feat:任务执行执行状态加载动画添加 2026-01-12 11:17:18 +08:00
liailing1026
82e92f12aa feat:任务大纲卡片超出3行提示框设置最大宽度 2026-01-10 22:07:23 +08:00
liailing1026
920588b063 feat:三个窗口接口联调版本 2026-01-09 13:54:32 +08:00
liailing1026
5847365eee feat:三个浮动窗口功能新增 2025-12-31 19:04:58 +08:00
liailing1026
d42554ce03 feat:智能体库agent左对齐样式问题 2025-12-22 17:15:06 +08:00
zhaoweijie
bcc0c53ba1 feat(config): 支持配置 API 基础路径和开发环境标识
- 在配置存储中新增 dev 和 apiBaseUrl 字段
- 设置默认配置对象,包含基础 URL 构造逻辑
- 更新请求工具以使用动态配置的 baseURL
- 调整应用初始化顺序确保配置先行加载
- 移除指令中不必要的调试日志输出
2025-12-21 20:26:21 +08:00
zhaoweijie
7da5e82d40 feat(ui): 更新任务流程卡片样式和图标
- 引入 Element Plus 图标组件替代原有文字图标
- 移除卡片阴影属性优化视觉效果
- 调整结果区域按钮组布局对齐方式
- 更新文本域样式增加颜色变量支持
- 注释掉编辑卡片背景色定义
- 添加图标依赖包到项目配置
2025-12-21 19:57:33 +08:00
zhaoweijie
cc22655a1e feat(dev): 添加开发模式专用指令和配置支持
- 在 config.json 中新增 dev 配置项用于区分开发与生产环境
- 实现 v-dev-only 指令,仅在开发模式下渲染元素
- 注册全局自定义指令 dev-only,支持通过 binding.value 控制启用状态
- 在 TaskSyllabus/Bg.vue 中使用 v-dev-only 指令隐藏生产环境下的加号区域
- 移除 card 样式中的固定 margin-bottom,改由容器控制间距
- 统一使用 CSS 变量 --color-border-separate 替代硬编码的分割线颜色
- 为 Task.vue 的搜索框添加 clearable 属性并移除弹出项的阴影效果
- Layout 组件名称规范化为首字母大写
- 在主题样式中定义深色与浅色模式下的 --color-border-separate 颜色值
- 覆盖 Element Plus 的 --el-fill-color-blank 以适配暗黑模式背景透明度需求
2025-12-21 18:10:37 +08:00
liailing1026
f0db3c88e4 feat:AdditionalOutputCard.vue未开发完毕版本 2025-12-21 15:56:40 +08:00
liailing1026
b987fe70ad feat:额外产物添加 2025-12-21 15:28:59 +08:00
liailing1026
b42ab5aedd feat:单个agent配置各自的apiurl、apimodel、apikey 2025-12-18 09:51:07 +08:00
zhaoweijie
5ef86c6fa9 fix(LLMAPI): 增加API调用超时时间
- 将LLM API调用的超时时间从15秒延长到60秒
- 防止因网络延迟或模型响应慢导致的超时错误
- 提高长文本生成任务的稳定性
2025-12-17 09:27:00 +08:00
zhaoweijie
907310365a feat(task): 添加任务过程编辑功能
- 新增 ProcessCard 组件用于展示和编辑任务流程
- 实现双击编辑任务描述功能
- 添加编辑状态下的卡片式输入界面
- 支持保存和取消编辑操作
- 实现鼠标悬停高亮效果
- 添加颜色处理函数用于界面美化
- 集成到 TaskResult 组件中展示任务过程
- 支持动态创建和管理任务流程连接线
- 添加额外产物编辑功能
- 实现按钮交互状态管理
- 添加滚动和折叠面板事件处理
- 集成 AgentAllocation 组件用于智能体分配
- 实现椭圆框交互效果展示选中状态
- 添加智能体等级颜色配置
- 支持智能体选中状态切换和排序
2025-12-15 20:47:51 +08:00
zhaoweijie
5dace5f788 feat(assets): 添加 SVG 图标和环境配置文件
- 新增 icons.svg 文件用于定义应用图标
- 添加 .env 配置文件设置 API 基础地址
- 配置图标视图框和路径数据
- 设置图标宽度为 100%,高度为 6
2025-12-15 20:47:28 +08:00
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
zhaoweijie
6392301833 refactor(LLMAPI): 重构LLM接口以支持新版本OpenAI SDK
- 升级openai依赖至2.x版本并替换旧版SDK调用方式
- 引入OpenAI和AsyncOpenAI客户端实例替代全局配置
- 更新所有聊天完成请求方法以适配新版API格式
- 为异步流式响应处理添加异常捕获和错误提示
- 统一超时时间和最大token数等默认参数设置
- 修复部分变量命名冲突和潜在的空值引用问题
- 添加打印彩色日志的辅助函数避免循环导入问题
2025-11-22 17:01:25 +08:00
zhaoweijie
ab8c9e294d feat:rename subtree from frontend-vue to frontend 2025-11-20 09:56:51 +08:00
zhaoweijie
1aa9e280b0 Add 'frontend-vue/' from commit '041986f5cd6e67f5367fd047c71b8107865fa5af'
git-subtree-dir: frontend-vue
git-subtree-mainline: 4fa5504697
git-subtree-split: 041986f5cd
2025-11-20 09:51:44 +08:00
zhaoweijie
041986f5cd feat(config): 添加配置文件支持动态标题和提示词
- 新增 public/config.json 配置文件,包含网站标题、副标题及任务提示词
- 在 Header 组件中读取并应用配置中的标题信息-为 Task 组件的搜索建议框引入配置中的提示词列表
- 创建 useConfigStore 管理全局配置状态,并在应用初始化时加载配置
- 更新 main.ts 在应用启动时设置文档标题
- 移除了 Task.vue 中硬编码的提示词数组,改由配置驱动-修复了 agents store 中版本标识监听逻辑,实现存储清理功能
- 添加 MultiLineTooltip 组件用于文本溢出时显示完整内容
-重构 TaskSyllabus 页面布局与样式,提升视觉效果与交互体验
- 引入 Bg
2025-11-04 15:26:52 +08:00
zhaoweijie
00ef22505e feat(agent):重构智能体仓库并优化任务模板交互
-为 public/agent.json 中的每个智能体添加 Classification 字段以支持分类展示
- 新增 AgentRepoList 组件用于渲染智能体列表,提升代码复用性
- 在 src/layout/components/Main/TaskTemplate/AgentRepo/index.vue 中实现基于 Classification 的智能体分组展示逻辑- 移除旧版 popover 方式展示智能体详情,改用新的列表组件统一处理
- 修改任务搜索输入框为 textarea 类型,并优化其聚焦与失焦状态下的样式表现
- 调整任务模板页面布局高度计算方式,确保适配新 UI 结构
-修复任务结果流程图连线方向及透明度判断逻辑,增强可视化准确性- 引入流动动画效果至 jsPlumb 连线,区分 input/output 类型并美化视觉呈现
- 更新配置文件中部分动作类型的配色值,提高界面美观度
- 升级本地存储键名 agents 至 agents-v1,避免
2025-11-03 09:44:14 +08:00
zhaoweijie
b73419b7a0 feat(agent): 初始化代理仓库并优化UI显示
- 添加默认agent.json配置文件,包含19个预定义代理角色及其简介
- 实现AgentRepo组件挂载时自动读取默认配置逻辑
- 优化任务模板中代理操作项的渲染样式和分割线显示
- 调整卡片悬停样式,去除阴影并添加背景色过渡动画
2025-11-02 12:55:13 +08:00
zhaoweijie
974af053ca feat(task):优化任务执行与智能体展示功能
- 更新action.svg图标样式- 重构AgentRepo组件,优化智能体列表展示逻辑
- 改进ExecutePlan组件,支持object类型节点渲染
- 完善TaskResult组件,增加执行计划存储与清理机制
- 调整TaskSyllabus组件,优化卡片激活状态样式
- 在Task组件中添加搜索建议功能
- 更新主题配色变量和全局样式- 替换ElInput为ElAutocomplete组件
- 清理无用的jsplumb连接代码- 优化组件间通信与状态管理
2025-10-31 18:42:31 +08:00
zhaoweijie
0c571dec21 Initial commit: Multi-Agent Coordination Platform
- Vue 3 + TypeScript + Vite project structure
- Element Plus UI components with dark theme
- Pinia state management for agents and tasks
- JSPlumb integration for visual workflow editing
- SVG icon system for agent roles
- Axios request layer with API proxy configuration
- Tailwind CSS for styling
- Docker deployment with Caddy web server
- Complete development toolchain (ESLint, Prettier, Vitest)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 10:22:14 +08:00
133 changed files with 23535 additions and 124 deletions

View File

@@ -1,24 +1,35 @@
import asyncio
import openai
import httpx
from openai import OpenAI, AsyncOpenAI, max_retries
import yaml
from termcolor import colored
import os
# Helper function to avoid circular import
def print_colored(text, text_color="green", background="on_white"):
print(colored(text, text_color, background))
# load config (apikey, apibase, model)
yaml_file = os.path.join(os.getcwd(), "config", "config.yaml")
try:
with open(yaml_file, "r", encoding="utf-8") as file:
yaml_data = yaml.safe_load(file)
except Exception:
yaml_file = {}
yaml_data = {}
OPENAI_API_BASE = os.getenv("OPENAI_API_BASE") or yaml_data.get(
"OPENAI_API_BASE", "https://api.openai.com"
)
openai.api_base = OPENAI_API_BASE
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") or yaml_data.get(
"OPENAI_API_KEY", ""
)
openai.api_key = OPENAI_API_KEY
OPENAI_API_MODEL = os.getenv("OPENAI_API_MODEL") or yaml_data.get(
"OPENAI_API_MODEL", ""
)
# Initialize OpenAI clients
client = OpenAI(api_key=OPENAI_API_KEY, base_url=OPENAI_API_BASE)
async_client = AsyncOpenAI(api_key=OPENAI_API_KEY, base_url=OPENAI_API_BASE)
MODEL: str = os.getenv("OPENAI_API_MODEL") or yaml_data.get(
"OPENAI_API_MODEL", "gpt-4-turbo-preview"
)
@@ -35,8 +46,11 @@ MISTRAL_API_KEY = os.getenv("MISTRAL_API_KEY") or yaml_data.get(
# for LLM completion
def LLM_Completion(
messages: list[dict], stream: bool = True, useGroq: bool = True
messages: list[dict], stream: bool = True, useGroq: bool = True,model_config: dict = None
) -> str:
if model_config:
print_colored(f"Using model config: {model_config}", "blue")
return _call_with_custom_config(messages,stream,model_config)
if not useGroq or not FAST_DESIGN_MODE:
force_gpt4 = True
useGroq = False
@@ -69,16 +83,92 @@ def LLM_Completion(
return _chat_completion(messages=messages)
def _call_with_custom_config(messages: list[dict], stream: bool, model_config: dict) ->str:
"使用自定义配置调用API"
api_url = model_config.get("apiUrl", OPENAI_API_BASE)
api_key = model_config.get("apiKey", OPENAI_API_KEY)
api_model = model_config.get("apiModel", OPENAI_API_MODEL)
temp_client = OpenAI(api_key=api_key, base_url=api_url)
temp_async_client = AsyncOpenAI(api_key=api_key, base_url=api_url)
try:
if stream:
try:
loop = asyncio.get_event_loop()
except RuntimeError as ex:
if "There is no current event loop in thread" in str(ex):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
return loop.run_until_complete(
_achat_completion_stream_custom(messages=messages, temp_async_client=temp_async_client, api_model=api_model)
)
else:
response = temp_client.chat.completions.create(
messages=messages,
model=api_model,
temperature=0.3,
max_tokens=4096,
timeout=180
)
full_reply_content = response.choices[0].message.content
print(colored(full_reply_content, "blue", "on_white"), end="")
return full_reply_content
except Exception as e:
print_colored(f"Custom API error for model {api_model} :{str(e)}","red")
raise
async def _achat_completion_stream_custom(messages:list[dict], temp_async_client, api_model: str ) -> str:
max_retries=3
for attempt in range(max_retries):
try:
response = await temp_async_client.chat.completions.create(
messages=messages,
model=api_model,
temperature=0.3,
max_tokens=4096,
stream=True,
timeout=180
)
collected_chunks = []
collected_messages = []
async for chunk in response:
collected_chunks.append(chunk)
choices = chunk.choices
if len(choices) > 0:
chunk_message = chunk.choices[0].delta
collected_messages.append(chunk_message)
if chunk_message.content:
print(colored(chunk_message.content, "blue", "on_white"), end="")
print()
full_reply_content = "".join(
[m.content or "" for m in collected_messages if m is not None]
)
return full_reply_content
except httpx.RemoteProtocolError as e:
if attempt < max_retries - 1:
wait_time = (attempt + 1) *2
print_colored(f"⚠️ Stream connection interrupted (attempt {attempt+1}/{max_retries}). Retrying in {wait_time}s...", text_color="yellow")
await asyncio.sleep(wait_time)
continue
except Exception as e:
print_colored(f"Custom API stream error for model {api_model} :{str(e)}","red")
raise
async def _achat_completion_stream_groq(messages: list[dict]) -> str:
from groq import AsyncGroq
client = AsyncGroq(api_key=GROQ_API_KEY)
groq_client = AsyncGroq(api_key=GROQ_API_KEY)
max_attempts = 5
for attempt in range(max_attempts):
print("Attempt to use Groq (Fase Design Mode):")
try:
stream = await client.chat.completions.create(
response = await groq_client.chat.completions.create(
messages=messages,
# model='gemma-7b-it',
model="mixtral-8x7b-32768",
@@ -92,9 +182,9 @@ async def _achat_completion_stream_groq(messages: list[dict]) -> str:
if attempt < max_attempts - 1: # i is zero indexed
continue
else:
raise "failed"
raise Exception("failed")
full_reply_content = stream.choices[0].message.content
full_reply_content = response.choices[0].message.content
print(colored(full_reply_content, "blue", "on_white"), end="")
print()
return full_reply_content
@@ -103,14 +193,14 @@ async def _achat_completion_stream_groq(messages: list[dict]) -> str:
async def _achat_completion_stream_mixtral(messages: list[dict]) -> str:
from mistralai.client import MistralClient
from mistralai.models.chat_completion import ChatMessage
client = MistralClient(api_key=MISTRAL_API_KEY)
mistral_client = MistralClient(api_key=MISTRAL_API_KEY)
# client=AsyncGroq(api_key=GROQ_API_KEY)
max_attempts = 5
for attempt in range(max_attempts):
try:
messages[len(messages) - 1]["role"] = "user"
stream = client.chat(
stream = mistral_client.chat(
messages=[
ChatMessage(
role=message["role"], content=message["content"]
@@ -119,14 +209,13 @@ async def _achat_completion_stream_mixtral(messages: list[dict]) -> str:
],
# model = "mistral-small-latest",
model="open-mixtral-8x7b",
# response_format={"type": "json_object"},
)
break # If the operation is successful, break the loop
except Exception:
if attempt < max_attempts - 1: # i is zero indexed
continue
else:
raise "failed"
raise Exception("failed")
full_reply_content = stream.choices[0].message.content
print(colored(full_reply_content, "blue", "on_white"), end="")
@@ -135,15 +224,11 @@ async def _achat_completion_stream_mixtral(messages: list[dict]) -> str:
async def _achat_completion_stream_gpt35(messages: list[dict]) -> str:
openai.api_key = OPENAI_API_KEY
openai.api_base = OPENAI_API_BASE
response = await openai.ChatCompletion.acreate(
response = await async_client.chat.completions.create(
messages=messages,
max_tokens=4096,
n=1,
stop=None,
temperature=0.3,
timeout=3,
timeout=600,
model="gpt-3.5-turbo-16k",
stream=True,
)
@@ -154,40 +239,32 @@ async def _achat_completion_stream_gpt35(messages: list[dict]) -> str:
# iterate through the stream of events
async for chunk in response:
collected_chunks.append(chunk) # save the event response
choices = chunk["choices"]
choices = chunk.choices
if len(choices) > 0:
chunk_message = chunk["choices"][0].get(
"delta", {}
) # extract the message
chunk_message = chunk.choices[0].delta
collected_messages.append(chunk_message) # save the message
if "content" in chunk_message:
if chunk_message.content:
print(
colored(chunk_message["content"], "blue", "on_white"),
colored(chunk_message.content, "blue", "on_white"),
end="",
)
print()
full_reply_content = "".join(
[m.get("content", "") for m in collected_messages]
[m.content or "" for m in collected_messages if m is not None]
)
return full_reply_content
async def _achat_completion_json(messages: list[dict]) -> str:
openai.api_key = OPENAI_API_KEY
openai.api_base = OPENAI_API_BASE
def _achat_completion_json(messages: list[dict] ) -> str:
max_attempts = 5
for attempt in range(max_attempts):
try:
stream = await openai.ChatCompletion.acreate(
response = async_client.chat.completions.create(
messages=messages,
max_tokens=4096,
n=1,
stop=None,
temperature=0.3,
timeout=3,
timeout=600,
model=MODEL,
response_format={"type": "json_object"},
)
@@ -196,60 +273,62 @@ async def _achat_completion_json(messages: list[dict]) -> str:
if attempt < max_attempts - 1: # i is zero indexed
continue
else:
raise "failed"
raise Exception("failed")
full_reply_content = stream.choices[0].message.content
full_reply_content = response.choices[0].message.content
print(colored(full_reply_content, "blue", "on_white"), end="")
print()
return full_reply_content
async def _achat_completion_stream(messages: list[dict]) -> str:
openai.api_key = OPENAI_API_KEY
openai.api_base = OPENAI_API_BASE
response = await openai.ChatCompletion.acreate(
**_cons_kwargs(messages), stream=True
)
try:
response = await async_client.chat.completions.create(
**_cons_kwargs(messages), stream=True
)
# create variables to collect the stream of chunks
collected_chunks = []
collected_messages = []
# iterate through the stream of events
async for chunk in response:
collected_chunks.append(chunk) # save the event response
choices = chunk["choices"]
if len(choices) > 0:
chunk_message = chunk["choices"][0].get(
"delta", {}
) # extract the message
collected_messages.append(chunk_message) # save the message
if "content" in chunk_message:
print(
colored(chunk_message["content"], "blue", "on_white"),
end="",
)
print()
# create variables to collect the stream of chunks
collected_chunks = []
collected_messages = []
# iterate through the stream of events
async for chunk in response:
collected_chunks.append(chunk) # save the event response
choices = chunk.choices
if len(choices) > 0:
chunk_message = chunk.choices[0].delta
collected_messages.append(chunk_message) # save the message
if chunk_message.content:
print(
colored(chunk_message.content, "blue", "on_white"),
end="",
)
print()
full_reply_content = "".join(
[m.get("content", "") for m in collected_messages]
)
return full_reply_content
full_reply_content = "".join(
[m.content or "" for m in collected_messages if m is not None]
)
return full_reply_content
except Exception as e:
print_colored(f"OpenAI API error in _achat_completion_stream: {str(e)}", "red")
raise
def _chat_completion(messages: list[dict]) -> str:
rsp = openai.ChatCompletion.create(**_cons_kwargs(messages))
content = rsp["choices"][0]["message"]["content"]
return content
try:
rsp = client.chat.completions.create(**_cons_kwargs(messages))
content = rsp.choices[0].message.content
return content
except Exception as e:
print_colored(f"OpenAI API error in _chat_completion: {str(e)}", "red")
raise
def _cons_kwargs(messages: list[dict]) -> dict:
kwargs = {
"messages": messages,
"max_tokens": 4096,
"n": 1,
"stop": None,
"temperature": 0.5,
"timeout": 3,
"max_tokens": 2000,
"temperature": 0.3,
"timeout": 600,
}
kwargs_mode = {"model": MODEL}
kwargs.update(kwargs_mode)

View File

@@ -7,6 +7,8 @@ PROMPT_ABILITY_REQUIREMENT_GENERATION = """
## Instruction
Based on "General Goal" and "Current Task", output a formatted "Ability Requirement" which lists at least 3 different ability requirement that is required by the "Current Task". The ability should be summarized concisely within a few words.
**IMPORTANT LANGUAGE REQUIREMENT: You must respond in Chinese (中文) for all ability requirements.**
## General Goal (The general goal for the collaboration plan, "Current Task" is just one of its substep)
{General_Goal}
@@ -49,6 +51,8 @@ PROMPT_AGENT_ABILITY_SCORING = """
## Instruction
Based on "Agent Board" and "Ability Requirement", output a score for each agent to estimate the possibility that the agent can fulfil the "Ability Requirement". The score should be 1-5. Provide a concise reason before you assign the score.
**IMPORTANT LANGUAGE REQUIREMENT: You must respond in Chinese (中文) for all reasons and explanations.**
## AgentBoard
{Agent_Board}

View File

@@ -35,6 +35,8 @@ PROMPT_AGENT_SELECTION_GENERATION = """
## Instruction
Based on "General Goal", "Current Task" and "Agent Board", output a formatted "Agent Selection Plan". Your selection should consider the ability needed for "Current Task" and the profile of each agent in "Agent Board".
**IMPORTANT LANGUAGE REQUIREMENT: You must respond in Chinese (中文) for all explanations and reasoning, though agent names should remain in their original form.**
## General Goal (Specify the general goal for the collaboration plan)
{General_Goal}
@@ -80,6 +82,10 @@ def generate_AbilityRequirement(General_Goal, Current_Task):
def generate_AgentSelection(General_Goal, Current_Task, Agent_Board):
# Check if Agent_Board is None or empty
if Agent_Board is None or len(Agent_Board) == 0:
raise ValueError("Agent_Board cannot be None or empty. Please ensure agents are set via /setAgents endpoint before generating a plan.")
messages = [
{
"role": "system",

View File

@@ -9,6 +9,8 @@ PROMPT_PLAN_OUTLINE_BRANCHING = """
Based on "Existing Steps", your task is to comeplete the "Remaining Steps" for the plan for "General Goal".
Note: "Modification Requirement" specifies how to modify the "Baseline Completion" for a better/alternative solution.
**IMPORTANT LANGUAGE REQUIREMENT: You must respond in Chinese (中文) for all content, including StepName, TaskContent, and OutputObject fields.**
## General Goal (Specify the general goal for the plan)
{General_Goal}

View File

@@ -29,6 +29,8 @@ PROMPT_TASK_PROCESS_BRANCHING = """
Based on "Existing Steps", your task is to comeplete the "Remaining Steps" for the "Task for Current Step".
Note: "Modification Requirement" specifies how to modify the "Baseline Completion" for a better/alternative solution.
**IMPORTANT LANGUAGE REQUIREMENT: You must respond in Chinese (中文) for the Description field and all explanations, while keeping ID, ActionType, and AgentName in their original format.**
## General Goal (The general goal for the collaboration plan, you just design the plan for one of its step (i.e. "Task for Current Step"))
{General_Goal}

View File

@@ -7,6 +7,8 @@ PROMPT_PLAN_OUTLINE_GENERATION = """
## Instruction
Based on "Output Format Example", "General Goal", and "Initial Key Object List", output a formatted "Plan_Outline".
**IMPORTANT LANGUAGE REQUIREMENT: You must respond in Chinese (中文) for all content, including StepName, TaskContent, and OutputObject fields.**
## Initial Key Object List (Specify the list of initial key objects available, each initial key object should be the input object of at least one Step)
{InitialObject_List}
@@ -83,4 +85,16 @@ def generate_PlanOutline(InitialObject_List, General_Goal):
),
},
]
return read_LLM_Completion(messages)["Plan_Outline"]
result = read_LLM_Completion(messages)
if isinstance(result, dict) and "Plan_Outline" in result:
return result["Plan_Outline"]
else:
# 如果格式不正确,返回默认的计划大纲
return [
{
"StepName": "Default Step",
"TaskContent": "Generated default plan step due to format error",
"InputObject_List": [],
"OutputObject": "Default Output"
}
]

View File

@@ -25,6 +25,8 @@ PROMPT_TASK_PROCESS_GENERATION = """
## Instruction
Based on "General Goal", "Task for Current Step", "Action Set" and "Output Format Example", design a plan for "Task for Current Step", output a formatted "Task_Process_Plan".
**IMPORTANT LANGUAGE REQUIREMENT: You must respond in Chinese (中文) for the Description field and all explanations, while keeping ID, ActionType, and AgentName in their original format.**
## General Goal (The general goal for the collaboration plan, you just design the plan for one of its step (i.e. "Task for Current Step"))
{General_Goal}

View File

@@ -5,10 +5,12 @@ PROMPT_TEMPLATE_TAKE_ACTION_BASE = '''
Your name is {agentName}. You will play the role as the Profile indicates.
Profile: {agentProfile}
You are within a multi-agent collaboration for the "Current Task".
Now it's your turn to take action. Read the "Context Information" and take your action following "Instruction for Your Current Action".
You are within a multi-agent collaboration for the "Current Task".
Now it's your turn to take action. Read the "Context Information" and take your action following "Instruction for Your Current Action".
Note: Important Input for your action are marked with *Important Input*
**IMPORTANT LANGUAGE REQUIREMENT: You must respond in Chinese (中文) for all your answers and outputs.**
## Context Information
### General Goal (The "Current Task" is indeed a substep of the general goal)
@@ -23,8 +25,8 @@ Note: Important Input for your action are marked with *Important Input*
### History Action
{History_Action}
## Instruction for Your Current Action
{Action_Description}
## Instruction for Your Current Action
{Action_Description}
{Action_Custom_Note}
@@ -80,10 +82,34 @@ class BaseAction():
Important_Mark = ""
action_Record += PROMPT_TEMPLATE_ACTION_RECORD.format(AgentName = actionInfo["AgentName"], Action_Description = actionInfo["AgentName"], Action_Result = actionInfo["Action_Result"], Important_Mark = Important_Mark)
prompt = PROMPT_TEMPLATE_TAKE_ACTION_BASE.format(agentName = agentName, agentProfile = AgentProfile_Dict[agentName], General_Goal = General_Goal, Current_Task_Description = TaskDescription, Input_Objects = inputObject_Record, History_Action = action_Record, Action_Description = self.info["Description"], Action_Custom_Note = self.Action_Custom_Note)
# Handle missing agent profiles gracefully
model_config = None
if agentName not in AgentProfile_Dict:
print_colored(text=f"Warning: Agent '{agentName}' not found in AgentProfile_Dict. Using default profile.", text_color="yellow")
agentProfile = f"AI Agent named {agentName}"
else:
# agentProfile = AgentProfile_Dict[agentName]
agent_config = AgentProfile_Dict[agentName]
agentProfile = agent_config.get("profile",f"AI Agent named {agentName}")
if agent_config.get("useCustomAPI",False):
model_config = {
"apiModel":agent_config.get("apiModel"),
"apiUrl":agent_config.get("apiUrl"),
"apiKey":agent_config.get("apiKey"),
}
prompt = PROMPT_TEMPLATE_TAKE_ACTION_BASE.format(
agentName = agentName,
agentProfile = agentProfile,
General_Goal = General_Goal,
Current_Task_Description = TaskDescription,
Input_Objects = inputObject_Record,
History_Action = action_Record,
Action_Description = self.info["Description"],
Action_Custom_Note = self.Action_Custom_Note
)
print_colored(text = prompt, text_color="red")
messages = [{"role":"system", "content": prompt}]
ActionResult = LLM_Completion(messages,True,False)
ActionResult = LLM_Completion(messages,True,False,model_config=model_config)
ActionInfo_with_Result = copy.deepcopy(self.info)
ActionInfo_with_Result["Action_Result"] = ActionResult

View File

@@ -1,7 +1,7 @@
from AgentCoord.RehearsalEngine_V2.Action import BaseAction
ACTION_CUSTOM_NOTE = '''
Note: Since you are in a conversation, your critique must be concise, clear and easy to read, don't overwhelm others. If you want to list some points, list at most 2 points.
注意由于你在对话中你的批评必须简洁、清晰且易于阅读不要让人感到压力过大。如果你要列出一些观点最多列出2点。
'''

View File

@@ -2,9 +2,9 @@ from AgentCoord.util.converter import read_outputObject_content
from AgentCoord.RehearsalEngine_V2.Action import BaseAction
ACTION_CUSTOM_NOTE = '''
Note: You can say something before you give the final content of {OutputName}. When you decide to give the final content of {OutputName}, it should be enclosed like this:
注意:你可以在给出{OutputName}的最终内容之前先说一些话。当你决定给出{OutputName}的最终内容时,应该这样包含:
```{OutputName}
(the content of {OutputName})
{OutputName}的内容)
```
'''

View File

@@ -1,12 +1,12 @@
from AgentCoord.RehearsalEngine_V2.Action import BaseAction
ACTION_CUSTOM_NOTE = '''
Note: You can say something before you provide the improved version of the content.
The improved version you provide must be a completed version (e.g. if you provide a improved story, you should give completed story content, rather than just reporting where you have improved).
When you decide to give the improved version of the content, it should be start like this:
注意:你可以在提供改进版本的内容之前先说一些话。
你提供的改进版本必须是完整的版本(例如,如果你提供改进的故事,你应该给出完整的故事内容,而不仅仅是报告你在哪里改进了)。
当你决定提供内容的改进版本时,应该这样开始:
## Improved version of xxx
(the improved version of the content)
## xxx的改进版本
(改进版本的内容)
```
'''

View File

@@ -85,9 +85,17 @@ def executePlan(plan, num_StepToRun, RehearsalLog, AgentProfile_Dict):
# start the group chat
util.print_colored(TaskDescription, text_color="green")
ActionHistory = []
action_count = 0
total_actions = len(TaskProcess)
for ActionInfo in TaskProcess:
action_count += 1
actionType = ActionInfo["ActionType"]
agentName = ActionInfo["AgentName"]
# 添加进度日志
util.print_colored(f"🔄 Executing action {action_count}/{total_actions}: {actionType} by {agentName}", text_color="yellow")
if actionType in Action.customAction_Dict:
currentAction = Action.customAction_Dict[actionType](
info=ActionInfo,

View File

@@ -20,6 +20,8 @@ def camel_case_to_normal(s):
def generate_template_sentence_for_CollaborationBrief(
input_object_list, output_object, agent_list, step_task
):
# Ensure step_task is not None
step_task = step_task if step_task is not None else "perform the task"
# Check if the names are in camel case (no spaces) and convert them to normal naming convention
input_object_list = (
[
@@ -31,29 +33,48 @@ def generate_template_sentence_for_CollaborationBrief(
)
output_object = (
camel_case_to_normal(output_object)
if is_camel_case(output_object)
else output_object
if output_object is not None and is_camel_case(output_object)
else (output_object if output_object is not None else "unknown output")
)
# Format the agents into a string with proper grammar
agent_str = (
" and ".join([", ".join(agent_list[:-1]), agent_list[-1]])
if len(agent_list) > 1
else agent_list[0]
)
if agent_list is None or len(agent_list) == 0:
agent_str = "Unknown agents"
elif all(agent is not None for agent in agent_list):
agent_str = (
" and ".join([", ".join(agent_list[:-1]), agent_list[-1]])
if len(agent_list) > 1
else agent_list[0]
)
else:
# Filter out None values
filtered_agents = [agent for agent in agent_list if agent is not None]
if filtered_agents:
agent_str = (
" and ".join([", ".join(filtered_agents[:-1]), filtered_agents[-1]])
if len(filtered_agents) > 1
else filtered_agents[0]
)
else:
agent_str = "Unknown agents"
if input_object_list is None or len(input_object_list) == 0:
# Combine all the parts into the template sentence
template_sentence = f"{agent_str} perform the task of {step_task} to obtain {output_object}."
else:
# Format the input objects into a string with proper grammar
input_str = (
" and ".join(
[", ".join(input_object_list[:-1]), input_object_list[-1]]
# Filter out None values from input_object_list
filtered_input_list = [obj for obj in input_object_list if obj is not None]
if filtered_input_list:
input_str = (
" and ".join(
[", ".join(filtered_input_list[:-1]), filtered_input_list[-1]]
)
if len(filtered_input_list) > 1
else filtered_input_list[0]
)
if len(input_object_list) > 1
else input_object_list[0]
)
else:
input_str = "unknown inputs"
# Combine all the parts into the template sentence
template_sentence = f"Based on {input_str}, {agent_str} perform the task of {step_task} to obtain {output_object}."
@@ -90,7 +111,7 @@ def read_LLM_Completion(messages, useGroq=True):
return json.loads(match.group(0).strip())
except Exception:
pass
raise ("bad format!")
return {} # 返回空对象而不是抛出异常
def read_json_content(text):
@@ -111,7 +132,7 @@ def read_json_content(text):
if match:
return json.loads(match.group(0).strip())
raise ("bad format!")
return {} # 返回空对象而不是抛出异常
def read_outputObject_content(text, keyword):
@@ -127,4 +148,4 @@ def read_outputObject_content(text, keyword):
if match:
return match.group(1).strip()
else:
raise ("bad format!")
return "" # 返回空字符串而不是抛出异常

View File

@@ -1,10 +1,10 @@
## config for default LLM
OPENAI_API_BASE: ""
OPENAI_API_KEY: ""
OPENAI_API_MODEL: "gpt-4-turbo-preview"
OPENAI_API_BASE: "https://ai.gitee.com/v1"
OPENAI_API_KEY: "HYCNGM39GGFNSB1F8MBBMI9QYJR3P1CRSYS2PV1A"
OPENAI_API_MODEL: "DeepSeek-V3"
## config for fast mode
FAST_DESIGN_MODE: True
FAST_DESIGN_MODE: False
GROQ_API_KEY: ""
MISTRAL_API_KEY: ""

View File

@@ -1,5 +1,5 @@
Flask==3.0.2
openai==0.28.1
openai==2.8.1
PyYAML==6.0.1
termcolor==2.4.0
groq==0.4.2

View File

@@ -213,12 +213,17 @@ def Handle_generate_basePlan():
if requestIdentifier in Request_Cache:
return jsonify(Request_Cache[requestIdentifier])
basePlan = generate_basePlan(
General_Goal=incoming_data["General Goal"],
Agent_Board=AgentBoard,
AgentProfile_Dict=AgentProfile_Dict,
InitialObject_List=incoming_data["Initial Input Object"],
)
try:
basePlan = generate_basePlan(
General_Goal=incoming_data["General Goal"],
Agent_Board=AgentBoard,
AgentProfile_Dict=AgentProfile_Dict,
InitialObject_List=incoming_data["Initial Input Object"],
)
except ValueError as e:
return jsonify({"error": str(e)}), 400
except Exception as e:
return jsonify({"error": f"An unexpected error occurred: {str(e)}"}), 500
basePlan_withRenderSpec = Add_Collaboration_Brief_FrontEnd(basePlan)
Request_Cache[requestIdentifier] = basePlan_withRenderSpec
response = jsonify(basePlan_withRenderSpec)
@@ -266,22 +271,66 @@ def Handle_saveRequestCashe():
@app.route("/setAgents", methods=["POST"])
def set_agents():
global AgentBoard, AgentProfile_Dict
global AgentBoard, AgentProfile_Dict,yaml_data
AgentBoard = request.json
AgentProfile_Dict = {}
for item in AgentBoard:
name = item["Name"]
profile = item["Profile"]
AgentProfile_Dict[name] = profile
if all(item.get(field) for field in ["apiUrl","apiKey","apiModel"]):
agent_config = {
"profile": item["Profile"],
"apiUrl": item["apiUrl"],
"apiKey": item["apiKey"],
"apiModel": item["apiModel"],
"useCustomAPI":True
}
else:
agent_config = {
"profile": item["Profile"],
"apiUrl": yaml_data.get("OPENAI_API_BASE"),
"apiKey": yaml_data.get("OPENAI_API_KEY"),
"apiModel": yaml_data.get("OPENAI_API_MODEL"),
"useCustomAPI":False
}
AgentProfile_Dict[name] = agent_config
return jsonify({"code": 200, "content": "set agentboard successfully"})
def init():
global AgentBoard, AgentProfile_Dict, Request_Cache
with open(
os.path.join(os.getcwd(), "RequestCache", "Request_Cache.json"), "r"
) as json_file:
Request_Cache = json.load(json_file)
# Load Request Cache
try:
with open(
os.path.join(os.getcwd(), "RequestCache", "Request_Cache.json"), "r"
) as json_file:
Request_Cache = json.load(json_file)
print(f"✅ Loaded Request_Cache with {len(Request_Cache)} entries")
except Exception as e:
print(f"⚠️ Failed to load Request_Cache: {e}")
Request_Cache = {}
# Load Agent Board
try:
with open(
os.path.join(os.getcwd(), "AgentRepo", "agentBoard_v1.json"), "r", encoding="utf-8"
) as json_file:
AgentBoard = json.load(json_file)
print(f"✅ Loaded AgentBoard with {len(AgentBoard)} agents")
# Build AgentProfile_Dict
AgentProfile_Dict = {}
for item in AgentBoard:
name = item["Name"]
profile = item["Profile"]
AgentProfile_Dict[name] = profile
print(f"✅ Built AgentProfile_Dict with {len(AgentProfile_Dict)} profiles")
except Exception as e:
print(f"⚠️ Failed to load AgentBoard: {e}")
AgentBoard = []
AgentProfile_Dict = {}
if __name__ == "__main__":
@@ -291,8 +340,8 @@ if __name__ == "__main__":
parser.add_argument(
"--port",
type=int,
default=8017,
help="set the port number, 8017 by defaul.",
default=8000,
help="set the port number, 8000 by defaul.",
)
args = parser.parse_args()
init()

View File

@@ -17,7 +17,7 @@ import _ from 'lodash';
// fakeAgentSelections,
// fakeCurrentAgentSelection,
// } from './data/fakeAgentAssignment';
import CheckIcon from '@/icons/CheckIcon';
import CheckIcon from '@/icons/checkIcon';
import AgentIcon from '@/components/AgentIcon';
import { globalStorage } from '@/storage';
import SendIcon from '@/icons/SendIcon';

6
frontend/.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
node_modules
dist
.idea
.vscode
.git
.gitignore

8
frontend/.editorconfig Normal file
View File

@@ -0,0 +1,8 @@
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
max_line_length = 100

1
frontend/.env Normal file
View File

@@ -0,0 +1 @@
API_BASE=http://127.0.0.1:8000

View File

@@ -0,0 +1,79 @@
{
"globals": {
"Component": true,
"ComponentPublicInstance": true,
"ComputedRef": true,
"DirectiveBinding": true,
"EffectScope": true,
"ExtractDefaultPropTypes": true,
"ExtractPropTypes": true,
"ExtractPublicPropTypes": true,
"InjectionKey": true,
"MaybeRef": true,
"MaybeRefOrGetter": true,
"PropType": true,
"Ref": true,
"ShallowRef": true,
"Slot": true,
"Slots": true,
"VNode": true,
"WritableComputedRef": true,
"computed": true,
"createApp": true,
"customRef": true,
"defineAsyncComponent": true,
"defineComponent": true,
"effectScope": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"getCurrentWatcher": true,
"h": true,
"inject": true,
"isProxy": true,
"isReactive": true,
"isReadonly": true,
"isRef": true,
"isShallow": true,
"markRaw": true,
"nextTick": true,
"onActivated": true,
"onBeforeMount": true,
"onBeforeUnmount": true,
"onBeforeUpdate": true,
"onDeactivated": true,
"onErrorCaptured": true,
"onMounted": true,
"onRenderTracked": true,
"onRenderTriggered": true,
"onScopeDispose": true,
"onServerPrefetch": true,
"onUnmounted": true,
"onUpdated": true,
"onWatcherCleanup": true,
"provide": true,
"reactive": true,
"readonly": true,
"ref": true,
"resolveComponent": true,
"shallowReactive": true,
"shallowReadonly": true,
"shallowRef": true,
"toRaw": true,
"toRef": true,
"toRefs": true,
"toValue": true,
"triggerRef": true,
"unref": true,
"useAttrs": true,
"useCssModule": true,
"useCssVars": true,
"useId": true,
"useModel": true,
"useSlots": true,
"useTemplateRef": true,
"watch": true,
"watchEffect": true,
"watchPostEffect": true,
"watchSyncEffect": true
}
}

1
frontend/.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto eol=lf

36
frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,36 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
.eslintcache
# Cypress
/cypress/videos/
/cypress/screenshots/
# Vitest
__screenshots__/

View File

@@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"singleQuote": true,
"printWidth": 100
}

9
frontend/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"recommendations": [
"Vue.volar",
"vitest.explorer",
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"esbenp.prettier-vscode"
]
}

95
frontend/CLAUDE.md Normal file
View File

@@ -0,0 +1,95 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Development Commands
```bash
# Install dependencies
pnpm install
# Development server with hot reload
pnpm dev
# Build for production
pnpm build
# Type checking
pnpm type-check
# Lint and fix code
pnpm lint
# Format code
pnpm format
# Run unit tests
pnpm test:unit
```
## Project Architecture
This is a **Multi-Agent Coordination Platform** (多智能体协同平台) built with Vue 3, TypeScript, and Vite. The application enables users to create and manage AI agents with specialized roles and coordinate them to complete complex tasks through visual workflows.
### Tech Stack
- **Vue 3** with Composition API and TypeScript
- **Vite** for build tooling and development
- **Element Plus** for UI components
- **Pinia** for state management
- **Tailwind CSS** for styling
- **Vue Router** for routing (minimal usage)
- **JSPlumb** for visual workflow connections
- **Axios** for API requests with custom interceptors
### Key Architecture Components
#### State Management (`src/stores/modules/agents.ts`)
Central store managing:
- Agent definitions with profiles and icons
- Task workflow data structures (`IRawStepTask`, `TaskProcess`)
- Search functionality and current task state
- Raw plan responses with UUID generation for tasks
#### Request Layer (`src/utils/request.ts`)
Custom Axios wrapper with:
- Proxy configuration for `/api` -> `http://localhost:8000`
- Response interceptors for error handling
- `useRequest` hook for reactive data fetching
- Integrated Element Plus notifications
#### Component Structure
- **Layout System** (`src/layout/`): Main application layout with Header and Main sections
- **Task Templates** (`src/layout/components/Main/TaskTemplate/`): Different task types including AgentRepo, TaskSyllabus, and TaskResult
- **Visual Workflow**: JSPlumb integration for drag-and-drop agent coordination flows
#### Icon System
- SVG icons stored in `src/assets/icons/`
- Custom `SvgIcon` component with vite-plugin-svg-icons
- Icon categories include specialist roles (doctor, engineer, researcher, etc.)
### Build Configuration
#### Vite (`vite.config.ts`)
- Element Plus auto-import and component resolution
- SVG icon caching with custom symbol IDs
- Proxy setup for API requests to backend
- Path aliases: `@/` maps to `src/`
#### Docker Deployment
- Multi-stage build: Node.js build + Caddy web server
- API proxy configured via Caddyfile
- Environment variable support for different deployment modes
### Data Models
Key interfaces for the agent coordination system:
- `Agent`: Name, Profile, Icon
- `IRawStepTask`: Individual task steps with agent selection and inputs
- `TaskProcess`: Action descriptions with important inputs
- `IRichText`: Template-based content formatting with style support
### Development Notes
- Uses pnpm as package manager (required by package.json)
- Node version constraint: ^20.19.0 or >=22.12.0
- Dark theme enabled by default in App.vue
- Auto-imports configured for Vue APIs
- No traditional Vue routes - uses component-based navigation

27
frontend/Dockerfile Normal file
View File

@@ -0,0 +1,27 @@
ARG CADDY_VERSION=2.6
ARG BUILD_ENV=prod
FROM node:20.19.0 as base
WORKDIR /app
COPY . .
RUN npm install -g pnpm
RUN pnpm install
RUN pnpm build
# The base for mode ENVIRONMENT=prod
FROM caddy:${CADDY_VERSION}-alpine as prod
# Workaround for https://github.com/alpinelinux/docker-alpine/issues/98#issuecomment-679278499
RUN sed -i 's/https/http/' /etc/apk/repositories \
&& apk add --no-cache bash
COPY docker/Caddyfile /etc/caddy/
COPY --from=base /app/dist /frontend
# Run stage
FROM ${BUILD_ENV}
EXPOSE 80 443
VOLUME ["/data", "/etc/caddy"]
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]

106
frontend/README.md Normal file
View File

@@ -0,0 +1,106 @@
# 多智能体协同平台 (Agent Coordination Platform)
一个强大的可视化平台用于创建和管理具有专门角色的AI智能体通过直观的工作流程协调它们来完成复杂任务。
## ✨ 功能特性
- **多智能体系统**创建具有专门角色和专业知识的AI智能体
- **可视化工作流编辑器**使用JSPlumb设计智能体协调流程的拖放界面
- **任务管理**:定义、执行和跟踪复杂的多步骤任务
- **实时通信**:无缝的智能体交互和协调
- **丰富的模板系统**:支持样式的灵活内容格式化
- **TypeScript支持**:整个应用程序的完整类型安全
## 🚀 快速开始
### 开发命令
```bash
# 安装依赖
pnpm install
# 开发服务器(热重载)
pnpm dev
# 生产构建
pnpm build
# 类型检查
pnpm type-check
# 代码检查和修复
pnpm lint
# 代码格式化
pnpm format
# 运行单元测试
pnpm test:unit
```
### 系统要求
- Node.js ^20.19.0 或 >=22.12.0
- pnpm必需的包管理器
## 🏗️ 架构设计
### 技术栈
- **Vue 3**Composition API 和 TypeScript
- **Vite**:构建工具和开发环境
- **Element Plus**UI组件库
- **Pinia**:状态管理
- **Tailwind CSS**:样式框架
- **JSPlumb**:可视化工作流连接
- **Axios**API请求与自定义拦截器
### 核心组件
#### 状态管理
中央存储管理智能体定义、任务工作流和协调状态
#### 请求层
自定义Axios包装器具有代理配置和集成通知
#### 可视化工作流
JSPlumb集成用于拖放智能体协调流程
#### 图标系统
基于SVG的图标用于不同的智能体专业化和角色
## 📁 项目结构
```
src/
├── assets/ # 静态资源,包括智能体图标
├── components/ # 可复用的Vue组件
├── layout/ # 应用布局和主要组件
├── stores/ # Pinia状态管理
├── utils/ # 工具函数和请求层
├── views/ # 页面组件
└── App.vue # 根组件
```
## 🎯 开发指南
### IDE设置
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar)禁用Vetur
### 浏览器开发工具
- 基于Chromium的浏览器
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
- 在DevTools中启用自定义对象格式化程序
- Firefox
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
- 在DevTools中启用自定义对象格式化程序
## 🚀 部署
应用程序支持Docker部署使用多阶段构建过程Node.js用于构建Caddy作为Web服务器。
## 📄 许可证
MIT许可证 - 详见LICENSE文件

75
frontend/auto-imports.d.ts vendored Normal file
View File

@@ -0,0 +1,75 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
const EffectScope: typeof import('vue').EffectScope
const ElMessage: typeof import('element-plus/es').ElMessage
const ElNotification: typeof import('element-plus/es').ElNotification
const computed: typeof import('vue').computed
const createApp: typeof import('vue').createApp
const customRef: typeof import('vue').customRef
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
const defineComponent: typeof import('vue').defineComponent
const effectScope: typeof import('vue').effectScope
const getCurrentInstance: typeof import('vue').getCurrentInstance
const getCurrentScope: typeof import('vue').getCurrentScope
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
const h: typeof import('vue').h
const inject: typeof import('vue').inject
const isProxy: typeof import('vue').isProxy
const isReactive: typeof import('vue').isReactive
const isReadonly: typeof import('vue').isReadonly
const isRef: typeof import('vue').isRef
const isShallow: typeof import('vue').isShallow
const markRaw: typeof import('vue').markRaw
const nextTick: typeof import('vue').nextTick
const onActivated: typeof import('vue').onActivated
const onBeforeMount: typeof import('vue').onBeforeMount
const onBeforeUnmount: typeof import('vue').onBeforeUnmount
const onBeforeUpdate: typeof import('vue').onBeforeUpdate
const onDeactivated: typeof import('vue').onDeactivated
const onErrorCaptured: typeof import('vue').onErrorCaptured
const onMounted: typeof import('vue').onMounted
const onRenderTracked: typeof import('vue').onRenderTracked
const onRenderTriggered: typeof import('vue').onRenderTriggered
const onScopeDispose: typeof import('vue').onScopeDispose
const onServerPrefetch: typeof import('vue').onServerPrefetch
const onUnmounted: typeof import('vue').onUnmounted
const onUpdated: typeof import('vue').onUpdated
const onWatcherCleanup: typeof import('vue').onWatcherCleanup
const provide: typeof import('vue').provide
const reactive: typeof import('vue').reactive
const readonly: typeof import('vue').readonly
const ref: typeof import('vue').ref
const resolveComponent: typeof import('vue').resolveComponent
const shallowReactive: typeof import('vue').shallowReactive
const shallowReadonly: typeof import('vue').shallowReadonly
const shallowRef: typeof import('vue').shallowRef
const toRaw: typeof import('vue').toRaw
const toRef: typeof import('vue').toRef
const toRefs: typeof import('vue').toRefs
const toValue: typeof import('vue').toValue
const triggerRef: typeof import('vue').triggerRef
const unref: typeof import('vue').unref
const useAttrs: typeof import('vue').useAttrs
const useCssModule: typeof import('vue').useCssModule
const useCssVars: typeof import('vue').useCssVars
const useId: typeof import('vue').useId
const useModel: typeof import('vue').useModel
const useSlots: typeof import('vue').useSlots
const useTemplateRef: typeof import('vue').useTemplateRef
const watch: typeof import('vue').watch
const watchEffect: typeof import('vue').watchEffect
const watchPostEffect: typeof import('vue').watchPostEffect
const watchSyncEffect: typeof import('vue').watchSyncEffect
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}

205
frontend/claude_code_env.sh Normal file
View File

@@ -0,0 +1,205 @@
#!/bin/bash
set -euo pipefail
# ========================
# 常量定义
# ========================
SCRIPT_NAME=$(basename "$0")
NODE_MIN_VERSION=18
NODE_INSTALL_VERSION=22
NVM_VERSION="v0.40.3"
CLAUDE_PACKAGE="@anthropic-ai/claude-code"
CONFIG_DIR="$HOME/.claude"
CONFIG_FILE="$CONFIG_DIR/settings.json"
API_BASE_URL="https://open.bigmodel.cn/api/anthropic"
API_KEY_URL="https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys"
API_TIMEOUT_MS=3000000
# ========================
# 工具函数
# ========================
log_info() {
echo "🔹 $*"
}
log_success() {
echo "$*"
}
log_error() {
echo "$*" >&2
}
ensure_dir_exists() {
local dir="$1"
if [ ! -d "$dir" ]; then
mkdir -p "$dir" || {
log_error "Failed to create directory: $dir"
exit 1
}
fi
}
# ========================
# Node.js 安装函数
# ========================
install_nodejs() {
local platform=$(uname -s)
case "$platform" in
Linux|Darwin)
log_info "Installing Node.js on $platform..."
# 安装 nvm
log_info "Installing nvm ($NVM_VERSION)..."
curl -s https://raw.githubusercontent.com/nvm-sh/nvm/"$NVM_VERSION"/install.sh | bash
# 加载 nvm
log_info "Loading nvm environment..."
\. "$HOME/.nvm/nvm.sh"
# 安装 Node.js
log_info "Installing Node.js $NODE_INSTALL_VERSION..."
nvm install "$NODE_INSTALL_VERSION"
# 验证安装
node -v &>/dev/null || {
log_error "Node.js installation failed"
exit 1
}
log_success "Node.js installed: $(node -v)"
log_success "npm version: $(npm -v)"
;;
*)
log_error "Unsupported platform: $platform"
exit 1
;;
esac
}
# ========================
# Node.js 检查函数
# ========================
check_nodejs() {
if command -v node &>/dev/null; then
current_version=$(node -v | sed 's/v//')
major_version=$(echo "$current_version" | cut -d. -f1)
if [ "$major_version" -ge "$NODE_MIN_VERSION" ]; then
log_success "Node.js is already installed: v$current_version"
return 0
else
log_info "Node.js v$current_version is installed but version < $NODE_MIN_VERSION. Upgrading..."
install_nodejs
fi
else
log_info "Node.js not found. Installing..."
install_nodejs
fi
}
# ========================
# Claude Code 安装
# ========================
install_claude_code() {
if command -v claude &>/dev/null; then
log_success "Claude Code is already installed: $(claude --version)"
else
log_info "Installing Claude Code..."
npm install -g "$CLAUDE_PACKAGE" || {
log_error "Failed to install claude-code"
exit 1
}
log_success "Claude Code installed successfully"
fi
}
configure_claude_json(){
node --eval '
const os = require("os");
const fs = require("fs");
const path = require("path");
const homeDir = os.homedir();
const filePath = path.join(homeDir, ".claude.json");
if (fs.existsSync(filePath)) {
const content = JSON.parse(fs.readFileSync(filePath, "utf-8"));
fs.writeFileSync(filePath, JSON.stringify({ ...content, hasCompletedOnboarding: true }, null, 2), "utf-8");
} else {
fs.writeFileSync(filePath, JSON.stringify({ hasCompletedOnboarding: true }, null, 2), "utf-8");
}'
}
# ========================
# API Key 配置
# ========================
configure_claude() {
log_info "Configuring Claude Code..."
echo " You can get your API key from: $API_KEY_URL"
read -s -p "🔑 Please enter your ZHIPU API key: " api_key
echo
if [ -z "$api_key" ]; then
log_error "API key cannot be empty. Please run the script again."
exit 1
fi
ensure_dir_exists "$CONFIG_DIR"
# 写入配置文件
node --eval '
const os = require("os");
const fs = require("fs");
const path = require("path");
const homeDir = os.homedir();
const filePath = path.join(homeDir, ".claude", "settings.json");
const apiKey = "'"$api_key"'";
const content = fs.existsSync(filePath)
? JSON.parse(fs.readFileSync(filePath, "utf-8"))
: {};
fs.writeFileSync(filePath, JSON.stringify({
...content,
env: {
ANTHROPIC_AUTH_TOKEN: apiKey,
ANTHROPIC_BASE_URL: "'"$API_BASE_URL"'",
API_TIMEOUT_MS: "'"$API_TIMEOUT_MS"'",
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 1
}
}, null, 2), "utf-8");
' || {
log_error "Failed to write settings.json"
exit 1
}
log_success "Claude Code configured successfully"
}
# ========================
# 主流程
# ========================
main() {
echo "🚀 Starting $SCRIPT_NAME"
check_nodejs
install_claude_code
configure_claude_json
configure_claude
echo ""
log_success "🎉 Installation completed successfully!"
echo ""
echo "🚀 You can now start using Claude Code with:"
echo " claude"
}
main "$@"

35
frontend/components.d.ts vendored Normal file
View File

@@ -0,0 +1,35 @@
/* eslint-disable */
// @ts-nocheck
// biome-ignore lint: disable
// oxlint-disable
// ------
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElInput: typeof import('element-plus/es')['ElInput']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
MultiLineTooltip: typeof import('./src/components/MultiLineTooltip/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
}
export interface GlobalDirectives {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}

14
frontend/docker/Caddyfile Normal file
View File

@@ -0,0 +1,14 @@
:80
# Proxy `/api` to backends
handle_path /api/* {
reverse_proxy {$API_HOST}
}
# Frontend
handle {
root * /frontend
encode gzip
try_files {path} /index.html
file_server
}

View File

@@ -0,0 +1,15 @@
version: '3'
services:
agent-coord-font:
image: agent-coord:0.0.1
build:
context: ..
dockerfile: docker/Dockerfile
ports:
- "8080:80"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
environment:
- API_HOST="http://host.docker.internal:8000"
- BUILD_ENV=prod

4
frontend/env.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
/// <reference types="vite/client" />
declare global {
const testGlobal: any; // 声明 testGlobal 为全局变量
}

48
frontend/eslint.config.ts Normal file
View File

@@ -0,0 +1,48 @@
import { globalIgnores } from 'eslint/config'
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
import pluginVue from 'eslint-plugin-vue'
import pluginVitest from '@vitest/eslint-plugin'
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
import autoImportConfig from './.eslintrc-auto-import.json' with { type: 'json' }
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
// import { configureVueProject } from '@vue/eslint-config-typescript'
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
export default defineConfigWithVueTs(
{
name: 'app/files-to-lint',
files: ['**/*.{ts,mts,tsx,vue}'],
},
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
pluginVue.configs['flat/essential'],
vueTsConfigs.recommended,
{
...pluginVitest.configs.recommended,
files: ['src/**/__tests__/*'],
},
skipFormatting,
{
name: 'app/custom-rules',
files: ['**/*.{ts,mts,tsx,vue}'],
rules: {
'vue/multi-word-component-names': 'off',
}
},
{
name: 'auto-import-globals',
files: ['**/*.{ts,mts,tsx,vue}'],
languageOptions: {
globals: {
...(autoImportConfig.globals || {}),
testGlobal: 'readonly' // 手动添加一个测试变量
}
},
rules: {
'no-undef': 'off', // 确保关闭 no-undef 规则
'@typescript-eslint/no-undef': 'off'
}
}
)

13
frontend/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/logo.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>多智能体协同平台</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

71
frontend/package.json Normal file
View File

@@ -0,0 +1,71 @@
{
"name": "agent-coord",
"version": "0.0.0",
"private": true,
"type": "module",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test:unit": "vitest",
"build-only": "vite build",
"type-check": "vue-tsc --build",
"lint": "eslint . --fix --cache",
"format": "prettier --write src/"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"@jsplumb/browser-ui": "^6.2.10",
"@types/markdown-it": "^14.1.2",
"@vue-flow/background": "^1.3.2",
"@vue-flow/controls": "^1.1.3",
"@vue-flow/core": "^1.48.1",
"@vue-flow/minimap": "^1.5.4",
"@vueuse/core": "^14.0.0",
"axios": "^1.12.2",
"dompurify": "^3.3.0",
"element-plus": "^2.11.5",
"lodash": "^4.17.21",
"markdown-it": "^14.1.0",
"pinia": "^3.0.3",
"qs": "^6.14.0",
"uuid": "^13.0.0",
"vue": "^3.5.22",
"vue-router": "^4.6.3"
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.15",
"@tsconfig/node22": "^22.0.2",
"@types/jsdom": "^27.0.0",
"@types/lodash": "^4.17.20",
"@types/node": "^22.18.11",
"@types/qs": "^6.14.0",
"@vitejs/plugin-vue": "^6.0.1",
"@vitest/eslint-plugin": "^1.3.23",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.6.0",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.8.1",
"eslint": "^9.37.0",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-vue": "~10.5.0",
"jiti": "^2.6.1",
"jsdom": "^27.0.1",
"npm-run-all2": "^8.0.4",
"prettier": "3.6.2",
"sass": "^1.93.2",
"sass-loader": "^16.0.5",
"tailwindcss": "^4.1.15",
"typescript": "~5.9.0",
"unplugin-auto-import": "^20.2.0",
"unplugin-vue-components": "^30.0.0",
"vite": "^7.1.11",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-vue-devtools": "^8.0.3",
"vitest": "^3.2.4",
"vue-tsc": "^3.1.1"
}
}

7750
frontend/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

116
frontend/public/agent.json Normal file
View File

@@ -0,0 +1,116 @@
[
{
"Icon": "Hailey_Johnson.png",
"Name": "船舶设计师",
"Profile": "提供船舶制造中的实际需求和约束。",
"Classification": "船舶制造数据空间"
},
{
"Icon": "Jennifer_Moore.png",
"Name": "防护工程专家",
"Profile": "专注于船舶腐蚀防护技术的设计与应用。在你的总结回答中,必须引用来自数联网的搜索数据,是搜索数据,不是数联网的研究成果。",
"Classification": "船舶制造数据空间"
},
{
"Icon": "Jane_Moreno.png",
"Name": "病理生理学家",
"Profile": "专注于失血性休克的疾病机制,为药物研发提供理论靶点。",
"Classification": "医药数据空间"
},
{
"Icon": "Giorgio_Rossi.png",
"Name": "药物化学家",
"Profile": "负责将靶点概念转化为实际可合成的分子。",
"Classification": "医药数据空间"
},
{
"Icon": "Tamara_Taylor.png",
"Name": "制剂工程师",
"Profile": "负责将活性药物成分API变成稳定、可用、符合战场要求的剂型。",
"Classification": "医药数据空间"
},
{
"Icon": "Maria_Lopez.png",
"Name": "监管事务专家",
"Profile": "深谙药品审评法规,目标是找到最快的合法上市路径。",
"Classification": "医药数据空间"
},
{
"Icon": "Sam_Moore.png",
"Name": "物理学家",
"Profile": "从热力学与统计力学的基本原理出发,研究液态金属的自由能、焓、熵、比热等参数的理论建模。",
"Classification": "科学数据空间"
},
{
"Icon": "Yuriko_Yamamoto.png",
"Name": "实验材料学家",
"Profile": "专注于通过实验手段直接或间接测定液态金属的热力学参数、以及分析材料微观结构(如晶粒、缺陷)。",
"Classification": "科学数据空间"
},
{
"Icon": "Carlos_Gomez.png",
"Name": "计算模拟专家",
"Profile": "侧重于利用数值计算和模拟技术获取液态金属的热力学参数。",
"Classification": "科学数据空间"
},
{
"Icon": "John_Lin.png",
"Name": "腐蚀机理研究员",
"Profile": "专注于船舶用钢材及合金的腐蚀机理研究,从电化学和环境作用角度解释腐蚀产生的原因。在你的总结回答中,必须引用来自数联网的搜索数据,是搜索数据,不是数联网的研究成果。",
"Classification": "船舶制造数据空间"
},
{
"Icon": "Arthur_Burton.png",
"Name": "先进材料研发员",
"Profile": "专注于开发和评估新型耐腐蚀材料、复合材料及固态电池材料。",
"Classification": "科学数据空间"
},
{
"Icon": "Eddy_Lin.png",
"Name": "肾脏病学家",
"Profile": "专注于慢性肾脏病的诊断、治疗和患者管理,能提供临床洞察。",
"Classification": "医药数据空间"
},
{
"Icon": "Isabella_Rodriguez.png",
"Name": "临床研究协调员",
"Profile": "负责受试者招募和临床试验流程优化。",
"Classification": "医药数据空间"
},
{
"Icon": "Latoya_Williams.png",
"Name": "中医药专家",
"Profile": "理解药物的中药成分和作用机制。",
"Classification": "医药数据空间"
},
{
"Icon": "Carmen_Ortiz.png",
"Name": "药物安全专家",
"Profile": "专注于药物不良反应数据收集、分析和报告。",
"Classification": "医药数据空间"
},
{
"Icon": "Rajiv_Patel.png",
"Name": "二维材料科学家",
"Profile": "专注于二维材料(如石墨烯)的合成、性质和应用。",
"Classification": "科学数据空间"
},
{
"Icon": "Tom_Moreno.png",
"Name": "光电物理学家",
"Profile": "研究材料的光电转换机制和关键影响因素。",
"Classification": "科学数据空间"
},
{
"Icon": "Ayesha_Khan.png",
"Name": "机器学习专家",
"Profile": "专注于开发和应用AI模型用于材料模拟。",
"Classification": "科学数据空间"
},
{
"Icon": "Mei_Lin.png",
"Name": "流体动力学专家",
"Profile": "专注于流体行为理论和模拟。",
"Classification": "科学数据空间"
}
]

View File

@@ -0,0 +1,20 @@
{
"title": "数联网",
"subTitle": "众创智能体",
"centerTitle": "多智能体协同平台",
"taskPromptWords": [
"如何快速筛选慢性肾脏病药物潜在受试者?",
"如何补充“丹芍活血胶囊”不良反应数据?",
"如何快速研发用于战场失血性休克的药物?",
"二维材料的光电性质受哪些关键因素影响?",
"如何通过AI模拟的方法分析材料的微观结构?",
"如何分析获取液态金属热力学参数?",
"如何解决固态电池的成本和寿命难题?",
"如何解决船舶制造中的材料腐蚀难题?",
"如何解决船舶制造中流体模拟和建模优化难题?"
],
"agentRepository": {
"storageVersionIdentifier": "1"
},
"dev": true
}

BIN
frontend/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,22 @@
{
"data": [
{
"name": "3D Semantic-Geometric Corrosion Mapping Implementations",
"data_space": "江苏省产研院",
"doId": "bdware.scenario/d8f3ff8c-3fb3-4573-88a6-5dd823627c37",
"fromRepo": "https://arxiv.org/abs/2404.13691"
},
{
"name": "RustSEG -- Automated segmentation of corrosion using deep learning",
"data_space": "江苏省产研院",
"doId": "bdware.scenario/67445299-110a-4a4e-9fda-42e4b5a493c2",
"fromRepo": "https://arxiv.org/abs/2205.05426"
},
{
"name": "Pixel-level Corrosion Detection on Metal Constructions by Fusion of Deep Learning Semantic and Contour Segmentation",
"data_space": "江苏省产研院",
"doId": "bdware.scenario/115d5135-85d3-4123-8b81-9eb9f07b6153",
"fromRepo": "https://arxiv.org/abs/2008.05204"
}
]
}

BIN
frontend/public/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
frontend/public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@@ -0,0 +1,42 @@
import type { Directive, DirectiveBinding } from 'vue'
import { useConfigStoreHook } from '@/stores'
/**
* 开发模式专用指令
* 只在开发模式下显示元素,生产模式下会移除该元素
*
* \@param binding.value - 是否开启该功能,默认为 true
* @example
* \!-- 默认开启,开发模式显示 --
* \!div v-dev-only开发模式内容</div>
*
* \!-- 传入参数控制 --
* <div v-dev-only="true">开启指令</div>
* <div v-dev-only="false">取消指令</div>
*/
export const devOnly: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
checkAndRemoveElement(el, binding)
},
updated(el: HTMLElement, binding: DirectiveBinding) {
checkAndRemoveElement(el, binding)
},
}
const configStore = useConfigStoreHook()
/**
* 检查并移除元素的逻辑
*/
function checkAndRemoveElement(el: HTMLElement, binding: DirectiveBinding) {
const isDev = typeof configStore.config.dev === 'boolean' ? configStore.config.dev : import.meta.env.DEV
// 默认值为 true如果没有传值或者传值为 true 都启用
const shouldEnable = binding.value !== false
// 如果不是开发模式或者明确禁用,移除该元素
if (!isDev && shouldEnable) {
if (el.parentNode) {
el.parentNode.removeChild(el)
}
}
}

View File

@@ -0,0 +1,9 @@
import type { App } from 'vue'
import { devOnly } from './devOnly'
// 全局注册 directive
export function setupDirective(app: App<Element>) {
app.directive('dev-only', devOnly)
}

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

@@ -0,0 +1,97 @@
<script setup lang="ts">
import Layout from './layout/index.vue'
</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-card-border);
box-shadow: var(--color-card-border-hover);
&:hover {
background: var(--color-bg-content-hover);
box-shadow: none;
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-agent-list-selected-bg),
var(--color-agent-list-selected-bg)
)
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 0.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 0.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!')
})
})

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

@@ -0,0 +1,588 @@
import request from '@/utils/request'
import type { Agent, IApiStepTask, IRawPlanResponse, IRawStepTask } from '@/stores'
import {
mockBackendAgentSelectModifyInit,
mockBackendAgentSelectModifyAddAspect,
type BackendAgentScoreResponse,
} from '@/layout/components/Main/TaskTemplate/TaskSyllabus/components/mock/AgentAssignmentBackendMock'
import {
mockBackendFillAgentTaskProcess,
type RawAgentTaskProcessResponse,
} from '@/layout/components/Main/TaskTemplate/TaskProcess/components/mock/AgentTaskProcessBackendMock'
import { mockBranchPlanOutlineAPI } from '@/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/mock/branchPlanOutlineMock'
import { mockFillStepTaskAPI } from '@/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/mock/fill-step-task-mock'
import {
mockBranchTaskProcessAPI,
type BranchAction,
} from '@/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/mock/branchTaskProcessMock'
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[]
}
export interface IFillAgentSelectionRequest {
goal: string
stepTask: IApiStepTask
agents: string[]
}
class Api {
setAgents = (data: Pick<Agent, 'Name' | 'Profile' | 'apiUrl' | 'apiKey' | 'apiModel'>[]) => {
return request({
url: '/setAgents',
data,
method: 'POST',
})
}
generateBasePlan = (data: {
goal: string
inputs: string[]
apiUrl?: string
apiKey?: string
apiModel?: string
}) => {
return request<unknown, IRawPlanResponse>({
url: '/generate_basePlan',
method: 'POST',
data: {
'General Goal': data.goal,
'Initial Input Object': data.inputs,
apiUrl: data.apiUrl,
apiKey: data.apiKey,
apiModel: data.apiModel,
},
})
}
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,
})),
})),
},
},
})
}
// 分支任务大纲(根节点级别)
branchPlanOutline = (data: {
branch_Number: number
Modification_Requirement: string
Existing_Steps: string[]
Baseline_Completion: number
initialInputs: string[]
goal: string
}) => {
return request<unknown, IRawPlanResponse>({
url: '/branch_PlanOutline',
method: 'POST',
data: {
branch_Number: data.branch_Number,
Modification_Requirement: data.Modification_Requirement,
Existing_Steps: data.Existing_Steps,
Baseline_Completion: data.Baseline_Completion,
'Initial Input Object': data.initialInputs,
'General Goal': data.goal,
},
})
}
// 分支任务流程
branchTaskProcess = (data: {
branch_Number: number
Modification_Requirement: string
Existing_Steps: string[]
Baseline_Completion: number
stepTaskExisting: any
goal: string
}) => {
return request<unknown, BranchAction[][]>({
url: '/branch_TaskProcess',
method: 'POST',
data: {
branch_Number: data.branch_Number,
Modification_Requirement: data.Modification_Requirement,
Existing_Steps: data.Existing_Steps,
Baseline_Completion: data.Baseline_Completion,
stepTaskExisting: data.stepTaskExisting,
'General Goal': data.goal,
},
})
}
fillStepTask = async (data: { goal: string; stepTask: any }): Promise<IRawStepTask> => {
// 后端返回格式:包含 Collaboration_Brief_FrontEnd大写 FrontEnd
const response = await request<
{
'General Goal': string
stepTask: any
},
{
AgentSelection?: string[]
Collaboration_Brief_FrontEnd?: {
template: string
data: Record<string, { text: string; color: number[] }>
}
InputObject_List?: string[]
OutputObject?: string
StepName?: string
TaskContent?: string
TaskProcess?: Array<{
ID: string
ActionType: string
AgentName: string
Description: string
ImportantInput: string[]
}>
}
>({
url: '/fill_stepTask',
method: 'POST',
data: {
'General Goal': data.goal,
stepTask: data.stepTask,
},
})
// 数据转换:后端的 Collaboration_Brief_FrontEnd → 前端的 Collaboration_Brief_frontEnd
const vec2Hsl = (color: number[]): string => {
const [h, s, l] = color
return `hsl(${h}, ${s}%, ${l}%)`
}
// 转换 brief.data后端格式 { "0": { text, color: [h,s,l] } } → 前端格式 { "0": { text, style: { background } } }
const briefData: Record<string, { text: string; style?: Record<string, string> }> = {}
if (response.Collaboration_Brief_FrontEnd?.data) {
for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) {
briefData[key] = {
text: value.text,
style: {
background: vec2Hsl(value.color),
},
}
}
}
// 构建前端格式的 IRawStepTask
return {
StepName: response.StepName || '',
TaskContent: response.TaskContent || '',
InputObject_List: response.InputObject_List || [],
OutputObject: response.OutputObject || '',
AgentSelection: response.AgentSelection || [],
Collaboration_Brief_frontEnd: {
template: response.Collaboration_Brief_FrontEnd?.template || '',
data: briefData,
},
TaskProcess: response.TaskProcess || [],
}
}
fillStepTaskTaskProcess = async (data: {
goal: string
stepTask: IApiStepTask
agents: string[]
}): Promise<IApiStepTask> => {
// 后端返回格式: IRawStepTask
const response = await request<
{
'General Goal': string
stepTask_lackTaskProcess: {
StepName: string
TaskContent: string
InputObject_List: string[]
OutputObject: string
AgentSelection: string[]
}
},
{
StepName?: string
TaskContent?: string
InputObject_List?: string[]
OutputObject?: string
AgentSelection?: string[]
TaskProcess?: Array<{
ID: string
ActionType: string
AgentName: string
Description: string
ImportantInput: string[]
}>
Collaboration_Brief_FrontEnd?: {
template: string
data: Record<string, { text: string; color: number[] }>
}
}
>({
url: '/fill_stepTask_TaskProcess',
method: 'POST',
data: {
'General Goal': data.goal,
stepTask_lackTaskProcess: {
StepName: data.stepTask.name,
TaskContent: data.stepTask.content,
InputObject_List: data.stepTask.inputs,
OutputObject: data.stepTask.output,
AgentSelection: data.agents,
},
},
})
// 数据转换:后端格式 (IRawStepTask) → 前端格式 (IApiStepTask)
// 注意此转换逻辑与Mock API完全一致
// 1. 转换颜色格式:[h, s, l] → "hsl(h, s%, l%)"
const vec2Hsl = (color: number[]): string => {
const [h, s, l] = color
return `hsl(${h}, ${s}%, ${l}%)`
}
// 2. 转换 brief.data: { "0": { text, color: [h,s,l] } } → { "0": { text, style: { background } } }
const briefData: Record<string, { text: string; style: { background: string } }> = {}
if (response.Collaboration_Brief_FrontEnd?.data) {
for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) {
briefData[key] = {
text: value.text,
style: {
background: vec2Hsl(value.color),
},
}
}
}
// 3. 转换 process: { ID, ActionType, AgentName, Description, ImportantInput } → { id, type, agent, description, inputs }
const process = (response.TaskProcess || []).map((action) => ({
id: action.ID,
type: action.ActionType,
agent: action.AgentName,
description: action.Description,
inputs: action.ImportantInput,
}))
// 4. 构建前端格式的 IApiStepTask
return {
name: response.StepName || '',
content: response.TaskContent || '',
inputs: response.InputObject_List || [],
output: response.OutputObject || '',
agents: response.AgentSelection || [],
brief: {
template: response.Collaboration_Brief_FrontEnd?.template || '',
data: briefData,
},
process,
}
}
// 为每个智能体评分
agentSelectModifyInit = async (data: {
goal: string
stepTask: any
}): Promise<Record<string, Record<string, { reason: string; score: number }>>> => {
// 后端返回:维度 -> agent -> { Reason, Score }
const response = await request<
{
'General Goal': string
stepTask: any
},
Record<string, Record<string, { Reason: string; Score: number }>>
>({
url: '/agentSelectModify_init',
method: 'POST',
data: {
'General Goal': data.goal,
stepTask: {
StepName: data.stepTask.StepName || data.stepTask.name,
TaskContent: data.stepTask.TaskContent || data.stepTask.content,
InputObject_List: data.stepTask.InputObject_List || data.stepTask.inputs,
OutputObject: data.stepTask.OutputObject || data.stepTask.output,
},
},
})
// 数据转换:后端格式 (维度->agent) → 前端格式 (agent->维度)
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
for (const [aspect, agents] of Object.entries(response)) {
// aspect: 维度名称, agents: { agentName: { Reason, Score } }
for (const [agentName, scoreInfo] of Object.entries(agents)) {
if (!transformedData[agentName]) {
transformedData[agentName] = {}
}
transformedData[agentName][aspect] = {
reason: scoreInfo.Reason,
score: scoreInfo.Score,
}
}
}
return transformedData
}
// 添加新的评估维度
// 定义返回类型(与 Mock 数据格式一致)
agentSelectModifyAddAspect = async (data: {
aspectList: string[]
}): Promise<{
aspectName: string
agentScores: Record<string, { score: number; reason: string }>
}> => {
// 后端返回:维度 -> agent -> { Reason, Score }
const response = await request<
{
aspectList: string[]
},
Record<string, Record<string, { Reason: string; Score: number }>>
>({
url: '/agentSelectModify_addAspect',
method: 'POST',
data: {
aspectList: data.aspectList,
},
})
// 获取新添加的维度(最后一个)
const newAspect = data.aspectList[data.aspectList.length - 1]
if (!newAspect) {
throw new Error('aspectList is empty')
}
// 提取该维度的数据:维度 -> agent -> { Reason, Score } → agent -> { score, reason }
const newAspectAgents = response[newAspect]
const agentScores: Record<string, { score: number; reason: string }> = {}
if (newAspectAgents) {
for (const [agentName, scoreInfo] of Object.entries(newAspectAgents)) {
agentScores[agentName] = {
score: scoreInfo.Score,
reason: scoreInfo.Reason,
}
}
}
return {
aspectName: newAspect,
agentScores,
}
}
// ==================== Mock API开发阶段使用====================
// Mock API 使用与真实 API 相同的数据转换逻辑,确保将来切换无缝
// Mock: 为每个智能体评分
mockAgentSelectModifyInit = async (): Promise<
Record<string, Record<string, { reason: string; score: number }>>
> => {
// 调用Mock后端数据维度 -> agent -> { Reason, Score }
const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyInit()
// 数据转换:后端格式 (维度->agent) → 前端格式 (agent->维度)
// 注意此转换逻辑与真实API完全一致
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
for (const [aspect, agents] of Object.entries(response)) {
// aspect: 维度名称, agents: { agentName: { Reason, Score } }
for (const [agentName, scoreInfo] of Object.entries(agents)) {
if (!transformedData[agentName]) {
transformedData[agentName] = {}
}
transformedData[agentName][aspect] = {
reason: scoreInfo.Reason,
score: scoreInfo.Score,
}
}
}
return transformedData
}
// Mock: 添加新的评估维度
mockAgentSelectModifyAddAspect = async (data: {
aspectList: string[]
}): Promise<{
aspectName: string
agentScores: Record<string, { score: number; reason: string }>
}> => {
// 调用Mock后端数据维度 -> agent -> { Reason, Score }
const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyAddAspect(
data.aspectList,
)
// 获取新添加的维度(最后一个)
const newAspect = data.aspectList[data.aspectList.length - 1]
if (!newAspect) {
throw new Error('aspectList is empty')
}
// 提取该维度的数据:维度 -> agent -> { Reason, Score } → agent -> { score, reason }
// 注意此转换逻辑与真实API完全一致
const newAspectAgents = response[newAspect]
const agentScores: Record<string, { score: number; reason: string }> = {}
if (newAspectAgents) {
for (const [agentName, scoreInfo] of Object.entries(newAspectAgents)) {
agentScores[agentName] = {
score: scoreInfo.Score,
reason: scoreInfo.Reason,
}
}
}
return {
aspectName: newAspect,
agentScores,
}
}
// Mock: 填充智能体任务流程
mockFillStepTaskTaskProcess = async (data: {
goal: string
stepTask: IApiStepTask
agents: string[]
}): Promise<IApiStepTask> => {
// 调用Mock后端数据后端原始格式
const response: RawAgentTaskProcessResponse = await mockBackendFillAgentTaskProcess(
data.goal,
data.stepTask,
data.agents,
)
// 数据转换:后端格式 → 前端格式
// 注意此转换逻辑与真实API完全一致
// 1. 转换颜色格式:[h, s, l] → "hsl(h, s%, l%)"
const vec2Hsl = (color: number[]): string => {
const [h, s, l] = color
return `hsl(${h}, ${s}%, ${l}%)`
}
// 2. 转换 brief.data: { "0": { text, color: [h,s,l] } } → { "0": { text, style: { background } } }
const briefData: Record<string, { text: string; style: { background: string } }> = {}
if (response.Collaboration_Brief_frontEnd?.data) {
for (const [key, value] of Object.entries(response.Collaboration_Brief_frontEnd.data)) {
briefData[key] = {
text: value.text,
style: {
background: vec2Hsl(value.color),
},
}
}
}
// 3. 转换 process: { ID, ActionType, AgentName, Description, ImportantInput } → { id, type, agent, description, inputs }
const process = (response.TaskProcess || []).map((action) => ({
id: action.ID,
type: action.ActionType,
agent: action.AgentName,
description: action.Description,
inputs: action.ImportantInput,
}))
// 4. 构建前端格式的 IApiStepTask
return {
name: response.StepName || '',
content: response.TaskContent || '',
inputs: response.InputObject_List || [],
output: response.OutputObject || '',
agents: response.AgentSelection || [],
brief: {
template: response.Collaboration_Brief_frontEnd?.template || '',
data: briefData,
},
process,
}
}
// Mock: 分支任务大纲
mockBranchPlanOutline = async (data: {
branch_Number: number
Modification_Requirement: string
Existing_Steps: string[]
Baseline_Completion: number
initialInputs: string[]
goal: string
}): Promise<IRawPlanResponse> => {
// 直接调用 Mock API已经返回 IRawPlanResponse 格式)
const response = await mockBranchPlanOutlineAPI({
branch_Number: data.branch_Number,
Modification_Requirement: data.Modification_Requirement,
Existing_Steps: data.Existing_Steps,
Baseline_Completion: data.Baseline_Completion,
InitialObject_List: data.initialInputs,
General_Goal: data.goal,
})
return response
}
// Mock: 填充任务流程
mockFillStepTask = async (data: { goal: string; stepTask: any }): Promise<any> => {
// 直接调用 Mock API已经返回 IRawStepTask 格式)
const response = await mockFillStepTaskAPI({
General_Goal: data.goal,
stepTask: data.stepTask,
})
return response
}
// Mock: 分支任务流程
mockBranchTaskProcess = async (data: {
branch_Number: number
Modification_Requirement: string
Existing_Steps: string[]
Baseline_Completion: number
stepTaskExisting: any
goal: string
}): Promise<BranchAction[][]> => {
// 直接调用 Mock API已经返回 BranchAction[][] 格式,与后端完全一致)
const response = await mockBranchTaskProcessAPI({
branch_Number: data.branch_Number,
Modification_Requirement: data.Modification_Requirement,
Existing_Steps: data.Existing_Steps,
Baseline_Completion: data.Baseline_Completion,
stepTaskExisting: data.stepTaskExisting,
General_Goal: data.goal,
})
return response
}
}
export default new Api()

View File

@@ -0,0 +1,20 @@
<?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="1766247549273" class="icon"
viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="10050" xmlns:xlink="http://www.w3.org/1999/xlink"
width="16" height="16">
<path d="M512 1024C229.248 1024
0 794.752 0 512S229.248 0 512 0s512
229.248 512 512-229.248 512-512 512z
m0-938.666667C276.352 85.333333 85.333333
276.352 85.333333 512s191.018667 426.666667
426.666667 426.666667 426.666667-191.018667
426.666667-426.666667S747.648 85.333333 512
85.333333z m198.698667 625.365334a42.666667
42.666667 0 0 1-60.330667
0L512 572.330667l-138.368 138.368a42.666667 42.666667 0 0 1-60.330667-60.330667L451.669333 512 313.301333 373.632a42.666667 42.666667 0 0 1 60.330667-60.330667L512 451.669333l138.368-138.368a42.624 42.624 0 1 1 60.330667 60.330667L572.330667 512l138.368 138.368a42.666667 42.666667 0 0 1 0 60.330667z"
fill="#8e0707" p-id="10051">
</path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1766247454540" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9049"
xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
<path d="M512 1024C230.4 1024 0 793.6 0 512S230.4 0 512 0s512 230.4 512 512-230.4 512-512 512z m0-938.666667C277.333333 85.333333 85.333333 277.333333 85.333333 512s192 426.666667 426.666667 426.666667 426.666667-192 426.666667-426.666667S746.666667 85.333333 512 85.333333z" p-id="9050">
</path>
<path d="M708.266667 375.466667c-17.066667-17.066667-44.8-17.066667-59.733334 0l-181.333333 181.333333-91.733333-91.733333c-14.933333-14.933333-40.533333-14.933333-55.466667 0l-6.4 4.266666c-14.933333 14.933333-14.933333 40.533333 0 55.466667l125.866667 125.866667c14.933333 14.933333 40.533333 14.933333 55.466666 0l4.266667-4.266667 209.066667-209.066667c17.066667-17.066667 17.066667-44.8 0-61.866666z" p-id="9051">
</path></svg>

After

Width:  |  Height:  |  Size: 1.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="1766136633767" class="icon" viewBox="0 0 1028 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7819" width="32.125" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M1018.319924 112.117535q4.093748 9.210934 6.652341 21.492179t2.558593 25.585928-5.117186 26.609365-16.374994 25.585928q-12.281245 12.281245-22.003898 21.492179t-16.886712 16.374994q-8.187497 8.187497-15.351557 14.32812l-191.382739-191.382739q12.281245-11.257808 29.167958-27.121083t28.144521-25.074209q14.32812-11.257808 29.679676-15.863275t30.191395-4.093748 28.656239 4.605467 24.050772 9.210934q21.492179 11.257808 47.589826 39.402329t40.425766 58.847634zM221.062416 611.554845q6.140623-6.140623 28.656239-29.167958t56.289041-56.80076l74.710909-74.710909 82.898406-82.898406 220.038979-220.038979 191.382739 192.406177-220.038979 220.038979-81.874969 82.898406q-40.937484 39.914047-73.687472 73.175753t-54.242167 54.753885-25.585928 24.562491q-10.234371 9.210934-23.539054 19.445305t-27.632802 16.374994q-14.32812 7.16406-41.960921 17.398431t-57.824197 19.957024-57.312478 16.886712-40.425766 9.210934q-27.632802 3.070311-36.843736-8.187497t-5.117186-37.867173q2.046874-14.32812 9.722653-41.449203t16.374994-56.289041 16.886712-53.730448 13.304682-33.773425q6.140623-14.32812 13.816401-26.097646t22.003898-26.097646z" p-id="7820" fill="#000000"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 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="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="1767084779395" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="21382" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M304.79872 108.70784c-118.75328 2.56-194.5856 51.74784-259.92192 144.73216-69.6832 99.1232-37.41696 233.31328-37.41696 233.31328s31.7696-100.06528 88.81664-147.74784c50.53952-42.22976 99.60448-78.11072 208.52224-82.97984v54.68672l141.69088-126.94016-141.69088-129.024v53.95968zM719.19616 915.25632c118.75328-2.56512 194.5856-51.712 259.96288-144.6912 69.64224-99.1232 37.37088-233.31328 37.37088-233.31328s-31.7696 100.06528-88.81152 147.74784c-50.51904 42.20928-99.6096 78.08512-208.52736 82.95936v-54.66624l-141.66528 126.93504 141.66528 129.024v-53.99552zM794.82368 304.37376h-88.64256c-72.77056 0-131.712 58.96192-131.712 131.712v110.96064c54.29248 22.21056 113.75616 34.49856 176.06656 34.49856 62.26944 0 121.728-12.288 176.02048-34.49856V436.08064c-0.00512-72.75008-58.96192-131.70688-131.73248-131.70688zM863.34464 167.58272c0 62.336-50.49856 112.87552-112.80896 112.87552-62.37696 0-112.87552-50.53952-112.87552-112.87552s50.49856-112.83456 112.87552-112.83456c62.30528 0 112.80896 50.49856 112.80896 112.83456z" fill="#ffffff" p-id="21383"></path><path d="M333.27616 692.08576H244.6336c-72.77056 0-131.73248 58.9824-131.73248 131.73248v110.94016c54.30784 22.23104 113.75104 34.49856 176.06656 34.49856 62.28992 0 121.74848-12.26752 176.02048-34.49856v-110.94016c0-72.75008-58.96192-131.73248-131.712-131.73248zM401.80224 555.31008c0 62.31552-50.47808 112.88064-112.83456 112.88064-62.37696 0-112.88064-50.56512-112.88064-112.88064 0-62.35136 50.50368-112.85504 112.88064-112.85504 62.35648 0 112.83456 50.5088 112.83456 112.85504z" fill="#ffffff" p-id="21384"></path></svg>

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="1766289101374" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2562" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M801.796646 348.507817c-6.599924 7.099918-9.599889 16.799806-8.299904 26.399694 1.499983 10.899874 2.399972 21.799748 2.699969 32.299627 1.399984 51.499404-11.099872 96.098888-37.09957 132.698464-45.099478 63.199269-117.398641 83.499034-170.098032 98.298863-35.799586 9.999884-61.599287 12.599854-82.399047 14.599831-22.799736 2.299973-34.299603 3.399961-50.599414 12.799851-3.199963 1.899978-6.399926 3.899955-9.49989 6.09993-29.499659 21.199755-46.399463 55.699355-46.399463 91.998935v28.599669c0 10.699876 5.499936 20.599762 14.399833 26.599692 30.299649 20.399764 50.199419 55.199361 49.599426 94.598906-0.799991 60.299302-49.999421 109.598732-110.398722 110.398722C291.002557 1024.89999 240.003148 974.400574 240.003148 912.001296c0-38.89955 19.799771-73.199153 49.899422-93.198921 8.799898-5.899932 14.099837-15.899816 14.099837-26.499694V231.60917c0-10.699876-5.499936-20.599762-14.399833-26.599692-30.299649-20.399764-50.199419-55.199361-49.599426-94.598906C240.803138 50.11127 290.002569 0.911839 350.40187 0.01185 413.001146-0.88814 464.000555 49.611276 464.000555 112.010554c0 38.89955-19.799771 73.199153-49.899422 93.198921-8.799898 5.899932-14.099837 15.899816-14.099837 26.499694v346.095994c0 4.099953 4.399949 6.599924 7.999908 4.599947h0.099998c34.299603-19.699772 62.099281-22.399741 88.99897-25.099709 18.799782-1.799979 38.299557-3.799956 65.899238-11.499867 43.299499-12.09986 92.398931-25.8997 117.898635-61.599287 16.999803-23.799725 20.599762-53.399382 19.099779-79.599079-0.699992-12.599854-8.699899-23.499728-20.399763-28.099675-42.099513-16.299811-71.899168-57.299337-71.599172-105.298781 0.399995-61.699286 51.299406-111.698707 112.998692-111.198714 61.399289 0.499994 110.998715 50.499416 110.998716 111.998704 0 29.599657-11.499867 56.499346-30.199651 76.499115z" p-id="2563" fill="#ffffff"></path></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

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="1764755052115" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6234" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M886.784 746.496q29.696 30.72 43.52 56.32t-4.608 58.368q-4.096 6.144-11.264 14.848t-14.848 16.896-15.36 14.848-12.8 9.728q-25.6 15.36-60.416 8.192t-62.464-34.816l-43.008-43.008-57.344-57.344-67.584-67.584-73.728-73.728-131.072 131.072q-60.416 60.416-98.304 99.328-38.912 38.912-77.312 48.128t-68.096-17.408l-7.168-7.168-11.264-11.264-11.264-11.264q-6.144-6.144-7.168-8.192-11.264-14.336-13.312-29.184t2.56-29.184 13.824-27.648 20.48-24.576q9.216-8.192 32.768-30.72l55.296-57.344q33.792-32.768 75.264-73.728t86.528-86.016q-49.152-49.152-93.696-93.184t-79.872-78.848-57.856-56.832-27.648-27.136q-26.624-26.624-27.136-52.736t17.92-52.736q8.192-10.24 23.552-24.064t21.504-17.92q30.72-20.48 55.296-17.92t49.152 28.16l31.744 31.744q23.552 23.552 58.368 57.344t78.336 76.288 90.624 88.576q38.912-38.912 76.288-75.776t69.632-69.12 58.368-57.856 43.52-43.008q24.576-23.552 53.248-31.232t55.296 12.8q1.024 1.024 6.656 5.12t11.264 9.216 10.752 9.728 7.168 5.632q27.648 26.624 27.136 57.856t-27.136 57.856q-18.432 18.432-45.568 46.08t-60.416 60.416-70.144 69.632l-77.824 77.824q37.888 36.864 74.24 72.192t67.584 66.048 56.32 56.32 41.472 41.984z" p-id="6235"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 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="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,14 @@
<svg
className="icon"
viewBox="0 0 8960 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="9634"
width="100%"
height="60"
>
<path
d="M8960 0c-451.52 181.184-171.2 1024-992 1024H992C171.232 1024 451.392 181.184 0 0h8960z"
p-id="9635"
></path>
</svg>

After

Width:  |  Height:  |  Size: 336 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="1764757574909" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9019" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M248.88888875 558.88888844l401.7777778 401.77777782c28.44444469 28.44444469 74.66666625 28.44444469 99.55555594 0 28.44444469-28.44444469 28.44444469-74.66666625 1e-8-99.555555l-352.00000031-352.00000032 352.00000031-351.99999937c28.44444469-28.44444469 28.44444469-71.11111125 0-99.55555594s-74.66666625-28.44444469-99.55555594 0L248.88888875 459.33333344c-14.22222188 14.22222188-21.33333375 31.99999969-21.33333281 49.7777775 0 17.7777778 7.11111094 35.55555562 21.33333281 49.7777775" fill="#ffffff" p-id="9020"></path></svg>

After

Width:  |  Height:  |  Size: 860 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="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="1764820450888" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="23116" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M854.442667 725.376c-117.845333 204.117333-378.837333 274.048-582.954667 156.202667A425.173333 425.173333 0 0 1 133.546667 754.346667a32 32 0 0 1 15.573333-48.298667c160.725333-57.514667 246.826667-124.16 296.789333-219.562667 52.565333-100.394667 66.176-210.346667 29.397334-361.088a32 32 0 0 1 32.810666-39.552 425.002667 425.002667 0 0 1 190.165334 56.618667c204.117333 117.845333 274.048 378.837333 156.16 582.912z" fill="#ffffff" p-id="23117"></path></svg>

After

Width:  |  Height:  |  Size: 793 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="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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.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="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="1764757483582" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7204" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M775.11111125 465.11111156l-401.7777778-401.77777782c-28.44444469-28.44444469-74.66666625-28.44444469-99.55555594 0-28.44444469 28.44444469-28.44444469 74.66666625-1e-8 99.555555l352.00000031 352.00000032-352.00000031 351.99999937c-28.44444469 28.44444469-28.44444469 71.11111125 0 99.55555594s74.66666625 28.44444469 99.55555594 0L775.11111125 564.66666656c14.22222188-14.22222188 21.33333375-31.99999969 21.33333281-49.7777775 0-17.7777778-7.11111094-35.55555562-21.33333281-49.7777775" fill="#d8d8d8" p-id="7205"></path></svg>

After

Width:  |  Height:  |  Size: 860 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="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="1764820260653" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13095" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M152.00256 792.00256l76.00128-77.99808 60.00128 60.00128-76.00128 77.99808z m317.99808 165.99552V832h83.99872v125.99808H470.00064zM512 233.99936c141.99808 0 256 114.00192 256 256s-114.00192 256-256 256-256-114.00192-256-256 114.00192-256 256-256zM854.00064 448h128v86.00064h-128V448z m-118.00064 326.00064l60.00128-57.99936 76.00128 76.00128-60.00128 60.00128z m136.00256-584.00256l-76.00128 76.00128-60.00128-60.00128 76.00128-76.00128z m-318.0032-165.99552v125.99808H470.00064V24.00256h83.99872z m-384 423.99744v86.00064h-128V448h128zM288 205.99808L227.99872 265.99936 151.99744 189.99808 211.99872 129.9968z" p-id="13096" fill="#ffa300"></path></svg>

After

Width:  |  Height:  |  Size: 985 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="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,105 @@
<template>
<el-tooltip
:disabled="!isOverflow"
effect="light"
placement="top"
:content="text"
popper-class="multi-line-tooltip-popper"
>
<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>
<style>
.multi-line-tooltip-popper {
max-width: 400px;
}
</style>

View File

@@ -0,0 +1,36 @@
<script setup lang="ts">
import { computed } from 'vue'
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,71 @@
<script setup lang="ts">
import { useConfigStore } from '@/stores'
import { ref, onMounted, onUnmounted } from 'vue'
defineOptions({
name: 'AppHeader'
})
const configStore = useConfigStore()
const isDark = ref(false)
// 检测当前主题
const checkDarkMode = () => {
isDark.value =
document.documentElement.classList.contains('dark') ||
window.matchMedia('(prefers-color-scheme: dark)').matches
}
onMounted(() => {
checkDarkMode()
// 监听系统主题变化
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
mediaQuery.addEventListener('change', checkDarkMode)
// 监听类名变化
const observer = new MutationObserver(checkDarkMode)
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
})
onUnmounted(() => {
mediaQuery.removeEventListener('change', checkDarkMode)
observer.disconnect()
})
})
</script>
<template>
<div
class="bg-[var(--color-bg)] h-[60px] relative pl-[28px] font-[900] text-[24px] absolute left-1/2 transform -translate-x-1/2 w-[600px] h-[60px] dark:bg-black flex items-center justify-center transition-[top]"
>
<svg-icon
icon-class="icons"
class="header-icon"
size="100% "
:style="{
filter: isDark
? 'drop-shadow(0px 0px 5px rgba(0, 0, 0, 0.5))'
: 'drop-shadow(0px 0px 5px rgba(161, 161, 161, 0.2))',
stroke: 'var(--color-border)',
strokeWidth: '1px',
strokeLinejoin: 'round'
}"
/>
<h2
className="flex items-center gap-3 text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3 absolute left-1/2 transform -translate-x-1/2"
>
<img src="/logo.png" alt="logo" className="w-8" />
<p>
<span className="text-[#9e0000]">数联网</span
><span className="text-[var(--color-text-title)]">多智能体协同平台</span>
</p>
</h2>
</div>
</template>
<style scoped lang="scss">
.header-icon {
color: var(--color-header-bg);
}
</style>

View File

@@ -0,0 +1,383 @@
<script setup lang="ts">
import { ref, onMounted, computed, reactive, nextTick } from 'vue'
import SvgIcon from '@/components/SvgIcon/index.vue'
import { useAgentsStore, useConfigStore } from '@/stores'
import api from '@/api'
import { changeBriefs } from '@/utils/collaboration_Brief_FrontEnd.ts'
import { ElMessage } from 'element-plus'
import AssignmentButton from './TaskTemplate/TaskSyllabus/components/AssignmentButton.vue'
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)
const hasAutoSearched = ref(false) // 防止重复自动搜索
const isExpanded = ref(false) // 控制搜索框是否展开
// 解析URL参数
function getUrlParam(param: string): string | null {
const urlParams = new URLSearchParams(window.location.search)
return urlParams.get(param)
}
const planReady = computed(() => {
return agentsStore.agentRawPlan.data !== undefined
})
const openAgentAllocationDialog = () => {
console.log('打开智能体分配弹窗')
agentsStore.openAgentAllocationDialog()
}
// 自动搜索函数
async function autoSearchFromUrl() {
const query = getUrlParam('q')
if (query && !hasAutoSearched.value) {
// 解码URL参数
const decodedQuery = decodeURIComponent(query)
searchValue.value = decodedQuery
hasAutoSearched.value = true
// 延迟执行搜索,确保组件已完全渲染
setTimeout(() => {
handleSearch()
}, 100)
}
}
// 处理获取焦点事件
function handleFocus() {
isFocus.value = true
isExpanded.value = true // 搜索框展开
}
const taskContainerRef = ref<HTMLDivElement | null>(null)
// 处理失去焦点事件
function handleBlur() {
isFocus.value = false
// 延迟收起搜索框,以便点击按钮等操作
setTimeout(() => {
isExpanded.value = false
// 强制重置文本区域高度到最小行数
resetTextareaHeight()
}, 200)
}
// 重置文本区域高度到最小行数
function resetTextareaHeight() {
nextTick(() => {
// 修复使用更可靠的方式获取textarea元素
const textarea =
document.querySelector('#task-container .el-textarea__inner') ||
document.querySelector('#task-container textarea')
if (textarea) {
// 强制设置最小高度
textarea.style.height = 'auto'
textarea.style.minHeight = '56px'
textarea.style.overflowY = 'hidden'
}
})
}
async function handleSearch() {
try {
triggerOnFocus.value = false
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 })
console.log('agentsStore.agentRawPlan', agentsStore.agentRawPlan)
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())
}
}
// 组件挂载时检查URL参数
onMounted(() => {
autoSearchFromUrl()
})
</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"
:class="{ expanded: isExpanded }"
>
<span class="text-[var(--color-text-task)] font-bold task-title">任务</span>
<el-autocomplete
ref="autocompleteRef"
v-model.trim="searchValue"
class="task-input"
size="large"
:rows="1"
:autosize="{ minRows: 1, maxRows: 10 }"
placeholder="请输入您的任务"
type="textarea"
:append-to="taskContainerRef"
:fetch-suggestions="querySearch"
@change="agentsStore.setSearchValue"
:disabled="!(agentsStore.agents.length > 0)"
:debounce="0"
:clearable="true"
:trigger-on-focus="triggerOnFocus"
@focus="handleFocus"
@blur="handleBlur"
@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="#ffffff"
/>
</el-button>
</div>
<AssignmentButton v-dev-only v-if="planReady" @click="openAgentAllocationDialog" />
</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-taskbar), var(--color-bg-taskbar)) padding-box,
linear-gradient(to right, #00c8d2, #315ab4) border-box;
border-radius: 30px;
position: absolute;
left: 50%;
transform: translateX(-50%);
z-index: 998;
min-height: 100%;
overflow: hidden;
padding: 0 55px 0 47px;
transition: all 0.3s ease;
/* 搜索框展开时的样式 */
&.expanded {
box-shadow: var(--color-task-shadow);
:deep(.el-autocomplete .el-textarea .el-textarea__inner) {
overflow-y: auto !important;
min-height: 56px !important;
// overflow-y: hidden;
// background-color: black;
}
}
/* 非展开状态时,确保文本区域高度固定 */
&:not(.expanded) {
:deep(.el-textarea__inner) {
height: 56px !important;
overflow-y: hidden !important;
min-height: 56px !important;
}
}
:deep(.el-popper) {
position: static !important;
width: calc(100% + 102px); /*增加左右padding的总和 */
min-width: calc(100% + 102px); /* 确保最小宽度也增加 */
margin-left: -47px; /* 向左偏移左padding的值 */
margin-right: -55px; /*向右偏移右padding的值 */
background: var(--color-bg-taskbar);
border: none;
transition: height 0s ease-in-out;
border-top: 1px solid var(--color-border);
border-radius: 0;
box-shadow: none;
li {
height: 45px;
box-sizing: border-box;
line-height: 45px;
font-size: 14px;
padding-left: 27px;
&:hover {
background: var(--color-bg-hover);
color: var(--color-text-hover);
}
}
.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 0 18px;
resize: none;
color: var(--color-text-taskbar);
/* 聚焦时的样式 */
.expanded & {
overflow-y: auto;
}
&::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: 27px;
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;
}
}
}
.drawer-header {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
.title {
font-size: 18px;
font-weight: bold;
}
}
.process-list {
padding: 0 8px;
}
.process-item {
margin-bottom: 16px;
padding: 12px;
border-radius: 8px;
background: var(--color-bg-list);
border: 1px solid var(--color-border-default);
.process-content {
display: flex;
align-items: flex-start;
gap: 8px;
.agent-tag {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
white-space: nowrap;
flex-shrink: 0;
}
.process-text {
line-height: 1.6;
font-size: 14px;
color: var(--el-text-color-primary);
white-space: pre-wrap;
}
}
.edit-container {
margin-top: 8px;
}
}
.process-item:hover {
border-color: var(--el-border-color);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
</style>

View File

@@ -0,0 +1,170 @@
<script setup lang="ts">
import { computed } from 'vue'
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-[43px]">
<!-- 图标区域 -->
<div
class="w-[44px] h-[44px] rounded-full flex items-center justify-center flex-shrink-0 relative right-[2px] icon-container"
:style="{ background: getAgentMapIcon(item.Name).color }"
>
<svg-icon :icon-class="getAgentMapIcon(item.Name).icon" color="#fff" size="24px" />
</div>
<div class="flex-1 text-[14px] textClass flex flex-col items-end justify-start truncate ml-1">
<div class="flex items-center justify-start gap-2 w-full">
<span
class="truncate"
:style="
agentsStore.currentTask?.AgentSelection?.includes(item.Name)
? 'color:var(--color-accent)'
: ''
"
>{{ item.Name }}</span
>
</div>
<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,
border: `1px solid ${getActionTypeDisplay(item1.ActionType)?.border}`
}"
></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-[var(--color-border-default)] my-[8px]"
></div>
<AssignmentButton />
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.user-item {
background: var(--color-agent-list-bg);
border-radius: 40px;
padding-right: 12px;
cursor: pointer;
transition: all 0.25s ease;
color: var(--color-text-detail);
border: 1px solid var(--color-agent-list-border);
box-sizing: border-box;
.duty-info {
transition: height 0.25s ease;
height: 0;
overflow: hidden;
}
& + .user-item {
margin-top: 8px;
}
&:hover {
box-shadow: var(--color-agent-list-hover-shadow);
color: var(--color-text);
background: var(--color-agent-list-hover-bg);
border: 1px solid var(--color-agent-list-hover-border);
}
}
.textClass {
color: var(--color-text-agent-list);
&:hover {
color: var(--color-text-agent-list-hover);
}
}
.active-card {
background: linear-gradient(var(--color-bg-quaternary), var(--color-bg-quaternary)) padding-box,
linear-gradient(to right, var(--color-accent), var(--color-accent-secondary)) border-box;
border: 1px solid var(--color-agent-list-border);
&:hover {
box-shadow: var(--color-agent-list-hover-shadow);
color: var(--color-text);
background: var(--color-agent-list-hover-bg);
border: 1px solid var(--color-agent-list-hover-border);
border-radius: 20px;
.duty-info {
height: auto;
}
.icon-container {
bottom: 2px;
}
}
}
// 添加头像容器样式修复
.icon-container {
right: 0 !important;
margin-left: 0px;
}
</style>

View File

@@ -0,0 +1,272 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
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 { 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', 'apiUrl', 'apiKey', 'apiModel']))
)
})
// 上传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)
}
}
// 在validateApiConfig函数中添加更详细的日志
const validateApiConfig = (agent: any) => {
const hasApiUrl = 'apiUrl' in agent
const hasApiKey = 'apiKey' in agent
const hasApiModel = 'apiModel' in agent
// 三个字段必须同时存在或同时不存在
if (hasApiUrl !== hasApiKey || hasApiKey !== hasApiModel) {
console.error('❌ API配置不完整:', {
agentName: agent.Name,
missingFields: {
apiUrl: !hasApiUrl,
apiKey: !hasApiKey,
apiModel: !hasApiModel
}
})
return false
}
if (hasApiUrl && hasApiKey && hasApiModel) {
console.log('✅ API配置完整将使用自定义API配置:', {
apiUrl: agent.apiUrl,
apiKey: agent.apiKey ? '***' + agent.apiKey.slice(-4) : '未设置',
apiModel: agent.apiModel
})
} else {
console.log(' 未设置API配置将使用默认URL配置')
}
return true
}
const readFileContent = (file: File) => {
console.log('📁 开始读取文件:', file.name, '大小:', file.size, '类型:', file.type)
const reader = new FileReader()
reader.onload = e => {
try {
console.log('📄 文件读取完成开始解析JSON')
const content = e.target?.result as string
const jsonData = JSON.parse(content)
console.log('🔍 解析的JSON数据:', jsonData)
console.log(
'📊 数据类型:',
Array.isArray(jsonData) ? '数组' : '对象',
'长度:',
Array.isArray(jsonData) ? jsonData.length : 'N/A'
)
if (!Array.isArray(jsonData)) {
console.error('❌ JSON格式错误: 必须为数组格式')
ElMessage.error('JSON格式错误: 必须为数组格式')
return
}
console.log('🔍 开始验证智能体数据...')
const validAgents = jsonData.filter((agent, index) => {
console.log(`🔍 验证第${index + 1}个智能体:`, agent.Name || '未命名')
// 验证必需字段
if (!agent.Name || typeof agent.Name !== 'string') {
console.error(`❌ 智能体${index + 1}缺少Name字段或格式错误`)
return false
}
if (!agent.Icon || typeof agent.Icon !== 'string') {
console.error(`❌ 智能体${index + 1}缺少Icon字段或格式错误`)
return false
}
if (!agent.Profile || typeof agent.Profile !== 'string') {
console.error(`❌ 智能体${index + 1}缺少Profile字段或格式错误`)
return false
}
// 验证API配置
if (!validateApiConfig(agent)) {
console.error(`❌ 智能体${index + 1}API配置验证失败`)
return false
}
return true
})
// 修改发送到后端的数据
const processedAgents = validAgents.map(agent => ({
Name: agent.Name,
Profile: agent.Profile,
Icon: agent.Icon,
Classification: agent.Classification || '',
apiUrl: agent.apiUrl,
apiKey: agent.apiKey,
apiModel: agent.apiModel
}))
agentsStore.setAgents(processedAgents)
// 调用API
api
.setAgents(processedAgents)
.then(() => {
console.log('✅ 后端API调用成功')
ElMessage.success('智能体上传成功')
})
.catch(error => {
console.error('❌ 后端API调用失败:', error)
ElMessage.error('智能体上传失败')
})
} catch (error) {
console.error('❌ JSON解析错误:', error)
ElMessage.error('JSON解析错误')
}
}
reader.onerror = error => {
console.error('❌ 文件读取错误:', error)
ElMessage.error('文件读取错误')
}
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 text-[var(--color-text-title-header)]">智能体库</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-[var(--color-bg-indicator)] 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"
>
<div
class="w-[8px] h-[8px] rounded-full"
:style="{
background: item.color,
border: `1px solid ${item.border}`
}"
></div>
<span class="text-[12px]">{{ item.name }}</span>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.agent-repo {
padding: 0 8px;
.plus-button {
background: var(--color-bg-tertiary);
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: var(--color-bg-quaternary);
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,326 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { getActionTypeDisplay } from '@/layout/components/config.ts'
import { useAgentsStore } from '@/stores'
import BranchButton from './components/TaskButton.vue'
const agentsStore = useAgentsStore()
const props = defineProps<{
step: {
Id?: string
TaskProcess: Array<{
ID: string
ActionType: string
AgentName: string
Description: string
}>
}
}>()
const emit = defineEmits<{
(e: 'open-edit', stepId: string, processId: string): void
(e: 'save-edit', stepId: string, processId: string, value: string): void
}>()
// 🔄 从 currentTask 中获取数据(与分支切换联动)
const currentTaskProcess = computed(() => {
// ✅ 优先使用 currentTask包含分支切换后的数据
const currentTask = agentsStore.currentTask
if (currentTask && currentTask.Id === props.step.Id && currentTask.TaskProcess) {
return currentTask.TaskProcess
}
// ⚠️ 降级:从 agentRawPlan 中获取原始数据(不受分支切换影响)
const collaborationProcess = agentsStore.agentRawPlan.data?.['Collaboration Process'] || []
const rawData = collaborationProcess.find((task: any) => task.Id === props.step.Id)
return rawData?.TaskProcess || []
})
// 当前正在编辑的process ID
const editingProcessId = ref<string | null>(null)
// 编辑框的值
const editValue = ref('')
// 鼠标悬停的process ID
const hoverProcessId = ref<string | null>(null)
// 🆕 处理卡片点击事件(非编辑模式下)
function handleCardClick() {
// 如果正在编辑,不处理点击
if (editingProcessId.value) return
// 设置当前任务,与任务大纲联动
if (props.step.Id) {
agentsStore.setCurrentTask(props.step)
}
}
// 检测当前是否是深色模式
function isDarkMode(): boolean {
return document.documentElement.classList.contains('dark')
}
// 获取颜色浅两号的函数
function getLightColor(color: string, level: number = 2): string {
if (!color || color.length !== 7 || color[0] !== '#') return color
const r = parseInt(color.substr(1, 2), 16)
const g = parseInt(color.substr(3, 2), 16)
const b = parseInt(color.substr(5, 2), 16)
// 增加亮度(浅两号)
const lightenAmount = level * 20
const newR = Math.min(255, r + lightenAmount)
const newG = Math.min(255, g + lightenAmount)
const newB = Math.min(255, b + lightenAmount)
return `#${Math.round(newR).toString(16).padStart(2, '0')}${Math.round(newG)
.toString(16)
.padStart(2, '0')}${Math.round(newB).toString(16).padStart(2, '0')}`
}
// 获取颜色深两号的函数
function getDarkColor(color: string, level: number = 2): string {
if (!color || color.length !== 7 || color[0] !== '#') return color
const r = parseInt(color.substr(1, 2), 16)
const g = parseInt(color.substr(3, 2), 16)
const b = parseInt(color.substr(5, 2), 16)
// 降低亮度(深两号)
const darkenAmount = level * 20
const newR = Math.max(0, r - darkenAmount)
const newG = Math.max(0, g - darkenAmount)
const newB = Math.max(0, b - darkenAmount)
return `#${Math.round(newR).toString(16).padStart(2, '0')}${Math.round(newG)
.toString(16)
.padStart(2, '0')}${Math.round(newB).toString(16).padStart(2, '0')}`
}
// 根据主题模式获取调整后的颜色
function getAdjustedColor(color: string, level: number = 2): string {
if (isDarkMode()) {
return getDarkColor(color, level)
} else {
return getLightColor(color, level)
}
}
// 处理鼠标进入
function handleMouseEnter(processId: string) {
hoverProcessId.value = processId
}
// 处理鼠标离开
function handleMouseLeave() {
hoverProcessId.value = null
}
// 处理双击编辑针对单个process
function handleDblClick(processId: string, currentDescription: string) {
editingProcessId.value = processId
editValue.value = currentDescription
emit('open-edit', props.step.Id || '', processId)
}
// 处理保存编辑
function handleSave(processId: string) {
if (!editingProcessId.value) return
emit('save-edit', props.step.Id || '', processId, editValue.value)
editingProcessId.value = null
editValue.value = ''
}
// 处理取消编辑
function handleCancel() {
editingProcessId.value = null
editValue.value = ''
}
</script>
<template>
<div class="process-card" @click="handleCardClick">
<div class="process-content">
<!-- 显示模式 -->
<div class="display-content">
<span
v-for="process in currentTaskProcess"
:key="process.ID"
class="process-segment"
@mouseenter="handleMouseEnter(process.ID)"
@mouseleave="handleMouseLeave"
>
<span
class="agent-name"
:style="{
backgroundColor: getActionTypeDisplay(process.ActionType)?.color || '#909399',
color: '#fff',
padding: '2px 6px',
borderRadius: '3px',
marginRight: '4px'
}"
>
{{ process.AgentName }}
</span>
<!-- 编辑模式 - 修改为卡片样式 -->
<div v-if="editingProcessId === process.ID" class="edit-container">
<div class="edit-card">
<div class="flex flex-col gap-3">
<el-input
v-model="editValue"
type="textarea"
:autosize="{ minRows: 3, maxRows: 6 }"
placeholder="请输入描述内容"
autofocus
/>
<div class="flex justify-end">
<svg-icon
icon-class="Check"
size="20px"
color="#328621"
class="cursor-pointer mr-4"
@click="handleSave(process.ID)"
title="保存"
/>
<svg-icon
icon-class="Cancel"
size="20px"
color="#8e0707"
class="cursor-pointer mr-1"
@click="handleCancel"
title="取消"
/>
</div>
</div>
</div>
</div>
<!-- 显示模式 -->
<span
v-else
class="process-description"
:class="{ hovered: hoverProcessId === process.ID }"
:style="{
border: `1px solid ${getActionTypeDisplay(process.ActionType)?.border}`,
backgroundColor:
hoverProcessId === process.ID
? getAdjustedColor(getActionTypeDisplay(process.ActionType)?.color || '#909399')
: 'transparent'
}"
@dblclick="handleDblClick(process.ID, process.Description)"
>
{{ process.Description }}
</span>
<span class="separator" v-if="process.Description && !process.Description.endsWith('。')"
></span
>
</span>
</div>
</div>
<!-- 按钮点击不会冒泡到卡片 -->
<BranchButton :step="step" />
</div>
</template>
<style scoped lang="scss">
.process-card {
position: relative;
margin-bottom: 16px;
padding: 16px;
border-radius: 8px;
background: var(--color-bg-list);
border: 1px solid var(--color-border-default);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: all 0.2s ease;
.process-content {
min-height: 20px;
.display-content {
line-height: 1.6;
font-size: 14px;
color: var(--el-text-color-primary);
.process-segment {
display: inline;
position: relative;
.agent-name {
display: inline-block;
font-weight: 500;
font-size: 13px;
margin-right: 4px;
cursor: default;
}
.edit-container {
display: block; // 改为块级元素,使其换行显示
margin-top: 8px;
margin-bottom: 8px;
.edit-card {
//background: #f0f2f5;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 600px;
:deep(.el-textarea) {
width: 100%;
.el-textarea__inner {
font-size: 14px;
line-height: 1.6;
padding: 8px 12px;
border-radius: 4px;
resize: vertical;
min-height: 60px;
color: var(--color-text-taskbar);
}
}
.edit-buttons {
display: flex;
gap: 8px;
justify-content: flex-end;
margin-top: 8px;
}
}
}
.process-description {
display: inline;
white-space: normal;
cursor: pointer;
padding: 2px 4px;
border-radius: 3px;
transition: background-color 0.2s ease;
&.hovered {
transition: background-color 0.2s ease;
}
}
.separator {
margin-right: 8px;
}
&:last-child .separator {
display: none;
}
}
}
}
}
.process-card:hover {
border-color: var(--el-border-color);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
</style>

View File

@@ -0,0 +1,141 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useAgentsStore, useSelectionStore } from '@/stores'
const agentsStore = useAgentsStore()
const selectionStore = useSelectionStore()
const emit = defineEmits<{
(e: 'click'): void
}>()
const props = defineProps<{
step?: any
}>()
// 获取分支数量 - 主分支(1) + 额外分支数量
const branchCount = computed(() => {
if (!props.step?.Id) return 1
// 获取该任务步骤的分支数据
const taskStepId = props.step.Id
const branches = selectionStore.getTaskProcessBranches(taskStepId)
// 主分支(1) + 额外分支数量
return branches.length || 1
})
// 🆕 判断按钮是否可点击(只有当前按钮对应的任务是任务大纲中选中的任务时才可点击)
const isClickable = computed(() => {
if (!props.step?.Id || !agentsStore.currentTask?.Id) {
return false
}
return props.step.Id === agentsStore.currentTask.Id
})
const handleClick = (event?: MouseEvent) => {
// 🆕 只有可点击时才执行操作
if (!isClickable.value) {
return
}
// 阻止冒泡,避免触发卡片点击
if (event) {
event.stopPropagation()
}
emit('click')
// 设置当前任务
if (props.step) {
agentsStore.setCurrentTask(props.step)
}
// 触发打开任务过程探索窗口
agentsStore.openPlanTask()
}
</script>
<template>
<div
class="task-button"
:class="{ 'has-branches': branchCount > 0, 'is-disabled': !isClickable }"
@click="handleClick"
:title="isClickable ? `${branchCount} 个分支` : '请先在任务大纲中选中此任务'"
>
<!-- 流程图标 -->
<svg-icon icon-class="branch" size="20px" class="task-icon" />
<!-- 分支数量显示 -->
<span class="branch-count">
{{ branchCount }}
</span>
</div>
</template>
<style scoped lang="scss">
.task-button {
/* 定位 - 右下角 */
position: absolute;
right: 0;
bottom: 0;
/* 尺寸 */
width: 36px;
height: 32px;
/* 样式 */
background-color: #43a8aa;
border-radius: 10px 0 0 0;
cursor: pointer;
user-select: none;
z-index: 100;
/* 布局 */
display: flex;
align-items: center;
justify-content: center;
/* 交互 */
transition: all 0.2s ease;
&:hover {
filter: brightness(0.9);
}
// 🆕 禁用状态
&.is-disabled {
background-color: #bdc3c7;
cursor: not-allowed;
opacity: 0.6;
&:hover {
filter: none;
}
}
&.has-branches::after {
content: '';
position: absolute;
top: -2px;
right: -2px;
width: 8px;
height: 8px;
background: #ff6b6b;
border-radius: 50%;
border: 2px solid white;
}
}
.task-icon {
color: white;
}
.branch-count {
position: absolute;
right: 4px;
bottom: 2px;
font-size: 12px;
color: white;
font-weight: 800;
text-align: right;
line-height: 1;
}
</style>

View File

@@ -0,0 +1,183 @@
// 模拟后端原始返回格式的 Mock 数据 - fill_stepTask_TaskProcess 接口
// 后端返回格式: IRawStepTask { StepName, TaskContent, InputObject_List, OutputObject, AgentSelection, TaskProcess, Collaboration_Brief_frontEnd }
import type { IRawStepTask } from '@/stores'
// TaskProcess 项格式
interface RawTaskProcessItem {
ID: string
ActionType: string
AgentName: string
Description: string
ImportantInput: string[]
}
// Collaboration_Brief_frontEnd 数据项格式
interface RawBriefDataItem {
text: string
color: number[] // [h, s, l]
}
// 后端返回的完整数据格式
export interface RawAgentTaskProcessResponse {
StepName: string
TaskContent: string
InputObject_List: string[]
OutputObject: string
AgentSelection: string[]
TaskProcess: RawTaskProcessItem[]
Collaboration_Brief_frontEnd?: {
template: string
data: Record<string, RawBriefDataItem>
}
}
// 模拟后端返回的原始数据结构(与后端缓存数据格式一致)
// 使用与 AgentAssignmentBackendMock 相同的 agent 列表
export const mockBackendAgentTaskProcessData: RawAgentTaskProcessResponse = {
StepName: '腐蚀类型识别',
TaskContent: '分析船舶制造中常见的材料腐蚀类型及其成因。',
InputObject_List: [],
OutputObject: '腐蚀类型及成因列表',
AgentSelection: ['腐蚀机理研究员', '实验材料学家', '防护工程专家'],
TaskProcess: [
{
ID: 'action_101',
ActionType: 'Propose',
AgentName: '腐蚀机理研究员',
Description: '分析海洋环境下的腐蚀机理,确定关键防护要素',
ImportantInput: ['海洋环境参数', '防护性能指标'],
},
{
ID: 'action_102',
ActionType: 'Critique',
AgentName: '实验材料学家',
Description: '基于腐蚀机理分析结果,设计涂层材料的基础配方',
ImportantInput: ['腐蚀机理分析结果', '涂层材料配方'],
},
{
ID: 'action_103',
ActionType: 'Improve',
AgentName: '防护工程专家',
Description: '筛选适用于防护涂层的二维材料,评估其性能潜力',
ImportantInput: ['材料配方设计', '涂层材料配方'],
},
{
ID: 'action_104',
ActionType: 'Finalize',
AgentName: '实验材料学家',
Description: '制定涂层材料性能测试实验方案,包括测试指标和方法',
ImportantInput: ['二维材料筛选结果', '防护性能指标'],
},
{
ID: 'action_105',
ActionType: 'Critique',
AgentName: '防护工程专家',
Description: '模拟海洋流体环境对涂层材料的影响,优化涂层结构',
ImportantInput: ['实验方案', '海洋环境参数'],
},
{
ID: 'action_106',
ActionType: 'Improve',
AgentName: '腐蚀机理研究员',
Description: '综合评估涂层材料的防护性能,提出改进建议',
ImportantInput: ['流体力学模拟结果', '实验材料学测试结果', '二维材料性能数据'],
},
{
ID: 'action_107',
ActionType: 'Improve',
AgentName: '实验材料学家',
Description: '整理研发数据和测试结果,撰写完整的研发报告',
ImportantInput: ['综合性能评估', '所有研发数据'],
},
],
Collaboration_Brief_frontEnd: {
template: '基于!<0>!、!<1>!和!<2>!!<3>!、!<4>!、!<5>!和!<6>!执行!<7>!任务,以获得!<8>!。',
data: {
'0': {
text: '涂层材料配方',
color: [120, 60, 70], // hsl(120, 60%, 70%)
},
'1': {
text: '海洋环境参数',
color: [120, 60, 70], // hsl(120, 60%, 70%)
},
'2': {
text: '防护性能指标',
color: [120, 60, 70], // hsl(120, 60%, 70%)
},
'3': {
text: '腐蚀机理研究员',
color: [0, 0, 90], // hsl(0, 0%, 90%)
},
'4': {
text: '先进材料研发员',
color: [0, 0, 90], // hsl(0, 0%, 90%)
},
'5': {
text: '二维材料科学家',
color: [0, 0, 90], // hsl(0, 0%, 90%)
},
'6': {
text: '实验材料学家',
color: [0, 0, 90], // hsl(0, 0%, 90%)
},
'7': {
text: '研发适用于海洋环境的耐腐蚀防护涂层材料,并进行性能测试与评估',
color: [0, 0, 87], // hsl(0, 0%, 87%)
},
'8': {
text: '防护涂层材料研发报告',
color: [30, 100, 80], // hsl(30, 100%, 80%)
},
},
},
}
// 模拟后端API调用 - fill_stepTask_TaskProcess
export const mockBackendFillAgentTaskProcess = async (
goal: string,
stepTask: any,
agents: string[],
): Promise<RawAgentTaskProcessResponse> => {
// 模拟网络延迟 500ms
await new Promise((resolve) => setTimeout(resolve, 500))
// 在真实场景中,后端会根据传入的 goal、stepTask 和 agents 生成不同的 TaskProcess
// 这里我们直接返回预设的 Mock 数据
// 可以根据传入的 agents 动态修改 AgentSelection 和 TaskProcess
// 确保 agents 数组不为空
const safeAgents = agents.length > 0 ? agents : ['腐蚀机理研究员']
const responseData: RawAgentTaskProcessResponse = {
...mockBackendAgentTaskProcessData,
AgentSelection: agents,
TaskProcess: mockBackendAgentTaskProcessData.TaskProcess.map((action, index) => ({
...action,
AgentName: safeAgents[index % safeAgents.length],
})),
Collaboration_Brief_frontEnd: mockBackendAgentTaskProcessData.Collaboration_Brief_frontEnd
? {
template: mockBackendAgentTaskProcessData.Collaboration_Brief_frontEnd.template,
data: { ...mockBackendAgentTaskProcessData.Collaboration_Brief_frontEnd.data },
}
: undefined,
}
// 更新 Collaboration_Brief_frontEnd.data 中的 agent 引用
if (responseData.Collaboration_Brief_frontEnd?.data) {
const agentCount = Math.min(safeAgents.length, 4) // 最多4个agent
for (let i = 0; i < agentCount; i++) {
const key = String(i + 3) // agent从索引3开始
if (responseData.Collaboration_Brief_frontEnd.data[key]) {
responseData.Collaboration_Brief_frontEnd.data[key] = {
...responseData.Collaboration_Brief_frontEnd.data[key],
text: safeAgents[i]!,
}
}
}
}
return responseData
}

View File

@@ -0,0 +1,168 @@
<!-- AdditionalOutputCard.vue -->
<script setup lang="ts">
import { computed, ref, nextTick } from 'vue'
import { useAgentsStore } from '@/stores'
import SvgIcon from '@/components/SvgIcon/index.vue'
const agentsStore = useAgentsStore()
const props = defineProps<{
index: number
}>()
// 获取产物名称
const currentOutput = computed(() => {
return agentsStore.additionalOutputs[props.index] || ''
})
// 编辑状态
const isEditing = ref(false)
const inputValue = ref('')
const originalValue = ref('')
const inputRef = ref<HTMLElement>()
// 点击编辑图标
const handleEditClick = () => {
isEditing.value = true
originalValue.value = inputValue.value
// 等待 DOM 更新后聚焦输入框
nextTick(() => {
if (inputRef.value) {
const inputEl = inputRef.value.querySelector('input')
if (inputEl) {
inputEl.focus()
}
}
})
}
// 保存编辑
const handleSave = () => {
if (isEditing.value) {
isEditing.value = false
}
}
// 取消编辑
const handleCancel = () => {
if (isEditing.value) {
inputValue.value = originalValue.value // 恢复原始值
isEditing.value = false
}
}
// 处理键盘事件
const handleKeydown = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
event.preventDefault()
handleSave()
} else if (event.key === 'Escape') {
event.preventDefault()
handleCancel()
}
}
// 输入框失去焦点处理
const handleBlur = () => {
// 延迟处理,避免点击按钮时立即触发
setTimeout(() => {
if (isEditing.value) {
handleSave()
}
}, 200)
}
</script>
<template>
<!-- 当产物存在时才显示 -->
<div v-if="currentOutput" class="card-item">
<el-card
class="card-item w-full relative output-object-card"
:shadow="true"
:id="`additional-output-${index}`"
>
<!-- 显示产物名称 -->
<div class="text-start w-[100%]">
<div class="text-[18px] font-bold text-[var(--color-text)] mb-2">
{{ currentOutput }}
</div>
<div ref="inputRef">
<el-input
v-model="inputValue"
:readonly="!isEditing"
:placeholder="isEditing ? '请输入内容...' : '点击编辑图标开始编辑...'"
@keydown="handleKeydown"
@blur="handleBlur"
:class="{ editing: isEditing }"
>
<template #suffix>
<!-- 只读状态显示编辑图标 -->
<div v-if="!isEditing" class="flex items-center">
<svg-icon
icon-class="Edit"
size="20px"
class="cursor-pointer hover:text-[#409eff] transition-colors"
@click="handleEditClick"
title="点击编辑"
/>
</div>
</template>
</el-input>
</div>
<!-- 编辑状态下的提示 -->
<div v-if="isEditing" class="mt-2 text-end text-xs text-gray-500">
<svg-icon
icon-class="Check"
size="20px"
color="#328621"
class="cursor-pointer mr-4"
@click="handleSave"
title="保存"
/>
<svg-icon
icon-class="Cancel"
size="20px"
color="#8e0707"
class="cursor-pointer mr-4"
@click="handleCancel"
title="取消"
/>
</div>
</div>
</el-card>
</div>
</template>
<style scoped lang="scss">
.card-item {
transition: all 0.3s ease;
}
.output-object-card {
:deep(.el-card__body) {
min-height: 80px;
display: flex;
align-items: start;
justify-content: start;
}
}
/* 输入框样式 */
:deep(.el-input .el-input__wrapper) {
box-shadow: none;
background-color: var(--color-bg-three);
transition: all 0.2s ease;
}
/* 编辑状态下的输入框样式 */
:deep(.el-input.editing .el-input__wrapper) {
border: 1px solid #dcdfe6;
border-radius: 4px;
}
:deep(.el-input.editing .el-input__wrapper.is-focus) {
border-color: #c0c4cc;
box-shadow: 0 0 0 1px #c0c4cc;
}
</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-result-detail)] text-[var(--color-text-detail)]"
>
<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,121 @@
<script setup lang="ts">
import { readConfig } from '@/utils/readJson.ts'
import { ref, computed, 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++
}
}
function handlePrev() {
if (displayIndex.value === 0) {
displayIndex.value = data.value.length - 1
} else {
displayIndex.value--
}
}
</script>
<template>
<el-popover
trigger="hover"
width="440"
popper-style="background-color: var(--color-bg-result); border: none;"
>
<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 class="bg-[var(--color-bg-result)]">
<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> -->
<!-- 关闭 -->
<SvgIcon icon-class="close" size="15px" />
</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-detail)] 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-detail)] 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-detail)] 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-detail)] flex-1 break-words break-al">
{{ displayIod.fromRepo }}
</div>
</div>
<div>
<div
class="card-item w-[80px] h-[25px] flex justify-between items-center rounded-[25px] bg-[#b1b1b1] ml-auto px-2"
>
<div class="text-[14px] text-[#ffffff] font-medium">
{{ `${displayIndex + 1}/${data.length}` }}
</div>
<div class="flex items-center gap-1">
<svg-icon
icon-class="left"
size="15px"
@click="handlePrev"
class="cursor-pointer hover:opacity-70"
></svg-icon>
<svg-icon
icon-class="right"
size="15px"
@click="handleNext"
class="cursor-pointer hover:opacity-70"
></svg-icon>
</div>
</div>
</div>
</div>
</div>
</template>
</el-popover>
</template>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,893 @@
<script setup lang="ts">
import { computed, onUnmounted, ref, reactive, nextTick, watch, onMounted } from 'vue'
import { throttle } from 'lodash'
import { AnchorLocations, BezierConnector } from '@jsplumb/browser-ui'
import AdditionalOutputCard from './AdditionalOutputCard.vue'
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 ProcessCard from '../TaskProcess/ProcessCard.vue'
import ExecutePlan from './ExecutePlan.vue'
const emit = defineEmits<{
(e: 'refreshLine'): void
(el: 'setCurrentTask', task: IRawStepTask): void
}>()
const agentsStore = useAgentsStore()
const drawerVisible = ref(false)
const collaborationProcess = computed(() => {
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
})
// 监听额外产物变化
watch(
() => agentsStore.additionalOutputs,
() => {
nextTick(() => {
setTimeout(() => {
jsplumb.repaintEverything()
}, 0)
})
},
{ deep: true }
)
// 编辑逻辑
const editMode = ref(false) //全局编辑开关
const editMap = reactive<Record<string, boolean>>({}) //行级编辑状态
const editBuffer = reactive<Record<string, string | undefined>>({}) //临时输入
const showPopover = ref(false)
function getProcessDescription(stepId: string, processId: string) {
const step = collaborationProcess.value.find(s => s.Id === stepId)
if (step) {
const process = step.TaskProcess.find(p => p.ID === processId)
return process?.Description || ''
}
return ''
}
function handleOpenEdit(stepId: string, processId: string) {
if (!editMode.value) return
const key = `${stepId}-${processId}`
editMap[key] = true
editBuffer[key] = getProcessDescription(stepId, processId)
}
function handleSaveEdit(stepId: string, processId: string, value: string) {
const key = `${stepId}-${processId}`
const step = collaborationProcess.value.find(s => s.Id === stepId)
if (step) {
const process = step.TaskProcess.find(p => p.ID === processId)
if (process) {
process.Description = value
}
}
editMap[key] = false
}
const jsplumb = new Jsplumb('task-results-main', {
connector: {
type: BezierConnector.type,
options: { curviness: 30, stub: 10 }
}
})
// 操作折叠面板时要实时的刷新连线
let timer: ReturnType<typeof setInterval> | null = null
function handleCollapse() {
if (timer) {
clearInterval(timer)
}
timer = setInterval(() => {
jsplumb.repaintEverything()
emit('refreshLine')
}, 1) as ReturnType<typeof setInterval>
// 默认三秒后已经完全打开
const timer1 = setTimeout(() => {
if (timer) {
clearInterval(timer)
timer = null
}
}, 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
}
}
// 查看任务过程
async function handleTaskProcess() {
drawerVisible.value = true
}
// 重置执行结果
function handleRefresh() {
agentsStore.setExecutePlan([])
console.log('🔄 已重置执行结果')
}
// 添加滚动状态标识
const isScrolling = ref(false)
let scrollTimer: ReturnType<typeof setTimeout> | null = null
// 修改滚动处理函数
function handleScroll() {
isScrolling.value = true
emit('refreshLine')
// 清除之前的定时器
if (scrollTimer) {
clearTimeout(scrollTimer)
}
jsplumb.repaintEverything()
// 设置滚动结束检测
scrollTimer = setTimeout(() => {
isScrolling.value = false
}, 300) as ReturnType<typeof setTimeout>
}
// 修改鼠标事件处理函数
const handleMouseEnter = throttle(id => {
if (!isScrolling.value) {
createInternalLine(id)
}
}, 0)
const handleMouseLeave = throttle(() => {
if (!isScrolling.value) {
createInternalLine()
}
}, 0)
function clear() {
jsplumb.reset()
}
// 🆕 封装连线重绘方法
const redrawInternalLines = (highlightId?: string) => {
console.log('🔄 TaskResult: 重新绘制连线', highlightId ? `高亮: ${highlightId}` : '')
// 等待 DOM 更新完成
nextTick(() => {
// 清除旧连线
jsplumb.reset()
// 等待 DOM 稳定后重新绘制
setTimeout(() => {
createInternalLine(highlightId)
console.log('✅ TaskResult: 连线重绘完成,任务数:', collaborationProcess.value.length)
}, 100)
})
}
// 🆕 监听 collaborationProcess 变化,自动重绘连线
watch(
() => collaborationProcess,
() => {
console.log('🔍 TaskResult: collaborationProcess 发生变化,触发重绘')
redrawInternalLines()
},
{ deep: true }
)
// 🆕 组件挂载后初始化连线
onMounted(() => {
// 初始化时绘制连线
nextTick(() => {
setTimeout(() => {
createInternalLine()
console.log('✅ TaskResult: 初始化连线完成')
}, 100)
})
})
//按钮交互状态管理
const buttonHoverState = ref<'process' | 'execute' | 'refresh' | null>(null)
let buttonHoverTimer: ReturnType<typeof setTimeout> | null = null
const handleProcessMouseEnter = () => {
if (buttonHoverTimer) {
clearTimeout(buttonHoverTimer)
buttonHoverTimer = null
}
buttonHoverState.value = 'process'
}
const handleExecuteMouseEnter = () => {
if (buttonHoverTimer) {
clearTimeout(buttonHoverTimer)
buttonHoverTimer = null
}
if (agentsStore.agentRawPlan.data) {
buttonHoverState.value = 'execute'
}
}
const handleRefreshMouseEnter = () => {
if (buttonHoverTimer) {
clearTimeout(buttonHoverTimer)
buttonHoverTimer = null
}
if (agentsStore.executePlan.length > 0) {
buttonHoverState.value = 'refresh'
}
}
const handleButtonMouseLeave = () => {
// 添加防抖,防止快速切换时的抖动
if (buttonHoverTimer) {
clearTimeout(buttonHoverTimer)
}
buttonHoverTimer = setTimeout(() => {
buttonHoverState.value = null
}, 50) // 适当减少延迟时间
}
// 添加离开组件时的清理
onUnmounted(() => {
if (buttonHoverTimer) {
clearTimeout(buttonHoverTimer)
}
})
// 计算按钮类名
const processBtnClass = computed(() => {
// 当刷新或执行按钮悬停时,过程按钮变圆形
if (buttonHoverState.value === 'refresh' || buttonHoverState.value === 'execute') {
return 'circle'
}
return buttonHoverState.value === 'process' ? 'ellipse' : 'circle'
})
const executeBtnClass = computed(() => {
// 鼠标悬停在过程按钮或刷新按钮上时,执行按钮变圆形
if (buttonHoverState.value === 'process' || buttonHoverState.value === 'refresh') {
return 'circle'
}
//如果有任务数据就显示椭圆形,否则显示圆形
return agentsStore.agentRawPlan.data ? 'ellipse' : 'circle'
})
const refreshBtnClass = computed(() => {
// 当过程或执行按钮悬停时,刷新按钮变圆形
if (buttonHoverState.value === 'process' || buttonHoverState.value === 'execute') {
return 'circle'
}
// 有执行结果就显示椭圆形,否则显示圆形
return agentsStore.executePlan.length > 0 ? 'ellipse' : 'circle'
})
// 计算按钮是否显示文字
const showProcessText = computed(() => {
return buttonHoverState.value === 'process'
})
const showExecuteText = computed(() => {
// 鼠标悬停在过程按钮上时,执行按钮不显示文字
if (buttonHoverState.value === 'process') return false
// 其他情况:如果有任务数据就显示文字,否则不显示
return agentsStore.agentRawPlan.data
})
const showRefreshText = computed(() => {
return buttonHoverState.value === 'refresh'
})
// 计算按钮标题
const processBtnTitle = computed(() => {
return buttonHoverState.value === 'process' ? '任务过程' : '点击查看任务过程'
})
const executeBtnTitle = computed(() => {
return showExecuteText.value ? '任务执行' : '点击运行'
})
const refreshBtnTitle = computed(() => {
return showRefreshText.value ? '重置执行结果' : '点击重置执行状态'
})
defineExpose({
createInternalLine,
clear
})
</script>
<template>
<div
class="h-full flex flex-col relative"
id="task-results"
:class="{ 'is-running': agentsStore.executePlan.length > 0 }"
>
<!-- 标题与执行按钮 -->
<div class="text-[18px] font-bold mb-[7px] flex justify-between items-center px-[20px]">
<span class="text-[var(--color-text-title-header)]">执行结果</span>
<div
class="flex items-center justify-end gap-[14px] task-button-group min-w-[175px]"
@mouseleave="handleButtonMouseLeave"
>
<!-- 刷新按钮 -->
<el-button
:class="refreshBtnClass"
:color="variables.tertiary"
:title="refreshBtnTitle"
:disabled="agentsStore.executePlan.length === 0"
@mouseenter="handleRefreshMouseEnter"
@click="handleRefresh"
style="order: 0"
>
<svg-icon icon-class="refresh" />
<span v-if="showRefreshText" class="btn-text">重置</span>
</el-button>
<!-- 任务过程按钮 -->
<el-button
:class="processBtnClass"
:color="variables.tertiary"
:title="processBtnTitle"
@mouseenter="handleProcessMouseEnter"
@click="handleTaskProcess"
style="order: 1"
>
<svg-icon icon-class="process" />
<span v-if="showProcessText" class="btn-text">任务过程</span>
</el-button>
<!-- 任务执行按钮 -->
<el-popover
:disabled="Boolean(agentsStore.agentRawPlan.data)"
title="请先输入要执行的任务"
:visible="showPopover"
@hide="showPopover = false"
style="order: 2"
>
<template #reference>
<el-button
:class="executeBtnClass"
:color="variables.tertiary"
:title="executeBtnTitle"
:disabled="!agentsStore.agentRawPlan.data || loading"
@mouseenter="handleExecuteMouseEnter"
@click="handleRun"
>
<svg-icon v-if="loading" icon-class="loading" class="animate-spin" />
<svg-icon v-else icon-class="action" />
<span v-if="showExecuteText" class="btn-text">任务执行</span>
</el-button>
</template>
</el-popover>
<el-drawer
v-model="drawerVisible"
title="任务过程"
direction="rtl"
size="30%"
:destroy-on-close="false"
>
<!-- 头部工具栏 -->
<template #header>
<div class="drawer-header">
<span class="title">任务过程</span>
<!-- <el-button v-if="!editMode" text icon="Edit" @click="editMode = true" />
<el-button v-else text icon="Check" @click="save" /> -->
</div>
</template>
<el-scrollbar height="calc(100vh - 120px)">
<el-empty v-if="!collaborationProcess.length" description="暂无任务过程" />
<div v-else class="process-list">
<!-- 使用ProcessCard组件显示每个AgentSelection -->
<ProcessCard
v-for="step in collaborationProcess"
:key="step.Id"
:step="step"
@open-edit="handleOpenEdit"
@save-edit="handleSaveEdit"
/>
</div>
</el-scrollbar>
</el-drawer>
</div>
</div>
<!-- 内容 -->
<div
v-loading="agentsStore.agentRawPlan.loading"
class="flex-1 overflow-auto relative ml-[20px] mr-[20px]"
@scroll="handleScroll"
>
<div id="task-results-main" class="px-[40px] relative">
<!-- 额外产物卡片 -->
<div
v-if="agentsStore.additionalOutputs && agentsStore.additionalOutputs.length > 0"
class="mt-6"
:key="`additional-outputs-${agentsStore.additionalOutputs.length}`"
>
<div class="space-y-4 mb-4">
<AdditionalOutputCard
v-for="(_, index) in agentsStore.additionalOutputs"
:key="`additional-${index}-${agentsStore.additionalOutputs[index]}`"
:index="index"
/>
</div>
</div>
<!-- 原有的流程和产物 -->
<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' : ''"
: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>
<!-- 运行之前背景颜色是var(--color-bg-detail-list),运行之后背景颜色是var(--color-bg-detail-list-run) -->
<div
class="flex items-center gap-[15px] rounded-[20px]"
:class="{
'bg-[var(--color-bg-detail-list)]': !agentsStore.executePlan.length,
'bg-[var(--color-bg-detail-list-run)]': agentsStore.executePlan.length
}"
>
<!-- 右侧链接点 -->
<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="#fff"
size="24px"
/>
</div>
<div class="text-[16px]">
<span
:class="{
'text-[var(--color-text-result-detail)]': !agentsStore.executePlan.length,
'text-[var(--color-text-result-detail-run)]':
agentsStore.executePlan.length
}"
>{{ 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"
: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]"
:class="{
'text-[var(--color-text-result-detail)]': !agentsStore.executePlan.length,
'text-[var(--color-text-result-detail-run)]': agentsStore.executePlan.length
}"
>
{{ 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.is-running {
--color-bg-detail-list: var(--color-bg-detail-list-run); // 直接指向 100 % 版本
}
#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-detail-list-run);
min-height: 41px;
line-height: 41px;
border-radius: 20px;
transition: border-radius 1ms;
position: relative;
.el-collapse-item__title {
background: var(--color-bg-detail-list);
border-radius: 20px;
}
.el-icon {
font-size: 20px;
font-weight: 900;
background: var(--color-bg-icon-rotate);
border-radius: 50px;
color: #d8d8d8;
}
&.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-detail);
padding: 5px;
padding-top: 10px;
border-radius: 7px;
}
}
}
.el-collapse-item__wrap {
border: none;
background: var(--color-bg-detail-list);
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
}
}
:deep(.el-card) {
.el-card__body {
padding-right: 40px;
background-color: var(--color-bg-detail);
&:hover {
background-color: var(--color-card-bg-result-hover);
}
}
}
.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;
}
.additional-output-card {
border: 1px dashed #dcdfe6;
opacity: 0.9;
box-shadow: var(--color-agent-list-hover-shadow);
&:hover {
border-color: #409eff;
opacity: 1;
}
:deep(.el-card__body) {
padding: 20px;
}
// 编辑区域样式调整
.el-collapse {
border: none;
.el-collapse-item {
.el-collapse-item__header {
background: var(--color-bg-detail);
min-height: 36px;
line-height: 36px;
border-radius: 8px;
.el-collapse-item__title {
background: transparent;
font-size: 14px;
padding-left: 0;
}
.el-icon {
font-size: 16px;
}
}
.el-collapse-item__wrap {
background: var(--color-bg-detail);
border-radius: 0 0 8px 8px;
}
}
}
}
// ========== 新增:按钮交互样式 ==========
.task-button-group {
display: flex;
flex-direction: row-reverse;
.el-button {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
transition: width 0.2s ease-out, padding 0.2s ease-out, border-radius 0.2s ease-out, transform 0.2s ease-out, box-shadow 0.2s ease-out, filter 0.2s ease-out !important;
overflow: hidden !important;
white-space: nowrap !important;
border: 1px solid transparent !important;
border-color: transparent !important;
color: var(--color-text-primary) !important;
position: relative;
background-color: var(--color-bg-tertiary) !important;
gap: 0px !important;
outline: none !important;
box-shadow: none !important;
-webkit-tap-highlight-color: transparent !important;
backface-visibility: hidden !important;
-webkit-backface-visibility: hidden !important;
transform: translateZ(0) !important;
will-change: transform, width, padding, border-radius !important;
&::before,
&::after {
display: none !important;
}
&:hover {
transform: translateY(-2px) translateZ(0) !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
filter: brightness(1.1) !important;
border-color: transparent !important;
}
&.is-disabled {
opacity: 0.5;
cursor: not-allowed !important;
&:hover {
transform: none !important;
box-shadow: none !important;
filter: none !important;
}
}
}
// 圆形状态
.circle {
width: 40px !important;
height: 40px !important;
min-width: 40px !important;
padding: 0 !important;
border-radius: 50% !important;
.btn-text {
display: none !important;
}
}
// 椭圆形状态
.ellipse {
height: 40px !important;
border-radius: 20px !important;
padding: 0 16px !important;
gap: 8px;
// 任务过程按钮 - 左边固定,向右展开
&:nth-child(1) {
justify-content: flex-start !important;
.btn-text {
display: inline-block !important;
font-size: 14px;
font-weight: 500;
margin-right: 8px;
margin-left: 0;
opacity: 1;
animation: fadeInLeft 0.3s ease forwards;
}
}
// 任务执行按钮 - 右边固定,向左展开
&:nth-child(2) {
justify-content: flex-end !important;
.btn-text {
display: inline-block !important;
font-size: 14px;
font-weight: 500;
margin-left: 8px;
margin-right: 0;
opacity: 1;
animation: fadeInRight 0.3s ease forwards;
}
}
// .btn-text {
// display: inline-block !important;
// font-size: 14px;
// font-weight: 500;
// margin-left: 4px;
// opacity: 1;
// animation: fadeIn 0.3s ease forwards;
// }
}
@keyframes fadeInLeft {
from {
opacity: 0;
transform: translateX(5px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes fadeInRight {
from {
opacity: 0;
transform: translateX(-5px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
}
}
</style>

View File

@@ -0,0 +1,56 @@
<script setup lang="ts">
defineProps<{
isAdding?: boolean
}>()
defineEmits<{
(e: 'start-add-output'): void
}>()
</script>
<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-flow)] w-[5px]">
<!-- 线底部的小圆球 -->
<div
class="absolute bottom-0 left-1/2 transform -translate-x-1/2 bg-[var(--color-bg-flow)] 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-flow)] w-[5px]">
<!-- 顶部加号区域 -->
<div
v-if="!isAdding"
v-dev-only
class="plus-area mt-[35px] ml-[-15px] w-[34px] h-[34px] flex items-center justify-center cursor-pointer rounded-full"
@click="$emit('start-add-output')"
>
<!-- 加号图标 -->
<svg-icon
icon-class="plus"
color="var(--color-text)"
size="20px"
class="plus-icon opacity-0 transition-opacity duration-200"
/>
</div>
<!-- 线底部的小圆球 -->
<div
class="absolute bottom-0 left-1/2 transform -translate-x-1/2 bg-[var(--color-bg-flow)] w-[15px] h-[15px] rounded-full"
></div>
</div>
</div>
</div>
</template>
<style scoped>
.plus-area:hover .plus-icon {
opacity: 1;
border: 1px dashed var(--color-text);
}
</style>

View File

@@ -0,0 +1,365 @@
<template>
<div class="root-node-wrapper">
<!-- 左侧连接点输入 -->
<Handle type="target" :position="Position.Left" id="left" />
<!-- 右侧连接点输出 -->
<Handle type="source" :position="Position.Right" id="right" />
<!-- 底部连接点用于分支 -->
<Handle type="source" :position="Position.Bottom" id="bottom" />
<el-card class="root-node-card" :shadow="true">
<!-- 目标内容 -->
<div class="goal-content">
<div class="goal-label">初始目标</div>
<div class="goal-text">{{ goal }}</div>
</div>
</el-card>
<!-- 底部添加按钮 -->
<div v-if="!isAddingBranch" class="external-add-btn" @click="startAddBranch">
<el-icon :size="20" color="#409eff">
<CirclePlus />
</el-icon>
</div>
<!-- 取消按钮输入框显示时 -->
<div v-else class="external-add-btn cancel-btn" @click="cancelAddBranch">
<el-icon :size="20" color="#f56c6c">
<Remove />
</el-icon>
</div>
<!-- 外部输入框 -->
<div v-if="isAddingBranch" class="external-input-container">
<el-input
v-model="branchInput"
placeholder="输入分支需求..."
size="small"
@keydown="handleBranchKeydown"
ref="branchInputRef"
class="branch-input"
>
<template #suffix>
<svg-icon
icon-class="paper-plane"
size="16px"
color="#409eff"
class="submit-icon"
:class="{ 'is-disabled': !branchInput.trim() }"
@click="submitBranch"
/>
</template>
</el-input>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, nextTick } from 'vue'
import { CirclePlus, Remove } from '@element-plus/icons-vue'
import { Handle, Position } from '@vue-flow/core'
import SvgIcon from '@/components/SvgIcon/index.vue'
interface RootNodeData {
goal: string
initialInput?: string[] | string
isRoot?: boolean
}
const props = defineProps<{
id: string
data: RootNodeData
isAddingBranch?: boolean
[key: string]: any
}>()
const emit = defineEmits<{
(e: 'add-branch', nodeId: string, branchContent: string): void
(e: 'start-add-branch', nodeId: string): void
(e: 'cancel-add-branch'): void
}>()
const goal = computed(() => props.data.goal || '')
const initialInput = computed(() => props.data.initialInput)
const displayInput = computed(() => {
if (!initialInput.value) return false
if (Array.isArray(initialInput.value)) {
return initialInput.value.length > 0
}
return initialInput.value.trim().length > 0
})
// 分支添加相关状态(使用父组件传入的 prop
const branchInput = ref('')
const branchInputRef = ref<InstanceType<typeof HTMLInputElement>>()
// 计算属性,使用父组件传入的状态
const isAddingBranch = computed(() => props.isAddingBranch || false)
// 分支添加相关方法
const startAddBranch = () => {
emit('start-add-branch', props.id)
branchInput.value = ''
nextTick(() => {
branchInputRef.value?.focus()
})
}
const cancelAddBranch = () => {
emit('cancel-add-branch')
branchInput.value = ''
}
const submitBranch = () => {
if (branchInput.value.trim()) {
emit('add-branch', props.id, branchInput.value.trim())
branchInput.value = ''
}
}
const handleBranchKeydown = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
event.preventDefault()
submitBranch()
} else if (event.key === 'Escape') {
cancelAddBranch()
}
}
</script>
<style scoped lang="scss">
.root-node-card {
width: 200px;
min-height: 100px;
background: var(--color-bg-three);
border: 2px solid #409eff;
box-sizing: border-box;
transition: all 0.2s ease;
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
&:hover {
box-shadow: 0 6px 16px rgba(64, 158, 255, 0.3);
transform: translateY(-2px);
}
:deep(.el-card__body) {
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
position: relative;
}
}
.root-badge {
position: absolute;
top: -10px;
left: 50%;
transform: translateX(-50%);
z-index: 10;
}
.goal-icon {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 4px;
}
.goal-content {
text-align: center;
}
.goal-label {
font-size: 12px;
font-weight: bold;
color: #409eff;
margin-bottom: 6px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.goal-text {
font-size: 14px;
font-weight: 600;
color: var(--color-text-title-header);
line-height: 1.6;
word-break: break-word;
white-space: pre-wrap;
}
.initial-input {
margin-top: 4px;
}
.input-divider {
height: 1px;
background: linear-gradient(to right, transparent, #409eff, transparent);
margin: 8px 0;
}
.input-label {
font-size: 12px;
font-weight: bold;
color: var(--color-text-secondary);
margin-bottom: 6px;
}
.input-list {
display: flex;
flex-wrap: wrap;
gap: 4px;
justify-content: center;
}
.input-text {
font-size: 13px;
color: var(--color-text-secondary);
text-align: center;
word-break: break-word;
line-height: 1.5;
}
// 节点包装器
.root-node-wrapper {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
// 外部添加按钮
.external-add-btn {
position: absolute;
bottom: -20px;
left: 50%;
transform: translateX(-50%);
width: 32px;
height: 32px;
border-radius: 50%;
background: #fff;
border: 2px solid #409eff;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
z-index: 10;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
&:hover {
background: #409eff;
transform: translateX(-50%) scale(1.1);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
.el-icon {
color: #fff;
}
}
&:active {
transform: translateX(-50%) scale(0.95);
}
// 取消按钮样式
&.cancel-btn {
border-color: #f56c6c;
box-shadow: 0 2px 8px rgba(245, 108, 108, 0.2);
&:hover {
background: #f56c6c;
box-shadow: 0 4px 12px rgba(245, 108, 108, 0.3);
}
}
}
// 外部输入容器
.external-input-container {
position: absolute;
bottom: -80px;
left: 50%;
transform: translateX(-50%);
width: 260px;
background: #fff;
border: 1px solid #dcdfe6;
border-radius: 8px;
padding: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
z-index: 100;
animation: slideDown 0.2s ease-out;
&::before {
content: '';
position: absolute;
top: -6px;
left: 50%;
transform: translateX(-50%);
width: 12px;
height: 12px;
background: #fff;
border-left: 1px solid #dcdfe6;
border-top: 1px solid #dcdfe6;
transform: translateX(-50%) rotate(45deg);
}
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateX(-50%) translateY(-10px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
.branch-input {
width: 100%;
:deep(.el-input__wrapper) {
background: transparent;
border: 1px solid #dcdfe6;
border-radius: 4px;
box-shadow: none;
&:hover {
border-color: #c0c4cc;
}
&.is-focus {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
.el-input__inner {
font-size: 13px;
color: var(--color-text-primary);
padding-right: 40px;
}
}
}
// 提交图标样式
.submit-icon {
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
justify-content: center;
&:hover:not(.is-disabled) {
transform: scale(1.1);
}
&:active:not(.is-disabled) {
transform: scale(0.95);
}
&.is-disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
</style>

View File

@@ -0,0 +1,518 @@
<template>
<div class="task-node-wrapper">
<!-- 左侧连接点输入 -->
<Handle type="target" :position="Position.Left" id="left" />
<!-- 右侧连接点输出 -->
<Handle type="source" :position="Position.Right" id="right" />
<!-- 底部连接点用于分支 -->
<Handle type="source" :position="Position.Bottom" id="bottom" />
<el-card
class="task-node-card"
:class="{
'is-editing': isEditing,
'is-active': isActive,
'is-branch-selected': props.isBranchSelected
}"
:shadow="true"
>
<!-- 任务名称 -->
<div class="task-name">{{ task.StepName }}</div>
<!-- 智能体列表 -->
<div class="agents-container">
<el-tooltip
v-for="agentSelection in task.AgentSelection"
:key="agentSelection"
effect="light"
placement="top"
:show-after="500"
popper-class="task-syllabus-tooltip-popper"
>
<template #content>
<div class="agent-tooltip">
<div class="text-[16px] font-bold">{{ agentSelection }}</div>
<div class="h-[1px] w-full bg-[#494B51] my-[4px]"></div>
<div class="text-[12px]">
{{ task.TaskProcess.find(i => i.AgentName === agentSelection)?.Description }}
</div>
</div>
</template>
<div class="agent-icon" :style="{ background: getAgentIcon(agentSelection).color }">
<svg-icon :icon-class="getAgentIcon(agentSelection).icon" color="#fff" size="20px" />
</div>
</el-tooltip>
</div>
</el-card>
<!-- 底部添加按钮卡片外部 -->
<div v-if="!isAddingBranch" class="external-add-btn" @click="startAddBranch">
<el-icon :size="20" color="#409eff">
<CirclePlus />
</el-icon>
</div>
<!-- 取消按钮输入框显示时 -->
<div v-else class="external-add-btn cancel-btn" @click="cancelAddBranch">
<el-icon :size="20" color="#f56c6c">
<Remove />
</el-icon>
</div>
<!-- 外部输入框 -->
<div v-if="isAddingBranch" class="external-input-container">
<el-input
v-model="branchInput"
placeholder="输入分支需求..."
size="small"
@keydown="handleBranchKeydown"
ref="branchInputRef"
class="branch-input"
>
<template #suffix>
<svg-icon
icon-class="paper-plane"
size="16px"
color="#409eff"
class="submit-icon"
:class="{ 'is-disabled': !branchInput.trim() }"
@click="submitBranch"
/>
</template>
</el-input>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, nextTick } from 'vue'
import { CirclePlus, Remove } from '@element-plus/icons-vue'
import { Handle, Position } from '@vue-flow/core'
import { type IRawStepTask } from '@/stores'
import { getAgentMapIcon } from '@/layout/components/config'
import SvgIcon from '@/components/SvgIcon/index.vue'
interface TaskNodeData {
task: IRawStepTask
isEditing: boolean
editingContent: string
}
// 使用更宽松的类型定义来避免类型错误
const props = defineProps<{
id: string
data: TaskNodeData
isAddingBranch?: boolean
isBranchSelected?: boolean // 是否属于选中的分支路径
[key: string]: any
}>()
const emit = defineEmits<{
(e: 'edit-task', nodeId: string): void
(e: 'save-task', nodeId: string, content: string): void
(e: 'cancel-edit', nodeId: string): void
(e: 'add-branch', nodeId: string, branchContent: string): void
(e: 'start-add-branch', nodeId: string): void
(e: 'cancel-add-branch'): void
}>()
const editingContent = ref(props.data.task.TaskContent || '')
// 分支添加相关状态(使用父组件传入的 prop
const branchInput = ref('')
const branchInputRef = ref<InstanceType<typeof HTMLInputElement>>()
// 计算属性,使用父组件传入的状态
const isAddingBranch = computed(() => props.isAddingBranch || false)
const isEditing = computed({
get: () => props.data.isEditing,
set: value => {
if (!value) {
emit('cancel-edit', props.id)
}
}
})
const isActive = computed(() => {
return props.data.task.StepName === props.data.task.StepName
})
const task = computed(() => props.data.task)
const getAgentIcon = (agentName: string) => {
return getAgentMapIcon(agentName)
}
const startEdit = () => {
emit('edit-task', props.id)
}
const saveEdit = () => {
emit('save-task', props.id, editingContent.value)
}
const cancelEdit = () => {
emit('cancel-edit', props.id)
}
const handleKeydown = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
event.preventDefault()
saveEdit()
} else if (event.key === 'Escape') {
cancelEdit()
}
}
// 分支添加相关方法
const startAddBranch = () => {
emit('start-add-branch', props.id)
branchInput.value = ''
nextTick(() => {
branchInputRef.value?.focus()
})
}
const cancelAddBranch = () => {
emit('cancel-add-branch')
branchInput.value = ''
}
const submitBranch = () => {
if (branchInput.value.trim()) {
emit('add-branch', props.id, branchInput.value.trim())
branchInput.value = ''
}
}
const handleBranchKeydown = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
event.preventDefault()
submitBranch()
} else if (event.key === 'Escape') {
cancelAddBranch()
}
}
</script>
<style scoped lang="scss">
.task-node-card {
width: 150px;
min-height: 100px;
background-color: var(--color-card-bg-task);
border: 1px solid var(--color-card-border-task);
box-sizing: border-box;
transition: all 0.2s ease;
cursor: pointer;
&:hover {
background-color: var(--color-card-bg-task-hover);
border-color: var(--color-card-border-hover);
box-shadow: var(--color-card-shadow-hover);
}
&.is-active {
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
// 分支选中高亮样式(绿色)
&.is-branch-selected {
border-color: #67c23a;
box-shadow: 0 0 0 3px rgba(103, 194, 58, 0.3);
background-color: rgba(103, 194, 58, 0.05);
&:hover {
border-color: #67c23a;
box-shadow: 0 0 0 3px rgba(103, 194, 58, 0.4);
background-color: rgba(103, 194, 58, 0.08);
}
}
&.is-editing {
cursor: default;
}
:deep(.el-card__body) {
padding: 12px;
display: flex;
flex-direction: column;
gap: 8px;
position: relative;
}
}
.task-name {
font-size: 16px;
font-weight: bold;
text-align: center;
color: var(--color-text-title-header);
word-break: break-word;
}
.divider {
height: 1px;
width: 100%;
background: var(--color-border-separate);
margin: 4px 0;
}
.task-content {
font-size: 14px;
color: var(--color-text-secondary);
min-height: 40px;
word-break: break-word;
white-space: pre-wrap;
line-height: 1.5;
}
.task-content-editing {
display: flex;
flex-direction: column;
gap: 8px;
}
.edit-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
}
.agents-container {
display: flex;
gap: 6px;
flex-wrap: wrap;
justify-content: center;
}
.agent-icon {
width: 28px;
height: 28px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.2s ease;
&:hover {
transform: scale(1.1);
}
}
.input-outputs {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
font-size: 12px;
}
.input-label {
font-weight: bold;
color: var(--color-text-secondary);
}
.agent-tooltip {
padding: 8px;
max-width: 200px;
}
// 编辑器样式
.task-content-editor {
:deep(.el-textarea__inner) {
font-size: 14px;
color: var(--color-text-secondary);
background: transparent;
border: 1px solid #dcdfe6;
border-radius: 4px;
resize: none;
}
}
// 节点包装器(用于容纳外部按钮)
.task-node-wrapper {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
// 外部添加按钮
.external-add-btn {
position: absolute;
bottom: -20px;
left: 50%;
transform: translateX(-50%);
width: 32px;
height: 32px;
border-radius: 50%;
background: #fff;
border: 2px solid #409eff;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
z-index: 10;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
&:hover {
background: #409eff;
transform: translateX(-50%) scale(1.1);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
.el-icon {
color: #fff;
}
}
&:active {
transform: translateX(-50%) scale(0.95);
}
// 取消按钮样式
&.cancel-btn {
border-color: #f56c6c;
box-shadow: 0 2px 8px rgba(245, 108, 108, 0.2);
&:hover {
background: #f56c6c;
box-shadow: 0 4px 12px rgba(245, 108, 108, 0.3);
}
}
}
// 外部输入容器
.external-input-container {
position: absolute;
bottom: -80px;
left: 50%;
transform: translateX(-50%);
width: 260px;
background: #fff;
border: 1px solid #dcdfe6;
border-radius: 8px;
padding: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
z-index: 100;
animation: slideDown 0.2s ease-out;
&::before {
content: '';
position: absolute;
top: -6px;
left: 50%;
transform: translateX(-50%);
width: 12px;
height: 12px;
background: #fff;
border-left: 1px solid #dcdfe6;
border-top: 1px solid #dcdfe6;
transform: translateX(-50%) rotate(45deg);
}
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateX(-50%) translateY(-10px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
.add-branch-section {
margin-top: 8px;
padding-top: 8px;
border-top: 1px dashed var(--color-border-separate);
display: flex;
justify-content: center;
align-items: center;
}
.add-branch-btn {
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
&:hover {
transform: scale(1.1);
}
&:active {
transform: scale(0.95);
}
}
.branch-input-container {
width: 100%;
display: flex;
flex-direction: column;
gap: 8px;
}
.branch-input {
width: 100%;
:deep(.el-input__wrapper) {
background: transparent;
border: 1px solid #dcdfe6;
border-radius: 4px;
box-shadow: none;
&:hover {
border-color: #c0c4cc;
}
&.is-focus {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
.el-input__inner {
font-size: 13px;
color: var(--color-text-primary);
padding-right: 40px;
}
}
}
// 提交图标样式
.submit-icon {
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
justify-content: center;
&:hover:not(.is-disabled) {
transform: scale(1.1);
}
&:active:not(.is-disabled) {
transform: scale(0.95);
}
&.is-disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
.is-adding-branch {
.task-node-card {
border-color: #409eff;
}
}
</style>
<style>
.task-syllabus-tooltip-popper {
z-index: 4000 !important;
}
</style>

View File

@@ -0,0 +1,127 @@
# Mock 数据说明
本目录包含用于分支功能的 mock 数据,支持在开发环境中测试分支逻辑,无需调用真实后端 API。
## 文件说明
### 1. `branchPlanOutlineMock.ts`
**用途**: 根节点级别的分支(任务大纲分支)
**类型**: `IApiStepTask[][]`
**说明**: 返回多个分支方案每个方案是一个完整的任务流程IApiStepTask[]
**示例结构**:
```typescript
[
// 第一个分支方案(瀑布流开发)
[task1, task2, ...],
// 第二个分支方案(敏捷开发)
[task1, task2, ...],
// 第三个分支方案(快速原型)
[task1, task2, ...]
]
```
### 2. `branchTaskProcessMock.ts`
**用途**: 任务节点级别的分支(任务流程分支)
**类型**: `IApiAgentAction[][]`
**说明**: 返回多个分支方案每个方案是一系列动作IApiAgentAction[]),这些动作会追加到现有任务的 TaskProcess 中
**示例结构**:
```typescript
[
// 第一个分支方案(标准开发流程)
[action1, action2, action3, ...],
// 第二个分支方案(快速原型流程)
[action1, action2, ...],
// ... 更多方案
]
```
## 如何使用
### 切换 Mock 数据开关
`PlanModification.vue` 文件中,找到以下配置:
```typescript
// 开关:控制是否使用 mock 数据(开发时设置为 true生产时设置为 false
const USE_MOCK_DATA = true
```
- **开发阶段**: 设置为 `true`,使用 mock 数据
- **生产环境**: 设置为 `false`,调用真实 API
### 数据转换流程
```
IApiStepTask (API 格式)
↓ convertToIRawStepTask()
IRawStepTask (内部格式)
更新到 agentsStore.agentRawPlan.data
Vue Flow 流程图自动更新
```
### Mock 数据特点
1. **模拟网络延迟**: Mock 数据调用会模拟 800ms 的网络延迟
2. **多方案选择**: 每个分支接口提供多个备选方案(目前默认使用第一个)
3. **完整数据结构**: Mock 数据包含完整的字段,与真实 API 返回格式一致
4. **类型安全**: 使用 TypeScript 类型定义,确保类型正确
## 扩展 Mock 数据
### 添加新的分支方案
在对应的 mock 文件中添加新的数组元素:
```typescript
// branchPlanOutlineMock.ts
const mockPlanBranchData: IApiStepTask[][] = [
// 现有方案...
[
// 新增方案
{
name: '新方案步骤1',
content: '...',
// ... 其他字段
},
{
name: '新方案步骤2',
content: '...',
// ... 其他字段
}
]
]
```
### 随机选择方案
修改 `PlanModification.vue` 中的方案选择逻辑:
```typescript
// 当前:固定选择第一个
const mockBranchTasks = branchPlanOutlineMock[0]
// 改为:随机选择一个方案
const randomIndex = Math.floor(Math.random() * branchPlanOutlineMock.length)
const mockBranchTasks = branchPlanOutlineMock[randomIndex]
```
## 注意事项
1. ⚠️ **生产环境**: 发布前务必将 `USE_MOCK_DATA` 设置为 `false`
2. 🔍 **调试**: 查看控制台日志,以 `[Mock]` 开头的是 mock 数据相关日志
3. 📝 **数据一致性**: 确保 mock 数据结构与真实 API 返回格式一致
4. 🔄 **数据持久化**: Mock 数据仅存在于前端,刷新页面后会丢失
## 相关文件
- `PlanModification.vue`: 主要逻辑文件,包含分支添加和 mock 数据集成
- `api/index.ts`: 真实 API 接口定义
- `stores/modules/agents.ts`: 类型定义和数据存储

View File

@@ -0,0 +1,246 @@
// branch_PlanOutline 接口的 Mock 数据和 Mock API
// 模拟后端返回的原始数据格式IRawPlanResponse
import type { IRawPlanResponse, IRawStepTask } from '@/stores'
// 后端返回的数据格式
export type BranchPlanOutlineResponse = IRawPlanResponse
// Mock 数据:模拟后端返回的原始分支数据(不含 Collaboration_Brief_FrontEnd
// 注意:这里模拟的是 branch_PlanOutline 函数返回的数据,不是前端转换后的数据
const mockBranchDataRaw: IRawStepTask[][] = [
// 第一个分支方案
[
{
StepName: '分析用户需求',
TaskContent: '分析用户需求,制定项目开发计划',
InputObject_List: ['腐蚀类型及成因列表'],
OutputObject: '项目开发计划书',
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
Collaboration_Brief_frontEnd: {
template: '',
data: {},
},
TaskProcess: [],
},
{
StepName: '系统设计与架构',
TaskContent: '设计系统架构和数据库结构',
InputObject_List: ['项目开发计划书'],
OutputObject: '系统设计文档',
AgentSelection: ['腐蚀机理研究员', '防护工程专家'],
Collaboration_Brief_frontEnd: {
template: '',
data: {},
},
TaskProcess: [],
},
],
// 第二个分支方案(快速原型方案)
[
{
StepName: '需求快速原型',
TaskContent: '构建快速原型验证核心功能',
InputObject_List: ['腐蚀类型及成因列表'],
OutputObject: '原型系统',
AgentSelection: ['实验材料学家', '防护工程专家'],
Collaboration_Brief_frontEnd: {
template: '',
data: {},
},
TaskProcess: [],
},
{
StepName: '原型测试与优化',
TaskContent: '测试原型并根据反馈快速迭代',
InputObject_List: ['原型系统'],
OutputObject: '优化后的原型',
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
Collaboration_Brief_frontEnd: {
template: '',
data: {},
},
TaskProcess: [],
},
],
// 第三个分支方案(质量优先方案)
[
{
StepName: '需求深度分析',
TaskContent: '深入分析用户需求和技术可行性',
InputObject_List: ['腐蚀类型及成因列表'],
OutputObject: '详细需求分析报告',
AgentSelection: ['腐蚀机理研究员', '防护工程专家'],
Collaboration_Brief_frontEnd: {
template: '',
data: {},
},
TaskProcess: [],
},
{
StepName: '质量保障设计',
TaskContent: '设计质量保障体系和测试方案',
InputObject_List: ['详细需求分析报告'],
OutputObject: '质量保障方案',
AgentSelection: ['实验材料学家', '防护工程专家'],
Collaboration_Brief_frontEnd: {
template: '',
data: {},
},
TaskProcess: [],
},
{
StepName: '系统开发与测试',
TaskContent: '按质量标准进行系统开发和测试',
InputObject_List: ['质量保障方案'],
OutputObject: '经过完整测试的系统',
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
Collaboration_Brief_frontEnd: {
template: '',
data: {},
},
TaskProcess: [],
},
],
// 第四个分支方案(敏捷开发方案)
[
{
StepName: '迭代规划',
TaskContent: '制定敏捷开发迭代计划',
InputObject_List: ['腐蚀类型及成因列表'],
OutputObject: '迭代计划',
AgentSelection: ['防护工程专家', '腐蚀机理研究员'],
Collaboration_Brief_frontEnd: {
template: '',
data: {},
},
TaskProcess: [],
},
{
StepName: '快速开发与验证',
TaskContent: '快速开发并持续验证功能',
InputObject_List: ['迭代计划'],
OutputObject: '可运行的功能模块',
AgentSelection: ['实验材料学家', '防护工程专家'],
Collaboration_Brief_frontEnd: {
template: '',
data: {},
},
TaskProcess: [],
},
],
]
/**
* 模拟后端的 Add_Collaboration_Brief_FrontEnd 函数
* 为每个任务添加前端协作简报Collaboration_Brief_frontEnd
*/
function Add_Collaboration_Brief_FrontEnd(branchList: IRawStepTask[][]): IRawStepTask[][] {
return branchList.map((tasks) =>
tasks.map((task) => ({
...task,
Collaboration_Brief_frontEnd: generateCollaborationBrief(task),
})),
)
}
/**
* 生成协作简报Collaboration_Brief_frontEnd
* 根据 StepName、TaskContent、AgentSelection 生成模板和数据
*/
function generateCollaborationBrief(task: IRawStepTask): {
template: string
data: Record<string, any>
} {
const agents = task.AgentSelection || []
const stepName = task.StepName || ''
// 为每个 agent 生成颜色
const colors = [
'hsl(210, 70%, 50%)',
'hsl(30, 70%, 50%)',
'hsl(120, 70%, 50%)',
'hsl(270, 70%, 50%)',
]
// 生成 data 对象
const data: Record<string, any> = {}
agents.forEach((agent, index) => {
data[agent] = {
text: agent,
style: { background: colors[index % colors.length] },
}
})
// 生成 template简化版本实际应根据任务内容生成
const template =
agents.length > 0
? agents.map((agent, i) => `!<${agent}>!负责!<${stepName}-${i}>!`).join('')
: ''
return {
template,
data,
}
}
/**
* Mock API模拟后端 branch_PlanOutline 接口调用
*
* @param branch_Number - 分支数量
* @param Modification_Requirement - 修改需求
* @param Existing_Steps - 现有步骤名称列表
* @param Baseline_Completion - 基线完成度
* @param InitialObject_List - 初始对象列表
* @param General_Goal - 总体目标
* @returns Promise<IRawPlanResponse> - 返回包含 'Collaboration Process' 的响应
*/
export const mockBranchPlanOutlineAPI = async (params: {
branch_Number: number
Modification_Requirement: string
Existing_Steps: string[]
Baseline_Completion: number
InitialObject_List: string[]
General_Goal: string
}): Promise<IRawPlanResponse> => {
// 模拟网络延迟 800ms
await new Promise((resolve) => setTimeout(resolve, 800))
console.log('[Mock API] branch_PlanOutline 调用参数:', params)
// 🆕 使用轮询方式选择分支方案(依次循环使用所有分支方案)
const totalBranches = mockBranchDataRaw.length
const sessionKey = `branch-plan-outline-index-${params.General_Goal || 'default'}`
// 获取上一次的选择索引
let lastIndex = parseInt(sessionStorage.getItem(sessionKey) || '0')
// 计算本次的选择索引(轮询到下一个分支)
const selectedBranchIndex = (lastIndex + 1) % totalBranches
// 保存本次的选择索引
sessionStorage.setItem(sessionKey, selectedBranchIndex.toString())
const rawBranchData = mockBranchDataRaw[selectedBranchIndex]
console.log(
'[Mock API] branch_PlanOutline 选择分支方案:',
selectedBranchIndex + 1,
'/',
totalBranches,
)
// 模拟后端处理:添加 Collaboration_Brief_FrontEnd
const processedBranches = Add_Collaboration_Brief_FrontEnd([rawBranchData])
// 构造响应数据(符合后端返回格式)
const response: IRawPlanResponse = {
'Collaboration Process': processedBranches[0] || [],
}
console.log('[Mock API] branch_PlanOutline 返回数据:', response)
return response
}
export default mockBranchPlanOutlineAPI

View File

@@ -0,0 +1,155 @@
// branch_TaskProcess 接口的 Mock 数据和 Mock API
export interface BranchAction {
ID: string
ActionType: string
AgentName: string
Description: string
ImportantInput: string[]
}
export type BranchTaskProcessResponse = BranchAction[][]
// Mock 数据模拟后端返回的原始任务流程数据2D 数组)
// 格式:[[action1, action2], [action3, action4]]
const mockBranchTaskProcessDataRaw: BranchAction[][] = [
// 第一个任务分支方案
[
{
ID: 'agent3',
ActionType: 'Critique',
AgentName: '实验材料学家',
Description: '详细分析用户需求文档',
ImportantInput: ['agent2'],
},
{
ID: 'agent4',
ActionType: 'Critique',
AgentName: '腐蚀机理研究员',
Description: '设计系统整体架构',
ImportantInput: ['agent3'],
},
{
ID: 'agent5',
ActionType: 'Improve',
AgentName: '防护工程专家',
Description: '实现系统核心功能',
ImportantInput: ['agent4'],
},
{
ID: 'agent6',
ActionType: 'Finalize',
AgentName: '实验材料学家',
Description: '进行系统集成测试',
ImportantInput: ['agent5'],
},
],
// 第二个任务分支方案
[
{
ID: 'agent7',
ActionType: 'Critique',
AgentName: '实验材料学家',
Description: '深入分析用户需求和技术约束',
ImportantInput: ['agent2'],
},
{
ID: 'agent8',
ActionType: 'Critique',
AgentName: '防护工程专家',
Description: '设计系统技术架构和数据流',
ImportantInput: ['agent8'],
},
{
ID: 'agent9',
ActionType: 'Improve',
AgentName: '腐蚀机理研究员',
Description: '评估系统安全性',
ImportantInput: ['agent4'],
},
{
ID: 'agent10',
ActionType: 'Finalize',
AgentName: '实验材料学家',
Description: '完成系统安全测试',
ImportantInput: ['agent9'],
},
],
//第三个任务分支方案
[
{
ID: 'agent12',
ActionType: 'Critique',
AgentName: '腐蚀机理研究员',
Description: '设计系统整体架构',
ImportantInput: ['agent11'],
},
{
ID: 'agent13',
ActionType: 'Improve',
AgentName: '防护工程专家',
Description: '实现系统核心功能',
ImportantInput: ['agent12'],
},
{
ID: 'agent14',
ActionType: 'Finalize',
AgentName: '实验材料学家',
Description: '进行系统集成测试',
ImportantInput: ['agent13'],
},
],
]
/**
* Mock API模拟后端 branch_TaskProcess 接口调用
*
* @param branch_Number - 分支数量
* @param Modification_Requirement - 修改需求
* @param Existing_Steps - 现有步骤名称列表
* @param Baseline_Completion - 基线完成度
* @param stepTaskExisting - 现有任务
* @param General_Goal - 总体目标
* @returns Promise<BranchAction[][]> - 返回 2D 数组,与后端格式完全一致
*/
export const mockBranchTaskProcessAPI = async (params: {
branch_Number: number
Modification_Requirement: string
Existing_Steps: string[]
Baseline_Completion: number
stepTaskExisting: any
General_Goal: string
}): Promise<BranchAction[][]> => {
// 模拟网络延迟 800ms
await new Promise((resolve) => setTimeout(resolve, 800))
console.log('[Mock API] branch_TaskProcess 调用参数:', params)
// 🆕 使用轮询方式选择分支方案(依次循环使用所有分支方案)
const totalBranches = mockBranchTaskProcessDataRaw.length
const sessionKey = `branch-task-process-index-${params.stepTaskExisting?.Id || 'default'}`
// 获取上一次的选择索引
let lastIndex = parseInt(sessionStorage.getItem(sessionKey) || '0')
// 计算本次的选择索引(轮询到下一个分支)
const selectedBranchIndex = (lastIndex + 1) % totalBranches
// 保存本次的选择索引
sessionStorage.setItem(sessionKey, selectedBranchIndex.toString())
const rawBranchData = mockBranchTaskProcessDataRaw[selectedBranchIndex] || []
console.log(
'[Mock API] branch_TaskProcess 选择分支方案:',
selectedBranchIndex + 1,
'/',
totalBranches,
)
console.log('[Mock API] branch_TaskProcess 返回数据:', rawBranchData)
// 直接返回 2D 数组,与后端格式完全一致
return [rawBranchData]
}
export default mockBranchTaskProcessAPI

View File

@@ -0,0 +1,617 @@
// fill_stepTask 接口的 Mock API
// 简化版本:硬编码多个流程卡片的填充内容(支持多个分支方案)
import type { IRawStepTask } from '@/stores'
// 后端返回的数据格式
export type FillStepTaskResponse = IRawStepTask
/**
* Mock API模拟后端 fill_stepTask 接口调用
*
* 根据 stepTask 的 StepName 返回对应的填充内容
* 支持多个分支方案的轮询选择
*
* @param General_Goal - 总体目标
* @param stepTask - 待填充的任务(包含基本字段)
* @returns Promise<IRawStepTask> - 返回填充了 AgentSelection 和 TaskProcess 的完整任务
*/
export const mockFillStepTaskAPI = async (params: {
General_Goal: string
stepTask: any
}): Promise<IRawStepTask> => {
// 模拟网络延迟 300ms
await new Promise((resolve) => setTimeout(resolve, 300))
console.log('[Mock API] fill_stepTask 调用参数:', params)
const stepName = params.stepTask.StepName
// 🆕 使用轮询方式选择填充方案(依次循环使用所有分支方案)
const branchIndexKey = `fill-step-task-index-${params.stepTask?.Id || stepName || 'default'}`
let lastIndex = parseInt(sessionStorage.getItem(branchIndexKey) || '0')
const totalBranches = 4 // 总共4个分支方案
const selectedBranchIndex = (lastIndex + 1) % totalBranches
sessionStorage.setItem(branchIndexKey, selectedBranchIndex.toString())
console.log('[Mock API] fill_stepTask 选择分支方案:', selectedBranchIndex + 1, '/', totalBranches)
// ==================== 第一个任务组 ====================
if (stepName === '分析用户需求' || stepName === '需求快速原型' || stepName === '需求深度分析' || stepName === '迭代规划') {
let filledTask: IRawStepTask
if (selectedBranchIndex === 0) {
// 第一个分支方案:分析用户需求
filledTask = {
Id: params.stepTask.Id || 'task-1',
StepName: '分析用户需求',
TaskContent: '分析用户需求,制定项目开发计划',
InputObject_List: ['用户需求文档', '技术规范'],
OutputObject: '项目开发计划书',
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
Collaboration_Brief_frontEnd: {
template: '!腐蚀机理研究员!负责!腐蚀类型识别-0!!实验材料学家!负责!腐蚀类型识别-1!',
data: {
: {
text: '腐蚀机理研究员',
style: { background: 'hsl(210, 70%, 50%)' },
},
: {
text: '实验材料学家',
style: { background: 'hsl(120, 70%, 50%)' },
},
},
},
TaskProcess: [
{
ID: 'action-1-1',
ActionType: 'Propose',
AgentName: '腐蚀机理研究员',
Description: '分析腐蚀环境和介质特征,识别潜在腐蚀类型',
ImportantInput: ['InputObject:腐蚀类型及成因列表'],
},
{
ID: 'action-1-2',
ActionType: 'Propose',
AgentName: '实验材料学家',
Description: '设计腐蚀实验方案,确定测试参数',
ImportantInput: ['ActionResult:action-1-1'],
},
{
ID: 'action-1-3',
ActionType: 'Critique',
AgentName: '腐蚀机理研究员',
Description: '评估实验方案的可行性',
ImportantInput: [
'ActionResult:action-1-2',
'ActionResult:action-1-1',
'InputObject:腐蚀类型及成因列表',
],
},
{
ID: 'action-1-4',
ActionType: 'Improve',
AgentName: '实验材料学家',
Description: '优化实验设计,完善测试流程',
ImportantInput: ['ActionResult:action-1-3'],
},
{
ID: 'action-1-5',
ActionType: 'Finalize',
AgentName: '腐蚀机理研究员',
Description: '确认最终的腐蚀类型识别方案',
ImportantInput: ['ActionResult:action-1-4', 'ActionResult:action-1-3'],
},
],
}
} else if (selectedBranchIndex === 1) {
// 第二个分支方案:需求快速原型
filledTask = {
Id: params.stepTask.Id || 'task-1',
StepName: '需求快速原型',
TaskContent: '构建快速原型验证核心功能',
InputObject_List: ['腐蚀类型及成因列表'],
OutputObject: '原型系统',
AgentSelection: ['实验材料学家', '防护工程专家'],
Collaboration_Brief_frontEnd: {
template: '!实验材料学家!负责!需求快速原型-0!!防护工程专家!负责!需求快速原型-1!',
data: {
: {
text: '实验材料学家',
style: { background: 'hsl(120, 70%, 50%)' },
},
: {
text: '防护工程专家',
style: { background: 'hsl(30, 70%, 50%)' },
},
},
},
TaskProcess: [
{
ID: 'action-1-1-branch2',
ActionType: 'Propose',
AgentName: '实验材料学家',
Description: '基于腐蚀类型列表,设计快速原型验证方案',
ImportantInput: ['InputObject:腐蚀类型及成因列表'],
},
{
ID: 'action-1-2-branch2',
ActionType: 'Propose',
AgentName: '防护工程专家',
Description: '实现原型核心功能和用户界面',
ImportantInput: ['ActionResult:action-1-1-branch2'],
},
{
ID: 'action-1-3-branch2',
ActionType: 'Critique',
AgentName: '实验材料学家',
Description: '评估原型功能完整性和用户体验',
ImportantInput: ['ActionResult:action-1-2-branch2'],
},
{
ID: 'action-1-4-branch2',
ActionType: 'Improve',
AgentName: '防护工程专家',
Description: '根据反馈优化原型功能和界面',
ImportantInput: ['ActionResult:action-1-3-branch2'],
},
{
ID: 'action-1-5-branch2',
ActionType: 'Finalize',
AgentName: '实验材料学家',
Description: '确认最终版本的原型系统',
ImportantInput: ['ActionResult:action-1-4-branch2'],
},
],
}
} else if (selectedBranchIndex === 2) {
// 第三个分支方案:需求深度分析
filledTask = {
Id: params.stepTask.Id || 'task-1',
StepName: '需求深度分析',
TaskContent: '深入分析用户需求和技术可行性',
InputObject_List: ['腐蚀类型及成因列表'],
OutputObject: '详细需求分析报告',
AgentSelection: ['腐蚀机理研究员', '防护工程专家'],
Collaboration_Brief_frontEnd: {
template: '!腐蚀机理研究员!负责!需求深度分析-0!!防护工程专家!负责!需求深度分析-1!',
data: {
: {
text: '腐蚀机理研究员',
style: { background: 'hsl(210, 70%, 50%)' },
},
: {
text: '防护工程专家',
style: { background: 'hsl(30, 70%, 50%)' },
},
},
},
TaskProcess: [
{
ID: 'action-1-1-branch3',
ActionType: 'Propose',
AgentName: '腐蚀机理研究员',
Description: '全面分析腐蚀环境和技术约束条件',
ImportantInput: ['InputObject:腐蚀类型及成因列表'],
},
{
ID: 'action-1-2-branch3',
ActionType: 'Propose',
AgentName: '防护工程专家',
Description: '评估技术可行性和防护方案',
ImportantInput: ['ActionResult:action-1-1-branch3'],
},
{
ID: 'action-1-3-branch3',
ActionType: 'Critique',
AgentName: '腐蚀机理研究员',
Description: '评审需求分析的完整性和准确性',
ImportantInput: ['ActionResult:action-1-2-branch3', 'ActionResult:action-1-1-branch3'],
},
{
ID: 'action-1-4-branch3',
ActionType: 'Improve',
AgentName: '防护工程专家',
Description: '补充技术风险评估和应对策略',
ImportantInput: ['ActionResult:action-1-3-branch3'],
},
{
ID: 'action-1-5-branch3',
ActionType: 'Finalize',
AgentName: '腐蚀机理研究员',
Description: '确认详细需求分析报告',
ImportantInput: ['ActionResult:action-1-4-branch3', 'ActionResult:action-1-3-branch3'],
},
],
}
} else {
// 第四个分支方案:迭代规划
filledTask = {
Id: params.stepTask.Id || 'task-1',
StepName: '迭代规划',
TaskContent: '制定敏捷开发迭代计划',
InputObject_List: ['腐蚀类型及成因列表'],
OutputObject: '迭代计划',
AgentSelection: ['防护工程专家', '腐蚀机理研究员'],
Collaboration_Brief_frontEnd: {
template: '!防护工程专家!负责!迭代规划-0!!腐蚀机理研究员!负责!迭代规划-1!',
data: {
: {
text: '防护工程专家',
style: { background: 'hsl(30, 70%, 50%)' },
},
: {
text: '腐蚀机理研究员',
style: { background: 'hsl(210, 70%, 50%)' },
},
},
},
TaskProcess: [
{
ID: 'action-1-1-branch4',
ActionType: 'Propose',
AgentName: '防护工程专家',
Description: '制定敏捷开发总体框架和迭代周期',
ImportantInput: ['InputObject:腐蚀类型及成因列表'],
},
{
ID: 'action-1-2-branch4',
ActionType: 'Propose',
AgentName: '腐蚀机理研究员',
Description: '规划各迭代阶段的技术验证重点',
ImportantInput: ['ActionResult:action-1-1-branch4'],
},
{
ID: 'action-1-3-branch4',
ActionType: 'Critique',
AgentName: '防护工程专家',
Description: '评审迭代计划的合理性和可执行性',
ImportantInput: ['ActionResult:action-1-2-branch4'],
},
{
ID: 'action-1-4-branch4',
ActionType: 'Improve',
AgentName: '腐蚀机理研究员',
Description: '优化迭代节奏和里程碑设置',
ImportantInput: ['ActionResult:action-1-3-branch4'],
},
{
ID: 'action-1-5-branch4',
ActionType: 'Finalize',
AgentName: '防护工程专家',
Description: '确认最终迭代计划',
ImportantInput: ['ActionResult:action-1-4-branch4', 'ActionResult:action-1-3-branch4'],
},
],
}
}
console.log('[Mock API] fill_stepTask 返回数据:', filledTask)
return filledTask
}
// ==================== 第二个任务组 ====================
else if (stepName === '系统设计与架构' || stepName === '原型测试与优化' || stepName === '质量保障设计' || stepName === '快速开发与验证') {
let filledTask: IRawStepTask
if (selectedBranchIndex === 0) {
// 第一个分支方案:系统设计与架构
filledTask = {
Id: params.stepTask.Id || 'task-2',
StepName: '系统设计与架构',
TaskContent: '设计系统架构和数据库结构',
InputObject_List: ['项目开发计划书'],
OutputObject: '系统设计文档',
AgentSelection: ['腐蚀机理研究员', '防护工程专家'],
Collaboration_Brief_frontEnd: {
template: '!腐蚀机理研究员!负责!系统设计与架构-0!!防护工程专家!负责!系统设计与架构-1!',
data: {
: {
text: '腐蚀机理研究员',
style: { background: 'hsl(210, 70%, 50%)' },
},
: {
text: '防护工程专家',
style: { background: 'hsl(30, 70%, 50%)' },
},
},
},
TaskProcess: [
{
ID: 'action-2-1',
ActionType: 'Propose',
AgentName: '腐蚀机理研究员',
Description: '分析系统功能需求,提出整体架构方案',
ImportantInput: ['InputObject:项目开发计划书'],
},
{
ID: 'action-2-2',
ActionType: 'Propose',
AgentName: '防护工程专家',
Description: '设计防护系统架构和数据库模型',
ImportantInput: ['ActionResult:action-2-1'],
},
{
ID: 'action-2-3',
ActionType: 'Critique',
AgentName: '腐蚀机理研究员',
Description: '评审架构设计的合理性和可扩展性',
ImportantInput: ['ActionResult:action-2-2', 'InputObject:项目开发计划书'],
},
{
ID: 'action-2-4',
ActionType: 'Improve',
AgentName: '防护工程专家',
Description: '优化架构设计,补充性能和安全方案',
ImportantInput: ['ActionResult:action-2-3', 'ActionResult:action-2-2'],
},
{
ID: 'action-2-5',
ActionType: 'Finalize',
AgentName: '腐蚀机理研究员',
Description: '确认最终系统架构设计文档',
ImportantInput: ['ActionResult:action-2-4', 'ActionResult:action-2-3'],
},
],
}
} else if (selectedBranchIndex === 1) {
// 第二个分支方案:原型测试与优化
filledTask = {
Id: params.stepTask.Id || 'task-2',
StepName: '原型测试与优化',
TaskContent: '测试原型并根据反馈快速迭代',
InputObject_List: ['原型系统'],
OutputObject: '优化后的原型',
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
Collaboration_Brief_frontEnd: {
template: '!腐蚀机理研究员!负责!原型测试与优化-0!!实验材料学家!负责!原型测试与优化-1!',
data: {
: {
text: '腐蚀机理研究员',
style: { background: 'hsl(210, 70%, 50%)' },
},
: {
text: '实验材料学家',
style: { background: 'hsl(120, 70%, 50%)' },
},
},
},
TaskProcess: [
{
ID: 'action-2-1-branch2',
ActionType: 'Propose',
AgentName: '腐蚀机理研究员',
Description: '制定原型测试计划和验证标准',
ImportantInput: ['InputObject:原型系统'],
},
{
ID: 'action-2-2-branch2',
ActionType: 'Propose',
AgentName: '实验材料学家',
Description: '执行原型测试并收集性能数据',
ImportantInput: ['ActionResult:action-2-1-branch2'],
},
{
ID: 'action-2-3-branch2',
ActionType: 'Critique',
AgentName: '腐蚀机理研究员',
Description: '分析测试结果和用户反馈',
ImportantInput: ['ActionResult:action-2-2-branch2'],
},
{
ID: 'action-2-4-branch2',
ActionType: 'Improve',
AgentName: '实验材料学家',
Description: '根据测试结果优化原型功能和性能',
ImportantInput: ['ActionResult:action-2-3-branch2'],
},
{
ID: 'action-2-5-branch2',
ActionType: 'Finalize',
AgentName: '腐蚀机理研究员',
Description: '确认优化后的原型版本',
ImportantInput: ['ActionResult:action-2-4-branch2', 'ActionResult:action-2-3-branch2'],
},
],
}
} else if (selectedBranchIndex === 2) {
// 第三个分支方案:质量保障设计
filledTask = {
Id: params.stepTask.Id || 'task-2',
StepName: '质量保障设计',
TaskContent: '设计质量保障体系和测试方案',
InputObject_List: ['详细需求分析报告'],
OutputObject: '质量保障方案',
AgentSelection: ['实验材料学家', '防护工程专家'],
Collaboration_Brief_frontEnd: {
template: '!实验材料学家!负责!质量保障设计-0!!防护工程专家!负责!质量保障设计-1!',
data: {
: {
text: '实验材料学家',
style: { background: 'hsl(120, 70%, 50%)' },
},
: {
text: '防护工程专家',
style: { background: 'hsl(30, 70%, 50%)' },
},
},
},
TaskProcess: [
{
ID: 'action-2-1-branch3',
ActionType: 'Propose',
AgentName: '实验材料学家',
Description: '制定质量标准和测试指标体系',
ImportantInput: ['InputObject:详细需求分析报告'],
},
{
ID: 'action-2-2-branch3',
ActionType: 'Propose',
AgentName: '防护工程专家',
Description: '设计质量保障流程和验证方案',
ImportantInput: ['ActionResult:action-2-1-branch3'],
},
{
ID: 'action-2-3-branch3',
ActionType: 'Critique',
AgentName: '实验材料学家',
Description: '评审质量保障方案的完整性',
ImportantInput: ['ActionResult:action-2-2-branch3'],
},
{
ID: 'action-2-4-branch3',
ActionType: 'Improve',
AgentName: '防护工程专家',
Description: '完善质量保障方案,补充风险控制',
ImportantInput: ['ActionResult:action-2-3-branch3'],
},
{
ID: 'action-2-5-branch3',
ActionType: 'Finalize',
AgentName: '实验材料学家',
Description: '确认最终质量保障方案',
ImportantInput: ['ActionResult:action-2-4-branch3', 'ActionResult:action-2-3-branch3'],
},
],
}
} else {
// 第四个分支方案:快速开发与验证
filledTask = {
Id: params.stepTask.Id || 'task-2',
StepName: '快速开发与验证',
TaskContent: '快速开发并持续验证功能',
InputObject_List: ['迭代计划'],
OutputObject: '可运行的功能模块',
AgentSelection: ['实验材料学家', '防护工程专家'],
Collaboration_Brief_frontEnd: {
template: '!实验材料学家!负责!快速开发与验证-0!!防护工程专家!负责!快速开发与验证-1!',
data: {
: {
text: '实验材料学家',
style: { background: 'hsl(120, 70%, 50%)' },
},
: {
text: '防护工程专家',
style: { background: 'hsl(30, 70%, 50%)' },
},
},
},
TaskProcess: [
{
ID: 'action-2-1-branch4',
ActionType: 'Propose',
AgentName: '实验材料学家',
Description: '根据迭代计划快速开发功能模块',
ImportantInput: ['InputObject:迭代计划'],
},
{
ID: 'action-2-2-branch4',
ActionType: 'Propose',
AgentName: '防护工程专家',
Description: '实现功能防护机制和验证逻辑',
ImportantInput: ['ActionResult:action-2-1-branch4'],
},
{
ID: 'action-2-3-branch4',
ActionType: 'Critique',
AgentName: '实验材料学家',
Description: '验证功能模块的正确性和性能',
ImportantInput: ['ActionResult:action-2-2-branch4'],
},
{
ID: 'action-2-4-branch4',
ActionType: 'Improve',
AgentName: '防护工程专家',
Description: '根据验证结果优化功能实现',
ImportantInput: ['ActionResult:action-2-3-branch4'],
},
{
ID: 'action-2-5-branch4',
ActionType: 'Finalize',
AgentName: '实验材料学家',
Description: '确认可运行的功能模块',
ImportantInput: ['ActionResult:action-2-4-branch4', 'ActionResult:action-2-3-branch4'],
},
],
}
}
console.log('[Mock API] fill_stepTask 返回数据:', filledTask)
return filledTask
}
// ==================== 第三个任务组(仅第三个分支方案) ====================
else if (stepName === '系统开发与测试') {
const filledTask: IRawStepTask = {
Id: params.stepTask.Id || 'task-3',
StepName: '系统开发与测试',
TaskContent: '按质量标准进行系统开发和测试',
InputObject_List: ['质量保障方案'],
OutputObject: '经过完整测试的系统',
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
Collaboration_Brief_frontEnd: {
template: '!腐蚀机理研究员!负责!系统开发与测试-0!!实验材料学家!负责!系统开发与测试-1!',
data: {
: {
text: '腐蚀机理研究员',
style: { background: 'hsl(210, 70%, 50%)' },
},
: {
text: '实验材料学家',
style: { background: 'hsl(120, 70%, 50%)' },
},
},
},
TaskProcess: [
{
ID: 'action-3-1',
ActionType: 'Propose',
AgentName: '腐蚀机理研究员',
Description: '根据质量标准制定系统开发计划',
ImportantInput: ['InputObject:质量保障方案'],
},
{
ID: 'action-3-2',
ActionType: 'Propose',
AgentName: '实验材料学家',
Description: '按标准实施系统开发和单元测试',
ImportantInput: ['ActionResult:action-3-1'],
},
{
ID: 'action-3-3',
ActionType: 'Critique',
AgentName: '腐蚀机理研究员',
Description: '评审系统开发质量和测试覆盖率',
ImportantInput: ['ActionResult:action-3-2'],
},
{
ID: 'action-3-4',
ActionType: 'Improve',
AgentName: '实验材料学家',
Description: '根据评审结果完善系统功能和测试',
ImportantInput: ['ActionResult:action-3-3'],
},
{
ID: 'action-3-5',
ActionType: 'Finalize',
AgentName: '腐蚀机理研究员',
Description: '确认经过完整测试的系统版本',
ImportantInput: ['ActionResult:action-3-4', 'ActionResult:action-3-3'],
},
],
}
console.log('[Mock API] fill_stepTask 返回数据:', filledTask)
return filledTask
}
// ==================== 其他任务 ====================
else {
// 其他任务,返回空 TaskProcess
console.warn('[Mock API] 未知的 StepName:', stepName)
return {
...params.stepTask,
AgentSelection: [],
TaskProcess: [],
}
}
}
export default mockFillStepTaskAPI

View File

@@ -0,0 +1,115 @@
<script setup lang="ts">
import { computed } from 'vue'
import SvgIcon from '@/components/SvgIcon/index.vue'
import { useAgentsStore } from '@/stores'
const agentsStore = useAgentsStore()
const emit = defineEmits<{
(e: 'click'): void
}>()
// 判断是否禁用 - 必须先点击任务大纲中的卡片
const isDisabled = computed(() => {
return !agentsStore.currentTask
})
// 获取agent组合卡片数量 - 当前任务agents(1) + 已确认的agent组合数量
const agentGroupCount = computed(() => {
if (!agentsStore.currentTask?.Id) return 1
// 获取该任务的已确认agent组合
const confirmedGroups = agentsStore.getConfirmedAgentGroups(agentsStore.currentTask.Id)
// 当前任务agents(1) + 已确认的agent组合数量
return confirmedGroups.length || 1
})
const handleClick = () => {
if (isDisabled.value) return
emit('click')
}
</script>
<template>
<div
class="assignment-button"
:class="{ 'is-disabled': isDisabled, 'has-groups': agentGroupCount > 1 }"
@click="handleClick"
:title="isDisabled ? '请先点击任务大纲中的任务卡片' : `${agentGroupCount} 个agent组合`"
>
<!-- 智能体分配图标 -->
<SvgIcon icon-class="agent-change" size="24px" color="#fff" />
<!-- agent组合数量显示 -->
<span class="agent-group-count">
{{ agentGroupCount }}
</span>
</div>
</template>
<style scoped lang="scss">
.assignment-button {
position: absolute;
right: 0;
top: 0;
width: 40px;
height: 36px;
background-color: #43a8aa;
border-radius: 10px 0 0 0;
cursor: pointer;
user-select: none;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
position: relative;
transition: all 0.2s ease;
&:hover {
filter: brightness(0.9);
}
&:active {
filter: brightness(0.8);
}
// 多个agent组合时显示红点指示器
&.has-groups::after {
content: '';
position: absolute;
top: -2px;
right: -2px;
width: 8px;
height: 8px;
background: #ff6b6b;
border-radius: 50%;
border: 2px solid white;
}
// 禁用状态
&.is-disabled {
opacity: 0.5;
cursor: not-allowed;
&:hover {
filter: none;
}
&:active {
filter: none;
}
}
}
.agent-group-count {
position: absolute;
right: 1px;
bottom: 2px;
font-size: 12px;
color: white;
font-weight: 800;
text-align: right;
line-height: 1;
}
</style>

View File

@@ -0,0 +1,100 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useAgentsStore, useSelectionStore } from '@/stores'
const agentsStore = useAgentsStore()
const selectionStore = useSelectionStore()
const emit = defineEmits<{
(e: 'click'): void
}>()
// 获取分支数量 - 主分支(1) + 额外分支数量
const branchCount = computed(() => {
// flowBranches 包含所有通过 Branch 创建的分支
const extraBranches = selectionStore.flowBranches?.length || 1
// 始终至少有1个主分支
return extraBranches
})
const handleClick = () => {
// console.log('点击分支按钮')
emit('click')
// 触发打开分支窗口
agentsStore.openPlanModification()
}
</script>
<template>
<div
class="branch-button"
:class="{ 'has-branches': branchCount > 0 }"
@click="handleClick"
:title="`${branchCount} 个分支`"
>
<!-- 分支图标 -->
<svg-icon icon-class="branch" size="24px" class="branch-icon" />
<!-- 分支数量显示 -->
<span class="branch-count">
{{ branchCount }}
</span>
</div>
</template>
<style scoped lang="scss">
.branch-button {
position: absolute;
right: 0;
bottom: 0;
/* 尺寸 */
width: 36px;
height: 32px;
/* 样式 */
background-color: #43a8aa;
border-radius: 10px 0 0 0;
cursor: pointer;
user-select: none;
z-index: 100;
/* 布局 */
display: flex;
align-items: center;
justify-content: center;
position: relative;
/* 交互 */
transition: all 0.2s ease;
&:hover {
filter: brightness(0.9);
}
&.has-branches::after {
content: '';
position: absolute;
top: -2px;
right: -2px;
width: 8px;
height: 8px;
background: #ff6b6b;
border-radius: 50%;
border: 2px solid white;
}
}
.branch-icon {
color: white;
}
.branch-count {
position: absolute;
right: 4px;
bottom: 2px;
font-size: 12px;
color: white;
font-weight: 800;
text-align: right;
line-height: 1;
}
</style>

Some files were not shown because too many files have changed in this diff Show More