Compare commits
2 Commits
23db6fc4a1
...
c00c0072b8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c00c0072b8 | ||
|
|
6392301833 |
24
backend/.claude/settings.local.json
Normal 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": []
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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": [],
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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"
|
||||
]
|
||||
|
||||
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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"
|
||||
]
|
||||
|
||||
@ -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 "" # 返回空字符串而不是抛出异常
|
||||
|
||||
@ -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
|
||||
@ -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
@ -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
|
||||
@ -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()
|
||||
|
||||
159
backend/代码逻辑架构分析.md
Normal 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实现了一个完整的多智能体协作平台,通过精心设计的模块化架构,支持复杂任务的规划和执行。
|
||||
@ -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';
|
||||
|
||||
9
frontend/.claude/settings.local.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(npm run type-check:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
1
frontend/.env
Normal file
@ -0,0 +1 @@
|
||||
API_BASE=http://127.0.0.1:8000
|
||||
6
frontend/components.d.ts
vendored
@ -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
@ -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
BIN
frontend/public/logo.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
@ -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;
|
||||
}
|
||||
|
||||
1
frontend/src/assets/icons/agent-change.svg
Normal 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 |
1
frontend/src/assets/icons/close.svg
Normal 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 |
14
frontend/src/assets/icons/icons.svg
Normal 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 |
1
frontend/src/assets/icons/left.svg
Normal 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 |
1
frontend/src/assets/icons/moon.svg
Normal 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 |
1
frontend/src/assets/icons/process.svg
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
1
frontend/src/assets/icons/right.svg
Normal 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 |
1
frontend/src/assets/icons/sunny.svg
Normal 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 |
@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
iconClass: string
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 }}: </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>
|
||||
@ -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 }}: </span>
|
||||
<span
|
||||
:class="{
|
||||
'text-[var(--color-text-result-detail)]': !agentsStore.executePlan.length,
|
||||
'text-[var(--color-text-result-detail-run)]':
|
||||
agentsStore.executePlan.length
|
||||
}"
|
||||
>{{ item1.AgentName }}: </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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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'
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { readConfig } from '@/utils/readJson.ts'
|
||||
import { store } from '@/stores'
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||