Compare commits

...

2 Commits

Author SHA1 Message Date
liailing1026
c00c0072b8 feat 2025-12-11 14:05:08 +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
59 changed files with 16222 additions and 376 deletions

View File

@ -0,0 +1,24 @@
{
"permissions": {
"allow": [
"Read(//Users/zhaoweijie/Desktop/agent/AgentCoord/**)",
"Bash(python3:*)",
"Bash(source:*)",
"Bash(pip install:*)",
"Bash(python:*)",
"Bash(tree:*)",
"Bash(export FAST_DESIGN_MODE=True:*)",
"Bash(echo:*)",
"Bash(chmod:*)",
"Bash(lsof:*)",
"Bash(curl:*)",
"Bash(xargs kill:*)",
"Bash(pip:*)",
"WebSearch",
"WebFetch(domain:pypi.org)",
"Bash(cp:*)"
],
"deny": [],
"ask": []
}
}

View File

@ -1,24 +1,30 @@
import asyncio
import openai
from openai import OpenAI, AsyncOpenAI
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
# 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"
)
@ -71,14 +77,14 @@ def LLM_Completion(
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 +98,9 @@ async def _achat_completion_stream_groq(messages: list[dict]) -> str:
if attempt < max_attempts - 1: # i is zero indexed
continue
else:
raise "failed"
raise Exception("failed")
full_reply_content = stream.choices[0].message.content
full_reply_content = response.choices[0].message.content
print(colored(full_reply_content, "blue", "on_white"), end="")
print()
return full_reply_content
@ -103,14 +109,14 @@ async def _achat_completion_stream_groq(messages: list[dict]) -> str:
async def _achat_completion_stream_mixtral(messages: list[dict]) -> str:
from mistralai.client import MistralClient
from mistralai.models.chat_completion import ChatMessage
client = MistralClient(api_key=MISTRAL_API_KEY)
mistral_client = MistralClient(api_key=MISTRAL_API_KEY)
# client=AsyncGroq(api_key=GROQ_API_KEY)
max_attempts = 5
for attempt in range(max_attempts):
try:
messages[len(messages) - 1]["role"] = "user"
stream = client.chat(
stream = mistral_client.chat(
messages=[
ChatMessage(
role=message["role"], content=message["content"]
@ -119,14 +125,13 @@ async def _achat_completion_stream_mixtral(messages: list[dict]) -> str:
],
# model = "mistral-small-latest",
model="open-mixtral-8x7b",
# response_format={"type": "json_object"},
)
break # If the operation is successful, break the loop
except Exception:
if attempt < max_attempts - 1: # i is zero indexed
continue
else:
raise "failed"
raise Exception("failed")
full_reply_content = stream.choices[0].message.content
print(colored(full_reply_content, "blue", "on_white"), end="")
@ -135,15 +140,11 @@ async def _achat_completion_stream_mixtral(messages: list[dict]) -> str:
async def _achat_completion_stream_gpt35(messages: list[dict]) -> str:
openai.api_key = OPENAI_API_KEY
openai.api_base = OPENAI_API_BASE
response = await openai.ChatCompletion.acreate(
response = await async_client.chat.completions.create(
messages=messages,
max_tokens=4096,
n=1,
stop=None,
temperature=0.3,
timeout=3,
timeout=30,
model="gpt-3.5-turbo-16k",
stream=True,
)
@ -154,40 +155,33 @@ async def _achat_completion_stream_gpt35(messages: list[dict]) -> str:
# iterate through the stream of events
async for chunk in response:
collected_chunks.append(chunk) # save the event response
choices = chunk["choices"]
choices = chunk.choices
if len(choices) > 0:
chunk_message = chunk["choices"][0].get(
"delta", {}
) # extract the message
chunk_message = chunk.choices[0].delta
collected_messages.append(chunk_message) # save the message
if "content" in chunk_message:
if chunk_message.content:
print(
colored(chunk_message["content"], "blue", "on_white"),
colored(chunk_message.content, "blue", "on_white"),
end="",
)
print()
full_reply_content = "".join(
[m.get("content", "") for m in collected_messages]
[m.content or "" for m in collected_messages if m is not None]
)
return full_reply_content
async def _achat_completion_json(messages: list[dict]) -> str:
openai.api_key = OPENAI_API_KEY
openai.api_base = OPENAI_API_BASE
max_attempts = 5
for attempt in range(max_attempts):
try:
stream = 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=30,
model=MODEL,
response_format={"type": "json_object"},
)
@ -196,60 +190,62 @@ async def _achat_completion_json(messages: list[dict]) -> str:
if attempt < max_attempts - 1: # i is zero indexed
continue
else:
raise "failed"
raise Exception("failed")
full_reply_content = stream.choices[0].message.content
full_reply_content = response.choices[0].message.content
print(colored(full_reply_content, "blue", "on_white"), end="")
print()
return full_reply_content
async def _achat_completion_stream(messages: list[dict]) -> str:
openai.api_key = OPENAI_API_KEY
openai.api_base = OPENAI_API_BASE
response = await openai.ChatCompletion.acreate(
**_cons_kwargs(messages), stream=True
)
try:
response = await async_client.chat.completions.create(
**_cons_kwargs(messages), stream=True
)
# create variables to collect the stream of chunks
collected_chunks = []
collected_messages = []
# iterate through the stream of events
async for chunk in response:
collected_chunks.append(chunk) # save the event response
choices = chunk["choices"]
if len(choices) > 0:
chunk_message = chunk["choices"][0].get(
"delta", {}
) # extract the message
collected_messages.append(chunk_message) # save the message
if "content" in chunk_message:
print(
colored(chunk_message["content"], "blue", "on_white"),
end="",
)
print()
# create variables to collect the stream of chunks
collected_chunks = []
collected_messages = []
# iterate through the stream of events
async for chunk in response:
collected_chunks.append(chunk) # save the event response
choices = chunk.choices
if len(choices) > 0:
chunk_message = chunk.choices[0].delta
collected_messages.append(chunk_message) # save the message
if chunk_message.content:
print(
colored(chunk_message.content, "blue", "on_white"),
end="",
)
print()
full_reply_content = "".join(
[m.get("content", "") for m in collected_messages]
)
return full_reply_content
full_reply_content = "".join(
[m.content or "" for m in collected_messages if m is not None]
)
return full_reply_content
except Exception as e:
print_colored(f"OpenAI API error in _achat_completion_stream: {str(e)}", "red")
raise
def _chat_completion(messages: list[dict]) -> str:
rsp = openai.ChatCompletion.create(**_cons_kwargs(messages))
content = rsp["choices"][0]["message"]["content"]
return content
try:
rsp = client.chat.completions.create(**_cons_kwargs(messages))
content = rsp.choices[0].message.content
return content
except Exception as e:
print_colored(f"OpenAI API error in _chat_completion: {str(e)}", "red")
raise
def _cons_kwargs(messages: list[dict]) -> dict:
kwargs = {
"messages": messages,
"max_tokens": 4096,
"n": 1,
"stop": None,
"temperature": 0.5,
"timeout": 3,
"max_tokens": 2000,
"temperature": 0.3,
"timeout": 15,
}
kwargs_mode = {"model": MODEL}
kwargs.update(kwargs_mode)

View File

@ -42,7 +42,7 @@ def generate_AbilityRequirement(General_Goal, Current_Task):
},
]
print(messages[1]["content"])
return read_LLM_Completion(messages)["AbilityRequirement"]
return read_LLM_Completion(messages)["AbilityRequirement"]
PROMPT_AGENT_ABILITY_SCORING = """
@ -83,7 +83,6 @@ class JSON_Agent(BaseModel):
class JSON_AGENT_ABILITY_SCORING(RootModel):
root: Dict[str, JSON_Agent]
def agentAbilityScoring(Agent_Board, Ability_Requirement_List):
scoreTable = {}
for Ability_Requirement in Ability_Requirement_List:
@ -105,7 +104,7 @@ def agentAbilityScoring(Agent_Board, Ability_Requirement_List):
return scoreTable
def AgentSelectModify_init(stepTask, General_Goal, Agent_Board):
async def AgentSelectModify_init(stepTask, General_Goal, Agent_Board):
Current_Task = {
"TaskName": stepTask["StepName"],
"InputObject_List": stepTask["InputObject_List"],
@ -125,10 +124,10 @@ def AgentSelectModify_init(stepTask, General_Goal, Agent_Board):
),
},
]
Ability_Requirement_List = read_LLM_Completion(messages)[
Ability_Requirement_List = await read_LLM_Completion(messages)[
"AbilityRequirement"
]
scoreTable = agentAbilityScoring(Agent_Board, Ability_Requirement_List)
scoreTable = await agentAbilityScoring(Agent_Board, Ability_Requirement_List)
return scoreTable

View File

@ -76,10 +76,13 @@ def generate_AbilityRequirement(General_Goal, Current_Task):
},
]
print(messages[1]["content"])
return read_LLM_Completion(messages)["AbilityRequirement"]
return read_LLM_Completion(messages)["AbilityRequirement"]
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",
@ -99,7 +102,7 @@ def generate_AgentSelection(General_Goal, Current_Task, Agent_Board):
agentboard_set = {agent["Name"] for agent in Agent_Board}
while True:
candidate = read_LLM_Completion(messages)["AgentSelectionPlan"]
candidate = read_LLM_Completion(messages)["AgentSelectionPlan"]
if len(candidate) > MAX_TEAM_SIZE:
teamSize = random.randint(2, MAX_TEAM_SIZE)
candidate = candidate[0:teamSize]

View File

@ -9,6 +9,14 @@ import AgentCoord.util as util
def generate_basePlan(
General_Goal, Agent_Board, AgentProfile_Dict, InitialObject_List
):
Agent_Board = [
{"Name": (a.get("Name") or "").strip(),"Profile": (a.get("Profile") or "").strip()}
for a in Agent_Board
if a and a.get("Name") is not None
]
if not Agent_Board: # 洗完后还是空 → 直接返回空计划
return {"Plan_Outline": []}
basePlan = {
"Initial Input Object": InitialObject_List,
"Collaboration Process": [],

View File

@ -95,7 +95,7 @@ def branch_PlanOutline(
},
{"role": "system", "content": prompt},
]
Remaining_Steps = read_LLM_Completion(messages, useGroq=False)[
Remaining_Steps = read_LLM_Completion(messages)[
"Remaining Steps"
]
branch_List.append(Remaining_Steps)

View File

@ -145,7 +145,7 @@ def branch_TaskProcess(
},
{"role": "system", "content": prompt},
]
Remaining_Steps = read_LLM_Completion(messages, useGroq=False)[
Remaining_Steps = read_LLM_Completion(messages)[
"Remaining Steps"
]

View File

@ -83,4 +83,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

@ -106,6 +106,9 @@ class TaskProcessPlan(BaseModel):
def generate_TaskProcess(General_Goal, Current_Task_Description):
# 新增参数验证
if not General_Goal or str(General_Goal).strip() == "":
raise ValueError("General_Goal cannot be empty")
messages = [
{
"role": "system",

View File

@ -80,10 +80,17 @@ 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
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]
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,stream=False)
ActionInfo_with_Result = copy.deepcopy(self.info)
ActionInfo_with_Result["Action_Result"] = ActionResult

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

@ -1,2 +1,23 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 导出常用模块和函数,根据实际需求调整
from .PlanEngine.planOutline_Generator import generate_PlanOutline
from .PlanEngine.basePlan_Generator import generate_basePlan
from .PlanEngine.AgentSelection_Generator import generate_AgentSelection
from .LLMAPI.LLMAPI import LLM_Completion
from .util.converter import read_LLM_Completion
# 定义包元数据
__version__ = "0.1.0"
__all__ = [
"generate_PlanOutline",
"generate_basePlan",
"generate_AgentSelection",
"LLM_Completion",
"read_LLM_Completion"
]

View File

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

View File

@ -1,12 +1,12 @@
## 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
GROQ_API_KEY: ""
MISTRAL_API_KEY: ""
## options under experimentation, leave them as Fasle unless you know what it is for
USE_CACHE: False
## options under experimentation, leave them as False unless you know what it is for
USE_CACHE: False

View File

@ -1,7 +1,7 @@
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
mistralai==1.5.2
socksio==1.0.0

13
backend/restart.ps1 Normal file
View File

@ -0,0 +1,13 @@
# restart.ps1
$port=8000
$env:PYTHONUNBUFFERED="1"
python server.py --port 8000
Write-Host "Killing PID on port $port..." -ForegroundColor Red
Get-NetTCPConnection -LocalPort $port -ErrorAction SilentlyContinue | ForEach-Object {
Stop-Process -Id $_.OwningProcess -Force
}
Write-Host "Starting Flask..." -ForegroundColor Green
python server.py --port $port

View File

@ -213,12 +213,17 @@ def Handle_generate_basePlan():
if requestIdentifier in Request_Cache:
return jsonify(Request_Cache[requestIdentifier])
basePlan = generate_basePlan(
General_Goal=incoming_data["General Goal"],
Agent_Board=AgentBoard,
AgentProfile_Dict=AgentProfile_Dict,
InitialObject_List=incoming_data["Initial Input Object"],
)
try:
basePlan = generate_basePlan(
General_Goal=incoming_data["General Goal"],
Agent_Board=AgentBoard,
AgentProfile_Dict=AgentProfile_Dict,
InitialObject_List=incoming_data["Initial Input Object"],
)
except ValueError as e:
return jsonify({"error": str(e)}), 400
except Exception as e:
return jsonify({"error": f"An unexpected error occurred: {str(e)}"}), 500
basePlan_withRenderSpec = Add_Collaboration_Brief_FrontEnd(basePlan)
Request_Cache[requestIdentifier] = basePlan_withRenderSpec
response = jsonify(basePlan_withRenderSpec)
@ -278,10 +283,38 @@ def set_agents():
def init():
global AgentBoard, AgentProfile_Dict, Request_Cache
with open(
os.path.join(os.getcwd(), "RequestCache", "Request_Cache.json"), "r"
) as json_file:
Request_Cache = json.load(json_file)
# Load Request Cache
try:
with open(
os.path.join(os.getcwd(), "RequestCache", "Request_Cache.json"), "r"
) as json_file:
Request_Cache = json.load(json_file)
print(f"✅ Loaded Request_Cache with {len(Request_Cache)} entries")
except Exception as e:
print(f"⚠️ Failed to load Request_Cache: {e}")
Request_Cache = {}
# Load Agent Board
try:
with open(
os.path.join(os.getcwd(), "AgentRepo", "agentBoard_v1.json"), "r", encoding="utf-8"
) as json_file:
AgentBoard = json.load(json_file)
print(f"✅ Loaded AgentBoard with {len(AgentBoard)} agents")
# Build AgentProfile_Dict
AgentProfile_Dict = {}
for item in AgentBoard:
name = item["Name"]
profile = item["Profile"]
AgentProfile_Dict[name] = profile
print(f"✅ Built AgentProfile_Dict with {len(AgentProfile_Dict)} profiles")
except Exception as e:
print(f"⚠️ Failed to load AgentBoard: {e}")
AgentBoard = []
AgentProfile_Dict = {}
if __name__ == "__main__":
@ -291,8 +324,8 @@ if __name__ == "__main__":
parser.add_argument(
"--port",
type=int,
default=8017,
help="set the port number, 8017 by defaul.",
default=8000,
help="set the port number, 8000 by defaul.",
)
args = parser.parse_args()
init()

View File

@ -0,0 +1,159 @@
# AgentCoord Backend 代码逻辑架构分析
## 🏗️ Backend 代码逻辑架构
### 📁 核心目录结构
```
backend/
├── server.py # Flask主服务器入口
├── config/config.yaml # LLM API配置
├── AgentRepo/agentBoard_v1.json # 智能体定义库
├── DataProcess/ # 数据处理层
├── RequestCache/ # 缓存机制
└── AgentCoord/ # 核心业务逻辑
├── LLMAPI/ # LLM接口封装
├── PlanEngine/ # 计划生成引擎
├── RehearsalEngine_V2/ # 计划执行引擎
└── util/ # 工具模块
```
### 🔄 主要工作流程
#### 1**计划生成流程** (PlanEngine)
```
用户目标 → 生成计划大纲 → 选择智能体 → 生成任务流程 → 输出完整计划
```
**核心模块:**
- `basePlan_Generator.py` - 整合所有计划生成组件
- `planOutline_Generator.py` - 生成高级计划大纲
- `taskProcess_Generator.py` - 生成详细任务执行流程
- `AgentSelection_Generator.py` - 选择最适合的智能体
#### 2**计划执行流程** (RehearsalEngine_V2)
```
协作计划 → 初始化执行环境 → 按步骤执行 → 智能体协作 → 记录执行日志
```
**动作类型:**
- **Propose** - 提出建议和方案
- **Critique** - 提供反馈和批评
- **Improve** - 基于反馈改进结果
- **Finalize** - 最终确定输出
#### 3**LLM接口层** (LLMAPI)
支持的模型:
- OpenAI GPT-4/GPT-3.5
- Groq Mixtral-8x7b (快速模式)
- Mistral Open-Mixtral-8x7b
### 🌐 API端点架构
#### **计划生成APIs**
- `POST /generate_basePlan` - 生成基础协作计划
- `POST /fill_stepTask` - 填充步骤任务详情
- `POST /branch_PlanOutline` - 处理计划分支
#### **计划执行APIs**
- `POST /executePlan` - 执行协作计划
- `POST /agentSelectModify_init` - 初始化智能体选择
#### **系统管理APIs**
- `POST /setAgents` - 设置智能体板
- `POST /_saveRequestCache` - 保存请求缓存
### 💾 数据流设计
#### **输入层**
- HTTP请求验证
- 参数提取和格式化
- 缓存检查
#### **业务逻辑层**
- LLM API调用
- 多智能体协调
- 任务状态管理
#### **输出层**
- 结果格式化
- 前端渲染模板生成
- JSON响应
### ⚙️ 配置与优化
#### **配置文件 (config.yaml)**
```yaml
OPENAI_API_BASE: "https://api.openai.com"
OPENAI_API_KEY: "your-key"
OPENAI_API_MODEL: "gpt-4-turbo-preview"
FAST_DESIGN_MODE: True # 启用快速模式
USE_CACHE: False # 缓存开关
```
#### **性能优化**
- 请求缓存机制
- 快速模式支持Groq
- 异步LLM调用
- 重试和错误处理
### 🔧 关键技术特点
1. **模块化架构** - 清晰的职责分离
2. **多LLM支持** - 灵活的模型切换
3. **智能体协作** - 复杂的多智能体工作流
4. **前端适配** - 自动生成渲染模板
5. **可扩展性** - 支持自定义智能体和动作
## 📋 详细模块说明
### 1. 服务器入口 (server.py)
- **功能**: Flask应用主入口提供RESTful API
- **特点**: 支持请求缓存、全局状态管理、参数化端口配置
### 2. 计划引擎 (PlanEngine)
**核心功能**: 生成多智能体协作计划
- **basePlan_Generator.py**: 整合所有生成器,生成完整协作计划
- **planOutline_Generator.py**: 基于目标生成计划大纲
- **taskProcess_Generator.py**: 为每个任务步骤生成执行流程
- **AgentSelection_Generator.py**: 选择合适的智能体执行任务
### 3. 排练引擎 (RehearsalEngine_V2)
**核心功能**: 执行生成的协作计划
- **ExecutePlan.py**: 计划执行控制器
- **Action模块**: 实现各种协作动作Propose, Critique, Improve, Finalize
### 4. LLM API接口 (LLMAPI)
**核心功能**: 封装多种大语言模型API
- 支持流式响应
- 异步处理
- 快速模式切换
### 5. 数据处理 (DataProcess)
**核心功能**: 格式转换和前端适配
- 颜色映射:不同元素类型的视觉区分
- 模板生成:为前端生成渲染模板
- 格式化:处理驼峰命名和自然语言转换
## 🚀 启动和调试
### 开发环境启动
```bash
cd backend
source venv/bin/activate
python server.py --port 8017
```
### 调试模式
Flask已内置debug=True支持
- 交互式调试器
- 自动重载
- 详细错误页面
### Docker部署
```bash
docker-compose up
```
这个backend实现了一个完整的多智能体协作平台通过精心设计的模块化架构支持复杂任务的规划和执行。

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

@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(npm run type-check:*)"
],
"deny": [],
"ask": []
}
}

1
frontend/.env Normal file
View File

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

View File

@ -16,7 +16,13 @@ declare module 'vue' {
ElCard: typeof import('element-plus/es')['ElCard']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElInput: typeof import('element-plus/es')['ElInput']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
MultiLineTooltip: typeof import('./src/components/MultiLineTooltip/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']

BIN
frontend/dist.zip Normal file

Binary file not shown.

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>

11986
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

BIN
frontend/public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -1,11 +1,5 @@
<script setup lang="ts">
import { onMounted } from 'vue'
import Layout from './layout/index.vue'
onMounted(() => {
document.documentElement.classList.add('dark')
})
</script>
<template>
@ -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;
}

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="1764640542560" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1335" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M838.611638 631.662714l67.309037-44.87798 87.97131 131.929203a40.41239 40.41239 0 0 1-33.646587 62.843448H555.939064v-80.82478h328.851364l-46.17879-69.069891zM718.135918 979.106157l-67.237652 44.862116-88.050627-131.921271a40.41239 40.41239 0 0 1 33.583133-62.779994h404.37772v80.761326h-328.772047l46.107404 69.149209v-0.071386zM701.510919 407.10625a294.54644 294.54644 0 0 0-84.695487-59.139309c44.34655-35.891279 72.670917-90.69984 72.670917-152.218682 0-108.300447-90.461886-197.31875-199.008219-195.621351A196.089325 196.089325 0 0 0 318.390354 107.673837a7.828661 7.828661 0 0 0 6.202648 11.278983c18.655533 2.014671 36.882751 6.194716 54.126428 12.214932a7.804866 7.804866 0 0 0 9.07395-3.140982 125.203058 125.203058 0 0 1 104.11247-57.632273 124.608175 124.608175 0 0 1 91.262995 37.898018 124.679561 124.679561 0 0 1 35.462963 83.759537 124.846128 124.846128 0 0 1-73.979659 117.953417 7.884184 7.884184 0 0 0-4.386271 8.582179 250.96134 250.96134 0 0 1 2.879234 71.409765l0.13484 0.063454a7.828661 7.828661 0 0 0 5.821923 8.526657 220.661962 220.661962 0 0 1 102.478524 58.369928 221.201323 221.201323 0 0 1 65.278503 150.338851 7.773139 7.773139 0 0 0 7.828661 7.51139h55.189286a7.844525 7.844525 0 0 0 7.574845-8.074546 291.05646 291.05646 0 0 0-85.940775-199.626897z" fill="#17A29E" p-id="1336"></path><path d="M458.386171 627.942712h89.145212a291.072323 291.072323 0 0 0-41.649747-52.38937 293.443924 293.443924 0 0 0-84.568578-59.13931A195.875168 195.875168 0 0 0 493.761885 367.550491c1.808445-108.173539-84.425806-197.310819-192.591413-199.111331l-0.824905-0.007932c-108.768422-0.650405-197.469454 86.987769-198.119859 195.764123a195.296148 195.296148 0 0 0 72.655053 152.21075c-31.441554 14.602397-59.401058 35.288464-84.560647 59.139309C4.371408 657.03646 6.187784 763.131873 4.371408 838.277504a7.836593 7.836593 0 0 0 7.828661 8.011092h54.816492a7.804866 7.804866 0 0 0 7.828662-7.511391c1.840172-56.601142 25.207179-173.483769 65.334025-213.420252 42.157381-42.157381 98.227094-65.334025 157.850241-65.334025a221.827933 221.827933 0 0 1 157.858173 65.334025c0.864563 0.832836 1.657741 1.729127 2.498509 2.585759zM298.037421 489.549113l-0.063454-0.071386a124.663697 124.663697 0 0 1-88.637578-36.636866c-23.668415-23.747732-36.70032-55.141695-36.70032-88.64551 0-33.829018 13.27779-65.643364 37.580747-89.454551a126.05969 126.05969 0 0 1 89.081757-35.764371 125.512397 125.512397 0 0 1 86.686362 36.414776c49.169069 48.820071 49.454613 128.264723 0.642474 177.449656a124.354358 124.354358 0 0 1-88.589988 36.708252z" fill="#17A29E" p-id="1337"></path></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

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

After

Width:  |  Height:  |  Size: 336 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764757574909" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9019" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M248.88888875 558.88888844l401.7777778 401.77777782c28.44444469 28.44444469 74.66666625 28.44444469 99.55555594 0 28.44444469-28.44444469 28.44444469-74.66666625 1e-8-99.555555l-352.00000031-352.00000032 352.00000031-351.99999937c28.44444469-28.44444469 28.44444469-71.11111125 0-99.55555594s-74.66666625-28.44444469-99.55555594 0L248.88888875 459.33333344c-14.22222188 14.22222188-21.33333375 31.99999969-21.33333281 49.7777775 0 17.7777778 7.11111094 35.55555562 21.33333281 49.7777775" fill="#ffffff" p-id="9020"></path></svg>

After

Width:  |  Height:  |  Size: 860 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764757483582" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7204" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M775.11111125 465.11111156l-401.7777778-401.77777782c-28.44444469-28.44444469-74.66666625-28.44444469-99.55555594 0-28.44444469 28.44444469-28.44444469 74.66666625-1e-8 99.555555l352.00000031 352.00000032-352.00000031 351.99999937c-28.44444469 28.44444469-28.44444469 71.11111125 0 99.55555594s74.66666625 28.44444469 99.55555594 0L775.11111125 564.66666656c14.22222188-14.22222188 21.33333375-31.99999969 21.33333281-49.7777775 0-17.7777778-7.11111094-35.55555562-21.33333281-49.7777775" fill="#d8d8d8" p-id="7205"></path></svg>

After

Width:  |  Height:  |  Size: 860 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="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

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

View File

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

View File

@ -1,11 +1,14 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ref, onMounted, computed, reactive } from 'vue'
import SvgIcon from '@/components/SvgIcon/index.vue'
import { useAgentsStore, useConfigStore } from '@/stores'
import api from '@/api'
import { changeBriefs } from '@/utils/collaboration_Brief_FrontEnd.ts'
import { ElMessage } from 'element-plus'
import { log } from '@jsplumb/browser-ui'
import ProcessCard from './TaskTemplate/TaskProcess/ProcessCard.vue'
import AgentAllocation from './TaskTemplate/TaskSyllabus/components/AgentAllocation.vue'
const emit = defineEmits<{
(e: 'search-start'): void
@ -14,10 +17,60 @@ const emit = defineEmits<{
const agentsStore = useAgentsStore()
const configStore = useConfigStore()
const searchValue = ref('')
const triggerOnFocus = ref(true)
const isFocus = ref(false)
const hasAutoSearched = ref(false) //
const agentAllocationVisible = ref(false) //
const isExpanded = ref(false) //
// URL
function getUrlParam(param: string): string | null {
const urlParams = new URLSearchParams(window.location.search)
return urlParams.get(param)
}
//
async function autoSearchFromUrl() {
const query = getUrlParam('q')
if (query && !hasAutoSearched.value) {
// URL
const decodedQuery = decodeURIComponent(query)
searchValue.value = decodedQuery
hasAutoSearched.value = true
//
setTimeout(() => {
handleSearch()
}, 100)
}
}
//
//
function handleAgentAllocation() {
agentAllocationVisible.value = true
}
//
function handleAgentAllocationClose() {
agentAllocationVisible.value = false
}
//
function handleFocus() {
isFocus.value = true
isExpanded.value = true //
}
//
function handleBlur() {
isFocus.value = false
// 便
setTimeout(() => {
isExpanded.value = false
}, 200)
}
async function handleSearch() {
try {
@ -31,7 +84,7 @@ async function handleSearch() {
agentsStore.setAgentRawPlan({ loading: true })
const data = await api.generateBasePlan({
goal: searchValue.value,
inputs: [],
inputs: []
})
data['Collaboration Process'] = changeBriefs(data['Collaboration Process'])
agentsStore.setAgentRawPlan({ data })
@ -47,7 +100,7 @@ const querySearch = (queryString: string, cb: (v: { value: string }[]) => void)
? configStore.config.taskPromptWords.filter(createFilter(queryString))
: configStore.config.taskPromptWords
// call callback function to return suggestions
cb(results.map((item) => ({ value: item })))
cb(results.map(item => ({ value: item })))
}
const createFilter = (queryString: string) => {
@ -57,6 +110,11 @@ const createFilter = (queryString: string) => {
}
const taskContainerRef = ref<HTMLDivElement | null>(null)
// URL
onMounted(() => {
autoSearchFromUrl()
})
</script>
<template>
@ -67,8 +125,13 @@ const taskContainerRef = ref<HTMLDivElement | null>(null)
: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>
<div
class="task-container"
ref="taskContainerRef"
id="task-container"
:class="{ expanded: isExpanded }"
>
<span class="text-[var(--color-text-task)] font-bold task-title">任务</span>
<el-autocomplete
v-model.trim="searchValue"
class="task-input"
@ -82,8 +145,8 @@ const taskContainerRef = ref<HTMLDivElement | null>(null)
:disabled="!(agentsStore.agents.length > 0)"
:debounce="0"
:trigger-on-focus="triggerOnFocus"
@focus="isFocus = true"
@blur="isFocus = false"
@focus="handleFocus"
@blur="handleBlur"
@select="isFocus = false"
>
</el-autocomplete>
@ -101,10 +164,27 @@ const taskContainerRef = ref<HTMLDivElement | null>(null)
v-if="!agentsStore.agentRawPlan.loading"
icon-class="paper-plane"
size="18px"
color="var(--color-text)"
color="#ffffff"
/>
</el-button>
</div>
<!-- 智能体分配 -->
<!-- <div class="agent-allocation-entry" @click="handleAgentAllocation" title="智能体分配">
<SvgIcon icon-class="agent-change" size="20px" color="var(--color-bg)" />
</div> -->
<!-- 智能体分配弹窗 -->
<el-dialog
v-model="agentAllocationVisible"
width="70%"
top="10vh"
:close-on-click-modal="false"
:close-on-press-escape="true"
:show-close="false"
custom-class="agent-allocation-dialog"
>
<AgentAllocation @close="handleAgentAllocationClose" />
</el-dialog>
</div>
</el-tooltip>
</template>
@ -120,10 +200,9 @@ const taskContainerRef = ref<HTMLDivElement | null>(null)
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,
background: linear-gradient(var(--color-bg-taskbar), var(--color-bg-taskbar)) padding-box,
linear-gradient(to right, #00c8d2, #315ab4) border-box;
border-radius: 40px;
border-radius: 30px;
position: absolute;
left: 50%;
transform: translateX(-50%);
@ -131,28 +210,35 @@ const taskContainerRef = ref<HTMLDivElement | null>(null)
min-height: 100%;
overflow: hidden;
padding: 0 55px 0 47px;
transition: all 0.3s ease;
/* 搜索框展开时的样式 */
&.expanded {
box-shadow: var(--color-task-shadow);
}
:deep(.el-popper) {
position: static !important;
width: 100%;
min-width: 100%;
background: var(--color-bg-tertiary);
box-shadow: none;
width: calc(100% + 102px); /*增加左右padding的总和 */
min-width: calc(100% + 102px); /* 确保最小宽度也增加 */
margin-left: -47px; /* 向左偏移左padding的值 */
margin-right: -55px; /*向右偏移右padding的值 */
background: var(--color-bg-taskbar);
border: none;
transition: height 0s ease-in-out;
border-top: 1px solid #494B51;
border-top: 1px solid var(--color-border);
border-radius: 0;
li {
height: 45px;
box-sizing: border-box;
line-height: 45px;
font-size: 14px;
padding-left: 27px;
&:hover {
background: #1C1E25;
color: #00F3FF;
background: var(--color-bg-hover);
color: var(--color-text-hover);
}
}
@ -166,7 +252,7 @@ const taskContainerRef = ref<HTMLDivElement | null>(null)
width: 100%;
.task-input {
height: 100%;
height: 100%;
}
.el-textarea__inner {
@ -175,8 +261,9 @@ const taskContainerRef = ref<HTMLDivElement | null>(null)
font-size: 14px;
height: 100%;
line-height: 1.5;
padding: 18px 0;
padding: 18px 0 0 18px;
resize: none;
color: var(--color-text-taskbar);
&::placeholder {
line-height: 1.2;
@ -192,11 +279,10 @@ const taskContainerRef = ref<HTMLDivElement | null>(null)
}
}
.task-title {
position: absolute;
top: 28px;
left: 10px;
left: 27px;
z-index: 999;
transform: translateY(-50%);
}
@ -221,4 +307,121 @@ const taskContainerRef = ref<HTMLDivElement | null>(null)
}
}
}
.drawer-header {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
.title {
font-size: 18px;
font-weight: bold;
}
}
.process-list {
padding: 0 8px;
}
.process-item {
margin-bottom: 16px;
padding: 12px;
border-radius: 8px;
background: var(--color-bg-list);
border: 1px solid var(--color-border-default);
.process-content {
display: flex;
align-items: flex-start;
gap: 8px;
.agent-tag {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
white-space: nowrap;
flex-shrink: 0;
}
.process-text {
line-height: 1.6;
font-size: 14px;
color: var(--el-text-color-primary);
white-space: pre-wrap;
}
}
.edit-container {
margin-top: 8px;
}
}
.process-item:hover {
border-color: var(--el-border-color);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.agent-allocation-entry {
position: absolute;
right: 0; /* 顶头 */
top: 0;
width: 40px;
height: 40px;
border-radius: 50%;
background: #00aaff; /* 纯蓝色背景 */
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 6px rgba(0, 170, 255, 0.35);
transition: transform 0.2s;
&:hover {
transform: scale(1.08);
}
&:active {
transform: scale(0.96);
}
}
//
:deep(.agent-allocation-dialog) {
.el-dialog {
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
&__header {
padding: 16px 20px;
border-bottom: 1px solid var(--color-border);
margin-right: 0;
.el-dialog__title {
font-size: 18px;
font-weight: bold;
color: var(--color-text-title);
}
}
&__body {
padding: 0;
max-height: 70vh;
overflow: hidden;
}
&__headerbtn {
top: 16px;
right: 20px;
.el-dialog__close {
color: var(--color-text);
font-size: 16px;
&:hover {
color: var(--color-primary);
}
}
}
}
}
</style>

View File

@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed } from 'vue'
import { getActionTypeDisplay, getAgentMapIcon } from '@/layout/components/config.ts'
import SvgIcon from '@/components/SvgIcon/index.vue'
import { type Agent, useAgentsStore } from '@/stores'
@ -10,9 +11,9 @@ const porps = defineProps<{
const taskProcess = computed(() => {
const list = agentsStore.currentTask?.TaskProcess ?? []
return list.map((item) => ({
return list.map(item => ({
...item,
key: uuidv4(),
key: uuidv4()
}))
})
@ -26,35 +27,38 @@ const agentsStore = useAgentsStore()
class="user-item"
:class="agentsStore.currentTask?.AgentSelection?.includes(item.Name) ? 'active-card' : ''"
>
<div class="flex items-center justify-between relative h-[41px]">
<div class="flex items-center justify-between relative h-[43px]">
<div
class="w-[44px] h-[44px] rounded-full flex items-center justify-center flex-shrink-0 relative right-[2px] icon-container"
:style="{ background: getAgentMapIcon(item.Name).color }"
>
<svg-icon
:icon-class="getAgentMapIcon(item.Name).icon"
color="var(--color-text)"
size="24px"
/>
<svg-icon :icon-class="getAgentMapIcon(item.Name).icon" color="#fff" size="24px" />
</div>
<div class="flex-1 text-[14px] flex flex-col items-end justify-end truncate ml-1">
<span
class="w-full truncate text-right"
:style="
agentsStore.currentTask?.AgentSelection?.includes(item.Name) ? 'color:#00F3FF' : ''
"
>{{ item.Name }}</span
>
<div class="flex-1 text-[14px] textClass flex flex-col items-end justify-end truncate ml-1">
<div class="flex items-center justify-end gap-2 w-full">
<span
class="truncate"
:style="
agentsStore.currentTask?.AgentSelection?.includes(item.Name)
? 'color:var(--color-accent)'
: ''
"
>{{ item.Name }}</span
>
</div>
<div
v-if="agentsStore.currentTask?.AgentSelection?.includes(item.Name)"
class="flex items-center gap-[7px] h-[8px] mr-1"
>
<!-- 小圆点 -->
<div
v-for="item1 in taskProcess.filter((i) => i.AgentName === item.Name)"
v-for="item1 in taskProcess.filter(i => i.AgentName === item.Name)"
:key="item1.key"
class="w-[6px] h-[6px] rounded-full"
:style="{ background: getActionTypeDisplay(item1.ActionType)?.color }"
:style="{
background: getActionTypeDisplay(item1.ActionType)?.color,
border: `1px solid ${getActionTypeDisplay(item1.ActionType)?.border}`
}"
></div>
</div>
</div>
@ -71,7 +75,7 @@ const agentsStore = useAgentsStore()
</div>
<div class="p-[8px] pt-0">
<div
v-for="(item1, index1) in taskProcess.filter((i) => i.AgentName === item.Name)"
v-for="(item1, index1) in taskProcess.filter(i => i.AgentName === item.Name)"
:key="item1.key"
class="text-[12px]"
>
@ -89,8 +93,8 @@ const agentsStore = useAgentsStore()
</div>
<!-- 分割线 -->
<div
v-if="index1 !== taskProcess.filter((i) => i.AgentName === item.Name).length - 1"
class="h-[1px] w-full bg-[#494B51] my-[8px]"
v-if="index1 !== taskProcess.filter(i => i.AgentName === item.Name).length - 1"
class="h-[1px] w-full bg-[var(--color-border-default)] my-[8px]"
></div>
</div>
</div>
@ -100,13 +104,14 @@ const agentsStore = useAgentsStore()
<style scoped lang="scss">
.user-item {
background: #1d222b;
background: var(--color-agent-list-bg);
border-radius: 40px;
padding-right: 12px;
cursor: pointer;
transition: all 0.25s ease;
color: #969696;
border: 2px solid transparent;
color: var(--color-text-detail);
border: 1px solid var(--color-agent-list-border);
box-sizing: border-box;
.duty-info {
transition: height 0.25s ease;
height: 0;
@ -118,16 +123,30 @@ const agentsStore = useAgentsStore()
}
&:hover {
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
color: #b8b8b8;
box-shadow: var(--color-agent-list-hover-shadow);
color: var(--color-text);
background: var(--color-agent-list-hover-bg);
border: 1px solid var(--color-agent-list-hover-border);
}
}
.textClass {
color: var(--color-text-agent-list);
&:hover {
color: var(--color-text-agent-list-hover);
}
}
.active-card {
background:
linear-gradient(#171B22, #171B22) padding-box,
linear-gradient(to right, #00c8d2, #315ab4) border-box;
background: linear-gradient(var(--color-bg-quaternary), var(--color-bg-quaternary)) padding-box,
linear-gradient(to right, var(--color-accent), var(--color-accent-secondary)) border-box;
border: 1px solid var(--color-agent-list-border);
&:hover {
box-shadow: var(--color-agent-list-hover-shadow);
color: var(--color-text);
background: var(--color-agent-list-hover-bg);
border: 1px solid var(--color-agent-list-hover-border);
border-radius: 20px;
.duty-info {
height: auto;
@ -138,4 +157,10 @@ const agentsStore = useAgentsStore()
}
}
}
//
.icon-container {
right: 0 !important;
margin-left: 0px;
}
</style>

View File

@ -1,4 +1,5 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { ElNotification } from 'element-plus'
import { pick } from 'lodash'
@ -7,7 +8,6 @@ import api from '@/api/index.ts'
import SvgIcon from '@/components/SvgIcon/index.vue'
import { agentMapDuty } from '@/layout/components/config.ts'
import { type Agent, useAgentsStore } from '@/stores'
import { onMounted } from 'vue'
import { readConfig } from '@/utils/readJson.ts'
import AgentRepoList from './AgentRepoList.vue'
@ -19,7 +19,7 @@ onMounted(async () => {
const res = await readConfig<Agent[]>('agent.json')
agentsStore.setAgents(res)
}
await api.setAgents(agentsStore.agents.map((item) => pick(item, ['Name', 'Profile'])))
await api.setAgents(agentsStore.agents.map(item => pick(item, ['Name', 'Profile'])))
})
// agent
@ -39,7 +39,7 @@ const handleFileSelect = (event: Event) => {
const readFileContent = async (file: File) => {
const reader = new FileReader()
reader.onload = async (e) => {
reader.onload = async e => {
if (!e.target?.result) {
return
}
@ -48,33 +48,33 @@ const readFileContent = async (file: File) => {
// JSON
if (Array.isArray(json)) {
const isValid = json.every(
(item) =>
item =>
typeof item.Name === 'string' &&
typeof item.Icon === 'string' &&
typeof item.Profile === 'string',
typeof item.Profile === 'string'
)
if (isValid) {
// JSON
agentsStore.setAgents(
json.map((item) => ({
json.map(item => ({
Name: item.Name,
Icon: item.Icon.replace(/\.png$/, ''),
Profile: item.Profile,
Classification: item.Classification,
})),
Classification: item.Classification
}))
)
await api.setAgents(json.map((item) => pick(item, ['Name', 'Profile'])))
await api.setAgents(json.map(item => pick(item, ['Name', 'Profile'])))
} else {
ElNotification.error({
title: '错误',
message: 'JSON 格式错误',
message: 'JSON 格式错误'
})
}
} else {
console.error('JSON is not an array')
ElNotification.error({
title: '错误',
message: 'JSON 格式错误',
message: 'JSON 格式错误'
})
}
} catch (e) {
@ -95,7 +95,7 @@ const agentList = computed(() => {
if (!agentsStore.agents.length) {
return {
selected,
unselected,
unselected
}
}
for (const agent of agentsStore.agents) {
@ -110,14 +110,14 @@ const agentList = computed(() => {
obj[agent.Classification] = arr
unselected.push({
title: agent.Classification,
data: arr,
data: arr
})
}
}
return {
selected,
unselected: unselected,
unselected: unselected
}
})
</script>
@ -126,7 +126,7 @@ const agentList = computed(() => {
<div class="agent-repo h-full flex flex-col" id="agent-repo">
<!-- 头部 -->
<div class="flex items-center justify-between">
<span class="text-[18px] font-bold">智能体库</span>
<span class="text-[18px] font-bold text-[var(--color-text-title-header)]">智能体库</span>
<!-- 上传文件 -->
<input type="file" accept=".json" @change="handleFileSelect" class="hidden" ref="fileInput" />
<div class="plus-button" @click="triggerFileSelect">
@ -144,14 +144,22 @@ const agentList = computed(() => {
</div>
</div>
<!-- 底部提示栏 -->
<div class="w-full grid grid-cols-3 gap-x-[10px] bg-[#1d222b] rounded-[20px] p-[8px] mt-[10px]">
<div
class="w-full grid grid-cols-3 gap-x-[10px] bg-[var(--color-bg-indicator)] rounded-[20px] p-[8px] mt-[10px]"
>
<div
v-for="item in Object.values(agentMapDuty)"
:key="item.key"
class="flex items-center justify-center gap-x-1"
>
<div
class="w-[8px] h-[8px] rounded-full"
:style="{
background: item.color,
border: `1px solid ${item.border}`
}"
></div>
<span class="text-[12px]">{{ item.name }}</span>
<div class="w-[8px] h-[8px] rounded-full" :style="{ background: item.color }"></div>
</div>
</div>
</div>
@ -162,7 +170,7 @@ const agentList = computed(() => {
padding: 0 8px;
.plus-button {
background: #1d2128;
background: var(--color-bg-tertiary);
width: 24px;
height: 24px;
padding: 0;
@ -174,7 +182,7 @@ const agentList = computed(() => {
transition: all 0.3s ease;
&:hover {
background: #374151;
background: var(--color-bg-quaternary);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
}
}

View File

@ -0,0 +1,283 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { getActionTypeDisplay } from '@/layout/components/config.ts'
const props = defineProps<{
step: {
Id?: string
TaskProcess: Array<{
ID: string
ActionType: string
AgentName: string
Description: string
}>
}
}>()
const emit = defineEmits<{
(e: 'open-edit', stepId: string, processId: string): void
(e: 'save-edit', stepId: string, processId: string, value: string): void
}>()
// process ID
const editingProcessId = ref<string | null>(null)
//
const editValue = ref('')
// process ID
const hoverProcessId = ref<string | null>(null)
//
function getLightColor(color: string, level: number = 2): string {
if (!color || color.length !== 7 || color[0] !== '#') return color
const r = parseInt(color.substr(1, 2), 16)
const g = parseInt(color.substr(3, 2), 16)
const b = parseInt(color.substr(5, 2), 16)
//
const lightenAmount = level * 20
const newR = Math.min(255, r + lightenAmount)
const newG = Math.min(255, g + lightenAmount)
const newB = Math.min(255, b + lightenAmount)
return `#${Math.round(newR).toString(16).padStart(2, '0')}${Math.round(newG)
.toString(16)
.padStart(2, '0')}${Math.round(newB).toString(16).padStart(2, '0')}`
}
//
function handleMouseEnter(processId: string) {
hoverProcessId.value = processId
}
//
function handleMouseLeave() {
hoverProcessId.value = null
}
// process
function handleDblClick(processId: string, currentDescription: string) {
editingProcessId.value = processId
editValue.value = currentDescription
emit('open-edit', props.step.Id || '', processId)
}
//
function handleSave(processId: string) {
if (!editingProcessId.value) return
emit('save-edit', props.step.Id || '', processId, editValue.value)
editingProcessId.value = null
editValue.value = ''
}
//
function handleCancel() {
editingProcessId.value = null
editValue.value = ''
}
</script>
<template>
<div class="process-card">
<div class="process-content">
<!-- 显示模式 -->
<div class="display-content">
<span
v-for="process in step.TaskProcess"
:key="process.ID"
class="process-segment"
@mouseenter="handleMouseEnter(process.ID)"
@mouseleave="handleMouseLeave"
>
<span
class="agent-name"
:style="{
backgroundColor: getActionTypeDisplay(process.ActionType)?.color || '#909399',
color: '#fff',
padding: '2px 6px',
borderRadius: '3px',
marginRight: '4px'
}"
>
{{ process.AgentName }}
</span>
<!-- 编辑模式 - 修改为卡片样式 -->
<div v-if="editingProcessId === process.ID" class="edit-container">
<div class="edit-card">
<el-input
v-model="editValue"
type="textarea"
:autosize="{ minRows: 3, maxRows: 6 }"
placeholder="请输入描述内容"
autofocus
/>
<div class="edit-buttons">
<el-button
type="success"
size="small"
icon="Check"
@click="handleSave(process.ID)"
title="保存"
>
</el-button>
<el-button
type="danger"
size="small"
icon="Close"
@click="handleCancel"
title="取消"
>
×
</el-button>
</div>
</div>
</div>
<!-- 显示模式 -->
<span
v-else
class="process-description"
:class="{ hovered: hoverProcessId === process.ID }"
:style="{
border: `1px solid ${getActionTypeDisplay(process.ActionType)?.border}`,
backgroundColor:
hoverProcessId === process.ID
? getLightColor(getActionTypeDisplay(process.ActionType)?.color || '#909399')
: 'transparent'
}"
@dblclick="handleDblClick(process.ID, process.Description)"
>
{{ process.Description }}
</span>
<span class="separator" v-if="!process.Description.endsWith('。')"></span>
</span>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.process-card {
margin-bottom: 16px;
padding: 16px;
border-radius: 8px;
background: var(--color-bg-list);
border: 1px solid var(--color-border-default);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
.process-content {
min-height: 20px;
.display-content {
line-height: 1.6;
font-size: 14px;
color: var(--el-text-color-primary);
.process-segment {
display: inline;
position: relative;
.agent-name {
display: inline-block;
font-weight: 500;
font-size: 13px;
margin-right: 4px;
cursor: default;
}
.edit-container {
display: block; // 使
margin-top: 8px;
margin-bottom: 8px;
.edit-card {
position: relative;
background: #f0f2f5;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 600px;
:deep(.el-textarea) {
width: 100%;
.el-textarea__inner {
font-size: 14px;
line-height: 1.6;
padding: 8px 12px;
border-radius: 4px;
resize: vertical;
min-height: 60px;
}
}
.edit-buttons {
position: absolute;
right: 12px;
bottom: 8px;
display: flex;
gap: 8px;
.el-button {
width: 32px;
height: 32px;
padding: 0;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
&:first-child {
background-color: #67c23a;
border-color: #67c23a;
}
&:last-child {
background-color: #f56c6c;
border-color: #f56c6c;
}
&:hover {
transform: scale(1.05);
}
}
}
}
}
.process-description {
display: inline;
white-space: normal;
cursor: pointer;
padding: 2px 4px;
border-radius: 3px;
transition: background-color 0.2s ease;
&.hovered {
transition: background-color 0.2s ease;
}
}
.separator {
margin-right: 8px;
}
&:last-child .separator {
display: none;
}
}
}
}
}
.process-card:hover {
border-color: var(--el-border-color);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
</style>

View File

@ -15,16 +15,14 @@ const md = new MarkdownIt({
html: true,
linkify: true,
typographer: true,
breaks: true,
breaks: true
})
function sanitize(str?: string) {
if (!str) {
return ''
}
const cleanStr = str
.replace(/\\n/g, '\n')
.replace(/\n\s*\d+\./g, '\n$&')
const cleanStr = str.replace(/\\n/g, '\n').replace(/\n\s*\d+\./g, '\n$&')
const html = md.render(cleanStr)
return html
// return DOMPurify.sanitize(html)
@ -43,7 +41,7 @@ const data = computed<Data | null>(() => {
return {
Description: props.nodeId,
Content: sanitize(result.content),
LogNodeType: result.LogNodeType,
LogNodeType: result.LogNodeType
}
}
@ -56,7 +54,7 @@ const data = computed<Data | null>(() => {
return {
Description: action.Description,
Content: sanitize(action.Action_Result),
LogNodeType: result.LogNodeType,
LogNodeType: result.LogNodeType
}
}
}
@ -75,9 +73,11 @@ const data = computed<Data | null>(() => {
class="text-[16px] flex items-center gap-1 text-[var(--color-text-secondary)] mb-1"
>
{{ data.Description }}
<Iod v-if="data.LogNodeType !== 'object'"/>
<Iod v-if="data.LogNodeType !== 'object'" />
</div>
<div class="rounded-[8px] p-[15px] text-[14px] bg-[var(--color-bg-quaternary)]">
<div
class="rounded-[8px] p-[15px] text-[14px] bg-[var(--color-bg-result-detail)] text-[var(--color-text-detail)]"
>
<div
class="markdown-content max-h-[240px] overflow-y-auto max-w-full"
v-html="data.Content"

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { readConfig } from '@/utils/readJson.ts'
import { onMounted } from 'vue'
import { ref, computed, onMounted } from 'vue'
interface Iod {
name: string
@ -28,10 +28,22 @@ function handleNext() {
displayIndex.value++
}
}
function handlePrev() {
if (displayIndex.value === 0) {
displayIndex.value = data.value.length - 1
} else {
displayIndex.value--
}
}
</script>
<template>
<el-popover trigger="hover" width="440">
<el-popover
trigger="hover"
width="440"
popper-style="background-color: var(--color-bg-result); border: none;"
>
<template #reference>
<div
class="rounded-full w-[20px] h-[20px] bg-[var(--color-bg-quaternary)] flex justify-center items-center cursor-pointer"
@ -40,34 +52,67 @@ function handleNext() {
</div>
</template>
<template #default v-if="data.length">
<div>
<div class="bg-[var(--color-bg-result)]">
<div class="flex justify-between items-center p-2 pb-0 rounded-[8px] text-[16px] font-bold">
<span>数联网搜索结果</span>
<div class="flex items-center gap-3">
<div>{{ `${displayIndex + 1}/${data.length}` }}</div>
<el-button type="primary" size="small" @click="handleNext">下一个</el-button>
<!-- <div>{{ `${displayIndex + 1}/${data.length}` }}</div>
<el-button type="primary" size="small" @click="handleNext">下一个</el-button> -->
<!-- 关闭 -->
<SvgIcon icon-class="close" size="15px" />
</div>
</div>
<!-- 分割线 -->
<div class="h-[1px] w-full bg-[#494B51] my-[8px]"></div>
<div class="p-2 pt-0">
<div class="flex items-center w-full gap-3">
<div class="font-bold w-[75px] text-right flex-shrink-0">名称:</div>
<div class="text-[var(--color-text-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 class="p-2 pt-0">
<div class="flex items-center w-full gap-3">
<div class="font-bold w-[75px] text-right flex-shrink-0">名称:</div>
<div class="text-[var(--color-text-detail)] flex-1 break-words">
{{ displayIod.name }}
</div>
</div>
<div class="flex items-center w-full gap-3">
<div class="font-bold w-[75px] text-right flex-shrink-0">数据空间:</div>
<div class="text-[var(--color-text-detail)] lex-1 break-words">
{{ displayIod.data_space }}
</div>
</div>
<div class="flex items-center w-full gap-3">
<div class="font-bold w-[75px] text-right flex-shrink-0">DOID:</div>
<div class="text-[var(--color-text-detail)] lex-1 break-words">
{{ displayIod.doId }}
</div>
</div>
<div class="flex items-center w-full gap-3">
<div class="font-bold w-[75px] text-right flex-shrink-0">来源仓库:</div>
<div class="text-[var(--color-text-detail)] flex-1 break-words break-al">
{{ displayIod.fromRepo }}
</div>
</div>
<div>
<div
class="card-item w-[80px] h-[25px] flex justify-between items-center rounded-[25px] bg-[#b1b1b1] ml-auto px-2"
>
<div class="text-[14px] text-[#ffffff] font-medium">
{{ `${displayIndex + 1}/${data.length}` }}
</div>
<div class="flex items-center gap-1">
<svg-icon
icon-class="left"
size="15px"
@click="handlePrev"
class="cursor-pointer hover:opacity-70"
></svg-icon>
<svg-icon
icon-class="right"
size="15px"
@click="handleNext"
class="cursor-pointer hover:opacity-70"
></svg-icon>
</div>
</div>
</div>
</div>
</div>
</template>
</el-popover>

View File

@ -0,0 +1,861 @@
<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 ProcessCard from '../TaskProcess/ProcessCard.vue'
import ExecutePlan from './ExecutePlan.vue'
const emit = defineEmits<{
(e: 'refreshLine'): void
(el: 'setCurrentTask', task: IRawStepTask): void
}>()
const agentsStore = useAgentsStore()
const drawerVisible = ref(false)
const collaborationProcess = computed(() => {
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
})
//
const editMode = ref(false) //
const editMap = reactive<Record<string, boolean>>({}) //
const editBuffer = reactive<Record<string, string | undefined>>({}) //
function getProcessDescription(stepId: string, processId: string) {
const step = collaborationProcess.value.find(s => s.Id === stepId)
if (step) {
const process = step.TaskProcess.find(p => p.ID === processId)
return process?.Description || ''
}
return ''
}
function save() {
Object.keys(editMap).forEach(key => {
if (editMap[key]) {
const [stepId, processId] = key.split('-')
const value = editBuffer[key]
// value
if (value !== undefined && value !== null) {
// @ts-ignore - TypeScript
handleSaveEdit(stepId, processId, value)
}
}
})
editMode.value = false
}
function handleOpenEdit(stepId: string, processId: string) {
if (!editMode.value) return
const key = `${stepId}-${processId}`
editMap[key] = true
editBuffer[key] = getProcessDescription(stepId, processId)
}
function handleSaveEdit(stepId: string, processId: string, value: string) {
const key = `${stepId}-${processId}`
const step = collaborationProcess.value.find(s => s.Id === stepId)
if (step) {
const process = step.TaskProcess.find(p => p.ID === processId)
if (process) {
process.Description = value
}
}
editMap[key] = false
ElMessage.success('已保存(前端内存)')
}
const jsplumb = new Jsplumb('task-results-main', {
connector: {
type: BezierConnector.type,
options: { curviness: 30, stub: 10 }
}
})
// 线
let timer: ReturnType<typeof setInterval> | null = null
function handleCollapse() {
if (timer) {
clearInterval(timer)
}
timer = setInterval(() => {
jsplumb.repaintEverything()
emit('refreshLine')
}, 1) as ReturnType<typeof setInterval>
//
const timer1 = setTimeout(() => {
if (timer) {
clearInterval(timer)
timer = null
}
}, 3000)
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
if (timer1) {
clearInterval(timer1)
}
})
}
// 线
function createInternalLine(id?: string) {
const arr: ConnectArg[] = []
jsplumb.reset()
collaborationProcess.value.forEach(item => {
// 线
arr.push({
sourceId: `task-results-${item.Id}-0`,
targetId: `task-results-${item.Id}-1`,
anchor: [AnchorLocations.Left, AnchorLocations.Left]
})
collaborationProcess.value.forEach(jitem => {
// 线
if (item.InputObject_List!.includes(jitem.OutputObject ?? '')) {
arr.push({
sourceId: `task-results-${jitem.Id}-1`,
targetId: `task-results-${item.Id}-0`,
anchor: [AnchorLocations.Left, AnchorLocations.Left],
config: {
type: 'output'
}
})
}
// InputObject线
jitem.TaskProcess.forEach(i => {
if (i.ImportantInput?.includes(`InputObject:${item.OutputObject}`)) {
const color = getActionTypeDisplay(i.ActionType)?.color ?? ''
const sourceId = `task-results-${item.Id}-1`
const targetId = `task-results-${jitem.Id}-0-${i.ID}`
arr.push({
sourceId,
targetId,
anchor: [AnchorLocations.Right, AnchorLocations.Right],
config: {
stops: [
[0, color],
[1, color]
],
transparent: targetId !== id
}
})
}
})
})
// TaskProcess线
item.TaskProcess?.forEach(i => {
if (!i.ImportantInput?.length) {
return
}
item.TaskProcess?.forEach(i2 => {
if (i.ImportantInput.includes(`ActionResult:${i2.ID}`)) {
const color = getActionTypeDisplay(i.ActionType)?.color ?? ''
const sourceId = `task-results-${item.Id}-0-${i2.ID}`
const targetId = `task-results-${item.Id}-0-${i.ID}`
arr.push({
sourceId,
targetId,
anchor: [AnchorLocations.Right, AnchorLocations.Right],
config: {
stops: [
[0, color],
[1, color]
],
transparent: targetId !== id
}
})
}
})
})
})
jsplumb.connects(arr)
jsplumb.repaintEverything()
}
const loading = ref(false)
//
const editingOutputId = ref<string | null>(null)
const editingOutputContent = ref('')
//
const additionalOutputContents = ref<Record<string, string>>({})
async function handleRun() {
try {
loading.value = true
const d = await api.executePlan(agentsStore.agentRawPlan.data!)
agentsStore.setExecutePlan(d)
} finally {
loading.value = false
}
}
//
async function handleTaskProcess() {
drawerVisible.value = true
}
//
function startOutputEditing(output: string) {
editingOutputId.value = output
editingOutputContent.value = getAdditionalOutputContent(output) || ''
}
//
function saveOutputEditing() {
if (editingOutputId.value && editingOutputContent.value.trim()) {
additionalOutputContents.value[editingOutputId.value] = editingOutputContent.value.trim()
}
editingOutputId.value = null
editingOutputContent.value = ''
}
//
function cancelOutputEditing() {
editingOutputId.value = null
editingOutputContent.value = ''
}
//
function getAdditionalOutputContent(output: string) {
return additionalOutputContents.value[output] || ''
}
//
function handleOutputKeydown(event: KeyboardEvent) {
if (event.key === 'Enter') {
event.preventDefault()
saveOutputEditing()
} else if (event.key === 'Escape') {
editingOutputId.value = null
editingOutputContent.value = ''
}
}
//
const isScrolling = ref(false)
let scrollTimer: ReturnType<typeof setTimeout> | null = null
//
function handleScroll() {
isScrolling.value = true
emit('refreshLine')
//
if (scrollTimer) {
clearTimeout(scrollTimer)
}
jsplumb.repaintEverything()
//
scrollTimer = setTimeout(() => {
isScrolling.value = false
}, 300) as ReturnType<typeof setTimeout>
}
//
const handleMouseEnter = throttle(id => {
if (!isScrolling.value) {
createInternalLine(id)
}
}, 100)
const handleMouseLeave = throttle(() => {
if (!isScrolling.value) {
createInternalLine()
}
}, 100)
function clear() {
jsplumb.reset()
}
// ========== ==========
const buttonHoverState = ref(null) // null | 'process' | 'execute'
const handleProcessMouseEnter = () => {
buttonHoverState.value = 'process'
}
const handleExecuteMouseEnter = () => {
if (agentsStore.agentRawPlan.data) {
buttonHoverState.value = 'execute'
}
}
const handleButtonMouseLeave = () => {
setTimeout(() => {
buttonHoverState.value = null
}, 150)
}
//
const processBtnClass = computed(() => {
return buttonHoverState.value === 'process' ? 'ellipse' : 'circle'
})
const executeBtnClass = computed(() => {
//
if (buttonHoverState.value === 'process') {
return 'circle'
}
//
return agentsStore.agentRawPlan.data ? 'ellipse' : 'circle'
})
//
const showProcessText = computed(() => {
return buttonHoverState.value === 'process'
})
const showExecuteText = computed(() => {
//
if (buttonHoverState.value === 'process') return false
//
return agentsStore.agentRawPlan.data
})
//
const processBtnTitle = computed(() => {
return buttonHoverState.value === 'process' ? '任务过程' : '点击查看任务过程'
})
const executeBtnTitle = computed(() => {
return showExecuteText.value ? '任务执行' : '点击运行'
})
defineExpose({
createInternalLine,
clear
})
</script>
<template>
<div
class="h-full flex flex-col relative"
id="task-results"
:class="{ 'is-running': agentsStore.executePlan.length > 0 }"
>
<!-- 标题与执行按钮 -->
<div class="text-[18px] font-bold mb-[18px] flex justify-between items-center px-[20px]">
<span class="text-[var(--color-text-title-header)]">执行结果</span>
<div
class="flex items-center gap-[14px] task-button-group"
@mouseleave="handleButtonMouseLeave"
>
<!-- 任务过程按钮 -->
<el-button
:class="processBtnClass"
:color="variables.tertiary"
:title="processBtnTitle"
@mouseenter="handleProcessMouseEnter"
@click="handleTaskProcess"
>
<svg-icon icon-class="process" />
<span v-if="showProcessText" class="btn-text">任务过程</span>
</el-button>
<!-- 任务执行按钮 -->
<el-popover
:disabled="Boolean(agentsStore.agentRawPlan.data)"
title="请先输入要执行的任务"
:visible="showPopover"
@hide="showPopover = false"
>
<template #reference>
<el-button
:class="executeBtnClass"
:color="variables.tertiary"
:title="executeBtnTitle"
:disabled="!agentsStore.agentRawPlan.data"
@mouseenter="handleExecuteMouseEnter"
@click="handleRun"
>
<svg-icon icon-class="action" />
<span v-if="showExecuteText" class="btn-text">任务执行</span>
</el-button>
</template>
</el-popover>
<el-drawer
v-model="drawerVisible"
title="任务过程"
direction="rtl"
size="30%"
:destroy-on-close="false"
>
<!-- 头部工具栏 -->
<template #header>
<div class="drawer-header">
<span class="title">任务过程</span>
<!-- <el-button v-if="!editMode" text icon="Edit" @click="editMode = true" />
<el-button v-else text icon="Check" @click="save" /> -->
</div>
</template>
<el-scrollbar height="calc(100vh - 120px)">
<el-empty v-if="!collaborationProcess.length" description="暂无任务过程" />
<div v-else class="process-list">
<!-- 使用ProcessCard组件显示每个AgentSelection -->
<ProcessCard
v-for="step in collaborationProcess"
:key="step.Id"
:step="step"
@open-edit="handleOpenEdit"
@save-edit="handleSaveEdit"
/>
</div>
</el-scrollbar>
</el-drawer>
</div>
</div>
<!-- 内容 -->
<div
v-loading="agentsStore.agentRawPlan.loading"
class="flex-1 overflow-auto relative"
@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="true"
:id="`task-results-${item.Id}-0`"
@click="emit('setCurrentTask', item)"
>
<div class="text-[18px] mb-[15px]">{{ item.StepName }}</div>
<!-- 折叠面板 -->
<el-collapse @change="handleCollapse">
<el-collapse-item
v-for="item1 in item.TaskProcess"
:key="`task-results-${item.Id}-${item1.ID}`"
:name="`task-results-${item.Id}-${item1.ID}`"
:disabled="Boolean(!agentsStore.executePlan.length || loading)"
@mouseenter="() => handleMouseEnter(`task-results-${item.Id}-0-${item1.ID}`)"
@mouseleave="handleMouseLeave"
>
<template v-if="loading" #icon>
<SvgIcon icon-class="loading" size="20px" class="animate-spin" />
</template>
<template v-else-if="!agentsStore.executePlan.length" #icon>
<span></span>
</template>
<template #title>
<!-- 运行之前背景颜色是var(--color-bg-detail-list),运行之后背景颜色是var(--color-bg-detail-list-run) -->
<div
class="flex items-center gap-[15px] rounded-[20px]"
:class="{
'bg-[var(--color-bg-detail-list)]': !agentsStore.executePlan.length,
'bg-[var(--color-bg-detail-list-run)]': agentsStore.executePlan.length
}"
>
<!-- 右侧链接点 -->
<div
class="absolute right-0 top-1/2 transform -translate-y-1/2"
:id="`task-results-${item.Id}-0-${item1.ID}`"
></div>
<div
class="w-[41px] h-[41px] rounded-full flex items-center justify-center"
:style="{ background: getAgentMapIcon(item1.AgentName).color }"
>
<svg-icon
:icon-class="getAgentMapIcon(item1.AgentName).icon"
color="#fff"
size="24px"
/>
</div>
<div class="text-[16px]">
<span>{{ 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="true"
: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
v-for="(output, index) in agentsStore.additionalOutputs"
:key="`additional-output-${index}`"
class="card-item"
>
<!-- 空的流程卡片位置 -->
<div class="w-full"></div>
<!-- 额外产物的编辑卡片 -->
<el-card
class="card-item w-full relative output-object-card additional-output-card"
:shadow="false"
:id="`additional-output-results-${index}`"
>
<!-- 产物名称行 -->
<div class="text-[18px] mb-3">
{{ output }}
</div>
<!-- 编辑区域行 -->
<div class="additional-output-editor">
<div v-if="editingOutputId === output" class="w-full">
<!-- 编辑状态输入框 + 按钮 -->
<div class="flex flex-col gap-3">
<el-input
v-model="editingOutputContent"
type="textarea"
:autosize="{ minRows: 3, maxRows: 6 }"
placeholder="请输入产物内容"
@keydown="handleOutputKeydown"
class="output-editor"
size="small"
/>
<div class="flex justify-end gap-2">
<el-button @click="saveOutputEditing" type="primary" size="small" class="px-3">
</el-button>
<el-button @click="cancelOutputEditing" size="small" class="px-3">
×
</el-button>
</div>
</div>
</div>
<div v-else class="w-full">
<!-- 非编辑状态折叠区域 + 编辑按钮 -->
<div
class="flex items-center justify-between p-3 bg-[var(--color-bg-quinary)] rounded-[8px]"
>
<div
class="text-[14px] text-[var(--color-text-secondary)] output-content-display"
>
{{ getAdditionalOutputContent(output) || '暂无内容,点击编辑' }}
</div>
<el-button
@click="startOutputEditing(output)"
size="small"
type="primary"
plain
class="flex items-center gap-1"
>
<svg-icon icon-class="action" size="12px" />
<span>编辑</span>
</el-button>
</div>
</div>
</div>
</el-card>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
#task-results.is-running {
--color-bg-detail-list: var(--color-bg-detail-list-run); // 100 %
}
#task-results {
:deep(.el-collapse) {
border: none;
border-radius: 20px;
.el-collapse-item + .el-collapse-item {
margin-top: 10px;
}
.el-collapse-item__header {
border: none;
background: var(--color-bg-detail-list-run);
min-height: 41px;
line-height: 41px;
border-radius: 20px;
transition: border-radius 1ms;
position: relative;
.el-collapse-item__title {
background: var(--color-bg-detail-list);
border-radius: 20px;
}
.el-icon {
font-size: 20px;
font-weight: 900;
background: var(--color-bg-icon-rotate);
border-radius: 50px;
color: #d8d8d8;
}
&.is-active {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
.output-object {
.el-collapse-item__header {
background: none;
.el-collapse-item__title {
background: none;
}
}
.el-collapse-item__wrap {
background: none;
.card-item {
background: var(--color-bg-detail);
padding: 25px;
padding-top: 10px;
border-radius: 7px;
}
}
}
.el-collapse-item__wrap {
border: none;
background: var(--color-bg-detail-list);
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
}
}
:deep(.el-card) {
.el-card__body {
padding-right: 40px;
background-color: var(--color-bg-detail);
&:hover {
background-color: var(--color-card-bg-result-hover);
}
}
}
.output-object-card {
:deep(.el-card__body) {
padding-top: 0;
padding-bottom: 0;
padding-right: 0;
}
}
.active-card {
background: linear-gradient(var(--color-bg-tertiary), var(--color-bg-tertiary)) padding-box,
linear-gradient(to right, #00c8d2, #315ab4) border-box;
}
.card-item + .card-item {
margin-top: 10px;
}
.additional-output-card {
border: 1px dashed #dcdfe6;
opacity: 0.9;
box-shadow: var(--color-agent-list-hover-shadow);
&:hover {
border-color: #409eff;
opacity: 1;
}
:deep(.el-card__body) {
padding: 20px;
}
//
.el-collapse {
border: none;
.el-collapse-item {
.el-collapse-item__header {
background: var(--color-bg-detail);
min-height: 36px;
line-height: 36px;
border-radius: 8px;
.el-collapse-item__title {
background: transparent;
font-size: 14px;
padding-left: 0;
}
.el-icon {
font-size: 16px;
}
}
.el-collapse-item__wrap {
background: var(--color-bg-detail);
border-radius: 0 0 8px 8px;
}
}
}
}
//
.additional-output-editor {
.output-editor {
:deep(.el-textarea__inner) {
font-size: 14px;
color: var(--color-text-primary);
background: var(--color-bg-detail);
border: 1px solid #dcdfe6;
resize: none;
padding: 12px;
}
}
.output-content-display {
word-break: break-word;
white-space: pre-wrap;
transition: all 0.2s ease;
line-height: 1.5;
min-height: 20px;
&:hover {
background-color: rgba(0, 0, 0, 0.05);
border-color: #409eff;
opacity: 1;
}
}
//
.el-button {
font-weight: bold;
font-size: 16px;
border-radius: 4px;
&.el-button--small {
padding: 4px 12px;
}
}
}
// ========== ==========
.task-button-group {
.el-button {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
transition: all 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important;
overflow: hidden !important;
white-space: nowrap !important;
border: none !important;
color: var(--color-text-primary) !important;
position: relative;
background-color: var(--color-bg-tertiary);
&:hover {
transform: translateY(-2px) !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
filter: brightness(1.1) !important;
}
&.is-disabled {
opacity: 0.5;
cursor: not-allowed !important;
&:hover {
transform: none !important;
box-shadow: none !important;
filter: none !important;
}
}
}
//
.circle {
width: 40px !important;
height: 40px !important;
min-width: 40px !important;
padding: 0 !important;
border-radius: 50% !important;
.btn-text {
display: none !important;
}
}
//
.ellipse {
height: 40px !important;
border-radius: 20px !important;
padding: 0 16px !important;
gap: 8px;
.btn-text {
display: inline-block !important;
font-size: 14px;
font-weight: 500;
margin-left: 4px;
opacity: 1;
animation: fadeIn 0.3s ease forwards;
}
}
.btn-text {
font-size: 14px;
font-weight: 500;
margin-left: 4px;
opacity: 0;
animation: fadeIn 0.3s ease forwards;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(-5px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
}
}
</style>

View File

@ -9,7 +9,7 @@ import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/
import variables from '@/styles/variables.module.scss'
import { type IRawStepTask, useAgentsStore } from '@/stores'
import api from '@/api'
import ProcessCard from '../TaskProcess/ProcessCard.vue'
import ExecutePlan from './ExecutePlan.vue'
const emit = defineEmits<{
@ -18,21 +18,68 @@ const emit = defineEmits<{
}>()
const agentsStore = useAgentsStore()
const drawerVisible = ref(false)
const collaborationProcess = computed(() => {
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
})
//
const editMode = ref(false) //
const editMap = reactive<Record<string, boolean>>({}) //
const editBuffer = reactive<Record<string, string | undefined>>({}) //
function getProcessDescription(stepId: string, processId: string) {
const step = collaborationProcess.value.find(s => s.Id === stepId)
if (step) {
const process = step.TaskProcess.find(p => p.ID === processId)
return process?.Description || ''
}
return ''
}
function save() {
Object.keys(editMap).forEach(key => {
if (editMap[key]) {
const [stepId, processId] = key.split('-')
const value = editBuffer[key]
// value
if (value !== undefined && value !== null) {
// @ts-ignore - TypeScript
handleSaveEdit(stepId, processId, value)
}
}
})
editMode.value = false
}
function handleOpenEdit(stepId: string, processId: string) {
if (!editMode.value) return
const key = `${stepId}-${processId}`
editMap[key] = true
editBuffer[key] = getProcessDescription(stepId, processId)
}
function handleSaveEdit(stepId: string, processId: string, value: string) {
const key = `${stepId}-${processId}`
const step = collaborationProcess.value.find(s => s.Id === stepId)
if (step) {
const process = step.TaskProcess.find(p => p.ID === processId)
if (process) {
process.Description = value
}
}
editMap[key] = false
ElMessage.success('已保存(前端内存)')
}
const jsplumb = new Jsplumb('task-results-main', {
connector: {
type: BezierConnector.type,
options: { curviness: 30, stub: 10 },
},
options: { curviness: 30, stub: 10 }
}
})
// 线
let timer: number
let timer: ReturnType<typeof setInterval> | null = null
function handleCollapse() {
if (timer) {
clearInterval(timer)
@ -40,11 +87,14 @@ function handleCollapse() {
timer = setInterval(() => {
jsplumb.repaintEverything()
emit('refreshLine')
}, 1)
}, 1) as ReturnType<typeof setInterval>
//
const timer1 = setTimeout(() => {
clearInterval(timer)
if (timer) {
clearInterval(timer)
timer = null
}
}, 3000)
onUnmounted(() => {
@ -61,14 +111,14 @@ function handleCollapse() {
function createInternalLine(id?: string) {
const arr: ConnectArg[] = []
jsplumb.reset()
collaborationProcess.value.forEach((item) => {
collaborationProcess.value.forEach(item => {
// 线
arr.push({
sourceId: `task-results-${item.Id}-0`,
targetId: `task-results-${item.Id}-1`,
anchor: [AnchorLocations.Left, AnchorLocations.Left],
anchor: [AnchorLocations.Left, AnchorLocations.Left]
})
collaborationProcess.value.forEach((jitem) => {
collaborationProcess.value.forEach(jitem => {
// 线
if (item.InputObject_List!.includes(jitem.OutputObject ?? '')) {
arr.push({
@ -76,12 +126,12 @@ function createInternalLine(id?: string) {
targetId: `task-results-${item.Id}-0`,
anchor: [AnchorLocations.Left, AnchorLocations.Left],
config: {
type: 'output',
},
type: 'output'
}
})
}
// InputObject线
jitem.TaskProcess.forEach((i) => {
jitem.TaskProcess.forEach(i => {
if (i.ImportantInput?.includes(`InputObject:${item.OutputObject}`)) {
const color = getActionTypeDisplay(i.ActionType)?.color ?? ''
const sourceId = `task-results-${item.Id}-1`
@ -93,21 +143,21 @@ function createInternalLine(id?: string) {
config: {
stops: [
[0, color],
[1, color],
[1, color]
],
transparent: targetId !== id,
},
transparent: targetId !== id
}
})
}
})
})
// TaskProcess线
item.TaskProcess?.forEach((i) => {
item.TaskProcess?.forEach(i => {
if (!i.ImportantInput?.length) {
return
}
item.TaskProcess?.forEach((i2) => {
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}`
@ -119,10 +169,10 @@ function createInternalLine(id?: string) {
config: {
stops: [
[0, color],
[1, color],
[1, color]
],
transparent: targetId !== id,
},
transparent: targetId !== id
}
})
}
})
@ -134,6 +184,14 @@ function createInternalLine(id?: string) {
}
const loading = ref(false)
//
const editingOutputId = ref<string | null>(null)
const editingOutputContent = ref('')
//
const additionalOutputContents = ref<Record<string, string>>({})
async function handleRun() {
try {
loading.value = true
@ -144,9 +202,51 @@ async function handleRun() {
}
}
//
async function handleTaskProcess() {
drawerVisible.value = true
}
//
function startOutputEditing(output: string) {
editingOutputId.value = output
editingOutputContent.value = getAdditionalOutputContent(output) || ''
}
//
function saveOutputEditing() {
if (editingOutputId.value && editingOutputContent.value.trim()) {
additionalOutputContents.value[editingOutputId.value] = editingOutputContent.value.trim()
}
editingOutputId.value = null
editingOutputContent.value = ''
}
//
function cancelOutputEditing() {
editingOutputId.value = null
editingOutputContent.value = ''
}
//
function getAdditionalOutputContent(output: string) {
return additionalOutputContents.value[output] || ''
}
//
function handleOutputKeydown(event: KeyboardEvent) {
if (event.key === 'Enter') {
event.preventDefault()
saveOutputEditing()
} else if (event.key === 'Escape') {
editingOutputId.value = null
editingOutputContent.value = ''
}
}
//
const isScrolling = ref(false)
let scrollTimer: number
let scrollTimer: ReturnType<typeof setTimeout> | null = null
//
function handleScroll() {
@ -161,11 +261,11 @@ function handleScroll() {
//
scrollTimer = setTimeout(() => {
isScrolling.value = false
}, 300)
}, 300) as ReturnType<typeof setTimeout>
}
//
const handleMouseEnter = throttle((id) => {
const handleMouseEnter = throttle(id => {
if (!isScrolling.value) {
createInternalLine(id)
}
@ -181,34 +281,160 @@ function clear() {
jsplumb.reset()
}
// ========== ==========
const buttonHoverState = ref<'process' | 'execute' | null>(null)
let buttonHoverTimer: ReturnType<typeof setTimeout> | null = null
const handleProcessMouseEnter = () => {
if (buttonHoverTimer) {
clearTimeout(buttonHoverTimer)
buttonHoverTimer = null
}
buttonHoverState.value = 'process'
}
const handleExecuteMouseEnter = () => {
if (buttonHoverTimer) {
clearTimeout(buttonHoverTimer)
buttonHoverTimer = null
}
if (agentsStore.agentRawPlan.data) {
buttonHoverState.value = 'execute'
}
}
const handleButtonMouseLeave = () => {
//
if (buttonHoverTimer) {
clearTimeout(buttonHoverTimer)
}
buttonHoverTimer = setTimeout(() => {
buttonHoverState.value = null
}, 50) //
}
//
onUnmounted(() => {
if (buttonHoverTimer) {
clearTimeout(buttonHoverTimer)
}
})
//
const processBtnClass = computed(() => {
return buttonHoverState.value === 'process' ? 'ellipse' : 'circle'
})
const executeBtnClass = computed(() => {
//
if (buttonHoverState.value === 'process') {
return 'circle'
}
//
return agentsStore.agentRawPlan.data ? 'ellipse' : 'circle'
})
//
const showProcessText = computed(() => {
return buttonHoverState.value === 'process'
})
const showExecuteText = computed(() => {
//
if (buttonHoverState.value === 'process') return false
//
return agentsStore.agentRawPlan.data
})
//
const processBtnTitle = computed(() => {
return buttonHoverState.value === 'process' ? '任务过程' : '点击查看任务过程'
})
const executeBtnTitle = computed(() => {
return showExecuteText.value ? '任务执行' : '点击运行'
})
defineExpose({
createInternalLine,
clear,
clear
})
</script>
<template>
<div class="h-full flex flex-col relative" id="task-results">
<div
class="h-full flex flex-col relative"
id="task-results"
:class="{ 'is-running': agentsStore.executePlan.length > 0 }"
>
<!-- 标题与执行按钮 -->
<div class="text-[18px] font-bold mb-[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" />
<span class="text-[var(--color-text-title-header)]">执行结果</span>
<div
class="flex items-center gap-[14px] task-button-group"
@mouseleave="handleButtonMouseLeave"
>
<!-- 任务过程按钮 -->
<el-button
:class="processBtnClass"
:color="variables.tertiary"
:title="processBtnTitle"
@mouseenter="handleProcessMouseEnter"
@click="handleTaskProcess"
>
<svg-icon icon-class="process" />
<span v-if="showProcessText" class="btn-text">任务过程</span>
</el-button>
<el-popover :disabled="Boolean(agentsStore.agentRawPlan.data)" title="请先输入要执行的任务">
<!-- 任务执行按钮 -->
<el-popover
:disabled="Boolean(agentsStore.agentRawPlan.data)"
title="请先输入要执行的任务"
:visible="showPopover"
@hide="showPopover = false"
>
<template #reference>
<el-button
circle
:class="executeBtnClass"
:color="variables.tertiary"
title="点击运行"
:title="executeBtnTitle"
:disabled="!agentsStore.agentRawPlan.data"
@mouseenter="handleExecuteMouseEnter"
@click="handleRun"
>
<svg-icon icon-class="action" />
<span v-if="showExecuteText" class="btn-text">任务执行</span>
</el-button>
</template>
</el-popover>
<el-drawer
v-model="drawerVisible"
title="任务过程"
direction="rtl"
size="30%"
:destroy-on-close="false"
>
<!-- 头部工具栏 -->
<template #header>
<div class="drawer-header">
<span class="title">任务过程</span>
<!-- <el-button v-if="!editMode" text icon="Edit" @click="editMode = true" />
<el-button v-else text icon="Check" @click="save" /> -->
</div>
</template>
<el-scrollbar height="calc(100vh - 120px)">
<el-empty v-if="!collaborationProcess.length" description="暂无任务过程" />
<div v-else class="process-list">
<!-- 使用ProcessCard组件显示每个AgentSelection -->
<ProcessCard
v-for="step in collaborationProcess"
:key="step.Id"
:step="step"
@open-edit="handleOpenEdit"
@save-edit="handleSaveEdit"
/>
</div>
</el-scrollbar>
</el-drawer>
</div>
</div>
<!-- 内容 -->
@ -218,11 +444,12 @@ defineExpose({
@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"
:shadow="true"
:id="`task-results-${item.Id}-0`"
@click="emit('setCurrentTask', item)"
>
@ -244,7 +471,14 @@ defineExpose({
<span></span>
</template>
<template #title>
<div class="flex items-center gap-[15px]">
<!-- 运行之前背景颜色是var(--color-bg-detail-list),运行之后背景颜色是var(--color-bg-detail-list-run) -->
<div
class="flex items-center gap-[15px] rounded-[20px]"
:class="{
'bg-[var(--color-bg-detail-list)]': !agentsStore.executePlan.length,
'bg-[var(--color-bg-detail-list-run)]': agentsStore.executePlan.length
}"
>
<!-- 右侧链接点 -->
<div
class="absolute right-0 top-1/2 transform -translate-y-1/2"
@ -256,12 +490,19 @@ defineExpose({
>
<svg-icon
:icon-class="getAgentMapIcon(item1.AgentName).icon"
color="var(--color-text)"
color="#fff"
size="24px"
/>
</div>
<div class="text-[16px]">
<span>{{ item1.AgentName }}:&nbsp; &nbsp;</span>
<span
:class="{
'text-[var(--color-text-result-detail)]': !agentsStore.executePlan.length,
'text-[var(--color-text-result-detail-run)]':
agentsStore.executePlan.length
}"
>{{ item1.AgentName }}:&nbsp; &nbsp;</span
>
<span :style="{ color: getActionTypeDisplay(item1.ActionType)?.color }">
{{ getActionTypeDisplay(item1.ActionType)?.name }}
</span>
@ -279,7 +520,7 @@ defineExpose({
<el-card
class="card-item w-full relative output-object-card"
shadow="hover"
:shadow="true"
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
:id="`task-results-${item.Id}-1`"
@click="emit('setCurrentTask', item)"
@ -297,31 +538,114 @@ defineExpose({
<span></span>
</template>
<template #title>
<div class="text-[18px]">{{ item.OutputObject }}</div>
<div
class="text-[18px]"
:class="{
'text-[var(--color-text-result-detail)]': !agentsStore.executePlan.length,
'text-[var(--color-text-result-detail-run)]': agentsStore.executePlan.length
}"
>
{{ item.OutputObject }}
</div>
</template>
<ExecutePlan :node-id="item.OutputObject" :execute-plans="agentsStore.executePlan" />
<ExecutePlan
:node-id="item.OutputObject"
:execute-plans="agentsStore.executePlan"
/>
</el-collapse-item>
</el-collapse>
</el-card>
</div>
<!-- 额外产物的编辑卡片 -->
<div
v-for="(output, index) in agentsStore.additionalOutputs"
:key="`additional-output-${index}`"
class="card-item"
>
<!-- 空的流程卡片位置 -->
<div class="w-full"></div>
<!-- 额外产物的编辑卡片 -->
<el-card
class="card-item w-full relative output-object-card additional-output-card"
:shadow="false"
:id="`additional-output-results-${index}`"
>
<!-- 产物名称行 -->
<div class="text-[18px] mb-3">
{{ output }}
</div>
<!-- 编辑区域行 -->
<div class="additional-output-editor">
<div v-if="editingOutputId === output" class="w-full">
<!-- 编辑状态输入框 + 按钮 -->
<div class="flex flex-col gap-3">
<el-input
v-model="editingOutputContent"
type="textarea"
:autosize="{ minRows: 3, maxRows: 6 }"
placeholder="请输入产物内容"
@keydown="handleOutputKeydown"
class="output-editor"
size="small"
/>
<div class="flex justify-end gap-2">
<el-button @click="saveOutputEditing" type="primary" size="small" class="px-3">
</el-button>
<el-button @click="cancelOutputEditing" size="small" class="px-3">
×
</el-button>
</div>
</div>
</div>
<div v-else class="w-full">
<!-- 非编辑状态折叠区域 + 编辑按钮 -->
<div
class="flex items-center justify-between p-3 bg-[var(--color-bg-quinary)] rounded-[8px]"
>
<div
class="text-[14px] text-[var(--color-text-secondary)] output-content-display"
>
{{ getAdditionalOutputContent(output) || '暂无内容,点击编辑' }}
</div>
<el-button
@click="startOutputEditing(output)"
size="small"
type="primary"
plain
class="flex items-center gap-1"
>
<svg-icon icon-class="action" size="12px" />
<span>编辑</span>
</el-button>
</div>
</div>
</div>
</el-card>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
#task-results.is-running {
--color-bg-detail-list: var(--color-bg-detail-list-run); // 100 %
}
#task-results {
:deep(.el-collapse) {
border: none;
border-radius: 20px;
.el-collapse-item + .el-collapse-item {
margin-top: 10px;
}
.el-collapse-item__header {
border: none;
background: var(--color-bg-secondary);
background: var(--color-bg-detail-list-run);
min-height: 41px;
line-height: 41px;
border-radius: 20px;
@ -329,13 +653,16 @@ defineExpose({
position: relative;
.el-collapse-item__title {
background: var(--color-bg-secondary);
background: var(--color-bg-detail-list);
border-radius: 20px;
}
.el-icon {
font-size: 20px;
font-weight: bold;
font-weight: 900;
background: var(--color-bg-icon-rotate);
border-radius: 50px;
color: #d8d8d8;
}
&.is-active {
@ -357,7 +684,7 @@ defineExpose({
background: none;
.card-item {
background: var(--color-bg-secondary);
background: var(--color-bg-detail);
padding: 25px;
padding-top: 10px;
border-radius: 7px;
@ -367,7 +694,7 @@ defineExpose({
.el-collapse-item__wrap {
border: none;
background: var(--color-bg-secondary);
background: var(--color-bg-detail-list);
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
}
@ -376,6 +703,10 @@ defineExpose({
:deep(.el-card) {
.el-card__body {
padding-right: 40px;
background-color: var(--color-bg-detail);
&:hover {
background-color: var(--color-card-bg-result-hover);
}
}
}
@ -388,13 +719,176 @@ defineExpose({
}
.active-card {
background:
linear-gradient(var(--color-bg-tertiary), var(--color-bg-tertiary)) padding-box,
background: linear-gradient(var(--color-bg-tertiary), var(--color-bg-tertiary)) padding-box,
linear-gradient(to right, #00c8d2, #315ab4) border-box;
}
.card-item + .card-item {
margin-top: 10px;
}
.additional-output-card {
border: 1px dashed #dcdfe6;
opacity: 0.9;
box-shadow: var(--color-agent-list-hover-shadow);
&:hover {
border-color: #409eff;
opacity: 1;
}
:deep(.el-card__body) {
padding: 20px;
}
//
.el-collapse {
border: none;
.el-collapse-item {
.el-collapse-item__header {
background: var(--color-bg-detail);
min-height: 36px;
line-height: 36px;
border-radius: 8px;
.el-collapse-item__title {
background: transparent;
font-size: 14px;
padding-left: 0;
}
.el-icon {
font-size: 16px;
}
}
.el-collapse-item__wrap {
background: var(--color-bg-detail);
border-radius: 0 0 8px 8px;
}
}
}
}
//
.additional-output-editor {
.output-editor {
:deep(.el-textarea__inner) {
font-size: 14px;
color: var(--color-text-primary);
background: var(--color-bg-detail);
border: 1px solid #dcdfe6;
resize: none;
padding: 12px;
}
}
.output-content-display {
word-break: break-word;
white-space: pre-wrap;
transition: all 0.2s ease;
line-height: 1.5;
min-height: 20px;
&:hover {
background-color: rgba(0, 0, 0, 0.05);
border-color: #409eff;
opacity: 1;
}
}
//
.el-button {
font-weight: bold;
font-size: 16px;
border-radius: 4px;
&.el-button--small {
padding: 4px 12px;
}
}
}
// ========== ==========
.task-button-group {
.el-button {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
transition: all 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important;
overflow: hidden !important;
white-space: nowrap !important;
border: none !important;
color: var(--color-text-primary) !important;
position: relative;
background-color: var(--color-bg-tertiary);
&:hover {
transform: translateY(-2px) !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
filter: brightness(1.1) !important;
}
&.is-disabled {
opacity: 0.5;
cursor: not-allowed !important;
&:hover {
transform: none !important;
box-shadow: none !important;
filter: none !important;
}
}
}
//
.circle {
width: 40px !important;
height: 40px !important;
min-width: 40px !important;
padding: 0 !important;
border-radius: 50% !important;
.btn-text {
display: none !important;
}
}
//
.ellipse {
height: 40px !important;
border-radius: 20px !important;
padding: 0 16px !important;
gap: 8px;
.btn-text {
display: inline-block !important;
font-size: 14px;
font-weight: 500;
margin-left: 4px;
opacity: 1;
animation: fadeIn 0.3s ease forwards;
}
}
.btn-text {
font-size: 14px;
font-weight: 500;
margin-left: 4px;
opacity: 0;
animation: fadeIn 0.3s ease forwards;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(-5px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
}
}
</style>

View File

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

View File

@ -0,0 +1,284 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useAgentsStore } from '@/stores/modules/agents'
import { getAgentMapIcon } from '@/layout/components/config.ts'
import SvgIcon from '@/components/SvgIcon/index.vue'
const emit = defineEmits<{
(e: 'close'): void
}>()
const agentsStore = useAgentsStore()
//
const currentTaskAgents = computed(() => {
return agentsStore.currentTask?.AgentSelection || []
})
//
const agents = computed(() => agentsStore.agents)
// -
const selectedAgents = ref<string[]>([])
//
const initializeSelectedAgents = () => {
selectedAgents.value = [...currentTaskAgents.value]
}
//
const toggleSelectAgent = (agentName: string) => {
const index = selectedAgents.value.indexOf(agentName)
if (index > -1) {
//
selectedAgents.value.splice(index, 1)
} else {
//
selectedAgents.value.push(agentName)
}
}
//
const isAgentSelected = (agentName: string) => {
return selectedAgents.value.includes(agentName)
}
// -
const sortedAgents = computed(() => {
const selected = agents.value.filter(agent => selectedAgents.value.includes(agent.Name))
const unselected = agents.value.filter(agent => !selectedAgents.value.includes(agent.Name))
return [...selected, ...unselected]
})
// - 5
const colorLevels = [
{ level: 5, color: '#1d59dc', textColor: '#FFFFFF' }, //
{ level: 4, color: '#4a71c7', textColor: '#FFFFFF' }, //
{ level: 3, color: '#6c8ed7', textColor: '#333333' }, //
{ level: 2, color: '#9cb7f0', textColor: '#333333' }, //
{ level: 1, color: '#c8d9fc', textColor: '#333333' } //
]
//
const getAgentLevel = (agentIndex: number) => {
// 使
return ((agentIndex % 5) + 1) % 6
}
//
const getLevelConfig = (level: number) => {
return colorLevels.find(config => config.level === level) || colorLevels[0]
}
//
onMounted(() => {
initializeSelectedAgents()
})
//
const handleClose = () => {
emit('close')
}
</script>
<template>
<div class="agent-allocation">
<!-- 头部 -->
<div class="allocation-header">
<span class="header-title">智能体分配</span>
<el-button class="close-button" text circle @click="handleClose" title="关闭">
<span class="close-icon"></span>
</el-button>
</div>
<!-- 分割线 -->
<el-divider class="header-divider" />
<!-- 内容区 -->
<div class="allocation-content">
<!-- 智能体整体容器 -->
<div class="agents-container selectagent">
<div
v-for="(agent, index) in agents"
:key="agent.Name"
class="agent-item"
:title="agent.Name"
@click="toggleSelectAgent(agent.Name)"
>
<!-- 头像部分独立元素 -->
<div class="agent-avatar" :style="{ background: getAgentMapIcon(agent.Name).color }">
<SvgIcon :icon-class="getAgentMapIcon(agent.Name).icon" size="24px" color="#fff" />
</div>
<!-- 等级色值部分独立元素通过CSS定位跟随头像 -->
<div
class="color-level"
:style="{
backgroundColor: getLevelConfig(getAgentLevel(index)).color,
color: getLevelConfig(getAgentLevel(index)).textColor
}"
>
{{ getAgentLevel(index) }}
</div>
</div>
</div>
<!-- 空状态 -->
<div v-if="agents.length === 0" class="empty-state">
<el-empty description="暂无智能体数据" />
<div class="empty-tip">请先在智能体库中上传智能体信息</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.agent-allocation {
height: 100%;
display: flex;
flex-direction: column;
.allocation-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
.header-title {
font-size: 18px;
font-weight: bold;
color: var(--color-text-title);
}
.close-button {
padding: 4px;
border: none;
&:hover {
background-color: var(--color-bg-hover);
}
}
}
.header-divider {
margin: 0;
border-color: var(--color-border);
}
.allocation-content {
flex: 1;
padding: 30px 20px;
overflow-y: auto;
display: flex;
flex-direction: column;
align-items: flex-start; //
gap: 20px;
.agents-container {
display: flex;
gap: 0; //
justify-content: flex-start; //
align-items: flex-start; //
flex-wrap: wrap;
max-width: 100%;
overflow-x: auto;
.agent-item {
display: flex;
flex-direction: column;
align-items: center;
margin: 0 0px; // item
padding: 0; //
.agent-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid var(--color-border);
transition: transform 0.2s ease;
}
.color-level {
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-weight: bold;
transition: all 0.3s ease;
cursor: pointer;
margin-top: 8px; //
}
}
}
.empty-state {
text-align: center;
padding: 40px 0;
width: 100%;
.empty-tip {
margin-top: 16px;
color: var(--color-text-secondary);
font-size: 14px;
}
}
}
}
//
@media (max-width: 768px) {
.agent-allocation {
.allocation-content {
padding: 20px 16px;
gap: 16px;
.agents-container {
.agent-item {
margin: 0 6px;
.agent-avatar {
width: 40px;
height: 40px;
}
.color-level {
width: 40px;
height: 40px;
font-size: 14px;
margin-top: 8px; //
}
}
}
}
}
}
//
@media (max-width: 480px) {
.agent-allocation {
.allocation-content {
.agents-container {
.agent-item {
margin: 0 4px;
.agent-avatar {
width: 35px;
height: 35px;
}
.color-level {
width: 35px;
height: 35px;
font-size: 12px;
margin-top: 8px; //
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,505 @@
<script setup lang="ts">
import { computed, ref, onMounted, watch } from 'vue'
import { useAgentsStore } from '@/stores/modules/agents'
import { getAgentMapIcon } from '@/layout/components/config.ts'
import SvgIcon from '@/components/SvgIcon/index.vue'
const emit = defineEmits<{
(e: 'close'): void
}>()
const agentsStore = useAgentsStore()
//
const currentTaskAgents = computed(() => {
return agentsStore.currentTask?.AgentSelection || []
})
//
const agents = computed(() => agentsStore.agents)
// -
const selectedAgents = ref<string[]>([])
//
const initializeSelectedAgents = () => {
selectedAgents.value = [...currentTaskAgents.value]
}
//
const toggleSelectAgent = (agentName: string) => {
const index = selectedAgents.value.indexOf(agentName)
if (index > -1) {
//
selectedAgents.value.splice(index, 1)
} else {
//
selectedAgents.value.push(agentName)
}
}
//
const isAgentSelected = (agentName: string) => {
return selectedAgents.value.includes(agentName)
}
// -
const sortedAgents = computed(() => {
const selected = agents.value.filter(agent => selectedAgents.value.includes(agent.Name))
const unselected = agents.value.filter(agent => !selectedAgents.value.includes(agent.Name))
return [...selected, ...unselected]
})
// - 5
const colorLevels = [
{ level: 5, color: '#1d59dc', textColor: '#FFFFFF' }, //
{ level: 4, color: '#4a71c7', textColor: '#FFFFFF' }, //
{ level: 3, color: '#6c8ed7', textColor: '#333333' }, //
{ level: 2, color: '#9cb7f0', textColor: '#333333' }, //
{ level: 1, color: '#c8d9fc', textColor: '#333333' } //
]
//
const getAgentLevel = (agentIndex: number) => {
// 使
return ((agentIndex % 5) + 1) % 6
}
//
const getLevelConfig = (level: number) => {
return colorLevels.find(config => config.level === level) || colorLevels[0]
}
// ========== ==========
const ellipseBoxRef = ref<HTMLElement>()
const isEllipseVisible = ref(false)
const ellipseStyle = ref({
width: '0px',
height: '0px',
left: '0px',
top: '0px',
opacity: 0
})
//
const ellipseAnimationConfig = {
duration: 300,
easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)'
}
//
const updateEllipseBox = () => {
if (!ellipseBoxRef.value || selectedAgents.value.length === 0) {
isEllipseVisible.value = false
ellipseStyle.value.opacity = 0
return
}
//
const selectedAvatars = document.querySelectorAll('.agent-item.selected .agent-avatar')
if (selectedAvatars.length === 0) {
isEllipseVisible.value = false
ellipseStyle.value.opacity = 0
return
}
//
let minLeft = Infinity
let maxRight = -Infinity
let minTop = Infinity
let maxBottom = -Infinity
selectedAvatars.forEach(avatar => {
const rect = avatar.getBoundingClientRect()
const containerRect = ellipseBoxRef.value!.parentElement!.getBoundingClientRect()
const left = rect.left - containerRect.left
const right = rect.right - containerRect.left
const top = rect.top - containerRect.top
const bottom = rect.bottom - containerRect.top
minLeft = Math.min(minLeft, left)
maxRight = Math.max(maxRight, right)
minTop = Math.min(minTop, top)
maxBottom = Math.max(maxBottom, bottom)
})
//
const padding = 8
const width = maxRight - minLeft + padding * 2
const height = maxBottom - minTop + padding * 2
ellipseStyle.value = {
width: `${width}px`,
height: `${height}px`,
left: `${minLeft - padding}px`,
top: `${minTop - padding}px`,
opacity: 1
}
isEllipseVisible.value = true
}
//
watch(
selectedAgents,
() => {
setTimeout(() => {
updateEllipseBox()
}, 50) // DOM
},
{ deep: true }
)
//
onMounted(() => {
initializeSelectedAgents()
//
setTimeout(() => {
updateEllipseBox()
}, 100)
})
//
const handleClose = () => {
emit('close')
}
</script>
<template>
<div class="agent-allocation">
<!-- 头部 -->
<div class="allocation-header">
<span class="header-title">智能体分配</span>
<el-button class="close-button" text circle @click="handleClose" title="关闭">
<span class="close-icon"></span>
</el-button>
</div>
<!-- 分割线 -->
<el-divider class="header-divider" />
<!-- 内容区 -->
<div class="allocation-content">
<!-- 椭圆框容器 -->
<div class="ellipse-box-container">
<!-- 椭圆框动画元素 -->
<div
ref="ellipseBoxRef"
class="ellipse-box"
:class="{ visible: isEllipseVisible }"
:style="ellipseStyle"
/>
<!-- 智能体整体容器 -->
<div class="agents-container">
<div
v-for="(agent, index) in agents"
:key="agent.Name"
class="agent-item"
:class="{ selected: isAgentSelected(agent.Name) }"
:title="agent.Name"
@click="toggleSelectAgent(agent.Name)"
>
<!-- 头像部分独立元素 -->
<div
class="agent-avatar"
:style="{
background: getAgentMapIcon(agent.Name).color,
borderColor: isAgentSelected(agent.Name) ? '#409eff' : 'var(--color-border)'
}"
>
<SvgIcon :icon-class="getAgentMapIcon(agent.Name).icon" size="24px" color="#fff" />
<!-- 选中标记 -->
<div v-if="isAgentSelected(agent.Name)" class="selected-indicator">
<svg-icon icon-class="check" size="12px" color="#fff" />
</div>
</div>
<!-- 等级色值部分独立元素通过CSS定位跟随头像 -->
<div
class="color-level"
:style="{
backgroundColor: getLevelConfig(getAgentLevel(index)).color,
color: getLevelConfig(getAgentLevel(index)).textColor
}"
>
{{ getAgentLevel(index) }}
</div>
<!-- 智能体名称 -->
<div class="agent-name">{{ agent.Name }}</div>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-if="agents.length === 0" class="empty-state">
<el-empty description="暂无智能体数据" />
<div class="empty-tip">请先在智能体库中上传智能体信息</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.agent-allocation {
height: 100%;
display: flex;
flex-direction: column;
.allocation-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
.header-title {
font-size: 18px;
font-weight: bold;
color: var(--color-text-title);
}
.close-button {
padding: 4px;
border: none;
&:hover {
background-color: var(--color-bg-hover);
}
}
}
.header-divider {
margin: 0;
border-color: var(--color-border);
}
.allocation-content {
flex: 1;
padding: 30px 20px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 20px;
//
.ellipse-box-container {
position: relative;
width: 100%;
min-height: 150px;
//
.ellipse-box {
position: absolute;
border: 2px solid #409eff;
border-radius: 50px; //
background: linear-gradient(
135deg,
rgba(64, 158, 255, 0.1) 0%,
rgba(64, 158, 255, 0.05) 100%
);
pointer-events: none; // 穿
z-index: 1;
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
opacity: 0;
&.visible {
opacity: 1;
}
//
&::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
border: 2px solid rgba(64, 158, 255, 0.3);
border-radius: 50px;
animation: pulse 2s infinite;
z-index: -1;
}
}
}
.agents-container {
display: flex;
flex-wrap: wrap;
gap: 16px;
justify-content: flex-start;
position: relative;
z-index: 2; //
.agent-item {
display: flex;
flex-direction: column;
align-items: center;
width: 80px;
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
cursor: pointer;
//
&.selected {
transform: scale(1.05);
z-index: 10;
//
order: -1;
}
.agent-avatar {
width: 56px;
height: 56px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 3px solid var(--color-border);
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
&:hover {
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
//
.selected-indicator {
position: absolute;
bottom: -4px;
right: -4px;
width: 20px;
height: 20px;
background: #409eff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid white;
}
}
.color-level {
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: bold;
margin-top: 8px;
transition: all 0.3s ease;
}
.agent-name {
margin-top: 6px;
font-size: 12px;
text-align: center;
color: var(--color-text-secondary);
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.empty-state {
text-align: center;
padding: 40px 0;
width: 100%;
.empty-tip {
margin-top: 16px;
color: var(--color-text-secondary);
font-size: 14px;
}
}
}
}
//
@keyframes pulse {
0% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.5;
transform: scale(1.02);
}
100% {
opacity: 1;
transform: scale(1);
}
}
//
@media (max-width: 768px) {
.agent-allocation {
.allocation-content {
padding: 20px 16px;
gap: 16px;
.agents-container {
gap: 12px;
.agent-item {
width: 70px;
.agent-avatar {
width: 50px;
height: 50px;
}
.color-level {
width: 28px;
height: 28px;
font-size: 12px;
}
.agent-name {
font-size: 11px;
}
}
}
}
}
}
//
@media (max-width: 480px) {
.agent-allocation {
.allocation-content {
.agents-container {
gap: 8px;
.agent-item {
width: 65px;
.agent-avatar {
width: 46px;
height: 46px;
}
.color-level {
width: 26px;
height: 26px;
font-size: 11px;
}
.agent-name {
font-size: 10px;
}
}
}
}
}
}
</style>

View File

@ -3,10 +3,9 @@ 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 { computed, ref } from 'vue'
import { AnchorLocations } from '@jsplumb/browser-ui'
import MultiLineTooltip from '@/components/MultiLineTooltip/index.vue'
import Bg from './Bg.vue'
const emit = defineEmits<{
@ -26,6 +25,90 @@ const collaborationProcess = computed(() => {
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
})
//
const editingTaskId = ref<string | null>(null)
const editingContent = ref('')
//
const showAddOutputForm = ref(false)
const newOutputName = ref('')
//
const startEditing = (task: IRawStepTask) => {
if (!task.Id) {
console.warn('Task ID is missing, cannot start editing')
return
}
editingTaskId.value = task.Id
editingContent.value = task.TaskContent || ''
}
//
const saveEditing = () => {
if (editingTaskId.value && editingContent.value.trim()) {
const taskToUpdate = collaborationProcess.value.find(item => item.Id === editingTaskId.value)
if (taskToUpdate) {
taskToUpdate.TaskContent = editingContent.value.trim()
}
}
editingTaskId.value = null
editingContent.value = ''
}
//
const cancelEditing = () => {
editingTaskId.value = null
editingContent.value = ''
}
//
const handleKeydown = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
event.preventDefault()
saveEditing()
} else if (event.key === 'Escape') {
cancelEditing()
}
}
//
const showAddOutputDialog = () => {
showAddOutputForm.value = true
newOutputName.value = ''
}
//
const addNewOutput = () => {
if (newOutputName.value.trim()) {
const success = agentsStore.addNewOutput(newOutputName.value.trim())
if (success) {
showAddOutputForm.value = false
newOutputName.value = ''
}
}
}
//
const cancelAddOutput = () => {
showAddOutputForm.value = false
newOutputName.value = ''
}
//
const handleAddOutputKeydown = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
event.preventDefault()
addNewOutput()
} else if (event.key === 'Escape') {
cancelAddOutput()
}
}
//
const removeAdditionalOutput = (output: string) => {
agentsStore.removeAdditionalOutput(output)
}
function handleCurrentTask(task: IRawStepTask, transparent: boolean): ConnectArg[] {
// 线
const arr: ConnectArg[] = [
@ -34,14 +117,14 @@ function handleCurrentTask(task: IRawStepTask, transparent: boolean): ConnectArg
targetId: `task-syllabus-output-object-${task.Id}`,
anchor: [AnchorLocations.Right, AnchorLocations.Left],
config: {
transparent,
},
},
transparent
}
}
]
// 线
task.InputObject_List?.forEach((item) => {
const id = collaborationProcess.value.find((i) => i.OutputObject === item)?.Id
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}`,
@ -49,8 +132,8 @@ function handleCurrentTask(task: IRawStepTask, transparent: boolean): ConnectArg
anchor: [AnchorLocations.Left, AnchorLocations.Right],
config: {
type: 'output',
transparent,
},
transparent
}
})
}
})
@ -61,7 +144,7 @@ function handleCurrentTask(task: IRawStepTask, transparent: boolean): ConnectArg
function changeTask(task?: IRawStepTask, isEmit?: boolean) {
jsplumb.reset()
const arr: ConnectArg[] = []
agentsStore.agentRawPlan.data?.['Collaboration Process']?.forEach((item) => {
agentsStore.agentRawPlan.data?.['Collaboration Process']?.forEach(item => {
arr.push(...handleCurrentTask(item, item.Id !== task?.Id))
})
jsplumb.connects(arr)
@ -76,26 +159,32 @@ function clear() {
defineExpose({
changeTask,
clear,
clear
})
</script>
<template>
<div class="h-full flex flex-col">
<div class="text-[18px] font-bold mb-[18px]">任务大纲</div>
<div class="text-[18px] font-bold mb-[18px] text-[var(--color-text-title-header)]">
任务大纲
</div>
<div
v-loading="agentsStore.agentRawPlan.loading"
class="flex-1 w-full overflow-y-auto relative"
@scroll="handleScroll"
>
<div v-show="collaborationProcess.length > 0" class="w-full relative min-h-full" id="task-syllabus">
<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)]"
class="card-item w-[168px] h-[41px] flex justify-center relative z-99 items-center rounded-[20px] bg-[var(--color-bg-flow)]"
>
流程
</div>
@ -103,7 +192,7 @@ defineExpose({
<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)]"
class="card-item w-[168px] h-[41px] flex justify-center relative z-99 items-center rounded-[20px] bg-[var(--color-bg-flow)]"
>
产物
</div>
@ -113,31 +202,50 @@ defineExpose({
<div
v-for="item in collaborationProcess"
:key="item.Id"
class="card-item w-full flex items-center gap-[14%]"
class="card-it w-full flex items-center gap-[14%] bg-[var(--color-card-bg)]"
>
<!-- 流程卡片 -->
<el-card
class="w-[43%] overflow-y-auto relative z-99 task-syllabus-flow-card"
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
shadow="hover"
:shadow="true"
:id="`task-syllabus-flow-${item.Id}`"
@click="changeTask(item, true)"
>
<MultiLineTooltip placement="right" :text="item.StepName" :lines="2">
<div class="text-[18px] font-bold text-center">
{{ item.StepName }}
</div>
<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 class="h-[1px] w-full bg-[#d6d6d6] my-[8px]"></div>
<!-- 任务内容区域 - 支持双击编辑 -->
<div v-if="editingTaskId === item.Id" class="w-full">
<div class="flex flex-col gap-3">
<el-input
v-model="editingContent"
type="textarea"
:autosize="{ minRows: 2, maxRows: 4 }"
placeholder="请输入任务内容"
@keydown="handleKeydown"
class="task-content-editor"
size="small"
/>
<div class="flex justify-end gap-2">
<el-button @click="saveEditing" type="primary" size="small" class="px-3">
</el-button>
<el-button @click="cancelEditing" size="small" class="px-3"> × </el-button>
</div>
</div>
</MultiLineTooltip>
<div class="h-[1px] w-full bg-[#494B51] my-[8px]"></div>
</div>
<div v-else @dblclick="startEditing(item)" class="w-full cursor-pointer">
<MultiLineTooltip placement="right" :text="item.TaskContent" :lines="3">
<div class="text-[14px] text-[var(--color-text-secondary)] task-content-display">
{{ item.TaskContent }}
</div>
</MultiLineTooltip>
</div>
<div class="h-[1px] w-full bg-[#d6d6d6] my-[8px]"></div>
<div
class="flex items-center gap-2 overflow-y-auto flex-wrap relative w-full max-h-[72px]"
>
@ -157,9 +265,7 @@ defineExpose({
<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
}}
{{ item.TaskProcess.find(i => i.AgentName === agentSelection)?.Description }}
</div>
</div>
</template>
@ -169,29 +275,106 @@ defineExpose({
>
<svg-icon
:icon-class="getAgentMapIcon(agentSelection).icon"
color="var(--color-text)"
color="#fff"
size="24px"
/>
</div>
</el-tooltip>
</div>
</el-card>
<!-- 产物卡片 -->
<!-- 产物卡片 -->
<el-card
class="w-[43%] relative"
shadow="hover"
class="w-[43%] relative task-syllabus-output-object-card"
:shadow="true"
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
:id="`task-syllabus-output-object-${item.Id}`"
>
<div class="text-[18px] font-bold text-center">{{ item.OutputObject }}</div>
</el-card>
</div>
<!-- 额外的产物列表 -->
<!-- <div
v-for="(output, index) in agentsStore.additionalOutputs"
:key="`additional-${index}`"
class="card-item w-full flex items-center gap-[14%]"
> -->
<!-- 空的流程卡片位置 -->
<!-- <div class="w-[43%]"></div> -->
<!-- 额外产物卡片 -->
<!-- <el-card class="w-[43%] relative additional-output-card group" :shadow="false">
<div class="flex items-center justify-between">
<div class="text-[18px] font-bold text-center flex-1">{{ output }}</div>
<button
@click="removeAdditionalOutput(output)"
class="opacity-0 group-hover:opacity-100 text-red-500 hover:text-red-700 transition-all duration-200 ml-2 text-lg font-bold w-5 h-5 flex items-center justify-center rounded hover:bg-red-50"
>
×
</button>
</div>
</el-card>
</div> -->
<!-- 添加新产物按钮 -->
<!-- <div class="card-item w-full flex items-center gap-[14%] mt-[20px]"> -->
<!-- 空的流程卡片位置 -->
<!-- <div class="w-[43%]"></div> -->
<!-- 添加新产物按钮 -->
<!-- <div class="w-[43%] relative"> -->
<!-- <div v-if="!showAddOutputForm" class="add-output-btn">
<button
@click="showAddOutputDialog"
class="w-full h-[50px] border-2 border-dashed border-gray-300 rounded-lg flex items-center justify-center text-gray-500 hover:border-blue-500 hover:text-blue-500 transition-all duration-200 group"
>
<svg-icon icon-class="plus" size="20px" class="mr-2" />
<span class="text-sm font-medium"></span>
</button>
</div> -->
<!-- 添加产物表单 -->
<!-- <div v-else class="add-output-form">
<el-card class="w-full" shadow="hover">
<div class="p-3">
<el-input
v-model="newOutputName"
placeholder="输入产物名称"
size="small"
@keydown="handleAddOutputKeydown"
@blur="addNewOutput"
class="w-full mb-3"
/>
<div class="flex gap-2">
<el-button @click="addNewOutput" type="primary" size="small" class="flex-1">
确定
</el-button>
<el-button @click="cancelAddOutput" size="small" class="flex-1">
取消
</el-button>
</div>
</div>
</el-card>
</div> -->
<!-- </div> -->
<!-- </div> -->
<!-- <div>
<el-button type="info" size="medium" class="flex-1"> branch </el-button>
</div> -->
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.task-syllabus-flow-card {
background-color: var(--color-card-bg-task);
border: 1px solid var(--color-card-border-task);
box-sizing: border-box;
transition: border-color 0.2s ease;
// box-shadow: var(--color-card-shadow);
margin-bottom: 100px;
&:hover {
background-color: var(--color-card-bg-task-hover);
border-color: var(--color-card-border-hover);
box-shadow: var(--color-card-shadow-hover);
}
:deep(.el-card__body) {
height: 100%;
display: flex;
@ -201,4 +384,150 @@ defineExpose({
overflow: auto;
}
}
.task-syllabus-output-object-card {
background-color: var(--color-card-bg-task);
border: 1px solid var(--color-card-border-task);
box-sizing: border-box;
transition: border-color 0.2s ease;
// box-shadow: var(--color-card-shadow-hover);
&:hover {
background-color: var(--color-card-bg-task-hover);
border-color: var(--color-card-border-hover);
box-shadow: var(--color-card-shadow-hover);
}
:deep(.el-card__body) {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: auto;
}
}
.task-content-editor {
:deep(.el-textarea__inner) {
font-size: 14px;
color: var(--color-text-secondary);
background: transparent;
border: 1px solid #dcdfe6;
border-radius: 4px;
resize: none;
}
}
.task-content-display {
min-height: 40px;
word-break: break-word;
white-space: pre-wrap;
}
.add-output-btn {
opacity: 0.8;
transition: opacity 0.2s ease;
&:hover {
opacity: 1;
}
button {
background: transparent;
cursor: pointer;
&:hover {
background-color: rgba(59, 130, 246, 0.05);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
&:active {
transform: translateY(0);
}
}
}
.add-output-form {
animation: slideDown 0.3s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
//
:deep(.el-card__body) {
padding: 16px;
}
//
:deep(.el-input__wrapper) {
background: transparent;
border: 1px solid #dcdfe6;
border-radius: 4px;
box-shadow: none;
transition: all 0.2s ease;
&:hover {
border-color: #c0c4cc;
}
&.is-focus {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
}
:deep(.el-input__inner) {
color: var(--color-text-primary);
font-size: 14px;
background: transparent;
}
// -
.task-content-editor {
.el-button {
font-weight: bold;
font-size: 16px;
border-radius: 4px;
&.el-button--small {
padding: 4px 12px;
}
}
//
html.dark & {
.el-button {
&.el-button--primary {
background-color: var(--color-bg-detail);
border-color: var(--color-border);
color: var(--color-text);
&:hover {
background-color: var(--color-bg-hover);
border-color: var(--color-text-hover);
}
}
&:not(.el-button--primary) {
background-color: var(--color-bg-detail);
border-color: var(--color-border);
color: var(--color-text);
&:hover {
background-color: var(--color-bg-hover);
border-color: var(--color-text-hover);
}
}
}
}
}
</style>

View File

@ -5,7 +5,7 @@ import TaskResult from './TaskResult/index.vue'
import { Jsplumb } from './utils.ts'
import { type IRawStepTask, useAgentsStore } from '@/stores'
import { BezierConnector } from '@jsplumb/browser-ui'
import { ref } from 'vue'
const agentsStore = useAgentsStore()
//
@ -15,9 +15,9 @@ const agentRepoJsplumb = new Jsplumb('task-template', {
options: {
curviness: 30, // 线
stub: 20, //
alwaysRespectStubs: true,
},
},
alwaysRespectStubs: true
}
}
})
//
@ -32,18 +32,16 @@ const taskResultRef = ref<{
}>()
const taskResultJsplumb = new Jsplumb('task-template')
function scrollToElementTop(elementId: string) {
const element = document.getElementById(elementId);
const element = document.getElementById(elementId)
if (element) {
element.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
})
}
}
function handleTaskSyllabusCurrentTask(task: IRawStepTask) {
scrollToElementTop(`task-results-${task.Id}-0`)
agentsStore.setCurrentTask(task)
@ -76,13 +74,14 @@ function clear() {
defineExpose({
changeTask,
resetAgentRepoLine,
clear,
clear
})
</script>
<template>
<!-- 删除overflow-hidden -->
<div
class="task-template flex gap-6 items-center h-[calc(100%-84px)] relative overflow-hidden"
class="task-template flex gap-6 items-center h-[calc(100%-84px)] relative"
id="task-template"
>
<!-- 智能体库 -->
@ -111,10 +110,10 @@ defineExpose({
<style scoped lang="scss">
.task-template {
& > div {
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.8);
box-shadow: var(--color-card-shadow-three);
border-radius: 24px;
border: 1px solid #414752;
background: var(--color-bg-quinary);
border: 1px solid var(--color-card-border-three);
background: var(--color-bg-three);
padding-top: 20px;
padding-bottom: 20px;
}

View File

@ -87,8 +87,8 @@ export class Jsplumb {
const stops = _config.stops ?? this.getStops(config.type)
// 如果config.transparent为true则将stops都加一些透明度
if (config.transparent) {
stops[0][1] = stops[0][1] + '30'
stops[1][1] = stops[1][1] + '30'
stops[0][1] = stops[0][1] + '80'
stops[1][1] = stops[1][1] + '80'
}
if (targetElement && sourceElement) {
this.instance.connect({

View File

@ -1,9 +1,9 @@
<script setup lang="ts">
import Task from './Task.vue'
import TaskTemplate from './TaskTemplate/index.vue'
import { nextTick } from 'vue'
import { nextTick, ref } from 'vue'
const taskTemplateRef = ref<{ changeTask: () => void, clear: () => void }>()
const taskTemplateRef = ref<{ changeTask: () => void; clear: () => void }>()
function handleSearch() {
nextTick(() => {

View File

@ -113,6 +113,7 @@ export interface AgentMapDuty {
name: string
key: string
color: string
border: string
}
// 职责映射
@ -122,21 +123,25 @@ export const agentMapDuty: Record<string, AgentMapDuty> = {
name: '提议',
key: 'propose',
color: '#06A3FF',
border:'#0f95e4'
},
Critique: {
name: '评审',
key: 'review',
color: '#FFFC08',
color: '#2cd235',
border:'#19ab21'
},
Improve: {
name: '改进',
key: 'improve',
color: '#BF65FF',
color: '#ff8a01',
border:'#dc7700'
},
Finalize: {
name: '总结',
key: 'summary',
color: '#FFA236',
color: '#bf65ff',
border:'#a13de8'
},
}

View File

@ -1,10 +1,83 @@
<script setup lang="ts">
import Header from './components/Header.vue'
import Main from './components/Main/index.vue'
import { ref, onMounted } from 'vue'
const isDarkMode = ref(false)
//
const initTheme = () => {
const savedTheme = localStorage.getItem('theme')
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
if (savedTheme === 'dark' || (!savedTheme && prefersDark)) {
enableDarkMode()
} else {
enableLightMode()
}
}
//
const enableDarkMode = () => {
document.documentElement.classList.add('dark')
isDarkMode.value = true
localStorage.setItem('theme', 'dark')
}
//
const enableLightMode = () => {
document.documentElement.classList.remove('dark')
isDarkMode.value = false
localStorage.setItem('theme', 'light')
}
//
const toggleTheme = () => {
if (isDarkMode.value) {
enableLightMode()
} else {
enableDarkMode()
}
}
//
onMounted(() => {
initTheme()
})
</script>
<template>
<div class="h-full">
<div class="h-full relative">
<!-- 主题切换按钮 - 放置在页面右上角 -->
<div class="absolute top-4 right-4 z-50">
<button
@click="toggleTheme"
class="w-10 h-10 rounded-full transition-all duration-300 flex items-center justify-center"
:class="{
'bg-[#ebebeb] hover:bg-[var(--color-bg-hover)]': !isDarkMode,
'bg-[#0e131c] hover:bg-[var(--color-bg-hover)]': isDarkMode
}"
:title="isDarkMode ? '切换到浅色模式' : '切换到深色模式'"
>
<!-- 浅色模式图标 -->
<svg-icon
v-if="!isDarkMode"
icon-class="sunny"
size="20px"
:color="isDarkMode ? 'var(--color-text-title)' : 'var(--color-text-title)'"
class="transition-opacity duration-300"
/>
<!-- 深色模式图标 -->
<svg-icon
v-else
icon-class="moon"
size="20px"
:color="isDarkMode ? 'var(--color-text-title)' : 'var(--color-text-title)'"
class="transition-opacity duration-300"
/>
</button>
</div>
<Header />
<Main />
</div>

View File

@ -1,4 +1,4 @@
import { ref } from 'vue'
import { ref, watch } from 'vue'
import { defineStore } from 'pinia'
import { v4 as uuidv4 } from 'uuid'
@ -64,11 +64,10 @@ function clearStorageByVersion() {
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`, '')
@ -127,6 +126,36 @@ export const useAgentsStore = defineStore('agents', () => {
executePlan.value = []
}
// 额外的产物列表
const additionalOutputs = ref<string[]>([])
// 添加新产物
function addNewOutput(outputObject: string) {
if (!outputObject.trim()) return false
const trimmedOutput = outputObject.trim()
if (!additionalOutputs.value.includes(trimmedOutput)) {
additionalOutputs.value.push(trimmedOutput)
return true
}
return false
}
// 删除额外产物
function removeAdditionalOutput(outputObject: string) {
const index = additionalOutputs.value.indexOf(outputObject)
if (index > -1) {
additionalOutputs.value.splice(index, 1)
return true
}
return false
}
// 清除额外产物
function clearAdditionalOutputs() {
additionalOutputs.value = []
}
return {
agents,
setAgents,
@ -139,6 +168,10 @@ export const useAgentsStore = defineStore('agents', () => {
executePlan,
setExecutePlan,
resetAgent,
additionalOutputs,
addNewOutput,
removeAdditionalOutput,
clearAdditionalOutputs,
}
})

View File

@ -1,4 +1,5 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { readConfig } from '@/utils/readJson.ts'
import { store } from '@/stores'

View File

@ -1,16 +1,207 @@
// 浅色模式
:root {
--color-bg: #fff;
--color-bg-secondary: #fafafa;
--color-bg-tertiary: #f5f5f5;
--color-text: #000;
--color-bg: #ffffff;
--color-bg-secondary: #ffffff;
--color-bg-tertiary: #f0f0f0; //正确
--color-bg-quaternary: rgba(0, 0, 0, 11%);
--color-bg-quinary: #f7f7f7;
--color-text: #36404F;
// 标题文字颜色
--color-text-title-header: #262626;
--color-text-secondary: #4f4f4f;
--color-border-default: #e0e0e0;
--color-accent: #222222;
--color-accent-secondary: #315ab4;
// 鼠标悬浮背景色
--color-bg-hover: #f0f0f0;
// 鼠标悬浮字体颜色
--color-text-hover: #222222;
// 分割线颜色
--color-border: #D8D8d8;
// 内容区鼠标悬浮颜色
--color-bg-content-hover: #f0f0f0;
//列表背景颜色
--color-bg-list: #E1E1E1;
// 详情卡文字内容
--color-text-detail: #4f4f4f;
// 详情卡背景颜色
--color-bg-detail: #f7f7f7;
// 任务栏背景颜色
--color-bg-taskbar: #ffffff;
// 详情列表背景颜色
--color-bg-detail-list: rgba(230, 230, 230, 0.6) ;//#E6E6E6 60%
// 详情列表运行后背景颜色
--color-bg-detail-list-run: #e6e6e6; //运行后背景色#e6e6e6 100%
// 旋转图标背景颜色
--color-bg-icon-rotate: #a7a7a7;
// 智能体列表背景颜色
--color-agent-list-bg: #ededed; //背景色#fbfcff
// 智能体边框颜色
--color-agent-list-border: #E0E0E070; //边框#FFFFFF60 60%
//智能体鼠标悬浮颜色
--color-agent-list-hover-bg: #f7f7f7; //hover背景色#FFFFFF 100%
// 智能体鼠标悬浮边框颜色
--color-agent-list-hover-border: #e0e0e0; //hover边框#E0E0E0 100%
//智能体鼠标悬浮阴影
--color-agent-list-hover-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.2); //hover阴影 #FFFFFF 100%
//任务框下拉阴影
--color-task-shadow: 0px 10px 10px 5px rgba(0, 0, 0, 0.2);
// 卡片边框颜色
--color-card-border: #ececec;
// 卡片鼠标悬浮边框颜色
--color-card-border-hover: #ececec;
// 任务大纲卡片颜色
--color-card-bg-task: #ededed;
// 任务大纲卡片悬浮颜色
--color-card-bg-task-hover: #f7f7f7;
// 任务大纲卡片悬浮阴影效果
--color-card-shadow-hover: 0px 2px 4px 0px rgba(0, 0, 0, 0.2);
// 卡片阴影效果
--color-card-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.17);
// 任务大纲边框颜色
--color-card-border-task: #E0E0E0; //边框颜色#FFFFFF60
// 执行结果卡片颜色
--color-card-bg-result: #ececec;
// 执行结果边框颜色
--color-card-border-result: #ececec;
// 执行结果卡片悬浮颜色
--color-card-bg-result-hover: #ededed;
// 头部背景颜色
--color-header-bg: #ffffff;
// 头部字体颜色
--color-text-title: #2a3342;
//结果卡背景颜色
--color-bg-result: #eaedee;
// 任务字体背景颜色
--color-text-task: #36404F;
// 指示灯背景颜色
--color-bg-indicator: #f7f7f7;
// 三个卡片背景颜色
--color-bg-three: #fbfcff;
// 三个卡片边框颜色
--color-card-border-three: #FFFFFF60;
// 流程卡片背景颜色
--color-bg-flow: #f0f0f0;
// 三个卡片阴影
--color-card-shadow-three: 0px 0px 5px 0px rgba(0, 0, 0, 0.17);
// 头部阴影
--color-header-shadow: 0px 0px 5px 0px rgba(161, 161, 161, 0.5);
// 执行结果详情背景颜色
--color-bg-result-detail: #f7f7f7;
// 智能体库字体颜色
--color-text-agent-list: #636364;
// 智能体库字体悬浮颜色
--color-text-agent-list-hover: #222222;
// 任务栏字体颜色
--color-text-taskbar: #222222;
// 智能体卡片被选中后背景颜色
--color-agent-list-selected-bg: #f7f7f7; //选中后背景色#171b22 100%
// 结果卡片运行前颜色
--color-text-result-detail: rgba(0,0,0,0.6);
// 结果卡片运行后颜色
--color-text-result-detail-run: #000;
}
// 深色模式
html.dark {
--color-bg: #131A27;
--color-bg-secondary: #050505;
--color-bg-secondary: rgba(5, 5, 5, 0.4);
--color-bg-tertiary: #20222A;
--color-bg-quaternary: #24252A;
--color-bg-quinary: #29303c;
--color-text: #fff;
// 标题文字颜色
--color-text-title-header: #ffffff;
--color-text-secondary: #C9C9C9;
--color-border-default: #494B51;
--color-accent: #00F3FF;
--color-accent-secondary: #315ab4;
// 鼠标悬浮背景色
--color-bg-hover: #1c1e25;
// 鼠标悬浮字体颜色
--color-text-hover: #00f3ff;
// 分割线颜色
--color-border: #494B51;
// 内容区鼠标悬浮颜色
--color-bg-content-hover: #171b22;
// 列表背景颜色
--color-bg-list: #171B22;
// 详情卡文字内容
--color-text-detail: #ffffff;
// 详情卡背景颜色
--color-bg-detail: #20222a;
// 任务栏背景颜色
--color-bg-taskbar: #20222a;
// 详情列表背景颜色
--color-bg-detail-list: #050505; //运行后背景色#18191f 60%
//详情列表运行后背景颜色
--color-bg-detail-list-run: #050505;
// 旋转图标背景颜色
--color-bg-icon-rotate: #151619;
// 智能体列表背景颜色
--color-agent-list-bg: #1d222b; //背景色#181b20 100%
// 智能体边框颜色
--color-agent-list-border: rgba(8,8,8,0.6);
//智能体鼠标悬浮颜色
--color-agent-list-hover-bg: rgba(23,27,34,1); //hover背景色#171b22 100%
// 智能体鼠标悬浮边框颜色
--color-agent-list-hover-border: rgba(8,8,8,1); //hover边框#080808 100%
//智能体鼠标悬浮阴影
--color-agent-list-hover-shadow: 0 2 4 #00000050; //hover阴影 0 2 4 #00000050
//任务框下拉阴影
--color-task-shadow: 0px 10px 10px 5px rgba(0, 0, 0, 0.2);
// 卡片边框颜色
--color-card-border: #1a1a1a;
// 卡片鼠标悬浮边框颜色
--color-card-border-hover: #151515;
// 任务大纲卡片颜色
--color-card-bg-task: #20222a;
// 任务大纲卡片悬浮颜色
--color-card-bg-task-hover: #171b22;
// 任务大纲边框颜色
--color-card-border-task: #151515;
// 执行结果卡片颜色
--color-card-bg-result: #1a1a1a;
// 执行结果边框颜色
--color-card-border-result: #151515;
// 执行结果卡片悬浮颜色
--color-card-bg-result-hover: #171b22;
// 头部背景颜色
--color-header-bg: #050505;
// 标题头部字体颜色
--color-text-title: #2c72e7;
// 卡片阴影效果
--color-card-shadow: 0px 0px 5px 5px rgba(0, 0, 0, 0.17);
// 任务大纲卡片悬浮阴影效果
--color-card-shadow-hover: 0px 2px 4px 0px rgba(0, 0, 0, 0.2);
// 结果卡背景颜色
--color-bg-result: #0d1119;
// 任务字体背景颜色
--color-text-task: #d7d7d7;
// 指示灯背景颜色
--color-bg-indicator: #17181a;
// 三个卡片背景颜色
--color-bg-three: #29303c;
// 三个卡片边框颜色
--color-card-border-three: #393d42;
// 流程卡片背景颜色
--color-bg-flow: #1C222a;
// 三个卡片阴影
--color-card-shadow-three: 0px 0px 6px 0px rgba(0, 0, 0, 0.8);
// 头部阴影
--color-header-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.5);
// 执行结果详情背景颜色
--color-bg-result-detail: #24252a;
// 智能体库字体颜色
--color-text-agent-list: #969696;
// 智能体库字体悬浮颜色
--color-text-agent-list-hover: #b8b8b8;
// 任务栏字体颜色
--color-text-taskbar: #ffffff;
// 智能体卡片被选中后背景颜色
--color-agent-list-selected-bg: #20222a; //选中后背景色#171b22 100%
// 结果卡片运行前颜色
--color-text-result-detail: #6c6e72;
// 结果卡片运行后颜色
--color-text-result-detail-run: #fff;
}

View File

@ -4,6 +4,96 @@ $bg-tertiary: var(--color-bg-tertiary);
$bg-quaternary: var(--color-bg-quaternary);
$text: var(--color-text);
$text-secondary: var(--color-text-secondary);
// 鼠标悬浮
$bg-hover: var(--color-bg-hover);
// 字体悬浮
$text-hover: var(--color-text-hover);
// 分割线
$border: var(--color-border);
// 内容区鼠标悬浮
$bg-content-hover: var(--color-bg-content-hover);
// 列表背景颜色
$bg-list: var(--color-bg-list);
// 详情卡文字内容
$text-detail: var(--color-text-detail);
// 详情卡背景颜色
$bg-detail: var(--color-bg-detail);
// 任务栏背景颜色
$bg-taskbar: var(--color-bg-taskbar);
// 详情列表背景颜色
$bg-detail-list: var(--color-bg-detail-list);
// 旋转图标背景颜色
$bg-icon-rotate: var(--color-bg-icon-rotate);
// 智能体列表背景颜色
$agent-list-bg: var(--color-agent-list-bg);
// 智能体边框颜色
$agent-list-border: var(--color-agent-list-border);
//智能体鼠标悬浮颜色
$agent-list-hover-bg: var(--color-agent-list-hover-bg);
// 智能体鼠标悬浮边框颜色
$agent-list-hover-border: var(--color-agent-list-hover-border);
//智能体鼠标悬浮阴影
$agent-list-hover-shadow: var(--color-agent-list-hover-shadow);
//任务框下拉阴影
$task-shadow: var(--color-task-shadow);
// 卡片边框颜色
$card-border: var(--color-card-border);
// 卡片鼠标悬浮边框颜色
$card-border-hover: var(--color-card-border-hover);
// 标题文字颜色
$text-title: var(--color-text-title);
// 任务大纲卡片颜色
$card-bg-task: var(--color-card-bg-task);
// 任务大纲边框颜色
$card-border-task: var(--color-card-border-task);
// 执行结果卡片颜色
$card-bg-result: var(--color-card-bg-result);
// 执行结果边框颜色
$card-border-result: var(--color-card-border-result);
// 头部背景颜色
$header-bg: var(--color-header-bg);
// 头部字体颜色
$header-text-title: var(--color-text-title);
// 卡片阴影效果
$card-shadow: var(--color-card-shadow);
// 任务大纲卡片悬浮颜色
$card-bg-task-hover: var(--color-card-bg-task-hover);
// 任务大纲卡片悬浮阴影效果
$card-shadow-hover: var(--color-card-shadow-hover);
// 结果卡背景颜色
$bg-result: var(--color-bg-result);
// 执行结果卡片悬浮颜色
$card-bg-result-hover: var(--color-card-bg-result-hover);
// 详情列表运行后背景颜色
$bg-detail-list-run: var(--color-bg-detail-list-run);
// 任务字体背景颜色
$text-task: var(--color-text-task);
// 指示灯背景颜色
$bg-indicator: var(--color-bg-indicator);
// 三个卡片背景颜色
$bg-three: var(--color-bg-three);
// 流程卡片背景颜色
$bg-flow: var(--color-bg-flow);
// 三个卡片边框颜色
$card-border-three: var(--color-card-border-three);
// 三个卡片阴影
$card-shadow-three: var(--color-card-shadow-three);
// 头部阴影
$header-shadow: var(--color-header-shadow);
// 执行结果详情背景颜色
$bg-result-detail: var(--color-bg-result-detail);
// 智能体库字体颜色
$text-agent-list: var(--color-text-agent-list);
// 智能体库字体悬浮颜色
$text-agent-list-hover: var(--color-text-agent-list-hover);
// 任务栏字体颜色
$text-taskbar: var(--color-text-taskbar);
// 智能体卡片被选中后背景颜色
$agent-list-selected-bg: var(--color-agent-list-selected-bg); //选中后背景色#171b22 100%
// 结果卡片运行前颜色
$text-result-detail: var(--color-text-result-detail);
:export {
bg: $bg;
@ -12,4 +102,49 @@ $text-secondary: var(--color-text-secondary);
bg-quaternary: $bg-quaternary;
text: $text;
text-secondary: $text-secondary;
bg-hover:$bg-hover;
text-hover:$text-hover;
border: $border;
bg-content-hover:$bg-content-hover;
bg-list: $bg-list;
text-detail: $text-detail;
bg-detail: $bg-detail;
bg-taskbar: $bg-taskbar;
bg-detail-list: $bg-detail-list;
bg-icon-rotate:$bg-icon-rotate;
agent-list-bg: $agent-list-bg;
agent-list-border: $agent-list-border;
agent-list-hover-bg: $agent-list-hover-bg;
agent-list-hover-border: $agent-list-hover-border;
agent-list-hover-shadow: $agent-list-hover-shadow;
task-shadow: $task-shadow;
card-border: $card-border;
card-border-hover: $card-border-hover;
text-title: $text-title;
card-border: $card-border;
card-bg-task: $card-bg-task;
card-border-task: $card-border-task;
card-bg-result: $card-bg-result;
card-border-result: $card-border-result;
header-bg: $header-bg;
header-text-title: $header-text-title;
card-shadow: $card-shadow;
card-bg-task-hover: $card-bg-task-hover;
card-shadow-hover: $card-shadow-hover;
bg-result: $bg-result;
card-bg-result-hover: $card-bg-result-hover;
bg-detail-list-run: $bg-detail-list-run;
text-task: $text-task;
bg-indicator: $bg-indicator;
bg-three: $bg-three;
bg-flow: $bg-flow;
card-border-three: $card-border-three;
card-shadow-three: $card-shadow-three;
header-shadow: $header-shadow;
bg-result-detail: $bg-result-detail;
text-agent-list: $text-agent-list;
text-agent-list-hover: $text-agent-list-hover;
text-taskbar: $text-taskbar;
agent-list-selected-bg: $agent-list-selected-bg;
text-result-detail: $text-result-detail;
}

View File

@ -46,9 +46,9 @@ export default defineConfig({
'/api': {
changeOrigin: true,
// 接口地址
target: 'http://localhost:8000',
rewrite: (path: string) =>
path.replace(/^\/api/, ''),
target: 'http://82.157.183.212:21092',
// rewrite: (path: string) =>
// path.replace(/^\/api/, ''),
configure: (proxy, options) => {
console.log('Proxy configured:', options)
}