Compare commits

...

37 Commits

Author SHA1 Message Date
liailing1026
b3e6c7a618 feat:部署服务器报错 2026-01-29 16:40:10 +08:00
liailing1026
1749ae4f1e feat:通知主题修改 2026-01-26 17:25:23 +08:00
liailing1026
418b2e5f8f feat:智能体探索窗口预加载完善 2026-01-26 16:46:58 +08:00
liailing1026
641d70033d feat:用户修改步骤后重新执行实现 2026-01-26 15:06:17 +08:00
liailing1026
b287867069 feat:暂停动画以及暂停正确响应 2026-01-24 21:11:49 +08:00
liailing1026
5699635d1a feat:暂停/继续按钮与文字间距增加 2026-01-23 17:07:01 +08:00
liailing1026
ac035d1237 feat:任务大纲停止以及执行结果暂停继续逻辑完善 2026-01-23 15:38:09 +08:00
liailing1026
53add0431e feat:配置文件 2026-01-22 17:25:10 +08:00
liailing1026
786c674d21 feat:RESTful API架构改WebSocket架构-执行结果可以分步显示版本 2026-01-22 17:22:30 +08:00
liailing1026
1c8036adf1 feat:任务执行结果性能优化 2026-01-21 15:36:20 +08:00
liailing1026
45314b7be6 feat:任务执行结果性能优化 2026-01-21 15:18:15 +08:00
liailing1026
c5848410c1 feat:代码优化及格式化注释 2026-01-19 09:12:06 +08:00
liailing1026
571b5101ff feat:任务过程探索分支提示词优化 2026-01-16 11:11:57 +08:00
liailing1026
029df6b5a5 feat:冗余代码清理 2026-01-14 17:54:00 +08:00
liailing1026
edb39d4c1f feat:任务大纲探索窗口分支创建根节点基线完成度计算修改 2026-01-14 10:03:22 +08:00
liailing1026
0e87777ae8 feat:修改任务过程大纲探索后端返回不符合用户要求的bug 2026-01-13 17:58:52 +08:00
liailing1026
244deceb91 Merge branch 'web' of https://gitea.internetapi.cn/iod/AgentCoord into web
合并远程web分支更新
2026-01-13 14:08:52 +08:00
liailing1026
69587c0481 feat:智能体探索窗口与任务过程探索窗口联动修改 2026-01-13 13:57:56 +08:00
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
157 changed files with 23942 additions and 5737 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,107 @@ 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
)
# 检查响应是否有效
if not response.choices or len(response.choices) == 0:
raise Exception(f"API returned empty response for model {api_model}")
if not response.choices[0] or not response.choices[0].message:
raise Exception(f"API returned invalid response format for model {api_model}")
full_reply_content = response.choices[0].message.content
if full_reply_content is None:
raise Exception(f"API returned None content for model {api_model}")
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 and choices[0] is not None:
chunk_message = choices[0].delta
if chunk_message is not None:
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]
)
# 检查最终结果是否为空
if not full_reply_content or full_reply_content.strip() == "":
raise Exception(f"Stream API returned empty content for model {api_model}")
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 +197,18 @@ 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")
# 检查响应是否有效
if not response.choices or len(response.choices) == 0:
raise Exception("Groq API returned empty response")
if not response.choices[0] or not response.choices[0].message:
raise Exception("Groq API returned invalid response format")
full_reply_content = response.choices[0].message.content
if full_reply_content is None:
raise Exception("Groq API returned None content")
full_reply_content = stream.choices[0].message.content
print(colored(full_reply_content, "blue", "on_white"), end="")
print()
return full_reply_content
@@ -103,14 +217,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,31 +233,35 @@ 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")
# 检查响应是否有效
if not stream.choices or len(stream.choices) == 0:
raise Exception("Mistral API returned empty response")
if not stream.choices[0] or not stream.choices[0].message:
raise Exception("Mistral API returned invalid response format")
full_reply_content = stream.choices[0].message.content
if full_reply_content is None:
raise Exception("Mistral API returned None content")
print(colored(full_reply_content, "blue", "on_white"), end="")
print()
return full_reply_content
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 +272,38 @@ 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"]
if len(choices) > 0:
chunk_message = chunk["choices"][0].get(
"delta", {}
) # extract the message
choices = chunk.choices
if len(choices) > 0 and choices[0] is not None:
chunk_message = choices[0].delta
if chunk_message is not None:
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]
)
# 检查最终结果是否为空
if not full_reply_content or full_reply_content.strip() == "":
raise Exception("Stream API (gpt-3.5) returned empty content")
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,18 +312,26 @@ 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")
# 检查响应是否有效
if not response.choices or len(response.choices) == 0:
raise Exception("OpenAI API returned empty response")
if not response.choices[0] or not response.choices[0].message:
raise Exception("OpenAI API returned invalid response format")
full_reply_content = response.choices[0].message.content
if full_reply_content is None:
raise Exception("OpenAI API returned None content")
full_reply_content = stream.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(
try:
response = await async_client.chat.completions.create(
**_cons_kwargs(messages), stream=True
)
@@ -217,39 +341,58 @@ async def _achat_completion_stream(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"]
if len(choices) > 0:
chunk_message = chunk["choices"][0].get(
"delta", {}
) # extract the message
choices = chunk.choices
if len(choices) > 0 and choices[0] is not None:
chunk_message = choices[0].delta
if chunk_message is not None:
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]
)
# 检查最终结果是否为空
if not full_reply_content or full_reply_content.strip() == "":
raise Exception("Stream API returned empty content")
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"]
try:
rsp = client.chat.completions.create(**_cons_kwargs(messages))
# 检查响应是否有效
if not rsp.choices or len(rsp.choices) == 0:
raise Exception("OpenAI API returned empty response")
if not rsp.choices[0] or not rsp.choices[0].message:
raise Exception("OpenAI API returned invalid response format")
content = rsp.choices[0].message.content
if content is None:
raise Exception("OpenAI API returned None 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}
@@ -133,5 +137,6 @@ def AgentSelectModify_init(stepTask, General_Goal, Agent_Board):
def AgentSelectModify_addAspect(aspectList, Agent_Board):
scoreTable = agentAbilityScoring(Agent_Board, aspectList)
newAspect = aspectList[-1]
scoreTable = agentAbilityScoring(Agent_Board, [newAspect])
return scoreTable

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

@@ -1,55 +1,53 @@
from AgentCoord.PlanEngine.planOutline_Generator import generate_PlanOutline
from AgentCoord.PlanEngine.AgentSelection_Generator import (
generate_AgentSelection,
)
from AgentCoord.PlanEngine.taskProcess_Generator import generate_TaskProcess
import AgentCoord.util as util
# from AgentCoord.PlanEngine.AgentSelection_Generator import (
# generate_AgentSelection,
# )
def generate_basePlan(
General_Goal, Agent_Board, AgentProfile_Dict, InitialObject_List
):
basePlan = {
"Initial Input Object": InitialObject_List,
"Collaboration Process": [],
}
"""
优化模式:生成大纲 + 智能体选择,但不生成任务流程
优化用户体验:
1. 快速生成大纲和分配智能体
2. 用户可以看到完整的大纲和智能体图标
3. TaskProcess由前端通过 fillStepTask API 异步填充
"""
# 参数保留以保持接口兼容性
_ = AgentProfile_Dict
PlanOutline = generate_PlanOutline(
InitialObject_List=[], General_Goal=General_Goal
InitialObject_List=InitialObject_List, General_Goal=General_Goal
)
basePlan = {
"General Goal": General_Goal,
"Initial Input Object": InitialObject_List,
"Collaboration Process": []
}
for stepItem in PlanOutline:
Current_Task = {
"TaskName": stepItem["StepName"],
"InputObject_List": stepItem["InputObject_List"],
"OutputObject": stepItem["OutputObject"],
"TaskContent": stepItem["TaskContent"],
# # 为每个步骤分配智能体
# Current_Task = {
# "TaskName": stepItem["StepName"],
# "InputObject_List": stepItem["InputObject_List"],
# "OutputObject": stepItem["OutputObject"],
# "TaskContent": stepItem["TaskContent"],
# }
# AgentSelection = generate_AgentSelection(
# General_Goal=General_Goal,
# Current_Task=Current_Task,
# Agent_Board=Agent_Board,
# )
# 添加智能体选择,但不添加任务流程
stepItem["AgentSelection"] = []
stepItem["TaskProcess"] = [] # 空数组,由前端异步填充
stepItem["Collaboration_Brief_frontEnd"] = {
"template": "",
"data": {}
}
AgentSelection = generate_AgentSelection(
General_Goal=General_Goal,
Current_Task=Current_Task,
Agent_Board=Agent_Board,
)
Current_Task_Description = {
"TaskName": stepItem["StepName"],
"AgentInvolved": [
{"Name": name, "Profile": AgentProfile_Dict[name]}
for name in AgentSelection
],
"InputObject_List": stepItem["InputObject_List"],
"OutputObject": stepItem["OutputObject"],
"CurrentTaskDescription": util.generate_template_sentence_for_CollaborationBrief(
stepItem["InputObject_List"],
stepItem["OutputObject"],
AgentSelection,
stepItem["TaskContent"],
),
}
TaskProcess = generate_TaskProcess(
General_Goal=General_Goal,
Current_Task_Description=Current_Task_Description,
)
# add the generated AgentSelection and TaskProcess to the stepItem
stepItem["AgentSelection"] = AgentSelection
stepItem["TaskProcess"] = TaskProcess
basePlan["Collaboration Process"].append(stepItem)
basePlan["General Goal"] = General_Goal
return basePlan

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}
@@ -55,27 +57,40 @@ Note: "Modification Requirement" specifies how to modify the "Baseline Completio
"ID": "Action4",
"ActionType": "Propose",
"AgentName": "Mia",
"Description": "Propose psychological theories on love and attachment that could be applied to AI's emotional development.",
"Description": "提议关于人工智能情感发展的心理学理论,重点关注爱与依恋的概念。",
"ImportantInput": [
"InputObject:Story Outline"
]
}},
{{
"ID": "Action5",
"ActionType": "Propose",
"ActionType": "Critique",
"AgentName": "Noah",
"Description": "Propose ethical considerations and philosophical questions regarding AI's capacity for love.",
"ImportantInput": []
"Description": "对Mia提出的心理学理论进行批判性评估分析其在AI情感发展场景中的适用性和局限性。",
"ImportantInput": [
"ActionResult:Action4"
]
}},
{{
"ID": "Action6",
"ActionType": "Finalize",
"ActionType": "Improve",
"AgentName": "Liam",
"Description": "Combine the poetic elements and ethical considerations into a cohesive set of core love elements for the story.",
"Description": "基于Noah的批判性反馈改进和完善心理学理论框架使其更贴合AI情感发展的实际需求。",
"ImportantInput": [
"ActionResult:Action1",
"ActionResult:Action4",
"ActionResult:Action5"
]
}},
{{
"ID": "Action7",
"ActionType": "Finalize",
"AgentName": "Mia",
"Description": "综合所有提议、批判和改进意见整合并提交最终的AI情感发展心理学理论框架。",
"ImportantInput": [
"ActionResult:Action4",
"ActionResult:Action5",
"ActionResult:Action6"
]
}}
]
}}
@@ -84,7 +99,12 @@ Note: "Modification Requirement" specifies how to modify the "Baseline Completio
ImportantInput: Specify if there is any previous result that should be taken special consideration during the execution the action. Should be of format "InputObject:xx" or "ActionResult:xx".
InputObject_List: List existing objects that should be utilized in current step.
AgentName: Specify the agent who will perform the action, You CAN ONLY USE THE NAME APPEARS IN "AgentInvolved".
ActionType: Specify the type of action, note that only the last action can be of type "Finalize", and the last action must be "Finalize".
ActionType: Specify the type of action. **CRITICAL REQUIREMENTS:**
1. The "Remaining Steps" MUST include ALL FOUR action types in the following order: Propose -> Critique -> Improve -> Finalize
2. Each action type (Propose, Critique, Improve, Finalize) MUST appear at least once
3. The actions must follow the sequence: Propose actions first, then Critique actions, then Improve actions, and Finalize must be the last action
4. Even if only one agent is involved in a phase, that phase must still have its corresponding action type
5. The last action must ALWAYS be of type "Finalize"
"""

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

@@ -9,6 +9,8 @@ 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)
@@ -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

@@ -0,0 +1,608 @@
"""
优化版执行计划 - 支持动态追加步骤
在执行过程中可以接收新的步骤并追加到执行队列
"""
import asyncio
import json
import time
from typing import List, Dict, Set, Generator, Any
import AgentCoord.RehearsalEngine_V2.Action as Action
import AgentCoord.util as util
from termcolor import colored
from AgentCoord.RehearsalEngine_V2.execution_state import execution_state_manager
from AgentCoord.RehearsalEngine_V2.dynamic_execution_manager import dynamic_execution_manager
# ==================== 配置参数 ====================
# 最大并发请求数
MAX_CONCURRENT_REQUESTS = 2
# 批次之间的延迟
BATCH_DELAY = 1.0
# 429错误重试次数和延迟
MAX_RETRIES = 3
RETRY_DELAY = 5.0
# ==================== 限流器 ====================
class RateLimiter:
"""
异步限流器,控制并发请求数量
"""
def __init__(self, max_concurrent: int = MAX_CONCURRENT_REQUESTS):
self.semaphore = asyncio.Semaphore(max_concurrent)
self.max_concurrent = max_concurrent
async def __aenter__(self):
await self.semaphore.acquire()
return self
async def __aexit__(self, *args):
self.semaphore.release()
# 全局限流器实例
rate_limiter = RateLimiter()
def build_action_dependency_graph(TaskProcess: List[Dict]) -> Dict[int, List[int]]:
"""
构建动作依赖图
Args:
TaskProcess: 任务流程列表
Returns:
依赖映射字典 {action_index: [dependent_action_indices]}
"""
dependency_map = {i: [] for i in range(len(TaskProcess))}
for i, action in enumerate(TaskProcess):
important_inputs = action.get('ImportantInput', [])
if not important_inputs:
continue
# 检查是否依赖其他动作的ActionResult
for j, prev_action in enumerate(TaskProcess):
if i == j:
continue
# 判断是否依赖前一个动作的结果
if any(
inp.startswith('ActionResult:') and
inp == f'ActionResult:{prev_action["ID"]}'
for inp in important_inputs
):
dependency_map[i].append(j)
return dependency_map
def get_parallel_batches(TaskProcess: List[Dict], dependency_map: Dict[int, List[int]]) -> List[List[int]]:
"""
将动作分为多个批次,每批内部可以并行执行
Args:
TaskProcess: 任务流程列表
dependency_map: 依赖图
Returns:
批次列表 [[batch1_indices], [batch2_indices], ...]
"""
batches = []
completed: Set[int] = set()
while len(completed) < len(TaskProcess):
# 找出所有依赖已满足的动作
ready_to_run = [
i for i in range(len(TaskProcess))
if i not in completed and
all(dep in completed for dep in dependency_map[i])
]
if not ready_to_run:
# 避免死循环
remaining = [i for i in range(len(TaskProcess)) if i not in completed]
if remaining:
print(colored(f"警告: 检测到循环依赖,强制串行执行: {remaining}", "yellow"))
ready_to_run = remaining[:1]
else:
break
batches.append(ready_to_run)
completed.update(ready_to_run)
return batches
async def execute_single_action_async(
ActionInfo: Dict,
General_Goal: str,
TaskDescription: str,
OutputName: str,
KeyObjects: Dict,
ActionHistory: List,
agentName: str,
AgentProfile_Dict: Dict,
InputName_List: List[str]
) -> Dict:
"""
异步执行单个动作
Args:
ActionInfo: 动作信息
General_Goal: 总体目标
TaskDescription: 任务描述
OutputName: 输出对象名称
KeyObjects: 关键对象字典
ActionHistory: 动作历史
agentName: 智能体名称
AgentProfile_Dict: 智能体配置字典
InputName_List: 输入名称列表
Returns:
动作执行结果
"""
actionType = ActionInfo["ActionType"]
# 创建动作实例
if actionType in Action.customAction_Dict:
currentAction = Action.customAction_Dict[actionType](
info=ActionInfo,
OutputName=OutputName,
KeyObjects=KeyObjects,
)
else:
currentAction = Action.BaseAction(
info=ActionInfo,
OutputName=OutputName,
KeyObjects=KeyObjects,
)
# 在线程池中运行,避免阻塞事件循环
loop = asyncio.get_event_loop()
ActionInfo_with_Result = await loop.run_in_executor(
None,
lambda: currentAction.run(
General_Goal=General_Goal,
TaskDescription=TaskDescription,
agentName=agentName,
AgentProfile_Dict=AgentProfile_Dict,
InputName_List=InputName_List,
OutputName=OutputName,
KeyObjects=KeyObjects,
ActionHistory=ActionHistory,
)
)
return ActionInfo_with_Result
async def execute_step_async_streaming(
stepDescrip: Dict,
General_Goal: str,
AgentProfile_Dict: Dict,
KeyObjects: Dict,
step_index: int,
total_steps: int
) -> Generator[Dict, None, None]:
"""
异步执行单个步骤,支持流式返回
Args:
stepDescrip: 步骤描述
General_Goal: 总体目标
AgentProfile_Dict: 智能体配置字典
KeyObjects: 关键对象字典
step_index: 步骤索引
total_steps: 总步骤数
Yields:
执行事件字典
"""
# 准备步骤信息
StepName = (
util.camel_case_to_normal(stepDescrip["StepName"])
if util.is_camel_case(stepDescrip["StepName"])
else stepDescrip["StepName"]
)
TaskContent = stepDescrip["TaskContent"]
InputName_List = (
[
(
util.camel_case_to_normal(obj)
if util.is_camel_case(obj)
else obj
)
for obj in stepDescrip["InputObject_List"]
]
if stepDescrip["InputObject_List"] is not None
else None
)
OutputName = (
util.camel_case_to_normal(stepDescrip["OutputObject"])
if util.is_camel_case(stepDescrip["OutputObject"])
else stepDescrip["OutputObject"]
)
Agent_List = stepDescrip["AgentSelection"]
TaskProcess = stepDescrip["TaskProcess"]
TaskDescription = (
util.converter.generate_template_sentence_for_CollaborationBrief(
input_object_list=InputName_List,
output_object=OutputName,
agent_list=Agent_List,
step_task=TaskContent,
)
)
# 初始化日志节点
inputObject_Record = [
{InputName: KeyObjects[InputName]} for InputName in InputName_List
]
stepLogNode = {
"LogNodeType": "step",
"NodeId": StepName,
"InputName_List": InputName_List,
"OutputName": OutputName,
"chatLog": [],
"inputObject_Record": inputObject_Record,
}
objectLogNode = {
"LogNodeType": "object",
"NodeId": OutputName,
"content": None,
}
# 返回步骤开始事件
yield {
"type": "step_start",
"step_index": step_index,
"total_steps": total_steps,
"step_name": StepName,
"task_description": TaskDescription,
}
# 构建动作依赖图
dependency_map = build_action_dependency_graph(TaskProcess)
batches = get_parallel_batches(TaskProcess, dependency_map)
ActionHistory = []
total_actions = len(TaskProcess)
completed_actions = 0
util.print_colored(
f"📋 步骤 {step_index + 1}/{total_steps}: {StepName} ({total_actions} 个动作, 分 {len(batches)} 批并行执行)",
text_color="cyan"
)
# 分批执行动作
for batch_index, batch_indices in enumerate(batches):
# 在每个批次执行前检查暂停状态
should_continue = await execution_state_manager.async_check_pause()
if not should_continue:
util.print_colored("🛑 用户请求停止执行", "red")
return
batch_size = len(batch_indices)
if batch_size > 1:
util.print_colored(
f"🚦 批次 {batch_index + 1}/{len(batches)}: 并行执行 {batch_size} 个动作",
text_color="blue"
)
else:
util.print_colored(
f"🔄 动作 {completed_actions + 1}/{total_actions}: 串行执行",
text_color="yellow"
)
# 并行执行当前批次的所有动作
tasks = [
execute_single_action_async(
TaskProcess[i],
General_Goal=General_Goal,
TaskDescription=TaskDescription,
OutputName=OutputName,
KeyObjects=KeyObjects,
ActionHistory=ActionHistory,
agentName=TaskProcess[i]["AgentName"],
AgentProfile_Dict=AgentProfile_Dict,
InputName_List=InputName_List
)
for i in batch_indices
]
# 等待当前批次完成
batch_results = await asyncio.gather(*tasks)
# 逐个返回结果
for i, result in enumerate(batch_results):
action_index_in_batch = batch_indices[i]
completed_actions += 1
util.print_colored(
f"✅ 动作 {completed_actions}/{total_actions} 完成: {result['ActionType']} by {result['AgentName']}",
text_color="green"
)
ActionHistory.append(result)
# 立即返回该动作结果
yield {
"type": "action_complete",
"step_index": step_index,
"step_name": StepName,
"action_index": action_index_in_batch,
"total_actions": total_actions,
"completed_actions": completed_actions,
"action_result": result,
"batch_info": {
"batch_index": batch_index,
"batch_size": batch_size,
"is_parallel": batch_size > 1
}
}
# 步骤完成
objectLogNode["content"] = KeyObjects[OutputName]
stepLogNode["ActionHistory"] = ActionHistory
yield {
"type": "step_complete",
"step_index": step_index,
"step_name": StepName,
"step_log_node": stepLogNode,
"object_log_node": objectLogNode,
}
def executePlan_streaming_dynamic(
plan: Dict,
num_StepToRun: int,
RehearsalLog: List,
AgentProfile_Dict: Dict,
existingKeyObjects: Dict = None,
execution_id: str = None
) -> Generator[str, None, None]:
"""
动态执行计划,支持在执行过程中追加新步骤
Args:
plan: 执行计划
num_StepToRun: 要运行的步骤数
RehearsalLog: 已执行的历史记录
AgentProfile_Dict: 智能体配置
existingKeyObjects: 已存在的KeyObjects
execution_id: 执行ID用于动态追加步骤
Yields:
SSE格式的事件字符串
"""
# 初始化执行状态
general_goal = plan.get("General Goal", "")
execution_state_manager.start_execution(general_goal)
print(colored(f"⏸️ 执行状态管理器已启动,支持暂停/恢复", "green"))
# 准备执行
KeyObjects = existingKeyObjects.copy() if existingKeyObjects else {}
finishedStep_index = -1
for logNode in RehearsalLog:
if logNode["LogNodeType"] == "step":
finishedStep_index += 1
if logNode["LogNodeType"] == "object":
KeyObjects[logNode["NodeId"]] = logNode["content"]
if existingKeyObjects:
print(colored(f"📦 使用已存在的 KeyObjects: {list(existingKeyObjects.keys())}", "cyan"))
# 确定要运行的步骤范围
if num_StepToRun is None:
run_to = len(plan["Collaboration Process"])
else:
run_to = (finishedStep_index + 1) + num_StepToRun
steps_to_run = plan["Collaboration Process"][(finishedStep_index + 1): run_to]
# 使用动态执行管理器
if execution_id:
# 初始化执行管理器使用传入的execution_id
actual_execution_id = dynamic_execution_manager.start_execution(general_goal, steps_to_run, execution_id)
print(colored(f"🚀 开始执行计划(动态模式),共 {len(steps_to_run)} 个步骤执行ID: {actual_execution_id}", "cyan"))
else:
print(colored(f"🚀 开始执行计划(流式推送),共 {len(steps_to_run)} 个步骤", "cyan"))
total_steps = len(steps_to_run)
# 使用队列实现流式推送
async def produce_events(queue: asyncio.Queue):
"""异步生产者"""
try:
step_index = 0
if execution_id:
# 动态模式:循环获取下一个步骤
# 等待新步骤的最大次数(避免无限等待)
max_empty_wait_cycles = 5 # 最多等待60次每次等待1秒
empty_wait_count = 0
while True:
# 检查暂停状态
should_continue = await execution_state_manager.async_check_pause()
if not should_continue:
print(colored("🛑 用户请求停止执行", "red"))
await queue.put({
"type": "error",
"message": "执行已被用户停止"
})
break
# 获取下一个步骤
stepDescrip = dynamic_execution_manager.get_next_step(execution_id)
if stepDescrip is None:
# 没有更多步骤了,检查是否应该继续等待
empty_wait_count += 1
# 获取执行信息
execution_info = dynamic_execution_manager.get_execution_info(execution_id)
if execution_info:
queue_total_steps = execution_info.get("total_steps", 0)
completed_steps = execution_info.get("completed_steps", 0)
# 如果没有步骤在队列中queue_total_steps为0立即退出
if queue_total_steps == 0:
print(colored(f"⚠️ 没有步骤在队列中,退出执行", "yellow"))
break
# 如果所有步骤都已完成,等待可能的新步骤
if completed_steps >= queue_total_steps:
if empty_wait_count >= max_empty_wait_cycles:
# 等待超时,退出执行
print(colored(f"✅ 所有步骤执行完成,等待超时", "green"))
break
else:
# 等待新步骤追加
print(colored(f"⏳ 等待新步骤追加... ({empty_wait_count}/{max_empty_wait_cycles})", "cyan"))
await asyncio.sleep(1)
continue
else:
# 还有步骤未完成,继续尝试获取
print(colored(f"⏳ 等待步骤就绪... ({completed_steps}/{queue_total_steps})", "cyan"))
await asyncio.sleep(0.5)
empty_wait_count = 0 # 重置等待计数
continue
else:
# 执行信息不存在,退出
print(colored(f"⚠️ 执行信息不存在,退出执行", "yellow"))
break
# 重置等待计数
empty_wait_count = 0
# 获取最新的总步骤数(用于显示)
execution_info = dynamic_execution_manager.get_execution_info(execution_id)
current_total_steps = execution_info.get("total_steps", total_steps) if execution_info else total_steps
# 执行步骤
async for event in execute_step_async_streaming(
stepDescrip,
plan["General Goal"],
AgentProfile_Dict,
KeyObjects,
step_index,
current_total_steps # 使用动态更新的总步骤数
):
if execution_state_manager.is_stopped():
await queue.put({
"type": "error",
"message": "执行已被用户停止"
})
return
await queue.put(event)
# 标记步骤完成
dynamic_execution_manager.mark_step_completed(execution_id)
# 更新KeyObjects
OutputName = stepDescrip.get("OutputObject", "")
if OutputName and OutputName in KeyObjects:
# 对象日志节点会在step_complete中发送
pass
step_index += 1
else:
# 非动态模式:按顺序执行所有步骤
for step_index, stepDescrip in enumerate(steps_to_run):
should_continue = await execution_state_manager.async_check_pause()
if not should_continue:
print(colored("🛑 用户请求停止执行", "red"))
await queue.put({
"type": "error",
"message": "执行已被用户停止"
})
return
async for event in execute_step_async_streaming(
stepDescrip,
plan["General Goal"],
AgentProfile_Dict,
KeyObjects,
step_index,
total_steps
):
if execution_state_manager.is_stopped():
await queue.put({
"type": "error",
"message": "执行已被用户停止"
})
return
await queue.put(event)
except Exception as e:
await queue.put({
"type": "error",
"message": f"执行出错: {str(e)}"
})
finally:
await queue.put(None)
# 运行异步任务并实时yield
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
queue = asyncio.Queue(maxsize=10)
producer_task = loop.create_task(produce_events(queue))
while True:
event = loop.run_until_complete(queue.get())
if event is None:
break
# 立即转换为SSE格式并发送
event_str = json.dumps(event, ensure_ascii=False)
yield f"data: {event_str}\n\n"
loop.run_until_complete(producer_task)
if not execution_state_manager.is_stopped():
complete_event = json.dumps({
"type": "execution_complete",
"total_steps": total_steps
}, ensure_ascii=False)
yield f"data: {complete_event}\n\n"
finally:
# 在关闭事件循环之前先清理执行记录
if execution_id:
# 清理执行记录
dynamic_execution_manager.cleanup(execution_id)
if 'producer_task' in locals():
if not producer_task.done():
producer_task.cancel()
# 确保所有任务都完成后再关闭事件循环
try:
pending = asyncio.all_tasks(loop)
for task in pending:
task.cancel()
loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
except Exception:
pass # 忽略清理过程中的错误
loop.close()
# 保留旧版本函数以保持兼容性
executePlan_streaming = executePlan_streaming_dynamic

View File

@@ -0,0 +1,241 @@
"""
动态执行管理器
用于在任务执行过程中动态追加新步骤
"""
import asyncio
from typing import Dict, List, Optional, Any
from threading import Lock
class DynamicExecutionManager:
"""
动态执行管理器
管理正在执行的任务,支持动态追加新步骤
"""
def __init__(self):
# 执行状态: goal -> execution_info
self._executions: Dict[str, Dict] = {}
# 线程锁
self._lock = Lock()
# 步骤队列: goal -> List[step]
self._step_queues: Dict[str, List] = {}
# 已执行的步骤索引: goal -> Set[step_index]
self._executed_steps: Dict[str, set] = {}
# 待执行的步骤索引: goal -> List[step_index]
self._pending_steps: Dict[str, List[int]] = {}
def start_execution(self, goal: str, initial_steps: List[Dict], execution_id: str = None) -> str:
"""
开始执行一个新的任务
Args:
goal: 任务目标
initial_steps: 初始步骤列表
execution_id: 执行ID如果不提供则自动生成
Returns:
执行ID
"""
with self._lock:
# 如果未提供execution_id则生成一个
if execution_id is None:
execution_id = f"{goal}_{asyncio.get_event_loop().time()}"
self._executions[execution_id] = {
"goal": goal,
"status": "running",
"total_steps": len(initial_steps),
"completed_steps": 0
}
# 初始化步骤队列
self._step_queues[execution_id] = initial_steps.copy()
# 初始化已执行步骤集合
self._executed_steps[execution_id] = set()
# 初始化待执行步骤索引
self._pending_steps[execution_id] = list(range(len(initial_steps)))
print(f"🚀 启动执行: {execution_id}")
print(f"📊 初始步骤数: {len(initial_steps)}")
print(f"📋 待执行步骤索引: {self._pending_steps[execution_id]}")
return execution_id
def add_steps(self, execution_id: str, new_steps: List[Dict]) -> int:
"""
向执行中追加新步骤
Args:
execution_id: 执行ID
new_steps: 新步骤列表
Returns:
追加的步骤数量
"""
with self._lock:
if execution_id not in self._step_queues:
print(f"⚠️ 警告: 执行ID {execution_id} 不存在,无法追加步骤")
return 0
current_count = len(self._step_queues[execution_id])
# 追加新步骤到队列
self._step_queues[execution_id].extend(new_steps)
# 添加新步骤的索引到待执行列表
new_indices = list(range(current_count, current_count + len(new_steps)))
self._pending_steps[execution_id].extend(new_indices)
# 更新总步骤数
old_total = self._executions[execution_id]["total_steps"]
self._executions[execution_id]["total_steps"] = len(self._step_queues[execution_id])
new_total = self._executions[execution_id]["total_steps"]
print(f" 追加了 {len(new_steps)} 个步骤到 {execution_id}")
print(f"📊 步骤总数: {old_total} -> {new_total}")
print(f"📋 待执行步骤索引: {self._pending_steps[execution_id]}")
return len(new_steps)
def get_next_step(self, execution_id: str) -> Optional[Dict]:
"""
获取下一个待执行的步骤
Args:
execution_id: 执行ID
Returns:
下一个步骤如果没有则返回None
"""
with self._lock:
if execution_id not in self._pending_steps:
print(f"⚠️ 警告: 执行ID {execution_id} 不存在")
return None
# 获取第一个待执行步骤的索引
if not self._pending_steps[execution_id]:
return None
step_index = self._pending_steps[execution_id].pop(0)
# 从队列中获取步骤
if step_index >= len(self._step_queues[execution_id]):
print(f"⚠️ 警告: 步骤索引 {step_index} 超出范围")
return None
step = self._step_queues[execution_id][step_index]
# 标记为已执行
self._executed_steps[execution_id].add(step_index)
step_name = step.get("StepName", "未知")
print(f"🎯 获取下一个步骤: {step_name} (索引: {step_index})")
print(f"📋 剩余待执行步骤: {len(self._pending_steps[execution_id])}")
return step
def mark_step_completed(self, execution_id: str):
"""
标记一个步骤完成
Args:
execution_id: 执行ID
"""
with self._lock:
if execution_id in self._executions:
self._executions[execution_id]["completed_steps"] += 1
completed = self._executions[execution_id]["completed_steps"]
total = self._executions[execution_id]["total_steps"]
print(f"📊 步骤完成进度: {completed}/{total}")
else:
print(f"⚠️ 警告: 执行ID {execution_id} 不存在")
def get_execution_info(self, execution_id: str) -> Optional[Dict]:
"""
获取执行信息
Args:
execution_id: 执行ID
Returns:
执行信息字典
"""
with self._lock:
return self._executions.get(execution_id)
def get_pending_count(self, execution_id: str) -> int:
"""
获取待执行步骤数量
Args:
execution_id: 执行ID
Returns:
待执行步骤数量
"""
with self._lock:
if execution_id not in self._pending_steps:
return 0
return len(self._pending_steps[execution_id])
def has_more_steps(self, execution_id: str) -> bool:
"""
检查是否还有更多步骤待执行
Args:
execution_id: 执行ID
Returns:
是否还有待执行步骤
"""
with self._lock:
if execution_id not in self._pending_steps:
return False
return len(self._pending_steps[execution_id]) > 0
def finish_execution(self, execution_id: str):
"""
完成执行
Args:
execution_id: 执行ID
"""
with self._lock:
if execution_id in self._executions:
self._executions[execution_id]["status"] = "completed"
def cancel_execution(self, execution_id: str):
"""
取消执行
Args:
execution_id: 执行ID
"""
with self._lock:
if execution_id in self._executions:
self._executions[execution_id]["status"] = "cancelled"
def cleanup(self, execution_id: str):
"""
清理执行记录
Args:
execution_id: 执行ID
"""
with self._lock:
self._executions.pop(execution_id, None)
self._step_queues.pop(execution_id, None)
self._executed_steps.pop(execution_id, None)
self._pending_steps.pop(execution_id, None)
# 全局单例
dynamic_execution_manager = DynamicExecutionManager()

View File

@@ -0,0 +1,190 @@
"""
全局执行状态管理器
用于支持任务的暂停、恢复和停止功能
使用轮询检查机制,确保线程安全
"""
import threading
import asyncio
import time
from typing import Optional
from enum import Enum
class ExecutionStatus(Enum):
"""执行状态枚举"""
RUNNING = "running" # 正在运行
PAUSED = "paused" # 已暂停
STOPPED = "stopped" # 已停止
IDLE = "idle" # 空闲
class ExecutionStateManager:
"""
全局执行状态管理器(单例模式)
功能:
- 管理任务执行状态(运行/暂停/停止)
- 使用轮询检查机制,避免异步事件的线程问题
- 提供线程安全的状态查询和修改接口
"""
_instance: Optional['ExecutionStateManager'] = None
_lock = threading.Lock()
def __new__(cls):
"""单例模式"""
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
"""初始化状态管理器"""
if self._initialized:
return
self._initialized = True
self._status = ExecutionStatus.IDLE
self._current_goal: Optional[str] = None # 当前执行的任务目标
# 使用简单的布尔标志,而不是 asyncio.Event
self._should_pause = False
self._should_stop = False
def get_status(self) -> ExecutionStatus:
"""获取当前执行状态"""
with self._lock:
return self._status
def set_goal(self, goal: str):
"""设置当前执行的任务目标"""
with self._lock:
self._current_goal = goal
def get_goal(self) -> Optional[str]:
"""获取当前执行的任务目标"""
with self._lock:
return self._current_goal
def start_execution(self, goal: str):
"""开始执行"""
with self._lock:
self._status = ExecutionStatus.RUNNING
self._current_goal = goal
self._should_pause = False
self._should_stop = False
print(f"🚀 [DEBUG] start_execution: 状态设置为 RUNNING, goal={goal}")
def pause_execution(self) -> bool:
"""
暂停执行
Returns:
bool: 是否成功暂停
"""
with self._lock:
if self._status != ExecutionStatus.RUNNING:
print(f"⚠️ [DEBUG] pause_execution: 当前状态不是RUNNING而是 {self._status}")
return False
self._status = ExecutionStatus.PAUSED
self._should_pause = True
print(f"⏸️ [DEBUG] pause_execution: 状态设置为PAUSED, should_pause=True")
return True
def resume_execution(self) -> bool:
"""
恢复执行
Returns:
bool: 是否成功恢复
"""
with self._lock:
if self._status != ExecutionStatus.PAUSED:
print(f"⚠️ [DEBUG] resume_execution: 当前状态不是PAUSED而是 {self._status}")
return False
self._status = ExecutionStatus.RUNNING
self._should_pause = False
print(f"▶️ [DEBUG] resume_execution: 状态设置为RUNNING, should_pause=False")
return True
def stop_execution(self) -> bool:
"""
停止执行
Returns:
bool: 是否成功停止
"""
with self._lock:
if self._status in [ExecutionStatus.IDLE, ExecutionStatus.STOPPED]:
return False
self._status = ExecutionStatus.STOPPED
self._should_stop = True
self._should_pause = False
print(f"🛑 [DEBUG] stop_execution: 状态设置为STOPPED")
return True
def reset(self):
"""重置状态为空闲"""
with self._lock:
self._status = ExecutionStatus.IDLE
self._current_goal = None
self._should_pause = False
self._should_stop = False
print(f"🔄 [DEBUG] reset: 状态重置为IDLE")
async def async_check_pause(self):
"""
异步检查是否需要暂停(轮询方式)
如果处于暂停状态,会阻塞当前协程直到恢复或停止
应该在执行循环的关键点调用此方法
Returns:
bool: 如果返回True表示应该继续执行False表示应该停止
"""
# 使用轮询检查,避免异步事件问题
while True:
# 检查停止标志
if self._should_stop:
print("🛑 [DEBUG] async_check_pause: 检测到停止信号")
return False
# 检查暂停状态
if self._should_pause:
# 处于暂停状态,等待恢复
print("⏸️ [DEBUG] async_check_pause: 检测到暂停,等待恢复...")
await asyncio.sleep(0.1) # 短暂睡眠避免占用CPU
# 如果恢复,继续执行
if not self._should_pause:
print("▶️ [DEBUG] async_check_pause: 从暂停中恢复!")
continue
# 如果停止了,返回
if self._should_stop:
return False
# 继续等待
continue
# 既没有停止也没有暂停,可以继续执行
return True
def is_paused(self) -> bool:
"""检查是否处于暂停状态"""
with self._lock:
return self._status == ExecutionStatus.PAUSED
def is_running(self) -> bool:
"""检查是否正在运行"""
with self._lock:
return self._status == ExecutionStatus.RUNNING
def is_stopped(self) -> bool:
"""检查是否已停止"""
with self._lock:
return self._status == ExecutionStatus.STOPPED
# 全局单例实例
execution_state_manager = ExecutionStateManager()

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
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
# 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(input_object_list[:-1]), input_object_list[-1]]
[", ".join(filtered_input_list[:-1]), filtered_input_list[-1]]
)
if len(input_object_list) > 1
else input_object_list[0]
if len(filtered_input_list) > 1
else filtered_input_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,7 +1,10 @@
Flask==3.0.2
openai==0.28.1
openai==2.8.1
PyYAML==6.0.1
termcolor==2.4.0
groq==0.4.2
mistralai==0.1.6
socksio==1.0.0
flask-socketio==5.3.6
python-socketio==5.11.0
simple-websocket==1.0.0
eventlet==0.40.4

File diff suppressed because it is too large Load Diff

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';

View File

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

View File

@@ -1 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 886 B

View File

@@ -1 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 603 B

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,204 +0,0 @@
<script setup lang="ts">
import SvgIcon from '@/components/SvgIcon/index.vue'
import { getAgentMapIcon } from '@/layout/components/config.ts'
import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/utils.ts'
import { type IRawStepTask, useAgentsStore } from '@/stores'
import { computed } from 'vue'
import { AnchorLocations } from '@jsplumb/browser-ui'
import MultiLineTooltip from '@/components/MultiLineTooltip/index.vue'
import Bg from './Bg.vue'
const emit = defineEmits<{
(el: 'resetAgentRepoLine'): void
(el: 'setCurrentTask', task: IRawStepTask): void
}>()
const jsplumb = new Jsplumb('task-syllabus')
const handleScroll = () => {
emit('resetAgentRepoLine')
}
const agentsStore = useAgentsStore()
const collaborationProcess = computed(() => {
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
})
function handleCurrentTask(task: IRawStepTask, transparent: boolean): ConnectArg[] {
// 创建当前流程与产出的连线
const arr: ConnectArg[] = [
{
sourceId: `task-syllabus-flow-${task.Id}`,
targetId: `task-syllabus-output-object-${task.Id}`,
anchor: [AnchorLocations.Right, AnchorLocations.Left],
config: {
transparent,
},
},
]
// 创建当前产出与流程的连线
task.InputObject_List?.forEach((item) => {
const id = collaborationProcess.value.find((i) => i.OutputObject === item)?.Id
if (id) {
arr.push({
sourceId: `task-syllabus-output-object-${id}`,
targetId: `task-syllabus-flow-${task.Id}`,
anchor: [AnchorLocations.Left, AnchorLocations.Right],
config: {
type: 'output',
transparent,
},
})
}
})
return arr
}
function changeTask(task?: IRawStepTask, isEmit?: boolean) {
jsplumb.reset()
const arr: ConnectArg[] = []
agentsStore.agentRawPlan.data?.['Collaboration Process']?.forEach((item) => {
arr.push(...handleCurrentTask(item, item.Id !== task?.Id))
})
jsplumb.connects(arr)
if (isEmit && task) {
emit('setCurrentTask', task)
}
}
function clear() {
jsplumb.reset()
}
defineExpose({
changeTask,
clear,
})
</script>
<template>
<div class="h-full flex flex-col">
<div class="text-[18px] font-bold mb-[18px]">任务大纲</div>
<div
v-loading="agentsStore.agentRawPlan.loading"
class="flex-1 w-full overflow-y-auto relative"
@scroll="handleScroll"
>
<div v-show="collaborationProcess.length > 0" class="w-full relative min-h-full" id="task-syllabus">
<Bg />
<div class="w-full flex items-center gap-[14%] mb-[35px]">
<div class="flex-1 flex justify-center">
<div
class="card-item w-[45%] h-[41px] flex justify-center relative z-99 items-center rounded-[20px] bg-[var(--color-bg-tertiary)]"
>
流程
</div>
</div>
<div class="flex-1 flex justify-center">
<div
class="card-item w-[45%] h-[41px] flex justify-center relative z-99 items-center rounded-[20px] bg-[var(--color-bg-tertiary)]"
>
产物
</div>
</div>
</div>
<div
v-for="item in collaborationProcess"
:key="item.Id"
class="card-item w-full flex items-center gap-[14%]"
>
<!-- 流程卡片 -->
<el-card
class="w-[43%] overflow-y-auto relative z-99 task-syllabus-flow-card"
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
shadow="hover"
:id="`task-syllabus-flow-${item.Id}`"
@click="changeTask(item, true)"
>
<MultiLineTooltip placement="right" :text="item.StepName" :lines="2">
<div class="text-[18px] font-bold text-center">
{{ item.StepName }}
</div>
</MultiLineTooltip>
<div class="h-[1px] w-full bg-[#494B51] my-[8px]"></div>
<MultiLineTooltip placement="right" :text="item.StepName" :lines="3">
<div
class="text-[14px] text-[var(--color-text-secondary)]"
:title="item.TaskContent"
>
{{ item.TaskContent }}
</div>
</MultiLineTooltip>
<div class="h-[1px] w-full bg-[#494B51] my-[8px]"></div>
<div
class="flex items-center gap-2 overflow-y-auto flex-wrap relative w-full max-h-[72px]"
>
<!-- 连接到智能体库的连接点 -->
<div
class="absolute left-[-10px] top-1/2 transform -translate-y-1/2"
:id="`task-syllabus-flow-agents-${item.Id}`"
></div>
<el-tooltip
v-for="agentSelection in item.AgentSelection"
:key="agentSelection"
effect="light"
placement="right"
>
<template #content>
<div class="w-[150px]">
<div class="text-[18px] font-bold">{{ agentSelection }}</div>
<div class="h-[1px] w-full bg-[#494B51] my-[8px]"></div>
<div>
{{
item.TaskProcess.find((i) => i.AgentName === agentSelection)?.Description
}}
</div>
</div>
</template>
<div
class="w-[31px] h-[31px] rounded-full flex items-center justify-center"
:style="{ background: getAgentMapIcon(agentSelection).color }"
>
<svg-icon
:icon-class="getAgentMapIcon(agentSelection).icon"
color="var(--color-text)"
size="24px"
/>
</div>
</el-tooltip>
</div>
</el-card>
<!-- 产物卡片 -->
<el-card
class="w-[43%] relative"
shadow="hover"
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
:id="`task-syllabus-output-object-${item.Id}`"
>
<div class="text-[18px] font-bold text-center">{{ item.OutputObject }}</div>
</el-card>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.task-syllabus-flow-card {
:deep(.el-card__body) {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: auto;
}
}
</style>

View File

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

View File

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

View File

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

View File

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

View File

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

1
frontend/.env Normal file
View File

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

View File

@@ -16,9 +16,16 @@ declare module 'vue' {
ElCard: typeof import('element-plus/es')['ElCard']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
MultiLineTooltip: typeof import('./src/components/MultiLineTooltip/index.vue')['default']
Notification: typeof import('./src/components/Notification/Notification.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']

View File

@@ -2,7 +2,7 @@
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/logo.jpg">
<link rel="icon" href="/logo.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>多智能体协同平台</title>
</head>

View File

@@ -17,8 +17,13 @@
"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",
@@ -27,6 +32,7 @@
"markdown-it": "^14.1.0",
"pinia": "^3.0.3",
"qs": "^6.14.0",
"socket.io-client": "^4.8.3",
"uuid": "^13.0.0",
"vue": "^3.5.22",
"vue-router": "^4.6.3"

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
"centerTitle": "多智能体协同平台",
"taskPromptWords": [
"如何快速筛选慢性肾脏病药物潜在受试者?",
"如何补充丹芍活血胶囊不良反应数据?",
"如何补充\"丹芍活血胶囊\"不良反应数据?",
"如何快速研发用于战场失血性休克的药物?",
"二维材料的光电性质受哪些关键因素影响?",
"如何通过AI模拟的方法分析材料的微观结构?",
@@ -15,5 +15,7 @@
],
"agentRepository": {
"storageVersionIdentifier": "1"
}
},
"dev": true,
"apiBaseUrl": "http://localhost:8000"
}

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 79 KiB

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

View File

@@ -1,15 +1,9 @@
<script setup lang="ts">
import { onMounted } from 'vue'
import Layout from './layout/index.vue'
onMounted(() => {
document.documentElement.classList.add('dark')
})
</script>
<template>
<layout />
<Layout />
</template>
<style lang="scss">
@@ -29,11 +23,12 @@ onMounted(() => {
.el-card {
border-radius: 8px;
background: var(--color-bg-tertiary);
border: 2px solid var(--color-bg-tertiary);
border: 2px solid var(--color-card-border);
box-shadow: var(--color-card-border-hover);
&:hover {
background: #171B22;
box-shadow: none !important;
background: var(--color-bg-content-hover);
box-shadow: none;
transition: background-color 0.3s ease-in-out;
}
@@ -50,14 +45,16 @@ onMounted(() => {
.active-card {
border: 2px solid transparent;
$bg: var(--el-input-bg-color, var(--el-fill-color-blank));
background:
linear-gradient(var(--color-bg-tertiary), var(--color-bg-tertiary)) padding-box,
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 {
@@ -71,14 +68,13 @@ onMounted(() => {
}
}
/* 2. 为jsPlumb连线绑定动画作用于SVG的path元素 */
/* jtk-connector是jsPlumb连线的默认SVG类path是实际的线条元素 */
.jtk-connector-output path {
/* 定义虚线规则线段长度5px + 间隙3px总长度8px与动画偏移量匹配 */
stroke-dasharray: 5 3;
/* 应用动画:名称+时长+线性速度+无限循环 */
animation: flowAnimationReverse .5s linear infinite;
animation: flowAnimationReverse 0.5s linear infinite;
/* 可选:设置线条基础样式(颜色、宽度) */
stroke-width: 2;
}
@@ -89,7 +85,7 @@ onMounted(() => {
/* 定义虚线规则线段长度5px + 间隙3px总长度8px与动画偏移量匹配 */
stroke-dasharray: 5 3;
/* 应用动画:名称+时长+线性速度+无限循环 */
animation: flowAnimationReverse .5s linear infinite;
animation: flowAnimationReverse 0.5s linear infinite;
/* 可选:设置线条基础样式(颜色、宽度) */
stroke-width: 2;
}

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

@@ -0,0 +1,906 @@
import request from '@/utils/request'
import websocket from '@/utils/websocket'
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[]
}
/**
* SSE 流式事件类型
*/
export type StreamingEvent =
| {
type: 'step_start'
step_index: number
total_steps: number
step_name: string
task_description?: string
}
| {
type: 'action_complete'
step_index: number
step_name: string
action_index: number
total_actions: number
completed_actions: number
action_result: ActionHistory
batch_info?: {
batch_index: number
batch_size: number
is_parallel: boolean
}
}
| {
type: 'step_complete'
step_index: number
step_name: string
step_log_node: any
object_log_node: any
}
| {
type: 'execution_complete'
total_steps: number
}
| {
type: 'error'
message: string
}
export interface IFillAgentSelectionRequest {
goal: string
stepTask: IApiStepTask
agents: string[]
}
class Api {
// 默认使用WebSocket
private useWebSocketDefault = true
setAgents = (data: Pick<Agent, 'Name' | 'Profile' | 'apiUrl' | 'apiKey' | 'apiModel'>[], useWebSocket: boolean = this.useWebSocketDefault) => {
// 如果启用WebSocket且已连接使用WebSocket
if (useWebSocket && websocket.connected) {
return websocket.send('set_agents', data)
}
// 否则使用REST API
return request({
url: '/setAgents',
data,
method: 'POST',
})
}
generateBasePlan = (data: {
goal: string
inputs: string[]
apiUrl?: string
apiKey?: string
apiModel?: string
useWebSocket?: boolean
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
}) => {
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
// 如果启用WebSocket且已连接使用WebSocket
if (useWs && websocket.connected) {
return websocket.send('generate_base_plan', {
'General Goal': data.goal,
'Initial Input Object': data.inputs,
apiUrl: data.apiUrl,
apiKey: data.apiKey,
apiModel: data.apiModel,
}, undefined, data.onProgress)
}
// 否则使用REST API
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,
})),
})),
},
},
})
}
/**
* 优化版流式执行计划(支持动态追加步骤)
* 步骤级流式 + 动作级智能并行 + 动态追加步骤
*/
executePlanOptimized = (
plan: IRawPlanResponse,
onMessage: (event: StreamingEvent) => void,
onError?: (error: Error) => void,
onComplete?: () => void,
useWebSocket?: boolean,
existingKeyObjects?: Record<string, any>,
enableDynamic?: boolean,
onExecutionStarted?: (executionId: string) => void,
executionId?: string,
restartFromStepIndex?: number, // 新增:从指定步骤重新执行的索引
rehearsalLog?: any[], // 新增:传递截断后的 RehearsalLog
) => {
const useWs = useWebSocket !== undefined ? useWebSocket : this.useWebSocketDefault
const data = {
RehearsalLog: rehearsalLog || [], // ✅ 使用传递的 RehearsalLog
num_StepToRun: null,
existingKeyObjects: existingKeyObjects || {},
enable_dynamic: enableDynamic || false,
execution_id: executionId || null,
restart_from_step_index: restartFromStepIndex ?? 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,
})),
})),
},
}
// 如果启用WebSocket且已连接使用WebSocket
if (useWs && websocket.connected) {
websocket.subscribe(
'execute_plan_optimized',
data,
// onProgress
(progressData) => {
try {
let event: StreamingEvent
// 处理不同类型的progress数据
if (typeof progressData === 'string') {
event = JSON.parse(progressData)
} else {
event = progressData as StreamingEvent
}
// 处理特殊事件类型
if (event && typeof event === 'object') {
// 检查是否是execution_started事件
if ('status' in event && event.status === 'execution_started') {
if ('execution_id' in event && onExecutionStarted) {
onExecutionStarted(event.execution_id as string)
}
return
}
}
onMessage(event)
} catch (e) {
// Failed to parse WebSocket data
}
},
// onComplete
() => {
onComplete?.()
},
// onError
(error) => {
onError?.(error)
}
)
return
}
// 否则使用原有的SSE方式
fetch('/api/executePlanOptimized', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(async (response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const reader = response.body?.getReader()
const decoder = new TextDecoder()
if (!reader) {
throw new Error('Response body is null')
}
let buffer = ''
while (true) {
const { done, value } = await reader.read()
if (done) {
onComplete?.()
break
}
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n')
buffer = lines.pop() || ''
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6)
try {
const event = JSON.parse(data)
onMessage(event)
} catch (e) {
// Failed to parse SSE data
}
}
}
}
})
.catch((error) => {
onError?.(error)
})
}
/**
* 分支任务大纲
*/
branchPlanOutline = (data: {
branch_Number: number
Modification_Requirement: string
Existing_Steps: IRawStepTask[]
Baseline_Completion: number
initialInputs: string[]
goal: string
useWebSocket?: boolean
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
}) => {
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
// 如果启用WebSocket且已连接使用WebSocket
if (useWs && websocket.connected) {
return websocket.send('branch_plan_outline', {
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,
}, undefined, data.onProgress)
}
// 否则使用REST API
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: BranchAction[]
Baseline_Completion: number
stepTaskExisting: any
goal: string
useWebSocket?: boolean
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
}) => {
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
// 如果启用WebSocket且已连接使用WebSocket
if (useWs && websocket.connected) {
return websocket.send('branch_task_process', {
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,
}, undefined, data.onProgress)
}
// 否则使用REST API
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
useWebSocket?: boolean
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
}): Promise<IRawStepTask> => {
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
let response: any
// 如果启用WebSocket且已连接使用WebSocket
if (useWs && websocket.connected) {
response = await websocket.send('fill_step_task', {
'General Goal': data.goal,
stepTask: data.stepTask,
}, undefined, data.onProgress)
} else {
// 否则使用REST API
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,
},
})
}
const vec2Hsl = (color: number[]): string => {
const [h, s, l] = color
return `hsl(${h}, ${s}%, ${l}%)`
}
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[]
useWebSocket?: boolean
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
}): Promise<IApiStepTask> => {
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
let response: any
// 如果启用WebSocket且已连接使用WebSocket
if (useWs && websocket.connected) {
response = await websocket.send('fill_step_task_process', {
'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,
},
}, undefined, data.onProgress)
} else {
// 否则使用REST API
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,
},
},
})
}
const vec2Hsl = (color: number[]): string => {
const [h, s, l] = color
return `hsl(${h}, ${s}%, ${l}%)`
}
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),
},
}
}
}
const process = (response.TaskProcess || []).map((action: any) => ({
id: action.ID,
type: action.ActionType,
agent: action.AgentName,
description: action.Description,
inputs: action.ImportantInput,
}))
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
useWebSocket?: boolean
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
}): Promise<Record<string, Record<string, { reason: string; score: number }>>> => {
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
let response: Record<string, Record<string, { Reason: string; Score: number }>>
// 如果启用WebSocket且已连接使用WebSocket
if (useWs && websocket.connected) {
response = await websocket.send('agent_select_modify_init', {
'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,
},
}, undefined, data.onProgress)
} else {
// 否则使用REST API
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,
},
},
})
}
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
for (const [aspect, agents] of Object.entries(response)) {
for (const [agentName, scoreInfo] of Object.entries(agents)) {
if (!transformedData[agentName]) {
transformedData[agentName] = {}
}
transformedData[agentName][aspect] = {
reason: scoreInfo.Reason,
score: scoreInfo.Score,
}
}
}
return transformedData
}
/**
* 添加新的评估维度
*/
agentSelectModifyAddAspect = async (data: {
aspectList: string[]
useWebSocket?: boolean
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
}): Promise<{
aspectName: string
agentScores: Record<string, { score: number; reason: string }>
}> => {
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
let response: Record<string, Record<string, { Reason: string; Score: number }>>
// 如果启用WebSocket且已连接使用WebSocket
if (useWs && websocket.connected) {
response = await websocket.send('agent_select_modify_add_aspect', {
aspectList: data.aspectList,
}, undefined, data.onProgress)
} else {
// 否则使用REST API
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')
}
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开发阶段使用====================
*为每个智能体评分
*/
mockAgentSelectModifyInit = async (): Promise<
Record<string, Record<string, { reason: string; score: number }>>
> => {
const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyInit()
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
for (const [aspect, agents] of Object.entries(response)) {
for (const [agentName, scoreInfo] of Object.entries(agents)) {
if (!transformedData[agentName]) {
transformedData[agentName] = {}
}
transformedData[agentName][aspect] = {
reason: scoreInfo.Reason,
score: scoreInfo.Score,
}
}
}
return transformedData
}
mockAgentSelectModifyAddAspect = async (data: {
aspectList: string[]
}): Promise<{
aspectName: string
agentScores: Record<string, { score: number; reason: string }>
}> => {
const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyAddAspect(
data.aspectList,
)
const newAspect = data.aspectList[data.aspectList.length - 1]
if (!newAspect) {
throw new Error('aspectList is empty')
}
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,
}
}
mockFillStepTaskTaskProcess = async (data: {
goal: string
stepTask: IApiStepTask
agents: string[]
}): Promise<IApiStepTask> => {
const response: RawAgentTaskProcessResponse = await mockBackendFillAgentTaskProcess(
data.goal,
data.stepTask,
data.agents,
)
const vec2Hsl = (color: number[]): string => {
const [h, s, l] = color
return `hsl(${h}, ${s}%, ${l}%)`
}
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),
},
}
}
}
const process = (response.TaskProcess || []).map((action) => ({
id: action.ID,
type: action.ActionType,
agent: action.AgentName,
description: action.Description,
inputs: action.ImportantInput,
}))
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,
}
}
mockBranchPlanOutline = async (data: {
branch_Number: number
Modification_Requirement: string
Existing_Steps: IRawStepTask[]
Baseline_Completion: number
initialInputs: string[]
goal: string
}): Promise<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
}
mockFillStepTask = async (data: { goal: string; stepTask: any }): Promise<any> => {
const response = await mockFillStepTaskAPI({
General_Goal: data.goal,
stepTask: data.stepTask,
})
return response
}
mockBranchTaskProcess = async (data: {
branch_Number: number
Modification_Requirement: string
Existing_Steps: BranchAction[]
Baseline_Completion: number
stepTaskExisting: any
goal: string
}): Promise<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
}
/**
* 向正在执行的任务追加新步骤
* @param executionId 执行ID
* @param newSteps 新步骤列表
* @returns 追加的步骤数量
*/
addStepsToExecution = async (executionId: string, newSteps: IRawStepTask[]): Promise<number> => {
if (!websocket.connected) {
throw new Error('WebSocket未连接')
}
const response = await websocket.send('add_steps_to_execution', {
execution_id: executionId,
new_steps: newSteps.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,
})),
})),
}) as { added_count: number }
return response?.added_count || 0
}
}
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="1769048650684" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6190" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M874.058005 149.941995a510.06838 510.06838 0 1 0 109.740156 162.738976 511.396369 511.396369 0 0 0-109.740156-162.738976z m66.278708 362.178731A428.336713 428.336713 0 1 1 512 83.663287a428.698892 428.698892 0 0 1 428.336713 428.336713z" fill="#36404f" p-id="6191"></path><path d="M417.954256 281.533601a41.046923 41.046923 0 0 0-41.77128 40.201839v385.116718a41.892007 41.892007 0 0 0 83.663287 0v-385.116718a41.167649 41.167649 0 0 0-41.892007-40.201839zM606.045744 281.533601a41.046923 41.046923 0 0 0-41.77128 40.201839v385.116718a41.892007 41.892007 0 0 0 83.663287 0v-385.116718a41.167649 41.167649 0 0 0-41.892007-40.201839z" fill="#36404f" p-id="6192"></path></svg>

After

Width:  |  Height:  |  Size: 1004 B

View File

@@ -0,0 +1,5 @@
<?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: 890 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

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

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

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

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,6 @@
<?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: 610 B

View File

Before

Width:  |  Height:  |  Size: 516 B

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

Before

Width:  |  Height:  |  Size: 783 B

After

Width:  |  Height:  |  Size: 783 B

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

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

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 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="1768992484327" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="10530" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M512 853.333333c-187.733333 0-341.333333-153.6-341.333333-341.333333s153.6-341.333333 341.333333-341.333333
341.333333 153.6 341.333333 341.333333-153.6 341.333333-341.333333 341.333333z m0-85.333333c140.8 0 256-115.2 256-256s-115.2-256-256-256-256
115.2-256 256 115.2 256 256 256z m-85.333333-341.333333h170.666666v170.666666h-170.666666v-170.666666z" fill="#ffffff" p-id="10531"></path></svg>

After

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

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.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="1769048534610" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4890" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M527.984 1001.6a480 480 0 1 1 480-480 480.384 480.384 0 0 1-480 480z m0-883.696A403.696 403.696 0 1 0 931.68 521.6 403.84 403.84 0 0 0 527.984 117.904z" fill="#36404f" p-id="4891"></path><path d="M473.136 729.6a47.088 47.088 0 0 1-18.112-3.888 38.768 38.768 0 0 1-23.056-34.992V384.384a39.632 39.632 0 0 1 23.056-34.992 46.016 46.016 0 0 1 43.632 3.888l211.568 153.168a38.72 38.72 0 0 1 16.464 31.104 37.632 37.632 0 0 1-16.464 31.104l-211.568 153.168a44.56 44.56 0 0 1-25.52 7.776z m41.168-266.704v149.296l102.896-74.64z" fill="#36404f" p-id="4892"></path></svg>

After

Width:  |  Height:  |  Size: 894 B

View File

@@ -1,5 +1,11 @@
<template>
<el-tooltip :disabled="!isOverflow" effect="light" placement="top" :content="text">
<el-tooltip
:disabled="!isOverflow"
effect="light"
placement="top"
:content="text"
popper-class="multi-line-tooltip-popper"
>
<div
ref="containerRef"
class="multi-line-ellipsis"
@@ -28,7 +34,7 @@ interface Props {
const props = withDefaults(defineProps<Props>(), {
text: '',
lines: 3,
maxWidth: '100%',
maxWidth: '100%'
})
const isOverflow = ref(false)
@@ -45,8 +51,8 @@ const containerStyle = computed(
overflow: 'hidden',
textOverflow: 'ellipsis',
lineHeight: '1.5',
wordBreak: 'break-all',
}) as HTMLAttributes['style'],
wordBreak: 'break-all'
} as HTMLAttributes['style'])
)
//
@@ -91,3 +97,9 @@ onMounted(() => {
white-space: normal;
}
</style>
<style>
.multi-line-tooltip-popper {
max-width: 400px;
}
</style>

View File

@@ -0,0 +1,260 @@
<template>
<teleport to="body">
<div class="notification-container">
<transition-group
name="notification"
tag="div"
class="notification-list"
>
<div
v-for="notification in notifications"
:key="notification.id"
:class="[
'notification-item',
`notification-${notification.type || 'info'}`
]"
:style="{ zIndex: notification.zIndex || 1000 }"
>
<div class="notification-content">
<div class="notification-icon">
<component :is="getIcon(notification.type)" />
</div>
<div class="notification-message">
<div class="notification-title">{{ notification.title }}</div>
<div v-if="notification.detailTitle" class="notification-detail-title">
{{ notification.detailTitle }}
</div>
<div v-if="notification.detailMessage" class="notification-detail-desc">
{{ notification.detailMessage }}
</div>
<div v-else-if="notification.message" class="notification-desc">
{{ notification.message }}
</div>
</div>
<div class="notification-close" @click="close(notification.id)">
<Close />
</div>
</div>
<div v-if="notification.showProgress" class="notification-progress">
<div
class="progress-bar"
:style="{ width: `${notification.progress || 0}%` }"
></div>
</div>
</div>
</transition-group>
</div>
</teleport>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import {
Close,
SuccessFilled as IconSuccess,
WarningFilled as IconWarning,
CircleCloseFilled,
InfoFilled
} from '@element-plus/icons-vue'
import type { NotificationItem } from '@/composables/useNotification'
const props = defineProps<{
notifications: NotificationItem[]
}>()
const emit = defineEmits<{
close: [id: string]
}>()
const close = (id: string) => {
emit('close', id)
}
const getIcon = (type?: string) => {
switch (type) {
case 'success':
return IconSuccess
case 'warning':
return IconWarning
case 'error':
return IconWarning
default:
return InfoFilled
}
}
</script>
<style scoped>
.notification-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
pointer-events: none;
}
.notification-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.notification-item {
pointer-events: auto;
min-width: 300px;
max-width: 450px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
overflow: hidden;
border-left: 4px solid #409eff;
}
.notification-success {
border-left-color: #67c23a;
}
.notification-warning {
border-left-color: #e6a23c;
}
.notification-error {
border-left-color: #f56c6c;
}
.notification-content {
display: flex;
align-items: flex-start;
padding: 12px 16px;
gap: 12px;
}
.notification-icon {
flex-shrink: 0;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.notification-icon .success {
color: #67c23a;
}
.notification-icon .warning {
color: #e6a23c;
}
.notification-icon .error {
color: #f56c6c;
}
.notification-icon .info {
color: #409eff;
}
.notification-message {
flex: 1;
min-width: 0;
}
.notification-title {
font-size: 14px;
font-weight: 500;
color: #303133;
margin-bottom: 4px;
}
.notification-detail-title {
font-size: 13px;
font-weight: 500;
color: #409eff;
margin-top: 4px;
margin-bottom: 2px;
}
.notification-detail-desc {
font-size: 12px;
color: #909399;
line-height: 1.4;
}
.notification-desc {
font-size: 13px;
color: #606266;
line-height: 1.5;
}
.notification-close {
flex-shrink: 0;
width: 20px;
height: 20px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: #909399;
transition: color 0.2s;
}
.notification-close:hover {
color: #606266;
}
.notification-progress {
height: 2px;
background: #f0f2f5;
overflow: hidden;
}
.progress-bar {
height: 100%;
background: #409eff;
transition: width 0.3s ease;
}
/* 进入动画 */
.notification-enter-active {
animation: slideInRight 0.3s ease-out;
}
/* 离开动画 */
.notification-leave-active {
animation: slideOutRight 0.3s ease-in;
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideOutRight {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(100%);
}
}
/* 列表项移动动画 */
.notification-move,
.notification-enter-active,
.notification-leave-active {
transition: all 0.3s ease;
}
.notification-leave-active {
position: absolute;
width: 100%;
}
</style>

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed } from 'vue'
const props = withDefaults(
defineProps<{
iconClass: string

View File

@@ -0,0 +1,163 @@
import { ref } from 'vue'
export interface NotificationItem {
id: string
title: string
message?: string
type?: 'success' | 'warning' | 'info' | 'error'
duration?: number
showProgress?: boolean
progress?: number
zIndex?: number
onClose?: () => void
// 详细进度信息
detailTitle?: string
detailMessage?: string
}
const notifications = ref<NotificationItem[]>([])
let notificationIdCounter = 0
let zIndexCounter = 1000
export function useNotification() {
const addNotification = (notification: Omit<NotificationItem, 'id' | 'zIndex'>) => {
const id = `notification-${notificationIdCounter++}`
const newNotification: NotificationItem = {
...notification,
id,
zIndex: ++zIndexCounter,
}
notifications.value.push(newNotification)
// 自动关闭
if (notification.duration && notification.duration > 0) {
setTimeout(() => {
removeNotification(id)
}, notification.duration)
}
return id
}
const removeNotification = (id: string) => {
const index = notifications.value.findIndex((n) => n.id === id)
if (index !== -1) {
const notification = notifications.value[index]
notifications.value.splice(index, 1)
notification.onClose?.()
}
}
const success = (title: string, message?: string, options?: Partial<NotificationItem>) => {
return addNotification({
title,
message,
type: 'success',
duration: 3000,
...options,
})
}
const warning = (title: string, message?: string, options?: Partial<NotificationItem>) => {
return addNotification({
title,
message,
type: 'warning',
duration: 3000,
...options,
})
}
const info = (title: string, message?: string, options?: Partial<NotificationItem>) => {
return addNotification({
title,
message,
type: 'info',
duration: 3000,
...options,
})
}
const error = (title: string, message?: string, options?: Partial<NotificationItem>) => {
return addNotification({
title,
message,
type: 'error',
duration: 5000,
...options,
})
}
const progress = (
title: string,
current: number,
total: number,
options?: Partial<NotificationItem>,
) => {
const progressPercent = Math.round((current / total) * 100)
return addNotification({
title,
message: `${current}/${total}`,
type: 'info',
showProgress: true,
progress: progressPercent,
duration: 0, // 不自动关闭
...options,
})
}
const updateProgress = (id: string, current: number, total: number) => {
const notification = notifications.value.find((n) => n.id === id)
if (notification) {
notification.progress = Math.round((current / total) * 100)
notification.message = `${current}/${total}`
}
}
const updateProgressDetail = (
id: string,
detailTitle: string,
detailMessage: string,
current?: number,
total?: number
) => {
const notification = notifications.value.find((n) => n.id === id)
if (notification) {
notification.detailTitle = detailTitle
notification.detailMessage = detailMessage
if (current !== undefined && total !== undefined) {
notification.progress = Math.round((current / total) * 100)
notification.message = `${current}/${total}`
}
}
}
// 更新通知的主标题
const updateNotificationTitle = (id: string, title: string) => {
const notification = notifications.value.find((n) => n.id === id)
if (notification) {
notification.title = title
}
}
const clear = () => {
notifications.value.forEach((n) => n.onClose?.())
notifications.value = []
}
return {
notifications,
addNotification,
removeNotification,
success,
warning,
info,
error,
progress,
updateProgress,
updateProgressDetail,
updateNotificationTitle,
clear,
}
}

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