Compare commits
1 Commits
web
...
23db6fc4a1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23db6fc4a1 |
@@ -1,17 +1,12 @@
|
||||
import asyncio
|
||||
|
||||
import httpx
|
||||
from openai import OpenAI, AsyncOpenAI, max_retries
|
||||
import openai
|
||||
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")
|
||||
yaml_data = {}
|
||||
try:
|
||||
with open(yaml_file, "r", encoding="utf-8") as file:
|
||||
yaml_data = yaml.safe_load(file)
|
||||
@@ -20,16 +15,11 @@ except Exception:
|
||||
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_MODEL = os.getenv("OPENAI_API_MODEL") or yaml_data.get(
|
||||
"OPENAI_API_MODEL", ""
|
||||
)
|
||||
|
||||
# Initialize OpenAI clients
|
||||
client = OpenAI(api_key=OPENAI_API_KEY, base_url=OPENAI_API_BASE)
|
||||
async_client = AsyncOpenAI(api_key=OPENAI_API_KEY, base_url=OPENAI_API_BASE)
|
||||
openai.api_key = OPENAI_API_KEY
|
||||
MODEL: str = os.getenv("OPENAI_API_MODEL") or yaml_data.get(
|
||||
"OPENAI_API_MODEL", "gpt-4-turbo-preview"
|
||||
)
|
||||
@@ -46,12 +36,30 @@ MISTRAL_API_KEY = os.getenv("MISTRAL_API_KEY") or yaml_data.get(
|
||||
|
||||
# for LLM completion
|
||||
def LLM_Completion(
|
||||
messages: list[dict], stream: bool = True, useGroq: bool = True,model_config: dict = None
|
||||
messages: list[dict], stream: bool = True, useGroq: bool = True
|
||||
) -> str:
|
||||
if model_config:
|
||||
print_colored(f"Using model config: {model_config}", "blue")
|
||||
return _call_with_custom_config(messages,stream,model_config)
|
||||
if not useGroq or not FAST_DESIGN_MODE:
|
||||
# 增强消息验证:确保所有消息的 role 和 content 非空且不是空白字符串
|
||||
if not messages or len(messages) == 0:
|
||||
raise ValueError("Messages list is empty")
|
||||
|
||||
# print(f"[DEBUG] LLM_Completion received {len(messages)} messages", flush=True)
|
||||
|
||||
for i, msg in enumerate(messages):
|
||||
if not isinstance(msg, dict):
|
||||
raise ValueError(f"Message at index {i} is not a dictionary")
|
||||
if not msg.get("role") or str(msg.get("role")).strip() == "":
|
||||
raise ValueError(f"Message at index {i} has empty 'role'")
|
||||
if not msg.get("content") or str(msg.get("content")).strip() == "":
|
||||
raise ValueError(f"Message at index {i} has empty 'content'")
|
||||
|
||||
# 额外验证:确保content不会因为格式化问题变成空
|
||||
content = str(msg.get("content")).strip()
|
||||
if len(content) < 10: # 设置最小长度阈值
|
||||
print(f"[WARNING] Message at index {i} has very short content: '{content}'", flush=True)
|
||||
# 修改1
|
||||
if not GROQ_API_KEY:
|
||||
useGroq = False
|
||||
elif not useGroq or not FAST_DESIGN_MODE:
|
||||
force_gpt4 = True
|
||||
useGroq = False
|
||||
else:
|
||||
@@ -83,107 +91,16 @@ def LLM_Completion(
|
||||
return _chat_completion(messages=messages)
|
||||
|
||||
|
||||
def _call_with_custom_config(messages: list[dict], stream: bool, model_config: dict) ->str:
|
||||
"使用自定义配置调用API"
|
||||
api_url = model_config.get("apiUrl", OPENAI_API_BASE)
|
||||
api_key = model_config.get("apiKey", OPENAI_API_KEY)
|
||||
api_model = model_config.get("apiModel", OPENAI_API_MODEL)
|
||||
|
||||
temp_client = OpenAI(api_key=api_key, base_url=api_url)
|
||||
temp_async_client = AsyncOpenAI(api_key=api_key, base_url=api_url)
|
||||
|
||||
try:
|
||||
if stream:
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
except RuntimeError as ex:
|
||||
if "There is no current event loop in thread" in str(ex):
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
return loop.run_until_complete(
|
||||
_achat_completion_stream_custom(messages=messages, temp_async_client=temp_async_client, api_model=api_model)
|
||||
)
|
||||
else:
|
||||
response = temp_client.chat.completions.create(
|
||||
messages=messages,
|
||||
model=api_model,
|
||||
temperature=0.3,
|
||||
max_tokens=4096,
|
||||
timeout=180
|
||||
|
||||
)
|
||||
# 检查响应是否有效
|
||||
if not response.choices or len(response.choices) == 0:
|
||||
raise Exception(f"API returned empty response for model {api_model}")
|
||||
if not response.choices[0] or not response.choices[0].message:
|
||||
raise Exception(f"API returned invalid response format for model {api_model}")
|
||||
|
||||
full_reply_content = response.choices[0].message.content
|
||||
if full_reply_content is None:
|
||||
raise Exception(f"API returned None content for model {api_model}")
|
||||
|
||||
print(colored(full_reply_content, "blue", "on_white"), end="")
|
||||
return full_reply_content
|
||||
except Exception as e:
|
||||
print_colored(f"Custom API error for model {api_model} :{str(e)}","red")
|
||||
raise
|
||||
|
||||
|
||||
async def _achat_completion_stream_custom(messages:list[dict], temp_async_client, api_model: str ) -> str:
|
||||
max_retries=3
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
response = await temp_async_client.chat.completions.create(
|
||||
messages=messages,
|
||||
model=api_model,
|
||||
temperature=0.3,
|
||||
max_tokens=4096,
|
||||
stream=True,
|
||||
timeout=180
|
||||
)
|
||||
|
||||
collected_chunks = []
|
||||
collected_messages = []
|
||||
async for chunk in response:
|
||||
collected_chunks.append(chunk)
|
||||
choices = chunk.choices
|
||||
if len(choices) > 0 and choices[0] is not None:
|
||||
chunk_message = choices[0].delta
|
||||
if chunk_message is not None:
|
||||
collected_messages.append(chunk_message)
|
||||
if chunk_message.content:
|
||||
print(colored(chunk_message.content, "blue", "on_white"), end="")
|
||||
print()
|
||||
full_reply_content = "".join(
|
||||
[m.content or "" for m in collected_messages if m is not None]
|
||||
)
|
||||
|
||||
# 检查最终结果是否为空
|
||||
if not full_reply_content or full_reply_content.strip() == "":
|
||||
raise Exception(f"Stream API returned empty content for model {api_model}")
|
||||
|
||||
return full_reply_content
|
||||
except httpx.RemoteProtocolError as e:
|
||||
if attempt < max_retries - 1:
|
||||
wait_time = (attempt + 1) *2
|
||||
print_colored(f"⚠️ Stream connection interrupted (attempt {attempt+1}/{max_retries}). Retrying in {wait_time}s...", text_color="yellow")
|
||||
await asyncio.sleep(wait_time)
|
||||
continue
|
||||
except Exception as e:
|
||||
print_colored(f"Custom API stream error for model {api_model} :{str(e)}","red")
|
||||
raise
|
||||
|
||||
|
||||
async def _achat_completion_stream_groq(messages: list[dict]) -> str:
|
||||
from groq import AsyncGroq
|
||||
groq_client = AsyncGroq(api_key=GROQ_API_KEY)
|
||||
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:
|
||||
response = await groq_client.chat.completions.create(
|
||||
stream = await client.chat.completions.create(
|
||||
messages=messages,
|
||||
# model='gemma-7b-it',
|
||||
model="mixtral-8x7b-32768",
|
||||
@@ -197,18 +114,9 @@ async def _achat_completion_stream_groq(messages: list[dict]) -> str:
|
||||
if attempt < max_attempts - 1: # i is zero indexed
|
||||
continue
|
||||
else:
|
||||
raise Exception("failed")
|
||||
|
||||
# 检查响应是否有效
|
||||
if not response.choices or len(response.choices) == 0:
|
||||
raise Exception("Groq API returned empty response")
|
||||
if not response.choices[0] or not response.choices[0].message:
|
||||
raise Exception("Groq API returned invalid response format")
|
||||
|
||||
full_reply_content = response.choices[0].message.content
|
||||
if full_reply_content is None:
|
||||
raise Exception("Groq API returned None content")
|
||||
raise "failed"
|
||||
|
||||
full_reply_content = stream.choices[0].message.content
|
||||
print(colored(full_reply_content, "blue", "on_white"), end="")
|
||||
print()
|
||||
return full_reply_content
|
||||
@@ -217,14 +125,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
|
||||
mistral_client = MistralClient(api_key=MISTRAL_API_KEY)
|
||||
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 = mistral_client.chat(
|
||||
stream = client.chat(
|
||||
messages=[
|
||||
ChatMessage(
|
||||
role=message["role"], content=message["content"]
|
||||
@@ -233,38 +141,41 @@ 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 Exception("failed")
|
||||
|
||||
# 检查响应是否有效
|
||||
if not stream.choices or len(stream.choices) == 0:
|
||||
raise Exception("Mistral API returned empty response")
|
||||
if not stream.choices[0] or not stream.choices[0].message:
|
||||
raise Exception("Mistral API returned invalid response format")
|
||||
raise "failed"
|
||||
|
||||
full_reply_content = stream.choices[0].message.content
|
||||
if full_reply_content is None:
|
||||
raise Exception("Mistral API returned None content")
|
||||
|
||||
print(colored(full_reply_content, "blue", "on_white"), end="")
|
||||
print()
|
||||
return full_reply_content
|
||||
|
||||
|
||||
async def _achat_completion_stream_gpt35(messages: list[dict]) -> str:
|
||||
response = await async_client.chat.completions.create(
|
||||
messages=messages,
|
||||
max_tokens=4096,
|
||||
temperature=0.3,
|
||||
timeout=600,
|
||||
model="gpt-3.5-turbo-16k",
|
||||
stream=True,
|
||||
)
|
||||
openai.api_key = OPENAI_API_KEY
|
||||
openai.api_base = OPENAI_API_BASE
|
||||
|
||||
kwargs = {
|
||||
"messages": messages,
|
||||
"max_tokens": 4096,
|
||||
"n": 1,
|
||||
"stop": None,
|
||||
"temperature": 0.3,
|
||||
"timeout": 3,
|
||||
"model": "gpt-3.5-turbo-16k",
|
||||
"stream": True,
|
||||
}
|
||||
# print("[DEBUG] about to call acreate with kwargs:", type(kwargs), kwargs)
|
||||
assert kwargs is not None, "kwargs is None right before acreate!"
|
||||
assert isinstance(kwargs, dict), "kwargs must be dict!"
|
||||
response = await openai.ChatCompletion.acreate(**kwargs)
|
||||
|
||||
|
||||
|
||||
# create variables to collect the stream of chunks
|
||||
collected_chunks = []
|
||||
@@ -272,38 +183,40 @@ async def _achat_completion_stream_gpt35(messages: list[dict]) -> str:
|
||||
# iterate through the stream of events
|
||||
async for chunk in response:
|
||||
collected_chunks.append(chunk) # save the event response
|
||||
choices = chunk.choices
|
||||
if len(choices) > 0 and choices[0] is not None:
|
||||
chunk_message = choices[0].delta
|
||||
if chunk_message is not None:
|
||||
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 chunk_message.content:
|
||||
if "content" in chunk_message:
|
||||
print(
|
||||
colored(chunk_message.content, "blue", "on_white"),
|
||||
colored(chunk_message["content"], "blue", "on_white"),
|
||||
end="",
|
||||
)
|
||||
print()
|
||||
|
||||
full_reply_content = "".join(
|
||||
[m.content or "" for m in collected_messages if m is not None]
|
||||
[m.get("content", "") for m in collected_messages]
|
||||
)
|
||||
|
||||
# 检查最终结果是否为空
|
||||
if not full_reply_content or full_reply_content.strip() == "":
|
||||
raise Exception("Stream API (gpt-3.5) returned empty content")
|
||||
|
||||
return full_reply_content
|
||||
|
||||
|
||||
def _achat_completion_json(messages: list[dict] ) -> str:
|
||||
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:
|
||||
response = async_client.chat.completions.create(
|
||||
stream = await openai.ChatCompletion.acreate(
|
||||
messages=messages,
|
||||
max_tokens=4096,
|
||||
n=1,
|
||||
stop=None,
|
||||
temperature=0.3,
|
||||
timeout=600,
|
||||
timeout=3,
|
||||
model=MODEL,
|
||||
response_format={"type": "json_object"},
|
||||
)
|
||||
@@ -312,26 +225,21 @@ def _achat_completion_json(messages: list[dict] ) -> str:
|
||||
if attempt < max_attempts - 1: # i is zero indexed
|
||||
continue
|
||||
else:
|
||||
raise Exception("failed")
|
||||
|
||||
# 检查响应是否有效
|
||||
if not response.choices or len(response.choices) == 0:
|
||||
raise Exception("OpenAI API returned empty response")
|
||||
if not response.choices[0] or not response.choices[0].message:
|
||||
raise Exception("OpenAI API returned invalid response format")
|
||||
|
||||
full_reply_content = response.choices[0].message.content
|
||||
if full_reply_content is None:
|
||||
raise Exception("OpenAI API returned None content")
|
||||
raise "failed"
|
||||
|
||||
full_reply_content = stream.choices[0].message.content
|
||||
print(colored(full_reply_content, "blue", "on_white"), end="")
|
||||
print()
|
||||
return full_reply_content
|
||||
|
||||
|
||||
async def _achat_completion_stream(messages: list[dict]) -> str:
|
||||
try:
|
||||
response = await async_client.chat.completions.create(
|
||||
# print(">>>> _achat_completion_stream 被调用", flush=True)
|
||||
# print(">>>> messages 实参 =", messages, flush=True)
|
||||
# print(">>>> messages 类型 =", type(messages), flush=True)
|
||||
openai.api_key = OPENAI_API_KEY
|
||||
openai.api_base = OPENAI_API_BASE
|
||||
response = await openai.ChatCompletion.acreate(
|
||||
**_cons_kwargs(messages), stream=True
|
||||
)
|
||||
|
||||
@@ -341,59 +249,83 @@ async def _achat_completion_stream(messages: list[dict]) -> str:
|
||||
# iterate through the stream of events
|
||||
async for chunk in response:
|
||||
collected_chunks.append(chunk) # save the event response
|
||||
choices = chunk.choices
|
||||
if len(choices) > 0 and choices[0] is not None:
|
||||
chunk_message = choices[0].delta
|
||||
if chunk_message is not None:
|
||||
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 chunk_message.content:
|
||||
if "content" in chunk_message:
|
||||
print(
|
||||
colored(chunk_message.content, "blue", "on_white"),
|
||||
colored(chunk_message["content"], "blue", "on_white"),
|
||||
end="",
|
||||
)
|
||||
print()
|
||||
|
||||
full_reply_content = "".join(
|
||||
[m.content or "" for m in collected_messages if m is not None]
|
||||
[m.get("content", "") for m in collected_messages]
|
||||
)
|
||||
|
||||
# 检查最终结果是否为空
|
||||
if not full_reply_content or full_reply_content.strip() == "":
|
||||
raise Exception("Stream API returned empty content")
|
||||
|
||||
return full_reply_content
|
||||
except Exception as e:
|
||||
print_colored(f"OpenAI API error in _achat_completion_stream: {str(e)}", "red")
|
||||
raise
|
||||
|
||||
|
||||
def _chat_completion(messages: list[dict]) -> str:
|
||||
try:
|
||||
rsp = client.chat.completions.create(**_cons_kwargs(messages))
|
||||
|
||||
# 检查响应是否有效
|
||||
if not rsp.choices or len(rsp.choices) == 0:
|
||||
raise Exception("OpenAI API returned empty response")
|
||||
if not rsp.choices[0] or not rsp.choices[0].message:
|
||||
raise Exception("OpenAI API returned invalid response format")
|
||||
|
||||
content = rsp.choices[0].message.content
|
||||
if content is None:
|
||||
raise Exception("OpenAI API returned None content")
|
||||
|
||||
rsp = openai.ChatCompletion.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": 2000,
|
||||
"temperature": 0.3,
|
||||
"timeout": 600,
|
||||
"max_tokens": 4096,
|
||||
"temperature": 0.5,
|
||||
}
|
||||
kwargs_mode = {"model": MODEL}
|
||||
kwargs.update(kwargs_mode)
|
||||
print("[DEBUG] kwargs =", kwargs)
|
||||
assert isinstance(kwargs, dict), f"_cons_kwargs returned {type(kwargs)}, must be dict"
|
||||
|
||||
# 添加调试信息
|
||||
print(f'[DEBUG] _cons_kwargs messages: {messages}', flush=True)
|
||||
|
||||
# 检查并修复消息中的null值
|
||||
for i, msg in enumerate(messages):
|
||||
# 确保msg是字典
|
||||
if not isinstance(msg, dict):
|
||||
print(f"[ERROR] Message {i} is not a dictionary: {msg}", flush=True)
|
||||
messages[i] = {"role": "user", "content": str(msg) if msg is not None else ""}
|
||||
continue
|
||||
|
||||
# 确保role和content存在且不为None
|
||||
if "role" not in msg or msg["role"] is None:
|
||||
print(f"[ERROR] Message {i} missing role, setting to 'user'", flush=True)
|
||||
msg["role"] = "user"
|
||||
else:
|
||||
msg["role"] = str(msg["role"]).strip()
|
||||
|
||||
if "content" not in msg or msg["content"] is None:
|
||||
print(f"[ERROR] Message {i} missing content, setting to empty string", flush=True)
|
||||
msg["content"] = ""
|
||||
else:
|
||||
msg["content"] = str(msg["content"]).strip()
|
||||
|
||||
# 根据不同的API提供商调整参数
|
||||
if "deepseek" in MODEL.lower():
|
||||
# DeepSeek API特殊处理
|
||||
print("[DEBUG] DeepSeek API detected, adjusting parameters", flush=True)
|
||||
kwargs.pop("n", None) # 移除n参数,DeepSeek可能不支持
|
||||
if "timeout" in kwargs:
|
||||
kwargs.pop("timeout", None)
|
||||
# DeepSeek可能不支持stop参数
|
||||
kwargs.pop("stop", None)
|
||||
else:
|
||||
# OpenAI兼容的API
|
||||
kwargs["n"] = 1
|
||||
kwargs["stop"] = None
|
||||
kwargs["timeout"] = 3
|
||||
|
||||
kwargs["model"] = MODEL
|
||||
|
||||
# 确保messages列表中的每个元素都有有效的role和content
|
||||
kwargs["messages"] = [msg for msg in messages if msg["role"] and msg["content"]]
|
||||
print(f"[DEBUG] Final kwargs for API call: {kwargs.keys()}", flush=True)
|
||||
|
||||
return kwargs
|
||||
@@ -7,8 +7,6 @@ PROMPT_ABILITY_REQUIREMENT_GENERATION = """
|
||||
## Instruction
|
||||
Based on "General Goal" and "Current Task", output a formatted "Ability Requirement" which lists at least 3 different ability requirement that is required by the "Current Task". The ability should be summarized concisely within a few words.
|
||||
|
||||
**IMPORTANT LANGUAGE REQUIREMENT: You must respond in Chinese (中文) for all ability requirements.**
|
||||
|
||||
## General Goal (The general goal for the collaboration plan, "Current Task" is just one of its substep)
|
||||
{General_Goal}
|
||||
|
||||
@@ -51,8 +49,6 @@ PROMPT_AGENT_ABILITY_SCORING = """
|
||||
## Instruction
|
||||
Based on "Agent Board" and "Ability Requirement", output a score for each agent to estimate the possibility that the agent can fulfil the "Ability Requirement". The score should be 1-5. Provide a concise reason before you assign the score.
|
||||
|
||||
**IMPORTANT LANGUAGE REQUIREMENT: You must respond in Chinese (中文) for all reasons and explanations.**
|
||||
|
||||
## AgentBoard
|
||||
{Agent_Board}
|
||||
|
||||
@@ -87,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:
|
||||
@@ -109,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"],
|
||||
@@ -129,14 +124,13 @@ 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
|
||||
|
||||
|
||||
def AgentSelectModify_addAspect(aspectList, Agent_Board):
|
||||
newAspect = aspectList[-1]
|
||||
scoreTable = agentAbilityScoring(Agent_Board, [newAspect])
|
||||
scoreTable = agentAbilityScoring(Agent_Board, aspectList)
|
||||
return scoreTable
|
||||
|
||||
@@ -35,8 +35,6 @@ PROMPT_AGENT_SELECTION_GENERATION = """
|
||||
## Instruction
|
||||
Based on "General Goal", "Current Task" and "Agent Board", output a formatted "Agent Selection Plan". Your selection should consider the ability needed for "Current Task" and the profile of each agent in "Agent Board".
|
||||
|
||||
**IMPORTANT LANGUAGE REQUIREMENT: You must respond in Chinese (中文) for all explanations and reasoning, though agent names should remain in their original form.**
|
||||
|
||||
## General Goal (Specify the general goal for the collaboration plan)
|
||||
{General_Goal}
|
||||
|
||||
@@ -80,12 +78,7 @@ def generate_AbilityRequirement(General_Goal, Current_Task):
|
||||
print(messages[1]["content"])
|
||||
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",
|
||||
|
||||
@@ -1,53 +1,63 @@
|
||||
from AgentCoord.PlanEngine.planOutline_Generator import generate_PlanOutline
|
||||
# from AgentCoord.PlanEngine.AgentSelection_Generator import (
|
||||
# generate_AgentSelection,
|
||||
# )
|
||||
from AgentCoord.PlanEngine.AgentSelection_Generator import (
|
||||
generate_AgentSelection,
|
||||
)
|
||||
from AgentCoord.PlanEngine.taskProcess_Generator import generate_TaskProcess
|
||||
import AgentCoord.util as util
|
||||
|
||||
|
||||
def generate_basePlan(
|
||||
General_Goal, Agent_Board, AgentProfile_Dict, InitialObject_List
|
||||
):
|
||||
"""
|
||||
优化模式:生成大纲 + 智能体选择,但不生成任务流程
|
||||
优化用户体验:
|
||||
1. 快速生成大纲和分配智能体
|
||||
2. 用户可以看到完整的大纲和智能体图标
|
||||
3. TaskProcess由前端通过 fillStepTask API 异步填充
|
||||
|
||||
"""
|
||||
# 参数保留以保持接口兼容性
|
||||
_ = AgentProfile_Dict
|
||||
PlanOutline = generate_PlanOutline(
|
||||
InitialObject_List=InitialObject_List, General_Goal=General_Goal
|
||||
)
|
||||
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 = {
|
||||
"General Goal": General_Goal,
|
||||
"Initial Input Object": InitialObject_List,
|
||||
"Collaboration Process": []
|
||||
"Collaboration Process": [],
|
||||
}
|
||||
|
||||
PlanOutline = generate_PlanOutline(
|
||||
InitialObject_List=[], General_Goal=General_Goal
|
||||
)
|
||||
for stepItem in PlanOutline:
|
||||
# # 为每个步骤分配智能体
|
||||
# Current_Task = {
|
||||
# "TaskName": stepItem["StepName"],
|
||||
# "InputObject_List": stepItem["InputObject_List"],
|
||||
# "OutputObject": stepItem["OutputObject"],
|
||||
# "TaskContent": stepItem["TaskContent"],
|
||||
# }
|
||||
# AgentSelection = generate_AgentSelection(
|
||||
# General_Goal=General_Goal,
|
||||
# Current_Task=Current_Task,
|
||||
# Agent_Board=Agent_Board,
|
||||
# )
|
||||
|
||||
# 添加智能体选择,但不添加任务流程
|
||||
stepItem["AgentSelection"] = []
|
||||
stepItem["TaskProcess"] = [] # 空数组,由前端异步填充
|
||||
stepItem["Collaboration_Brief_frontEnd"] = {
|
||||
"template": "",
|
||||
"data": {}
|
||||
Current_Task = {
|
||||
"TaskName": stepItem["StepName"],
|
||||
"InputObject_List": stepItem["InputObject_List"],
|
||||
"OutputObject": stepItem["OutputObject"],
|
||||
"TaskContent": stepItem["TaskContent"],
|
||||
}
|
||||
AgentSelection = generate_AgentSelection(
|
||||
General_Goal=General_Goal,
|
||||
Current_Task=Current_Task,
|
||||
Agent_Board=Agent_Board,
|
||||
)
|
||||
Current_Task_Description = {
|
||||
"TaskName": stepItem["StepName"],
|
||||
"AgentInvolved": [
|
||||
{"Name": name, "Profile": AgentProfile_Dict[name]}
|
||||
for name in AgentSelection
|
||||
],
|
||||
"InputObject_List": stepItem["InputObject_List"],
|
||||
"OutputObject": stepItem["OutputObject"],
|
||||
"CurrentTaskDescription": util.generate_template_sentence_for_CollaborationBrief(
|
||||
stepItem["InputObject_List"],
|
||||
stepItem["OutputObject"],
|
||||
AgentSelection,
|
||||
stepItem["TaskContent"],
|
||||
),
|
||||
}
|
||||
TaskProcess = generate_TaskProcess(
|
||||
General_Goal=General_Goal,
|
||||
Current_Task_Description=Current_Task_Description,
|
||||
)
|
||||
# add the generated AgentSelection and TaskProcess to the stepItem
|
||||
stepItem["AgentSelection"] = AgentSelection
|
||||
stepItem["TaskProcess"] = TaskProcess
|
||||
basePlan["Collaboration Process"].append(stepItem)
|
||||
|
||||
basePlan["General Goal"] = General_Goal
|
||||
return basePlan
|
||||
@@ -9,8 +9,6 @@ PROMPT_PLAN_OUTLINE_BRANCHING = """
|
||||
Based on "Existing Steps", your task is to comeplete the "Remaining Steps" for the plan for "General Goal".
|
||||
Note: "Modification Requirement" specifies how to modify the "Baseline Completion" for a better/alternative solution.
|
||||
|
||||
**IMPORTANT LANGUAGE REQUIREMENT: You must respond in Chinese (中文) for all content, including StepName, TaskContent, and OutputObject fields.**
|
||||
|
||||
## General Goal (Specify the general goal for the plan)
|
||||
{General_Goal}
|
||||
|
||||
@@ -97,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)
|
||||
|
||||
@@ -29,8 +29,6 @@ PROMPT_TASK_PROCESS_BRANCHING = """
|
||||
Based on "Existing Steps", your task is to comeplete the "Remaining Steps" for the "Task for Current Step".
|
||||
Note: "Modification Requirement" specifies how to modify the "Baseline Completion" for a better/alternative solution.
|
||||
|
||||
**IMPORTANT LANGUAGE REQUIREMENT: You must respond in Chinese (中文) for the Description field and all explanations, while keeping ID, ActionType, and AgentName in their original format.**
|
||||
|
||||
## General Goal (The general goal for the collaboration plan, you just design the plan for one of its step (i.e. "Task for Current Step"))
|
||||
{General_Goal}
|
||||
|
||||
@@ -57,39 +55,26 @@ Note: "Modification Requirement" specifies how to modify the "Baseline Completio
|
||||
"ID": "Action4",
|
||||
"ActionType": "Propose",
|
||||
"AgentName": "Mia",
|
||||
"Description": "提议关于人工智能情感发展的心理学理论,重点关注爱与依恋的概念。",
|
||||
"Description": "Propose psychological theories on love and attachment that could be applied to AI's emotional development.",
|
||||
"ImportantInput": [
|
||||
"InputObject:Story Outline"
|
||||
]
|
||||
}},
|
||||
{{
|
||||
"ID": "Action5",
|
||||
"ActionType": "Critique",
|
||||
"ActionType": "Propose",
|
||||
"AgentName": "Noah",
|
||||
"Description": "对Mia提出的心理学理论进行批判性评估,分析其在AI情感发展场景中的适用性和局限性。",
|
||||
"ImportantInput": [
|
||||
"ActionResult:Action4"
|
||||
]
|
||||
"Description": "Propose ethical considerations and philosophical questions regarding AI's capacity for love.",
|
||||
"ImportantInput": []
|
||||
}},
|
||||
{{
|
||||
"ID": "Action6",
|
||||
"ActionType": "Improve",
|
||||
"AgentName": "Liam",
|
||||
"Description": "基于Noah的批判性反馈,改进和完善心理学理论框架,使其更贴合AI情感发展的实际需求。",
|
||||
"ImportantInput": [
|
||||
"ActionResult:Action4",
|
||||
"ActionResult:Action5"
|
||||
]
|
||||
}},
|
||||
{{
|
||||
"ID": "Action7",
|
||||
"ActionType": "Finalize",
|
||||
"AgentName": "Mia",
|
||||
"Description": "综合所有提议、批判和改进意见,整合并提交最终的AI情感发展心理学理论框架。",
|
||||
"AgentName": "Liam",
|
||||
"Description": "Combine the poetic elements and ethical considerations into a cohesive set of core love elements for the story.",
|
||||
"ImportantInput": [
|
||||
"ActionResult:Action4",
|
||||
"ActionResult:Action5",
|
||||
"ActionResult:Action6"
|
||||
"ActionResult:Action1",
|
||||
"ActionResult:Action5"
|
||||
]
|
||||
}}
|
||||
]
|
||||
@@ -99,12 +84,7 @@ Note: "Modification Requirement" specifies how to modify the "Baseline Completio
|
||||
ImportantInput: Specify if there is any previous result that should be taken special consideration during the execution the action. Should be of format "InputObject:xx" or "ActionResult:xx".
|
||||
InputObject_List: List existing objects that should be utilized in current step.
|
||||
AgentName: Specify the agent who will perform the action, You CAN ONLY USE THE NAME APPEARS IN "AgentInvolved".
|
||||
ActionType: Specify the type of action. **CRITICAL REQUIREMENTS:**
|
||||
1. The "Remaining Steps" MUST include ALL FOUR action types in the following order: Propose -> Critique -> Improve -> Finalize
|
||||
2. Each action type (Propose, Critique, Improve, Finalize) MUST appear at least once
|
||||
3. The actions must follow the sequence: Propose actions first, then Critique actions, then Improve actions, and Finalize must be the last action
|
||||
4. Even if only one agent is involved in a phase, that phase must still have its corresponding action type
|
||||
5. The last action must ALWAYS be of type "Finalize"
|
||||
ActionType: Specify the type of action, note that only the last action can be of type "Finalize", and the last action must be "Finalize".
|
||||
|
||||
"""
|
||||
|
||||
@@ -165,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"
|
||||
]
|
||||
|
||||
|
||||
@@ -2,13 +2,11 @@ from AgentCoord.util.converter import read_LLM_Completion
|
||||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
import json
|
||||
|
||||
import asyncio
|
||||
PROMPT_PLAN_OUTLINE_GENERATION = """
|
||||
## Instruction
|
||||
Based on "Output Format Example", "General Goal", and "Initial Key Object List", output a formatted "Plan_Outline".
|
||||
|
||||
**IMPORTANT LANGUAGE REQUIREMENT: You must respond in Chinese (中文) for all content, including StepName, TaskContent, and OutputObject fields.**
|
||||
|
||||
## Initial Key Object List (Specify the list of initial key objects available, each initial key object should be the input object of at least one Step)
|
||||
{InitialObject_List}
|
||||
|
||||
@@ -71,6 +69,13 @@ class PlanOutline(BaseModel):
|
||||
|
||||
|
||||
def generate_PlanOutline(InitialObject_List, General_Goal):
|
||||
# 新增:校验 General_Goal 必须有有效内容
|
||||
if not isinstance(General_Goal, str) or len(General_Goal.strip()) == 0:
|
||||
raise ValueError("General_Goal 不能为空!必须提供具体的目标描述")
|
||||
|
||||
# 处理 InitialObject_List 为空的情况(可选,但更友好)
|
||||
if not InitialObject_List:
|
||||
InitialObject_List = ["无初始对象"] # 避免空列表导致的歧义
|
||||
messages = [
|
||||
{
|
||||
"role": "system",
|
||||
@@ -85,16 +90,11 @@ def generate_PlanOutline(InitialObject_List, General_Goal):
|
||||
),
|
||||
},
|
||||
]
|
||||
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"
|
||||
}
|
||||
]
|
||||
|
||||
# 二次校验 messages 内容(防止意外空值)
|
||||
for msg in messages:
|
||||
content = msg.get("content", "").strip()
|
||||
if not content:
|
||||
raise ValueError("生成的 LLM 请求消息内容为空,请检查参数")
|
||||
|
||||
return read_LLM_Completion(messages)["Plan_Outline"]
|
||||
|
||||
@@ -25,8 +25,6 @@ PROMPT_TASK_PROCESS_GENERATION = """
|
||||
## Instruction
|
||||
Based on "General Goal", "Task for Current Step", "Action Set" and "Output Format Example", design a plan for "Task for Current Step", output a formatted "Task_Process_Plan".
|
||||
|
||||
**IMPORTANT LANGUAGE REQUIREMENT: You must respond in Chinese (中文) for the Description field and all explanations, while keeping ID, ActionType, and AgentName in their original format.**
|
||||
|
||||
## General Goal (The general goal for the collaboration plan, you just design the plan for one of its step (i.e. "Task for Current Step"))
|
||||
{General_Goal}
|
||||
|
||||
@@ -108,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",
|
||||
|
||||
@@ -9,8 +9,6 @@ You are within a multi-agent collaboration for the "Current Task".
|
||||
Now it's your turn to take action. Read the "Context Information" and take your action following "Instruction for Your Current Action".
|
||||
Note: Important Input for your action are marked with *Important Input*
|
||||
|
||||
**IMPORTANT LANGUAGE REQUIREMENT: You must respond in Chinese (中文) for all your answers and outputs.**
|
||||
|
||||
## Context Information
|
||||
|
||||
### General Goal (The "Current Task" is indeed a substep of the general goal)
|
||||
@@ -82,34 +80,10 @@ 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)
|
||||
|
||||
# Handle missing agent profiles gracefully
|
||||
model_config = None
|
||||
if agentName not in AgentProfile_Dict:
|
||||
print_colored(text=f"Warning: Agent '{agentName}' not found in AgentProfile_Dict. Using default profile.", text_color="yellow")
|
||||
agentProfile = f"AI Agent named {agentName}"
|
||||
else:
|
||||
# agentProfile = AgentProfile_Dict[agentName]
|
||||
agent_config = AgentProfile_Dict[agentName]
|
||||
agentProfile = agent_config.get("profile",f"AI Agent named {agentName}")
|
||||
if agent_config.get("useCustomAPI",False):
|
||||
model_config = {
|
||||
"apiModel":agent_config.get("apiModel"),
|
||||
"apiUrl":agent_config.get("apiUrl"),
|
||||
"apiKey":agent_config.get("apiKey"),
|
||||
}
|
||||
prompt = PROMPT_TEMPLATE_TAKE_ACTION_BASE.format(
|
||||
agentName = agentName,
|
||||
agentProfile = agentProfile,
|
||||
General_Goal = General_Goal,
|
||||
Current_Task_Description = TaskDescription,
|
||||
Input_Objects = inputObject_Record,
|
||||
History_Action = action_Record,
|
||||
Action_Description = self.info["Description"],
|
||||
Action_Custom_Note = self.Action_Custom_Note
|
||||
)
|
||||
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)
|
||||
print_colored(text = prompt, text_color="red")
|
||||
messages = [{"role":"system", "content": prompt}]
|
||||
ActionResult = LLM_Completion(messages,True,False,model_config=model_config)
|
||||
ActionResult = LLM_Completion(messages,stream=False)
|
||||
ActionInfo_with_Result = copy.deepcopy(self.info)
|
||||
ActionInfo_with_Result["Action_Result"] = ActionResult
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from AgentCoord.RehearsalEngine_V2.Action import BaseAction
|
||||
|
||||
ACTION_CUSTOM_NOTE = '''
|
||||
注意:由于你在对话中,你的批评必须简洁、清晰且易于阅读,不要让人感到压力过大。如果你要列出一些观点,最多列出2点。
|
||||
Note: Since you are in a conversation, your critique must be concise, clear and easy to read, don't overwhelm others. If you want to list some points, list at most 2 points.
|
||||
|
||||
'''
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ from AgentCoord.util.converter import read_outputObject_content
|
||||
from AgentCoord.RehearsalEngine_V2.Action import BaseAction
|
||||
|
||||
ACTION_CUSTOM_NOTE = '''
|
||||
注意:你可以在给出{OutputName}的最终内容之前先说一些话。当你决定给出{OutputName}的最终内容时,应该这样包含:
|
||||
Note: You can say something before you give the final content of {OutputName}. When you decide to give the final content of {OutputName}, it should be enclosed like this:
|
||||
```{OutputName}
|
||||
({OutputName}的内容)
|
||||
(the content of {OutputName})
|
||||
```
|
||||
'''
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from AgentCoord.RehearsalEngine_V2.Action import BaseAction
|
||||
|
||||
ACTION_CUSTOM_NOTE = '''
|
||||
注意:你可以在提供改进版本的内容之前先说一些话。
|
||||
你提供的改进版本必须是完整的版本(例如,如果你提供改进的故事,你应该给出完整的故事内容,而不仅仅是报告你在哪里改进了)。
|
||||
当你决定提供内容的改进版本时,应该这样开始:
|
||||
Note: You can say something before you provide the improved version of the content.
|
||||
The improved version you provide must be a completed version (e.g. if you provide a improved story, you should give completed story content, rather than just reporting where you have improved).
|
||||
When you decide to give the improved version of the content, it should be start like this:
|
||||
|
||||
## xxx的改进版本
|
||||
(改进版本的内容)
|
||||
## Improved version of xxx
|
||||
(the improved version of the content)
|
||||
```
|
||||
|
||||
'''
|
||||
|
||||
@@ -85,17 +85,9 @@ 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,608 +0,0 @@
|
||||
"""
|
||||
优化版执行计划 - 支持动态追加步骤
|
||||
在执行过程中可以接收新的步骤并追加到执行队列
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import time
|
||||
from typing import List, Dict, Set, Generator, Any
|
||||
import AgentCoord.RehearsalEngine_V2.Action as Action
|
||||
import AgentCoord.util as util
|
||||
from termcolor import colored
|
||||
from AgentCoord.RehearsalEngine_V2.execution_state import execution_state_manager
|
||||
from AgentCoord.RehearsalEngine_V2.dynamic_execution_manager import dynamic_execution_manager
|
||||
|
||||
|
||||
# ==================== 配置参数 ====================
|
||||
# 最大并发请求数
|
||||
MAX_CONCURRENT_REQUESTS = 2
|
||||
|
||||
# 批次之间的延迟
|
||||
BATCH_DELAY = 1.0
|
||||
|
||||
# 429错误重试次数和延迟
|
||||
MAX_RETRIES = 3
|
||||
RETRY_DELAY = 5.0
|
||||
|
||||
|
||||
# ==================== 限流器 ====================
|
||||
class RateLimiter:
|
||||
"""
|
||||
异步限流器,控制并发请求数量
|
||||
"""
|
||||
|
||||
def __init__(self, max_concurrent: int = MAX_CONCURRENT_REQUESTS):
|
||||
self.semaphore = asyncio.Semaphore(max_concurrent)
|
||||
self.max_concurrent = max_concurrent
|
||||
|
||||
async def __aenter__(self):
|
||||
await self.semaphore.acquire()
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *args):
|
||||
self.semaphore.release()
|
||||
|
||||
|
||||
# 全局限流器实例
|
||||
rate_limiter = RateLimiter()
|
||||
|
||||
|
||||
def build_action_dependency_graph(TaskProcess: List[Dict]) -> Dict[int, List[int]]:
|
||||
"""
|
||||
构建动作依赖图
|
||||
|
||||
Args:
|
||||
TaskProcess: 任务流程列表
|
||||
|
||||
Returns:
|
||||
依赖映射字典 {action_index: [dependent_action_indices]}
|
||||
"""
|
||||
dependency_map = {i: [] for i in range(len(TaskProcess))}
|
||||
|
||||
for i, action in enumerate(TaskProcess):
|
||||
important_inputs = action.get('ImportantInput', [])
|
||||
if not important_inputs:
|
||||
continue
|
||||
|
||||
# 检查是否依赖其他动作的ActionResult
|
||||
for j, prev_action in enumerate(TaskProcess):
|
||||
if i == j:
|
||||
continue
|
||||
|
||||
# 判断是否依赖前一个动作的结果
|
||||
if any(
|
||||
inp.startswith('ActionResult:') and
|
||||
inp == f'ActionResult:{prev_action["ID"]}'
|
||||
for inp in important_inputs
|
||||
):
|
||||
dependency_map[i].append(j)
|
||||
|
||||
return dependency_map
|
||||
|
||||
|
||||
def get_parallel_batches(TaskProcess: List[Dict], dependency_map: Dict[int, List[int]]) -> List[List[int]]:
|
||||
"""
|
||||
将动作分为多个批次,每批内部可以并行执行
|
||||
|
||||
Args:
|
||||
TaskProcess: 任务流程列表
|
||||
dependency_map: 依赖图
|
||||
|
||||
Returns:
|
||||
批次列表 [[batch1_indices], [batch2_indices], ...]
|
||||
"""
|
||||
batches = []
|
||||
completed: Set[int] = set()
|
||||
|
||||
while len(completed) < len(TaskProcess):
|
||||
# 找出所有依赖已满足的动作
|
||||
ready_to_run = [
|
||||
i for i in range(len(TaskProcess))
|
||||
if i not in completed and
|
||||
all(dep in completed for dep in dependency_map[i])
|
||||
]
|
||||
|
||||
if not ready_to_run:
|
||||
# 避免死循环
|
||||
remaining = [i for i in range(len(TaskProcess)) if i not in completed]
|
||||
if remaining:
|
||||
print(colored(f"警告: 检测到循环依赖,强制串行执行: {remaining}", "yellow"))
|
||||
ready_to_run = remaining[:1]
|
||||
else:
|
||||
break
|
||||
|
||||
batches.append(ready_to_run)
|
||||
completed.update(ready_to_run)
|
||||
|
||||
return batches
|
||||
|
||||
|
||||
async def execute_single_action_async(
|
||||
ActionInfo: Dict,
|
||||
General_Goal: str,
|
||||
TaskDescription: str,
|
||||
OutputName: str,
|
||||
KeyObjects: Dict,
|
||||
ActionHistory: List,
|
||||
agentName: str,
|
||||
AgentProfile_Dict: Dict,
|
||||
InputName_List: List[str]
|
||||
) -> Dict:
|
||||
"""
|
||||
异步执行单个动作
|
||||
|
||||
Args:
|
||||
ActionInfo: 动作信息
|
||||
General_Goal: 总体目标
|
||||
TaskDescription: 任务描述
|
||||
OutputName: 输出对象名称
|
||||
KeyObjects: 关键对象字典
|
||||
ActionHistory: 动作历史
|
||||
agentName: 智能体名称
|
||||
AgentProfile_Dict: 智能体配置字典
|
||||
InputName_List: 输入名称列表
|
||||
|
||||
Returns:
|
||||
动作执行结果
|
||||
"""
|
||||
actionType = ActionInfo["ActionType"]
|
||||
|
||||
# 创建动作实例
|
||||
if actionType in Action.customAction_Dict:
|
||||
currentAction = Action.customAction_Dict[actionType](
|
||||
info=ActionInfo,
|
||||
OutputName=OutputName,
|
||||
KeyObjects=KeyObjects,
|
||||
)
|
||||
else:
|
||||
currentAction = Action.BaseAction(
|
||||
info=ActionInfo,
|
||||
OutputName=OutputName,
|
||||
KeyObjects=KeyObjects,
|
||||
)
|
||||
|
||||
# 在线程池中运行,避免阻塞事件循环
|
||||
loop = asyncio.get_event_loop()
|
||||
ActionInfo_with_Result = await loop.run_in_executor(
|
||||
None,
|
||||
lambda: currentAction.run(
|
||||
General_Goal=General_Goal,
|
||||
TaskDescription=TaskDescription,
|
||||
agentName=agentName,
|
||||
AgentProfile_Dict=AgentProfile_Dict,
|
||||
InputName_List=InputName_List,
|
||||
OutputName=OutputName,
|
||||
KeyObjects=KeyObjects,
|
||||
ActionHistory=ActionHistory,
|
||||
)
|
||||
)
|
||||
|
||||
return ActionInfo_with_Result
|
||||
|
||||
|
||||
async def execute_step_async_streaming(
|
||||
stepDescrip: Dict,
|
||||
General_Goal: str,
|
||||
AgentProfile_Dict: Dict,
|
||||
KeyObjects: Dict,
|
||||
step_index: int,
|
||||
total_steps: int
|
||||
) -> Generator[Dict, None, None]:
|
||||
"""
|
||||
异步执行单个步骤,支持流式返回
|
||||
|
||||
Args:
|
||||
stepDescrip: 步骤描述
|
||||
General_Goal: 总体目标
|
||||
AgentProfile_Dict: 智能体配置字典
|
||||
KeyObjects: 关键对象字典
|
||||
step_index: 步骤索引
|
||||
total_steps: 总步骤数
|
||||
|
||||
Yields:
|
||||
执行事件字典
|
||||
"""
|
||||
# 准备步骤信息
|
||||
StepName = (
|
||||
util.camel_case_to_normal(stepDescrip["StepName"])
|
||||
if util.is_camel_case(stepDescrip["StepName"])
|
||||
else stepDescrip["StepName"]
|
||||
)
|
||||
TaskContent = stepDescrip["TaskContent"]
|
||||
InputName_List = (
|
||||
[
|
||||
(
|
||||
util.camel_case_to_normal(obj)
|
||||
if util.is_camel_case(obj)
|
||||
else obj
|
||||
)
|
||||
for obj in stepDescrip["InputObject_List"]
|
||||
]
|
||||
if stepDescrip["InputObject_List"] is not None
|
||||
else None
|
||||
)
|
||||
OutputName = (
|
||||
util.camel_case_to_normal(stepDescrip["OutputObject"])
|
||||
if util.is_camel_case(stepDescrip["OutputObject"])
|
||||
else stepDescrip["OutputObject"]
|
||||
)
|
||||
Agent_List = stepDescrip["AgentSelection"]
|
||||
TaskProcess = stepDescrip["TaskProcess"]
|
||||
|
||||
TaskDescription = (
|
||||
util.converter.generate_template_sentence_for_CollaborationBrief(
|
||||
input_object_list=InputName_List,
|
||||
output_object=OutputName,
|
||||
agent_list=Agent_List,
|
||||
step_task=TaskContent,
|
||||
)
|
||||
)
|
||||
|
||||
# 初始化日志节点
|
||||
inputObject_Record = [
|
||||
{InputName: KeyObjects[InputName]} for InputName in InputName_List
|
||||
]
|
||||
stepLogNode = {
|
||||
"LogNodeType": "step",
|
||||
"NodeId": StepName,
|
||||
"InputName_List": InputName_List,
|
||||
"OutputName": OutputName,
|
||||
"chatLog": [],
|
||||
"inputObject_Record": inputObject_Record,
|
||||
}
|
||||
objectLogNode = {
|
||||
"LogNodeType": "object",
|
||||
"NodeId": OutputName,
|
||||
"content": None,
|
||||
}
|
||||
|
||||
# 返回步骤开始事件
|
||||
yield {
|
||||
"type": "step_start",
|
||||
"step_index": step_index,
|
||||
"total_steps": total_steps,
|
||||
"step_name": StepName,
|
||||
"task_description": TaskDescription,
|
||||
}
|
||||
|
||||
# 构建动作依赖图
|
||||
dependency_map = build_action_dependency_graph(TaskProcess)
|
||||
batches = get_parallel_batches(TaskProcess, dependency_map)
|
||||
|
||||
ActionHistory = []
|
||||
total_actions = len(TaskProcess)
|
||||
completed_actions = 0
|
||||
|
||||
util.print_colored(
|
||||
f"📋 步骤 {step_index + 1}/{total_steps}: {StepName} ({total_actions} 个动作, 分 {len(batches)} 批并行执行)",
|
||||
text_color="cyan"
|
||||
)
|
||||
|
||||
# 分批执行动作
|
||||
for batch_index, batch_indices in enumerate(batches):
|
||||
# 在每个批次执行前检查暂停状态
|
||||
should_continue = await execution_state_manager.async_check_pause()
|
||||
if not should_continue:
|
||||
util.print_colored("🛑 用户请求停止执行", "red")
|
||||
return
|
||||
|
||||
batch_size = len(batch_indices)
|
||||
|
||||
if batch_size > 1:
|
||||
util.print_colored(
|
||||
f"🚦 批次 {batch_index + 1}/{len(batches)}: 并行执行 {batch_size} 个动作",
|
||||
text_color="blue"
|
||||
)
|
||||
else:
|
||||
util.print_colored(
|
||||
f"🔄 动作 {completed_actions + 1}/{total_actions}: 串行执行",
|
||||
text_color="yellow"
|
||||
)
|
||||
|
||||
# 并行执行当前批次的所有动作
|
||||
tasks = [
|
||||
execute_single_action_async(
|
||||
TaskProcess[i],
|
||||
General_Goal=General_Goal,
|
||||
TaskDescription=TaskDescription,
|
||||
OutputName=OutputName,
|
||||
KeyObjects=KeyObjects,
|
||||
ActionHistory=ActionHistory,
|
||||
agentName=TaskProcess[i]["AgentName"],
|
||||
AgentProfile_Dict=AgentProfile_Dict,
|
||||
InputName_List=InputName_List
|
||||
)
|
||||
for i in batch_indices
|
||||
]
|
||||
|
||||
# 等待当前批次完成
|
||||
batch_results = await asyncio.gather(*tasks)
|
||||
|
||||
# 逐个返回结果
|
||||
for i, result in enumerate(batch_results):
|
||||
action_index_in_batch = batch_indices[i]
|
||||
completed_actions += 1
|
||||
|
||||
util.print_colored(
|
||||
f"✅ 动作 {completed_actions}/{total_actions} 完成: {result['ActionType']} by {result['AgentName']}",
|
||||
text_color="green"
|
||||
)
|
||||
|
||||
ActionHistory.append(result)
|
||||
|
||||
# 立即返回该动作结果
|
||||
yield {
|
||||
"type": "action_complete",
|
||||
"step_index": step_index,
|
||||
"step_name": StepName,
|
||||
"action_index": action_index_in_batch,
|
||||
"total_actions": total_actions,
|
||||
"completed_actions": completed_actions,
|
||||
"action_result": result,
|
||||
"batch_info": {
|
||||
"batch_index": batch_index,
|
||||
"batch_size": batch_size,
|
||||
"is_parallel": batch_size > 1
|
||||
}
|
||||
}
|
||||
|
||||
# 步骤完成
|
||||
objectLogNode["content"] = KeyObjects[OutputName]
|
||||
stepLogNode["ActionHistory"] = ActionHistory
|
||||
|
||||
yield {
|
||||
"type": "step_complete",
|
||||
"step_index": step_index,
|
||||
"step_name": StepName,
|
||||
"step_log_node": stepLogNode,
|
||||
"object_log_node": objectLogNode,
|
||||
}
|
||||
|
||||
|
||||
def executePlan_streaming_dynamic(
|
||||
plan: Dict,
|
||||
num_StepToRun: int,
|
||||
RehearsalLog: List,
|
||||
AgentProfile_Dict: Dict,
|
||||
existingKeyObjects: Dict = None,
|
||||
execution_id: str = None
|
||||
) -> Generator[str, None, None]:
|
||||
"""
|
||||
动态执行计划,支持在执行过程中追加新步骤
|
||||
|
||||
Args:
|
||||
plan: 执行计划
|
||||
num_StepToRun: 要运行的步骤数
|
||||
RehearsalLog: 已执行的历史记录
|
||||
AgentProfile_Dict: 智能体配置
|
||||
existingKeyObjects: 已存在的KeyObjects
|
||||
execution_id: 执行ID(用于动态追加步骤)
|
||||
|
||||
Yields:
|
||||
SSE格式的事件字符串
|
||||
"""
|
||||
# 初始化执行状态
|
||||
general_goal = plan.get("General Goal", "")
|
||||
execution_state_manager.start_execution(general_goal)
|
||||
|
||||
print(colored(f"⏸️ 执行状态管理器已启动,支持暂停/恢复", "green"))
|
||||
|
||||
# 准备执行
|
||||
KeyObjects = existingKeyObjects.copy() if existingKeyObjects else {}
|
||||
finishedStep_index = -1
|
||||
|
||||
for logNode in RehearsalLog:
|
||||
if logNode["LogNodeType"] == "step":
|
||||
finishedStep_index += 1
|
||||
if logNode["LogNodeType"] == "object":
|
||||
KeyObjects[logNode["NodeId"]] = logNode["content"]
|
||||
|
||||
if existingKeyObjects:
|
||||
print(colored(f"📦 使用已存在的 KeyObjects: {list(existingKeyObjects.keys())}", "cyan"))
|
||||
|
||||
# 确定要运行的步骤范围
|
||||
if num_StepToRun is None:
|
||||
run_to = len(plan["Collaboration Process"])
|
||||
else:
|
||||
run_to = (finishedStep_index + 1) + num_StepToRun
|
||||
|
||||
steps_to_run = plan["Collaboration Process"][(finishedStep_index + 1): run_to]
|
||||
|
||||
# 使用动态执行管理器
|
||||
if execution_id:
|
||||
# 初始化执行管理器,使用传入的execution_id
|
||||
actual_execution_id = dynamic_execution_manager.start_execution(general_goal, steps_to_run, execution_id)
|
||||
print(colored(f"🚀 开始执行计划(动态模式),共 {len(steps_to_run)} 个步骤,执行ID: {actual_execution_id}", "cyan"))
|
||||
else:
|
||||
print(colored(f"🚀 开始执行计划(流式推送),共 {len(steps_to_run)} 个步骤", "cyan"))
|
||||
|
||||
total_steps = len(steps_to_run)
|
||||
|
||||
# 使用队列实现流式推送
|
||||
async def produce_events(queue: asyncio.Queue):
|
||||
"""异步生产者"""
|
||||
try:
|
||||
step_index = 0
|
||||
|
||||
if execution_id:
|
||||
# 动态模式:循环获取下一个步骤
|
||||
# 等待新步骤的最大次数(避免无限等待)
|
||||
max_empty_wait_cycles = 5 # 最多等待60次,每次等待1秒
|
||||
empty_wait_count = 0
|
||||
|
||||
while True:
|
||||
# 检查暂停状态
|
||||
should_continue = await execution_state_manager.async_check_pause()
|
||||
if not should_continue:
|
||||
print(colored("🛑 用户请求停止执行", "red"))
|
||||
await queue.put({
|
||||
"type": "error",
|
||||
"message": "执行已被用户停止"
|
||||
})
|
||||
break
|
||||
|
||||
# 获取下一个步骤
|
||||
stepDescrip = dynamic_execution_manager.get_next_step(execution_id)
|
||||
|
||||
if stepDescrip is None:
|
||||
# 没有更多步骤了,检查是否应该继续等待
|
||||
empty_wait_count += 1
|
||||
|
||||
# 获取执行信息
|
||||
execution_info = dynamic_execution_manager.get_execution_info(execution_id)
|
||||
|
||||
if execution_info:
|
||||
queue_total_steps = execution_info.get("total_steps", 0)
|
||||
completed_steps = execution_info.get("completed_steps", 0)
|
||||
|
||||
# 如果没有步骤在队列中(queue_total_steps为0),立即退出
|
||||
if queue_total_steps == 0:
|
||||
print(colored(f"⚠️ 没有步骤在队列中,退出执行", "yellow"))
|
||||
break
|
||||
|
||||
# 如果所有步骤都已完成,等待可能的新步骤
|
||||
if completed_steps >= queue_total_steps:
|
||||
if empty_wait_count >= max_empty_wait_cycles:
|
||||
# 等待超时,退出执行
|
||||
print(colored(f"✅ 所有步骤执行完成,等待超时", "green"))
|
||||
break
|
||||
else:
|
||||
# 等待新步骤追加
|
||||
print(colored(f"⏳ 等待新步骤追加... ({empty_wait_count}/{max_empty_wait_cycles})", "cyan"))
|
||||
await asyncio.sleep(1)
|
||||
continue
|
||||
else:
|
||||
# 还有步骤未完成,继续尝试获取
|
||||
print(colored(f"⏳ 等待步骤就绪... ({completed_steps}/{queue_total_steps})", "cyan"))
|
||||
await asyncio.sleep(0.5)
|
||||
empty_wait_count = 0 # 重置等待计数
|
||||
continue
|
||||
else:
|
||||
# 执行信息不存在,退出
|
||||
print(colored(f"⚠️ 执行信息不存在,退出执行", "yellow"))
|
||||
break
|
||||
|
||||
# 重置等待计数
|
||||
empty_wait_count = 0
|
||||
|
||||
# 获取最新的总步骤数(用于显示)
|
||||
execution_info = dynamic_execution_manager.get_execution_info(execution_id)
|
||||
current_total_steps = execution_info.get("total_steps", total_steps) if execution_info else total_steps
|
||||
|
||||
# 执行步骤
|
||||
async for event in execute_step_async_streaming(
|
||||
stepDescrip,
|
||||
plan["General Goal"],
|
||||
AgentProfile_Dict,
|
||||
KeyObjects,
|
||||
step_index,
|
||||
current_total_steps # 使用动态更新的总步骤数
|
||||
):
|
||||
if execution_state_manager.is_stopped():
|
||||
await queue.put({
|
||||
"type": "error",
|
||||
"message": "执行已被用户停止"
|
||||
})
|
||||
return
|
||||
|
||||
await queue.put(event)
|
||||
|
||||
# 标记步骤完成
|
||||
dynamic_execution_manager.mark_step_completed(execution_id)
|
||||
|
||||
# 更新KeyObjects
|
||||
OutputName = stepDescrip.get("OutputObject", "")
|
||||
if OutputName and OutputName in KeyObjects:
|
||||
# 对象日志节点会在step_complete中发送
|
||||
pass
|
||||
|
||||
step_index += 1
|
||||
|
||||
else:
|
||||
# 非动态模式:按顺序执行所有步骤
|
||||
for step_index, stepDescrip in enumerate(steps_to_run):
|
||||
should_continue = await execution_state_manager.async_check_pause()
|
||||
if not should_continue:
|
||||
print(colored("🛑 用户请求停止执行", "red"))
|
||||
await queue.put({
|
||||
"type": "error",
|
||||
"message": "执行已被用户停止"
|
||||
})
|
||||
return
|
||||
|
||||
async for event in execute_step_async_streaming(
|
||||
stepDescrip,
|
||||
plan["General Goal"],
|
||||
AgentProfile_Dict,
|
||||
KeyObjects,
|
||||
step_index,
|
||||
total_steps
|
||||
):
|
||||
if execution_state_manager.is_stopped():
|
||||
await queue.put({
|
||||
"type": "error",
|
||||
"message": "执行已被用户停止"
|
||||
})
|
||||
return
|
||||
|
||||
await queue.put(event)
|
||||
|
||||
except Exception as e:
|
||||
await queue.put({
|
||||
"type": "error",
|
||||
"message": f"执行出错: {str(e)}"
|
||||
})
|
||||
finally:
|
||||
await queue.put(None)
|
||||
|
||||
# 运行异步任务并实时yield
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
try:
|
||||
queue = asyncio.Queue(maxsize=10)
|
||||
producer_task = loop.create_task(produce_events(queue))
|
||||
|
||||
while True:
|
||||
event = loop.run_until_complete(queue.get())
|
||||
if event is None:
|
||||
break
|
||||
|
||||
# 立即转换为SSE格式并发送
|
||||
event_str = json.dumps(event, ensure_ascii=False)
|
||||
yield f"data: {event_str}\n\n"
|
||||
|
||||
loop.run_until_complete(producer_task)
|
||||
|
||||
if not execution_state_manager.is_stopped():
|
||||
complete_event = json.dumps({
|
||||
"type": "execution_complete",
|
||||
"total_steps": total_steps
|
||||
}, ensure_ascii=False)
|
||||
yield f"data: {complete_event}\n\n"
|
||||
|
||||
finally:
|
||||
# 在关闭事件循环之前先清理执行记录
|
||||
if execution_id:
|
||||
# 清理执行记录
|
||||
dynamic_execution_manager.cleanup(execution_id)
|
||||
|
||||
if 'producer_task' in locals():
|
||||
if not producer_task.done():
|
||||
producer_task.cancel()
|
||||
|
||||
# 确保所有任务都完成后再关闭事件循环
|
||||
try:
|
||||
pending = asyncio.all_tasks(loop)
|
||||
for task in pending:
|
||||
task.cancel()
|
||||
loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
|
||||
except Exception:
|
||||
pass # 忽略清理过程中的错误
|
||||
|
||||
loop.close()
|
||||
|
||||
|
||||
# 保留旧版本函数以保持兼容性
|
||||
executePlan_streaming = executePlan_streaming_dynamic
|
||||
@@ -1,241 +0,0 @@
|
||||
"""
|
||||
动态执行管理器
|
||||
用于在任务执行过程中动态追加新步骤
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import Dict, List, Optional, Any
|
||||
from threading import Lock
|
||||
|
||||
|
||||
class DynamicExecutionManager:
|
||||
"""
|
||||
动态执行管理器
|
||||
管理正在执行的任务,支持动态追加新步骤
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# 执行状态: goal -> execution_info
|
||||
self._executions: Dict[str, Dict] = {}
|
||||
|
||||
# 线程锁
|
||||
self._lock = Lock()
|
||||
|
||||
# 步骤队列: goal -> List[step]
|
||||
self._step_queues: Dict[str, List] = {}
|
||||
|
||||
# 已执行的步骤索引: goal -> Set[step_index]
|
||||
self._executed_steps: Dict[str, set] = {}
|
||||
|
||||
# 待执行的步骤索引: goal -> List[step_index]
|
||||
self._pending_steps: Dict[str, List[int]] = {}
|
||||
|
||||
def start_execution(self, goal: str, initial_steps: List[Dict], execution_id: str = None) -> str:
|
||||
"""
|
||||
开始执行一个新的任务
|
||||
|
||||
Args:
|
||||
goal: 任务目标
|
||||
initial_steps: 初始步骤列表
|
||||
execution_id: 执行ID,如果不提供则自动生成
|
||||
|
||||
Returns:
|
||||
执行ID
|
||||
"""
|
||||
with self._lock:
|
||||
# 如果未提供execution_id,则生成一个
|
||||
if execution_id is None:
|
||||
execution_id = f"{goal}_{asyncio.get_event_loop().time()}"
|
||||
|
||||
self._executions[execution_id] = {
|
||||
"goal": goal,
|
||||
"status": "running",
|
||||
"total_steps": len(initial_steps),
|
||||
"completed_steps": 0
|
||||
}
|
||||
|
||||
# 初始化步骤队列
|
||||
self._step_queues[execution_id] = initial_steps.copy()
|
||||
|
||||
# 初始化已执行步骤集合
|
||||
self._executed_steps[execution_id] = set()
|
||||
|
||||
# 初始化待执行步骤索引
|
||||
self._pending_steps[execution_id] = list(range(len(initial_steps)))
|
||||
|
||||
print(f"🚀 启动执行: {execution_id}")
|
||||
print(f"📊 初始步骤数: {len(initial_steps)}")
|
||||
print(f"📋 待执行步骤索引: {self._pending_steps[execution_id]}")
|
||||
|
||||
return execution_id
|
||||
|
||||
def add_steps(self, execution_id: str, new_steps: List[Dict]) -> int:
|
||||
"""
|
||||
向执行中追加新步骤
|
||||
|
||||
Args:
|
||||
execution_id: 执行ID
|
||||
new_steps: 新步骤列表
|
||||
|
||||
Returns:
|
||||
追加的步骤数量
|
||||
"""
|
||||
with self._lock:
|
||||
if execution_id not in self._step_queues:
|
||||
print(f"⚠️ 警告: 执行ID {execution_id} 不存在,无法追加步骤")
|
||||
return 0
|
||||
|
||||
current_count = len(self._step_queues[execution_id])
|
||||
|
||||
# 追加新步骤到队列
|
||||
self._step_queues[execution_id].extend(new_steps)
|
||||
|
||||
# 添加新步骤的索引到待执行列表
|
||||
new_indices = list(range(current_count, current_count + len(new_steps)))
|
||||
self._pending_steps[execution_id].extend(new_indices)
|
||||
|
||||
# 更新总步骤数
|
||||
old_total = self._executions[execution_id]["total_steps"]
|
||||
self._executions[execution_id]["total_steps"] = len(self._step_queues[execution_id])
|
||||
new_total = self._executions[execution_id]["total_steps"]
|
||||
|
||||
print(f"➕ 追加了 {len(new_steps)} 个步骤到 {execution_id}")
|
||||
print(f"📊 步骤总数: {old_total} -> {new_total}")
|
||||
print(f"📋 待执行步骤索引: {self._pending_steps[execution_id]}")
|
||||
|
||||
return len(new_steps)
|
||||
|
||||
def get_next_step(self, execution_id: str) -> Optional[Dict]:
|
||||
"""
|
||||
获取下一个待执行的步骤
|
||||
|
||||
Args:
|
||||
execution_id: 执行ID
|
||||
|
||||
Returns:
|
||||
下一个步骤,如果没有则返回None
|
||||
"""
|
||||
with self._lock:
|
||||
if execution_id not in self._pending_steps:
|
||||
print(f"⚠️ 警告: 执行ID {execution_id} 不存在")
|
||||
return None
|
||||
|
||||
# 获取第一个待执行步骤的索引
|
||||
if not self._pending_steps[execution_id]:
|
||||
return None
|
||||
|
||||
step_index = self._pending_steps[execution_id].pop(0)
|
||||
|
||||
# 从队列中获取步骤
|
||||
if step_index >= len(self._step_queues[execution_id]):
|
||||
print(f"⚠️ 警告: 步骤索引 {step_index} 超出范围")
|
||||
return None
|
||||
|
||||
step = self._step_queues[execution_id][step_index]
|
||||
|
||||
# 标记为已执行
|
||||
self._executed_steps[execution_id].add(step_index)
|
||||
|
||||
step_name = step.get("StepName", "未知")
|
||||
print(f"🎯 获取下一个步骤: {step_name} (索引: {step_index})")
|
||||
print(f"📋 剩余待执行步骤: {len(self._pending_steps[execution_id])}")
|
||||
|
||||
return step
|
||||
|
||||
def mark_step_completed(self, execution_id: str):
|
||||
"""
|
||||
标记一个步骤完成
|
||||
|
||||
Args:
|
||||
execution_id: 执行ID
|
||||
"""
|
||||
with self._lock:
|
||||
if execution_id in self._executions:
|
||||
self._executions[execution_id]["completed_steps"] += 1
|
||||
completed = self._executions[execution_id]["completed_steps"]
|
||||
total = self._executions[execution_id]["total_steps"]
|
||||
print(f"📊 步骤完成进度: {completed}/{total}")
|
||||
else:
|
||||
print(f"⚠️ 警告: 执行ID {execution_id} 不存在")
|
||||
|
||||
def get_execution_info(self, execution_id: str) -> Optional[Dict]:
|
||||
"""
|
||||
获取执行信息
|
||||
|
||||
Args:
|
||||
execution_id: 执行ID
|
||||
|
||||
Returns:
|
||||
执行信息字典
|
||||
"""
|
||||
with self._lock:
|
||||
return self._executions.get(execution_id)
|
||||
|
||||
def get_pending_count(self, execution_id: str) -> int:
|
||||
"""
|
||||
获取待执行步骤数量
|
||||
|
||||
Args:
|
||||
execution_id: 执行ID
|
||||
|
||||
Returns:
|
||||
待执行步骤数量
|
||||
"""
|
||||
with self._lock:
|
||||
if execution_id not in self._pending_steps:
|
||||
return 0
|
||||
return len(self._pending_steps[execution_id])
|
||||
|
||||
def has_more_steps(self, execution_id: str) -> bool:
|
||||
"""
|
||||
检查是否还有更多步骤待执行
|
||||
|
||||
Args:
|
||||
execution_id: 执行ID
|
||||
|
||||
Returns:
|
||||
是否还有待执行步骤
|
||||
"""
|
||||
with self._lock:
|
||||
if execution_id not in self._pending_steps:
|
||||
return False
|
||||
return len(self._pending_steps[execution_id]) > 0
|
||||
|
||||
def finish_execution(self, execution_id: str):
|
||||
"""
|
||||
完成执行
|
||||
|
||||
Args:
|
||||
execution_id: 执行ID
|
||||
"""
|
||||
with self._lock:
|
||||
if execution_id in self._executions:
|
||||
self._executions[execution_id]["status"] = "completed"
|
||||
|
||||
def cancel_execution(self, execution_id: str):
|
||||
"""
|
||||
取消执行
|
||||
|
||||
Args:
|
||||
execution_id: 执行ID
|
||||
"""
|
||||
with self._lock:
|
||||
if execution_id in self._executions:
|
||||
self._executions[execution_id]["status"] = "cancelled"
|
||||
|
||||
def cleanup(self, execution_id: str):
|
||||
"""
|
||||
清理执行记录
|
||||
|
||||
Args:
|
||||
execution_id: 执行ID
|
||||
"""
|
||||
with self._lock:
|
||||
self._executions.pop(execution_id, None)
|
||||
self._step_queues.pop(execution_id, None)
|
||||
self._executed_steps.pop(execution_id, None)
|
||||
self._pending_steps.pop(execution_id, None)
|
||||
|
||||
|
||||
# 全局单例
|
||||
dynamic_execution_manager = DynamicExecutionManager()
|
||||
@@ -1,190 +0,0 @@
|
||||
"""
|
||||
全局执行状态管理器
|
||||
用于支持任务的暂停、恢复和停止功能
|
||||
使用轮询检查机制,确保线程安全
|
||||
"""
|
||||
|
||||
import threading
|
||||
import asyncio
|
||||
import time
|
||||
from typing import Optional
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ExecutionStatus(Enum):
|
||||
"""执行状态枚举"""
|
||||
RUNNING = "running" # 正在运行
|
||||
PAUSED = "paused" # 已暂停
|
||||
STOPPED = "stopped" # 已停止
|
||||
IDLE = "idle" # 空闲
|
||||
|
||||
|
||||
class ExecutionStateManager:
|
||||
"""
|
||||
全局执行状态管理器(单例模式)
|
||||
|
||||
功能:
|
||||
- 管理任务执行状态(运行/暂停/停止)
|
||||
- 使用轮询检查机制,避免异步事件的线程问题
|
||||
- 提供线程安全的状态查询和修改接口
|
||||
"""
|
||||
|
||||
_instance: Optional['ExecutionStateManager'] = None
|
||||
_lock = threading.Lock()
|
||||
|
||||
def __new__(cls):
|
||||
"""单例模式"""
|
||||
if cls._instance is None:
|
||||
with cls._lock:
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance._initialized = False
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
"""初始化状态管理器"""
|
||||
if self._initialized:
|
||||
return
|
||||
|
||||
self._initialized = True
|
||||
self._status = ExecutionStatus.IDLE
|
||||
self._current_goal: Optional[str] = None # 当前执行的任务目标
|
||||
# 使用简单的布尔标志,而不是 asyncio.Event
|
||||
self._should_pause = False
|
||||
self._should_stop = False
|
||||
|
||||
def get_status(self) -> ExecutionStatus:
|
||||
"""获取当前执行状态"""
|
||||
with self._lock:
|
||||
return self._status
|
||||
|
||||
def set_goal(self, goal: str):
|
||||
"""设置当前执行的任务目标"""
|
||||
with self._lock:
|
||||
self._current_goal = goal
|
||||
|
||||
def get_goal(self) -> Optional[str]:
|
||||
"""获取当前执行的任务目标"""
|
||||
with self._lock:
|
||||
return self._current_goal
|
||||
|
||||
def start_execution(self, goal: str):
|
||||
"""开始执行"""
|
||||
with self._lock:
|
||||
self._status = ExecutionStatus.RUNNING
|
||||
self._current_goal = goal
|
||||
self._should_pause = False
|
||||
self._should_stop = False
|
||||
print(f"🚀 [DEBUG] start_execution: 状态设置为 RUNNING, goal={goal}")
|
||||
|
||||
def pause_execution(self) -> bool:
|
||||
"""
|
||||
暂停执行
|
||||
|
||||
Returns:
|
||||
bool: 是否成功暂停
|
||||
"""
|
||||
with self._lock:
|
||||
if self._status != ExecutionStatus.RUNNING:
|
||||
print(f"⚠️ [DEBUG] pause_execution: 当前状态不是RUNNING,而是 {self._status}")
|
||||
return False
|
||||
self._status = ExecutionStatus.PAUSED
|
||||
self._should_pause = True
|
||||
print(f"⏸️ [DEBUG] pause_execution: 状态设置为PAUSED, should_pause=True")
|
||||
return True
|
||||
|
||||
def resume_execution(self) -> bool:
|
||||
"""
|
||||
恢复执行
|
||||
|
||||
Returns:
|
||||
bool: 是否成功恢复
|
||||
"""
|
||||
with self._lock:
|
||||
if self._status != ExecutionStatus.PAUSED:
|
||||
print(f"⚠️ [DEBUG] resume_execution: 当前状态不是PAUSED,而是 {self._status}")
|
||||
return False
|
||||
self._status = ExecutionStatus.RUNNING
|
||||
self._should_pause = False
|
||||
print(f"▶️ [DEBUG] resume_execution: 状态设置为RUNNING, should_pause=False")
|
||||
return True
|
||||
|
||||
def stop_execution(self) -> bool:
|
||||
"""
|
||||
停止执行
|
||||
|
||||
Returns:
|
||||
bool: 是否成功停止
|
||||
"""
|
||||
with self._lock:
|
||||
if self._status in [ExecutionStatus.IDLE, ExecutionStatus.STOPPED]:
|
||||
return False
|
||||
self._status = ExecutionStatus.STOPPED
|
||||
self._should_stop = True
|
||||
self._should_pause = False
|
||||
print(f"🛑 [DEBUG] stop_execution: 状态设置为STOPPED")
|
||||
return True
|
||||
|
||||
def reset(self):
|
||||
"""重置状态为空闲"""
|
||||
with self._lock:
|
||||
self._status = ExecutionStatus.IDLE
|
||||
self._current_goal = None
|
||||
self._should_pause = False
|
||||
self._should_stop = False
|
||||
print(f"🔄 [DEBUG] reset: 状态重置为IDLE")
|
||||
|
||||
async def async_check_pause(self):
|
||||
"""
|
||||
异步检查是否需要暂停(轮询方式)
|
||||
|
||||
如果处于暂停状态,会阻塞当前协程直到恢复或停止
|
||||
应该在执行循环的关键点调用此方法
|
||||
|
||||
Returns:
|
||||
bool: 如果返回True表示应该继续执行,False表示应该停止
|
||||
"""
|
||||
# 使用轮询检查,避免异步事件问题
|
||||
while True:
|
||||
# 检查停止标志
|
||||
if self._should_stop:
|
||||
print("🛑 [DEBUG] async_check_pause: 检测到停止信号")
|
||||
return False
|
||||
|
||||
# 检查暂停状态
|
||||
if self._should_pause:
|
||||
# 处于暂停状态,等待恢复
|
||||
print("⏸️ [DEBUG] async_check_pause: 检测到暂停,等待恢复...")
|
||||
await asyncio.sleep(0.1) # 短暂睡眠,避免占用CPU
|
||||
|
||||
# 如果恢复,继续执行
|
||||
if not self._should_pause:
|
||||
print("▶️ [DEBUG] async_check_pause: 从暂停中恢复!")
|
||||
continue
|
||||
# 如果停止了,返回
|
||||
if self._should_stop:
|
||||
return False
|
||||
# 继续等待
|
||||
continue
|
||||
|
||||
# 既没有停止也没有暂停,可以继续执行
|
||||
return True
|
||||
|
||||
def is_paused(self) -> bool:
|
||||
"""检查是否处于暂停状态"""
|
||||
with self._lock:
|
||||
return self._status == ExecutionStatus.PAUSED
|
||||
|
||||
def is_running(self) -> bool:
|
||||
"""检查是否正在运行"""
|
||||
with self._lock:
|
||||
return self._status == ExecutionStatus.RUNNING
|
||||
|
||||
def is_stopped(self) -> bool:
|
||||
"""检查是否已停止"""
|
||||
with self._lock:
|
||||
return self._status == ExecutionStatus.STOPPED
|
||||
|
||||
|
||||
# 全局单例实例
|
||||
execution_state_manager = ExecutionStateManager()
|
||||
@@ -1,2 +1,23 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- 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"
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import re
|
||||
import json
|
||||
from AgentCoord.LLMAPI.LLMAPI import LLM_Completion
|
||||
from AgentCoord.LLMAPI.LLMAPI import LLM_Completion,GROQ_API_KEY
|
||||
|
||||
|
||||
def create_agent_dict(agent_list):
|
||||
@@ -20,8 +20,6 @@ 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 = (
|
||||
[
|
||||
@@ -33,48 +31,29 @@ def generate_template_sentence_for_CollaborationBrief(
|
||||
)
|
||||
output_object = (
|
||||
camel_case_to_normal(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")
|
||||
if is_camel_case(output_object)
|
||||
else output_object
|
||||
)
|
||||
|
||||
# Format the agents into a string with proper grammar
|
||||
if agent_list is None or len(agent_list) == 0:
|
||||
agent_str = "Unknown agents"
|
||||
elif all(agent is not None for agent in agent_list):
|
||||
agent_str = (
|
||||
" and ".join([", ".join(agent_list[:-1]), agent_list[-1]])
|
||||
if len(agent_list) > 1
|
||||
else agent_list[0]
|
||||
)
|
||||
else:
|
||||
# Filter out None values
|
||||
filtered_agents = [agent for agent in agent_list if agent is not None]
|
||||
if filtered_agents:
|
||||
agent_str = (
|
||||
" and ".join([", ".join(filtered_agents[:-1]), filtered_agents[-1]])
|
||||
if len(filtered_agents) > 1
|
||||
else filtered_agents[0]
|
||||
)
|
||||
else:
|
||||
agent_str = "Unknown agents"
|
||||
|
||||
if input_object_list is None or len(input_object_list) == 0:
|
||||
# Combine all the parts into the template sentence
|
||||
template_sentence = f"{agent_str} perform the task of {step_task} to obtain {output_object}."
|
||||
else:
|
||||
# Format the input objects into a string with proper grammar
|
||||
# Filter out None values from input_object_list
|
||||
filtered_input_list = [obj for obj in input_object_list if obj is not None]
|
||||
if filtered_input_list:
|
||||
input_str = (
|
||||
" and ".join(
|
||||
[", ".join(filtered_input_list[:-1]), filtered_input_list[-1]]
|
||||
[", ".join(input_object_list[:-1]), input_object_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}."
|
||||
|
||||
@@ -94,24 +73,80 @@ def remove_render_spec(duty_spec):
|
||||
return duty_spec
|
||||
|
||||
|
||||
def read_LLM_Completion(messages, useGroq=True):
|
||||
for _ in range(3):
|
||||
def read_LLM_Completion(messages, useGroq=None):
|
||||
if useGroq is None:
|
||||
useGroq = bool(GROQ_API_KEY)
|
||||
|
||||
# 添加调试信息和输入验证
|
||||
print(f"[DEBUG] read_LLM_Completion called with {len(messages)} messages", flush=True)
|
||||
if not messages or len(messages) == 0:
|
||||
raise ValueError("No messages provided to read_LLM_Completion")
|
||||
|
||||
# 确保messages中的每个元素都是有效的
|
||||
for i, msg in enumerate(messages):
|
||||
if not isinstance(msg, dict):
|
||||
print(f"[ERROR] Message {i} is not a dictionary: {type(msg)}", flush=True)
|
||||
raise ValueError(f"Message {i} is not a dictionary")
|
||||
if 'content' not in msg or msg['content'] is None:
|
||||
print(f"[ERROR] Message {i} has no content or content is None", flush=True)
|
||||
msg['content'] = "" # 提供默认空字符串
|
||||
|
||||
for attempt in range(3):
|
||||
try:
|
||||
print(f"[DEBUG] Attempt {attempt + 1}/3 to get LLM response", flush=True)
|
||||
text = LLM_Completion(messages, useGroq=useGroq)
|
||||
|
||||
# 确保text是字符串类型
|
||||
if text is None:
|
||||
print(f"[ERROR] Null response from LLM on attempt {attempt + 1}", flush=True)
|
||||
continue
|
||||
|
||||
text = str(text).strip()
|
||||
if not text:
|
||||
print(f"[ERROR] Empty response from LLM on attempt {attempt + 1}", flush=True)
|
||||
continue
|
||||
|
||||
print(f"[DEBUG] LLM response length: {len(text)} characters", flush=True)
|
||||
|
||||
# 尝试从代码块中提取JSON
|
||||
pattern = r"(?:.*?```json)(.*?)(?:```.*?)"
|
||||
match = re.search(pattern, text, re.DOTALL)
|
||||
|
||||
if match:
|
||||
return json.loads(match.group(1).strip())
|
||||
json_content = match.group(1).strip()
|
||||
print(f"[DEBUG] Found JSON in code block, length: {len(json_content)}", flush=True)
|
||||
try:
|
||||
result = json.loads(json_content)
|
||||
print(f"[DEBUG] Successfully parsed JSON from code block", flush=True)
|
||||
return result
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"[ERROR] JSON decode error in code block: {e}", flush=True)
|
||||
print(f"[ERROR] JSON content was: {json_content}", flush=True)
|
||||
|
||||
# 尝试直接提取JSON对象
|
||||
pattern = r"\{.*\}"
|
||||
match = re.search(pattern, text, re.DOTALL)
|
||||
if match:
|
||||
json_content = match.group(0).strip()
|
||||
print(f"[DEBUG] Found JSON in plain text, length: {len(json_content)}", flush=True)
|
||||
try:
|
||||
return json.loads(match.group(0).strip())
|
||||
except Exception:
|
||||
pass
|
||||
return {} # 返回空对象而不是抛出异常
|
||||
result = json.loads(json_content)
|
||||
print(f"[DEBUG] Successfully parsed JSON from plain text", flush=True)
|
||||
return result
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"[ERROR] JSON decode error in plain text: {e}", flush=True)
|
||||
print(f"[ERROR] JSON content was: {json_content}", flush=True)
|
||||
|
||||
print(f"[ERROR] No valid JSON found in response on attempt {attempt + 1}", flush=True)
|
||||
print(f"[ERROR] Full response was: {text[:200]}..." if len(text) > 200 else f"[ERROR] Full response was: {text}", flush=True)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Exception on attempt {attempt + 1}: {e}", flush=True)
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
continue
|
||||
|
||||
raise ValueError(f"Failed to get valid JSON response after 3 attempts. Last error: bad format or empty response")
|
||||
|
||||
|
||||
def read_json_content(text):
|
||||
@@ -132,7 +167,7 @@ def read_json_content(text):
|
||||
if match:
|
||||
return json.loads(match.group(0).strip())
|
||||
|
||||
return {} # 返回空对象而不是抛出异常
|
||||
raise ("bad format!")
|
||||
|
||||
|
||||
def read_outputObject_content(text, keyword):
|
||||
@@ -148,4 +183,4 @@ def read_outputObject_content(text, keyword):
|
||||
if match:
|
||||
return match.group(1).strip()
|
||||
else:
|
||||
return "" # 返回空字符串而不是抛出异常
|
||||
raise ("bad format!")
|
||||
@@ -4,9 +4,9 @@ OPENAI_API_KEY: "HYCNGM39GGFNSB1F8MBBMI9QYJR3P1CRSYS2PV1A"
|
||||
OPENAI_API_MODEL: "DeepSeek-V3"
|
||||
|
||||
## config for fast mode
|
||||
FAST_DESIGN_MODE: False
|
||||
FAST_DESIGN_MODE: True
|
||||
GROQ_API_KEY: ""
|
||||
MISTRAL_API_KEY: ""
|
||||
|
||||
## options under experimentation, leave them as Fasle unless you know what it is for
|
||||
## options under experimentation, leave them as False unless you know what it is for
|
||||
USE_CACHE: False
|
||||
@@ -1,10 +1,7 @@
|
||||
Flask==3.0.2
|
||||
openai==2.8.1
|
||||
openai==0.28.1
|
||||
PyYAML==6.0.1
|
||||
termcolor==2.4.0
|
||||
groq==0.4.2
|
||||
mistralai==0.1.6
|
||||
flask-socketio==5.3.6
|
||||
python-socketio==5.11.0
|
||||
simple-websocket==1.0.0
|
||||
eventlet==0.40.4
|
||||
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
|
||||
1713
backend/server.py
@@ -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": []
|
||||
}
|
||||
}
|
||||
5
frontend/components.d.ts
vendored
@@ -16,16 +16,15 @@ 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']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
MultiLineTooltip: typeof import('./src/components/MultiLineTooltip/index.vue')['default']
|
||||
Notification: typeof import('./src/components/Notification/Notification.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
|
||||
|
||||
BIN
frontend/dist.zip
Normal file
11986
frontend/package-lock.json
generated
Normal file
@@ -17,13 +17,8 @@
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"@jsplumb/browser-ui": "^6.2.10",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@vue-flow/background": "^1.3.2",
|
||||
"@vue-flow/controls": "^1.1.3",
|
||||
"@vue-flow/core": "^1.48.1",
|
||||
"@vue-flow/minimap": "^1.5.4",
|
||||
"@vueuse/core": "^14.0.0",
|
||||
"axios": "^1.12.2",
|
||||
"dompurify": "^3.3.0",
|
||||
@@ -32,7 +27,6 @@
|
||||
"markdown-it": "^14.1.0",
|
||||
"pinia": "^3.0.3",
|
||||
"qs": "^6.14.0",
|
||||
"socket.io-client": "^4.8.3",
|
||||
"uuid": "^13.0.0",
|
||||
"vue": "^3.5.22",
|
||||
"vue-router": "^4.6.3"
|
||||
|
||||
9113
frontend/pnpm-lock.yaml
generated
@@ -4,7 +4,7 @@
|
||||
"centerTitle": "多智能体协同平台",
|
||||
"taskPromptWords": [
|
||||
"如何快速筛选慢性肾脏病药物潜在受试者?",
|
||||
"如何补充\"丹芍活血胶囊\"不良反应数据?",
|
||||
"如何补充“丹芍活血胶囊”不良反应数据?",
|
||||
"如何快速研发用于战场失血性休克的药物?",
|
||||
"二维材料的光电性质受哪些关键因素影响?",
|
||||
"如何通过AI模拟的方法分析材料的微观结构?",
|
||||
@@ -15,7 +15,5 @@
|
||||
],
|
||||
"agentRepository": {
|
||||
"storageVersionIdentifier": "1"
|
||||
},
|
||||
"dev": true,
|
||||
"apiBaseUrl": "http://localhost:8000"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import type { Directive, DirectiveBinding } from 'vue'
|
||||
import { useConfigStoreHook } from '@/stores'
|
||||
|
||||
/**
|
||||
* 开发模式专用指令
|
||||
* 只在开发模式下显示元素,生产模式下会移除该元素
|
||||
*
|
||||
* \@param binding.value - 是否开启该功能,默认为 true
|
||||
* @example
|
||||
* \!-- 默认开启,开发模式显示 --
|
||||
* \!div v-dev-only开发模式内容</div>
|
||||
*
|
||||
* \!-- 传入参数控制 --
|
||||
* <div v-dev-only="true">开启指令</div>
|
||||
* <div v-dev-only="false">取消指令</div>
|
||||
*/
|
||||
export const devOnly: Directive = {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||
checkAndRemoveElement(el, binding)
|
||||
},
|
||||
|
||||
updated(el: HTMLElement, binding: DirectiveBinding) {
|
||||
checkAndRemoveElement(el, binding)
|
||||
},
|
||||
}
|
||||
|
||||
const configStore = useConfigStoreHook()
|
||||
|
||||
/**
|
||||
* 检查并移除元素的逻辑
|
||||
*/
|
||||
function checkAndRemoveElement(el: HTMLElement, binding: DirectiveBinding) {
|
||||
const isDev = typeof configStore.config.dev === 'boolean' ? configStore.config.dev : import.meta.env.DEV
|
||||
// 默认值为 true,如果没有传值或者传值为 true 都启用
|
||||
const shouldEnable = binding.value !== false
|
||||
// 如果不是开发模式或者明确禁用,移除该元素
|
||||
if (!isDev && shouldEnable) {
|
||||
if (el.parentNode) {
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { App } from 'vue'
|
||||
|
||||
import { devOnly } from './devOnly'
|
||||
|
||||
|
||||
// 全局注册 directive
|
||||
export function setupDirective(app: App<Element>) {
|
||||
app.directive('dev-only', devOnly)
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import Layout from './layout/index.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Layout />
|
||||
<layout />
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -1,21 +1,5 @@
|
||||
import request from '@/utils/request'
|
||||
import websocket from '@/utils/websocket'
|
||||
import type { Agent, IApiStepTask, IRawPlanResponse, IRawStepTask } from '@/stores'
|
||||
import {
|
||||
mockBackendAgentSelectModifyInit,
|
||||
mockBackendAgentSelectModifyAddAspect,
|
||||
type BackendAgentScoreResponse,
|
||||
} from '@/layout/components/Main/TaskTemplate/TaskSyllabus/components/mock/AgentAssignmentBackendMock'
|
||||
import {
|
||||
mockBackendFillAgentTaskProcess,
|
||||
type RawAgentTaskProcessResponse,
|
||||
} from '@/layout/components/Main/TaskTemplate/TaskProcess/components/mock/AgentTaskProcessBackendMock'
|
||||
import { mockBranchPlanOutlineAPI } from '@/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/mock/branchPlanOutlineMock'
|
||||
import { mockFillStepTaskAPI } from '@/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/mock/fill-step-task-mock'
|
||||
import {
|
||||
mockBranchTaskProcessAPI,
|
||||
type BranchAction,
|
||||
} from '@/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/mock/branchTaskProcessMock'
|
||||
import type { Agent, IRawPlanResponse } from '@/stores'
|
||||
|
||||
export interface ActionHistory {
|
||||
ID: string
|
||||
@@ -35,64 +19,9 @@ export type IExecuteRawResponse = {
|
||||
ActionHistory: ActionHistory[]
|
||||
}
|
||||
|
||||
/**
|
||||
* SSE 流式事件类型
|
||||
*/
|
||||
export type StreamingEvent =
|
||||
| {
|
||||
type: 'step_start'
|
||||
step_index: number
|
||||
total_steps: number
|
||||
step_name: string
|
||||
task_description?: string
|
||||
}
|
||||
| {
|
||||
type: 'action_complete'
|
||||
step_index: number
|
||||
step_name: string
|
||||
action_index: number
|
||||
total_actions: number
|
||||
completed_actions: number
|
||||
action_result: ActionHistory
|
||||
batch_info?: {
|
||||
batch_index: number
|
||||
batch_size: number
|
||||
is_parallel: boolean
|
||||
}
|
||||
}
|
||||
| {
|
||||
type: 'step_complete'
|
||||
step_index: number
|
||||
step_name: string
|
||||
step_log_node: any
|
||||
object_log_node: any
|
||||
}
|
||||
| {
|
||||
type: 'execution_complete'
|
||||
total_steps: number
|
||||
}
|
||||
| {
|
||||
type: 'error'
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface IFillAgentSelectionRequest {
|
||||
goal: string
|
||||
stepTask: IApiStepTask
|
||||
agents: string[]
|
||||
}
|
||||
|
||||
class Api {
|
||||
// 默认使用WebSocket
|
||||
private useWebSocketDefault = true
|
||||
|
||||
setAgents = (data: Pick<Agent, 'Name' | 'Profile' | 'apiUrl' | 'apiKey' | 'apiModel'>[], useWebSocket: boolean = this.useWebSocketDefault) => {
|
||||
// 如果启用WebSocket且已连接,使用WebSocket
|
||||
if (useWebSocket && websocket.connected) {
|
||||
return websocket.send('set_agents', data)
|
||||
}
|
||||
|
||||
// 否则使用REST API
|
||||
// 智能体信息
|
||||
setAgents = (data: Pick<Agent, 'Name' | 'Profile'>[]) => {
|
||||
return request({
|
||||
url: '/setAgents',
|
||||
data,
|
||||
@@ -100,38 +29,13 @@ class Api {
|
||||
})
|
||||
}
|
||||
|
||||
generateBasePlan = (data: {
|
||||
goal: string
|
||||
inputs: string[]
|
||||
apiUrl?: string
|
||||
apiKey?: string
|
||||
apiModel?: string
|
||||
useWebSocket?: boolean
|
||||
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
|
||||
}) => {
|
||||
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
|
||||
|
||||
// 如果启用WebSocket且已连接,使用WebSocket
|
||||
if (useWs && websocket.connected) {
|
||||
return websocket.send('generate_base_plan', {
|
||||
'General Goal': data.goal,
|
||||
'Initial Input Object': data.inputs,
|
||||
apiUrl: data.apiUrl,
|
||||
apiKey: data.apiKey,
|
||||
apiModel: data.apiModel,
|
||||
}, undefined, data.onProgress)
|
||||
}
|
||||
|
||||
// 否则使用REST API
|
||||
generateBasePlan = (data: { goal: string; inputs: string[] }) => {
|
||||
return request<unknown, IRawPlanResponse>({
|
||||
url: '/generate_basePlan',
|
||||
method: 'POST',
|
||||
data: {
|
||||
'General Goal': data.goal,
|
||||
'Initial Input Object': data.inputs,
|
||||
apiUrl: data.apiUrl,
|
||||
apiKey: data.apiKey,
|
||||
apiModel: data.apiModel,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -146,749 +50,13 @@ class Api {
|
||||
plan: {
|
||||
'Initial Input Object': plan['Initial Input Object'],
|
||||
'General Goal': plan['General Goal'],
|
||||
'Collaboration Process': plan['Collaboration Process']?.map((step) => ({
|
||||
'Collaboration Process': plan['Collaboration Process']?.map(step => ({
|
||||
StepName: step.StepName,
|
||||
TaskContent: step.TaskContent,
|
||||
InputObject_List: step.InputObject_List,
|
||||
OutputObject: step.OutputObject,
|
||||
AgentSelection: step.AgentSelection,
|
||||
Collaboration_Brief_frontEnd: step.Collaboration_Brief_frontEnd,
|
||||
TaskProcess: step.TaskProcess.map((action) => ({
|
||||
ActionType: action.ActionType,
|
||||
AgentName: action.AgentName,
|
||||
Description: action.Description,
|
||||
ID: action.ID,
|
||||
ImportantInput: action.ImportantInput,
|
||||
})),
|
||||
})),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化版流式执行计划(支持动态追加步骤)
|
||||
* 步骤级流式 + 动作级智能并行 + 动态追加步骤
|
||||
*/
|
||||
executePlanOptimized = (
|
||||
plan: IRawPlanResponse,
|
||||
onMessage: (event: StreamingEvent) => void,
|
||||
onError?: (error: Error) => void,
|
||||
onComplete?: () => void,
|
||||
useWebSocket?: boolean,
|
||||
existingKeyObjects?: Record<string, any>,
|
||||
enableDynamic?: boolean,
|
||||
onExecutionStarted?: (executionId: string) => void,
|
||||
executionId?: string,
|
||||
restartFromStepIndex?: number, // 新增:从指定步骤重新执行的索引
|
||||
rehearsalLog?: any[], // 新增:传递截断后的 RehearsalLog
|
||||
) => {
|
||||
const useWs = useWebSocket !== undefined ? useWebSocket : this.useWebSocketDefault
|
||||
|
||||
const data = {
|
||||
RehearsalLog: rehearsalLog || [], // ✅ 使用传递的 RehearsalLog
|
||||
num_StepToRun: null,
|
||||
existingKeyObjects: existingKeyObjects || {},
|
||||
enable_dynamic: enableDynamic || false,
|
||||
execution_id: executionId || null,
|
||||
restart_from_step_index: restartFromStepIndex ?? null, // 新增:传递重新执行索引
|
||||
plan: {
|
||||
'Initial Input Object': plan['Initial Input Object'],
|
||||
'General Goal': plan['General Goal'],
|
||||
'Collaboration Process': plan['Collaboration Process']?.map((step) => ({
|
||||
StepName: step.StepName,
|
||||
TaskContent: step.TaskContent,
|
||||
InputObject_List: step.InputObject_List,
|
||||
OutputObject: step.OutputObject,
|
||||
AgentSelection: step.AgentSelection,
|
||||
Collaboration_Brief_frontEnd: step.Collaboration_Brief_frontEnd,
|
||||
TaskProcess: step.TaskProcess.map((action) => ({
|
||||
ActionType: action.ActionType,
|
||||
AgentName: action.AgentName,
|
||||
Description: action.Description,
|
||||
ID: action.ID,
|
||||
ImportantInput: action.ImportantInput,
|
||||
})),
|
||||
})),
|
||||
},
|
||||
}
|
||||
|
||||
// 如果启用WebSocket且已连接,使用WebSocket
|
||||
if (useWs && websocket.connected) {
|
||||
websocket.subscribe(
|
||||
'execute_plan_optimized',
|
||||
data,
|
||||
// onProgress
|
||||
(progressData) => {
|
||||
try {
|
||||
let event: StreamingEvent
|
||||
|
||||
// 处理不同类型的progress数据
|
||||
if (typeof progressData === 'string') {
|
||||
event = JSON.parse(progressData)
|
||||
} else {
|
||||
event = progressData as StreamingEvent
|
||||
}
|
||||
|
||||
// 处理特殊事件类型
|
||||
if (event && typeof event === 'object') {
|
||||
// 检查是否是execution_started事件
|
||||
if ('status' in event && event.status === 'execution_started') {
|
||||
if ('execution_id' in event && onExecutionStarted) {
|
||||
onExecutionStarted(event.execution_id as string)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
onMessage(event)
|
||||
} catch (e) {
|
||||
// Failed to parse WebSocket data
|
||||
}
|
||||
},
|
||||
// onComplete
|
||||
() => {
|
||||
onComplete?.()
|
||||
},
|
||||
// onError
|
||||
(error) => {
|
||||
onError?.(error)
|
||||
}
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// 否则使用原有的SSE方式
|
||||
|
||||
fetch('/api/executePlanOptimized', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
.then(async (response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
|
||||
const reader = response.body?.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
if (!reader) {
|
||||
throw new Error('Response body is null')
|
||||
}
|
||||
|
||||
let buffer = ''
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
|
||||
if (done) {
|
||||
onComplete?.()
|
||||
break
|
||||
}
|
||||
|
||||
buffer += decoder.decode(value, { stream: true })
|
||||
|
||||
const lines = buffer.split('\n')
|
||||
buffer = lines.pop() || ''
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.slice(6)
|
||||
try {
|
||||
const event = JSON.parse(data)
|
||||
onMessage(event)
|
||||
} catch (e) {
|
||||
// Failed to parse SSE data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
onError?.(error)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 分支任务大纲
|
||||
*/
|
||||
branchPlanOutline = (data: {
|
||||
branch_Number: number
|
||||
Modification_Requirement: string
|
||||
Existing_Steps: IRawStepTask[]
|
||||
Baseline_Completion: number
|
||||
initialInputs: string[]
|
||||
goal: string
|
||||
useWebSocket?: boolean
|
||||
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
|
||||
}) => {
|
||||
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
|
||||
|
||||
// 如果启用WebSocket且已连接,使用WebSocket
|
||||
if (useWs && websocket.connected) {
|
||||
return websocket.send('branch_plan_outline', {
|
||||
branch_Number: data.branch_Number,
|
||||
Modification_Requirement: data.Modification_Requirement,
|
||||
Existing_Steps: data.Existing_Steps,
|
||||
Baseline_Completion: data.Baseline_Completion,
|
||||
'Initial Input Object': data.initialInputs,
|
||||
'General Goal': data.goal,
|
||||
}, undefined, data.onProgress)
|
||||
}
|
||||
|
||||
// 否则使用REST API
|
||||
return request<unknown, IRawPlanResponse>({
|
||||
url: '/branch_PlanOutline',
|
||||
method: 'POST',
|
||||
data: {
|
||||
branch_Number: data.branch_Number,
|
||||
Modification_Requirement: data.Modification_Requirement,
|
||||
Existing_Steps: data.Existing_Steps,
|
||||
Baseline_Completion: data.Baseline_Completion,
|
||||
'Initial Input Object': data.initialInputs,
|
||||
'General Goal': data.goal,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 分支任务流程
|
||||
*/
|
||||
branchTaskProcess = (data: {
|
||||
branch_Number: number
|
||||
Modification_Requirement: string
|
||||
Existing_Steps: BranchAction[]
|
||||
Baseline_Completion: number
|
||||
stepTaskExisting: any
|
||||
goal: string
|
||||
useWebSocket?: boolean
|
||||
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
|
||||
}) => {
|
||||
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
|
||||
|
||||
// 如果启用WebSocket且已连接,使用WebSocket
|
||||
if (useWs && websocket.connected) {
|
||||
return websocket.send('branch_task_process', {
|
||||
branch_Number: data.branch_Number,
|
||||
Modification_Requirement: data.Modification_Requirement,
|
||||
Existing_Steps: data.Existing_Steps,
|
||||
Baseline_Completion: data.Baseline_Completion,
|
||||
stepTaskExisting: data.stepTaskExisting,
|
||||
'General Goal': data.goal,
|
||||
}, undefined, data.onProgress)
|
||||
}
|
||||
|
||||
// 否则使用REST API
|
||||
return request<unknown, BranchAction[][]>({
|
||||
url: '/branch_TaskProcess',
|
||||
method: 'POST',
|
||||
data: {
|
||||
branch_Number: data.branch_Number,
|
||||
Modification_Requirement: data.Modification_Requirement,
|
||||
Existing_Steps: data.Existing_Steps,
|
||||
Baseline_Completion: data.Baseline_Completion,
|
||||
stepTaskExisting: data.stepTaskExisting,
|
||||
'General Goal': data.goal,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fillStepTask = async (data: {
|
||||
goal: string
|
||||
stepTask: any
|
||||
useWebSocket?: boolean
|
||||
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
|
||||
}): Promise<IRawStepTask> => {
|
||||
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
|
||||
let response: any
|
||||
|
||||
// 如果启用WebSocket且已连接,使用WebSocket
|
||||
if (useWs && websocket.connected) {
|
||||
response = await websocket.send('fill_step_task', {
|
||||
'General Goal': data.goal,
|
||||
stepTask: data.stepTask,
|
||||
}, undefined, data.onProgress)
|
||||
} else {
|
||||
// 否则使用REST API
|
||||
response = await request<
|
||||
{
|
||||
'General Goal': string
|
||||
stepTask: any
|
||||
},
|
||||
{
|
||||
AgentSelection?: string[]
|
||||
Collaboration_Brief_FrontEnd?: {
|
||||
template: string
|
||||
data: Record<string, { text: string; color: number[] }>
|
||||
}
|
||||
InputObject_List?: string[]
|
||||
OutputObject?: string
|
||||
StepName?: string
|
||||
TaskContent?: string
|
||||
TaskProcess?: Array<{
|
||||
ID: string
|
||||
ActionType: string
|
||||
AgentName: string
|
||||
Description: string
|
||||
ImportantInput: string[]
|
||||
}>
|
||||
}
|
||||
>({
|
||||
url: '/fill_stepTask',
|
||||
method: 'POST',
|
||||
data: {
|
||||
'General Goal': data.goal,
|
||||
stepTask: data.stepTask,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const vec2Hsl = (color: number[]): string => {
|
||||
const [h, s, l] = color
|
||||
return `hsl(${h}, ${s}%, ${l}%)`
|
||||
}
|
||||
|
||||
const briefData: Record<string, { text: string; style?: Record<string, string> }> = {}
|
||||
if (response.Collaboration_Brief_FrontEnd?.data) {
|
||||
for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) {
|
||||
briefData[key] = {
|
||||
text: value.text,
|
||||
style: {
|
||||
background: vec2Hsl(value.color),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建前端格式的 IRawStepTask
|
||||
*/
|
||||
return {
|
||||
StepName: response.StepName || '',
|
||||
TaskContent: response.TaskContent || '',
|
||||
InputObject_List: response.InputObject_List || [],
|
||||
OutputObject: response.OutputObject || '',
|
||||
AgentSelection: response.AgentSelection || [],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: response.Collaboration_Brief_FrontEnd?.template || '',
|
||||
data: briefData,
|
||||
},
|
||||
TaskProcess: response.TaskProcess || [],
|
||||
}
|
||||
}
|
||||
|
||||
fillStepTaskTaskProcess = async (data: {
|
||||
goal: string
|
||||
stepTask: IApiStepTask
|
||||
agents: string[]
|
||||
useWebSocket?: boolean
|
||||
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
|
||||
}): Promise<IApiStepTask> => {
|
||||
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
|
||||
let response: any
|
||||
|
||||
// 如果启用WebSocket且已连接,使用WebSocket
|
||||
if (useWs && websocket.connected) {
|
||||
response = await websocket.send('fill_step_task_process', {
|
||||
'General Goal': data.goal,
|
||||
stepTask_lackTaskProcess: {
|
||||
StepName: data.stepTask.name,
|
||||
TaskContent: data.stepTask.content,
|
||||
InputObject_List: data.stepTask.inputs,
|
||||
OutputObject: data.stepTask.output,
|
||||
AgentSelection: data.agents,
|
||||
},
|
||||
}, undefined, data.onProgress)
|
||||
} else {
|
||||
// 否则使用REST API
|
||||
response = await request<
|
||||
{
|
||||
'General Goal': string
|
||||
stepTask_lackTaskProcess: {
|
||||
StepName: string
|
||||
TaskContent: string
|
||||
InputObject_List: string[]
|
||||
OutputObject: string
|
||||
AgentSelection: string[]
|
||||
}
|
||||
},
|
||||
{
|
||||
StepName?: string
|
||||
TaskContent?: string
|
||||
InputObject_List?: string[]
|
||||
OutputObject?: string
|
||||
AgentSelection?: string[]
|
||||
TaskProcess?: Array<{
|
||||
ID: string
|
||||
ActionType: string
|
||||
AgentName: string
|
||||
Description: string
|
||||
ImportantInput: string[]
|
||||
}>
|
||||
Collaboration_Brief_FrontEnd?: {
|
||||
template: string
|
||||
data: Record<string, { text: string; color: number[] }>
|
||||
}
|
||||
}
|
||||
>({
|
||||
url: '/fill_stepTask_TaskProcess',
|
||||
method: 'POST',
|
||||
data: {
|
||||
'General Goal': data.goal,
|
||||
stepTask_lackTaskProcess: {
|
||||
StepName: data.stepTask.name,
|
||||
TaskContent: data.stepTask.content,
|
||||
InputObject_List: data.stepTask.inputs,
|
||||
OutputObject: data.stepTask.output,
|
||||
AgentSelection: data.agents,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const vec2Hsl = (color: number[]): string => {
|
||||
const [h, s, l] = color
|
||||
return `hsl(${h}, ${s}%, ${l}%)`
|
||||
}
|
||||
|
||||
const briefData: Record<string, { text: string; style: { background: string } }> = {}
|
||||
if (response.Collaboration_Brief_FrontEnd?.data) {
|
||||
for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) {
|
||||
briefData[key] = {
|
||||
text: value.text,
|
||||
style: {
|
||||
background: vec2Hsl(value.color),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const process = (response.TaskProcess || []).map((action: any) => ({
|
||||
id: action.ID,
|
||||
type: action.ActionType,
|
||||
agent: action.AgentName,
|
||||
description: action.Description,
|
||||
inputs: action.ImportantInput,
|
||||
}))
|
||||
|
||||
return {
|
||||
name: response.StepName || '',
|
||||
content: response.TaskContent || '',
|
||||
inputs: response.InputObject_List || [],
|
||||
output: response.OutputObject || '',
|
||||
agents: response.AgentSelection || [],
|
||||
brief: {
|
||||
template: response.Collaboration_Brief_FrontEnd?.template || '',
|
||||
data: briefData,
|
||||
},
|
||||
process,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为每个智能体评分
|
||||
*/
|
||||
agentSelectModifyInit = async (data: {
|
||||
goal: string
|
||||
stepTask: any
|
||||
useWebSocket?: boolean
|
||||
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
|
||||
}): Promise<Record<string, Record<string, { reason: string; score: number }>>> => {
|
||||
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
|
||||
let response: Record<string, Record<string, { Reason: string; Score: number }>>
|
||||
|
||||
// 如果启用WebSocket且已连接,使用WebSocket
|
||||
if (useWs && websocket.connected) {
|
||||
response = await websocket.send('agent_select_modify_init', {
|
||||
'General Goal': data.goal,
|
||||
stepTask: {
|
||||
StepName: data.stepTask.StepName || data.stepTask.name,
|
||||
TaskContent: data.stepTask.TaskContent || data.stepTask.content,
|
||||
InputObject_List: data.stepTask.InputObject_List || data.stepTask.inputs,
|
||||
OutputObject: data.stepTask.OutputObject || data.stepTask.output,
|
||||
},
|
||||
}, undefined, data.onProgress)
|
||||
} else {
|
||||
// 否则使用REST API
|
||||
response = await request<
|
||||
{
|
||||
'General Goal': string
|
||||
stepTask: any
|
||||
},
|
||||
Record<string, Record<string, { Reason: string; Score: number }>>
|
||||
>({
|
||||
url: '/agentSelectModify_init',
|
||||
method: 'POST',
|
||||
data: {
|
||||
'General Goal': data.goal,
|
||||
stepTask: {
|
||||
StepName: data.stepTask.StepName || data.stepTask.name,
|
||||
TaskContent: data.stepTask.TaskContent || data.stepTask.content,
|
||||
InputObject_List: data.stepTask.InputObject_List || data.stepTask.inputs,
|
||||
OutputObject: data.stepTask.OutputObject || data.stepTask.output,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
|
||||
|
||||
for (const [aspect, agents] of Object.entries(response)) {
|
||||
for (const [agentName, scoreInfo] of Object.entries(agents)) {
|
||||
if (!transformedData[agentName]) {
|
||||
transformedData[agentName] = {}
|
||||
}
|
||||
transformedData[agentName][aspect] = {
|
||||
reason: scoreInfo.Reason,
|
||||
score: scoreInfo.Score,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return transformedData
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加新的评估维度
|
||||
*/
|
||||
agentSelectModifyAddAspect = async (data: {
|
||||
aspectList: string[]
|
||||
useWebSocket?: boolean
|
||||
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
|
||||
}): Promise<{
|
||||
aspectName: string
|
||||
agentScores: Record<string, { score: number; reason: string }>
|
||||
}> => {
|
||||
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
|
||||
let response: Record<string, Record<string, { Reason: string; Score: number }>>
|
||||
|
||||
// 如果启用WebSocket且已连接,使用WebSocket
|
||||
if (useWs && websocket.connected) {
|
||||
response = await websocket.send('agent_select_modify_add_aspect', {
|
||||
aspectList: data.aspectList,
|
||||
}, undefined, data.onProgress)
|
||||
} else {
|
||||
// 否则使用REST API
|
||||
response = await request<
|
||||
{
|
||||
aspectList: string[]
|
||||
},
|
||||
Record<string, Record<string, { Reason: string; Score: number }>>
|
||||
>({
|
||||
url: '/agentSelectModify_addAspect',
|
||||
method: 'POST',
|
||||
data: {
|
||||
aspectList: data.aspectList,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取新添加的维度
|
||||
*/
|
||||
const newAspect = data.aspectList[data.aspectList.length - 1]
|
||||
if (!newAspect) {
|
||||
throw new Error('aspectList is empty')
|
||||
}
|
||||
|
||||
const newAspectAgents = response[newAspect]
|
||||
const agentScores: Record<string, { score: number; reason: string }> = {}
|
||||
|
||||
if (newAspectAgents) {
|
||||
for (const [agentName, scoreInfo] of Object.entries(newAspectAgents)) {
|
||||
agentScores[agentName] = {
|
||||
score: scoreInfo.Score,
|
||||
reason: scoreInfo.Reason,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
aspectName: newAspect,
|
||||
agentScores,
|
||||
}
|
||||
}
|
||||
/**
|
||||
* ==================== Mock API(开发阶段使用)====================
|
||||
*为每个智能体评分
|
||||
*/
|
||||
mockAgentSelectModifyInit = async (): Promise<
|
||||
Record<string, Record<string, { reason: string; score: number }>>
|
||||
> => {
|
||||
const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyInit()
|
||||
|
||||
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
|
||||
|
||||
for (const [aspect, agents] of Object.entries(response)) {
|
||||
for (const [agentName, scoreInfo] of Object.entries(agents)) {
|
||||
if (!transformedData[agentName]) {
|
||||
transformedData[agentName] = {}
|
||||
}
|
||||
transformedData[agentName][aspect] = {
|
||||
reason: scoreInfo.Reason,
|
||||
score: scoreInfo.Score,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return transformedData
|
||||
}
|
||||
|
||||
mockAgentSelectModifyAddAspect = async (data: {
|
||||
aspectList: string[]
|
||||
}): Promise<{
|
||||
aspectName: string
|
||||
agentScores: Record<string, { score: number; reason: string }>
|
||||
}> => {
|
||||
const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyAddAspect(
|
||||
data.aspectList,
|
||||
)
|
||||
|
||||
const newAspect = data.aspectList[data.aspectList.length - 1]
|
||||
if (!newAspect) {
|
||||
throw new Error('aspectList is empty')
|
||||
}
|
||||
|
||||
const newAspectAgents = response[newAspect]
|
||||
const agentScores: Record<string, { score: number; reason: string }> = {}
|
||||
|
||||
if (newAspectAgents) {
|
||||
for (const [agentName, scoreInfo] of Object.entries(newAspectAgents)) {
|
||||
agentScores[agentName] = {
|
||||
score: scoreInfo.Score,
|
||||
reason: scoreInfo.Reason,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
aspectName: newAspect,
|
||||
agentScores,
|
||||
}
|
||||
}
|
||||
|
||||
mockFillStepTaskTaskProcess = async (data: {
|
||||
goal: string
|
||||
stepTask: IApiStepTask
|
||||
agents: string[]
|
||||
}): Promise<IApiStepTask> => {
|
||||
const response: RawAgentTaskProcessResponse = await mockBackendFillAgentTaskProcess(
|
||||
data.goal,
|
||||
data.stepTask,
|
||||
data.agents,
|
||||
)
|
||||
|
||||
const vec2Hsl = (color: number[]): string => {
|
||||
const [h, s, l] = color
|
||||
return `hsl(${h}, ${s}%, ${l}%)`
|
||||
}
|
||||
|
||||
const briefData: Record<string, { text: string; style: { background: string } }> = {}
|
||||
if (response.Collaboration_Brief_frontEnd?.data) {
|
||||
for (const [key, value] of Object.entries(response.Collaboration_Brief_frontEnd.data)) {
|
||||
briefData[key] = {
|
||||
text: value.text,
|
||||
style: {
|
||||
background: vec2Hsl(value.color),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const process = (response.TaskProcess || []).map((action) => ({
|
||||
id: action.ID,
|
||||
type: action.ActionType,
|
||||
agent: action.AgentName,
|
||||
description: action.Description,
|
||||
inputs: action.ImportantInput,
|
||||
}))
|
||||
|
||||
return {
|
||||
name: response.StepName || '',
|
||||
content: response.TaskContent || '',
|
||||
inputs: response.InputObject_List || [],
|
||||
output: response.OutputObject || '',
|
||||
agents: response.AgentSelection || [],
|
||||
brief: {
|
||||
template: response.Collaboration_Brief_frontEnd?.template || '',
|
||||
data: briefData,
|
||||
},
|
||||
process,
|
||||
}
|
||||
}
|
||||
|
||||
mockBranchPlanOutline = async (data: {
|
||||
branch_Number: number
|
||||
Modification_Requirement: string
|
||||
Existing_Steps: IRawStepTask[]
|
||||
Baseline_Completion: number
|
||||
initialInputs: string[]
|
||||
goal: string
|
||||
}): Promise<IRawPlanResponse> => {
|
||||
const response = await mockBranchPlanOutlineAPI({
|
||||
branch_Number: data.branch_Number,
|
||||
Modification_Requirement: data.Modification_Requirement,
|
||||
Existing_Steps: data.Existing_Steps,
|
||||
Baseline_Completion: data.Baseline_Completion,
|
||||
InitialObject_List: data.initialInputs,
|
||||
General_Goal: data.goal,
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
mockFillStepTask = async (data: { goal: string; stepTask: any }): Promise<any> => {
|
||||
const response = await mockFillStepTaskAPI({
|
||||
General_Goal: data.goal,
|
||||
stepTask: data.stepTask,
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
mockBranchTaskProcess = async (data: {
|
||||
branch_Number: number
|
||||
Modification_Requirement: string
|
||||
Existing_Steps: BranchAction[]
|
||||
Baseline_Completion: number
|
||||
stepTaskExisting: any
|
||||
goal: string
|
||||
}): Promise<BranchAction[][]> => {
|
||||
const response = await mockBranchTaskProcessAPI({
|
||||
branch_Number: data.branch_Number,
|
||||
Modification_Requirement: data.Modification_Requirement,
|
||||
Existing_Steps: data.Existing_Steps,
|
||||
Baseline_Completion: data.Baseline_Completion,
|
||||
stepTaskExisting: data.stepTaskExisting,
|
||||
General_Goal: data.goal,
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* 向正在执行的任务追加新步骤
|
||||
* @param executionId 执行ID
|
||||
* @param newSteps 新步骤列表
|
||||
* @returns 追加的步骤数量
|
||||
*/
|
||||
addStepsToExecution = async (executionId: string, newSteps: IRawStepTask[]): Promise<number> => {
|
||||
if (!websocket.connected) {
|
||||
throw new Error('WebSocket未连接')
|
||||
}
|
||||
|
||||
const response = await websocket.send('add_steps_to_execution', {
|
||||
execution_id: executionId,
|
||||
new_steps: newSteps.map(step => ({
|
||||
StepName: step.StepName,
|
||||
TaskContent: step.TaskContent,
|
||||
InputObject_List: step.InputObject_List,
|
||||
OutputObject: step.OutputObject,
|
||||
AgentSelection: step.AgentSelection,
|
||||
Collaboration_Brief_frontEnd: step.Collaboration_Brief_frontEnd,
|
||||
Collaboration_Brief_frontEnd: step.Collaboration_Brief_FrontEnd,
|
||||
TaskProcess: step.TaskProcess.map(action => ({
|
||||
ActionType: action.ActionType,
|
||||
AgentName: action.AgentName,
|
||||
@@ -897,9 +65,9 @@ class Api {
|
||||
ImportantInput: action.ImportantInput,
|
||||
})),
|
||||
})),
|
||||
}) as { added_count: number }
|
||||
|
||||
return response?.added_count || 0
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1766247549273" class="icon"
|
||||
viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="10050" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="16" height="16">
|
||||
<path d="M512 1024C229.248 1024
|
||||
0 794.752 0 512S229.248 0 512 0s512
|
||||
229.248 512 512-229.248 512-512 512z
|
||||
m0-938.666667C276.352 85.333333 85.333333
|
||||
276.352 85.333333 512s191.018667 426.666667
|
||||
426.666667 426.666667 426.666667-191.018667
|
||||
426.666667-426.666667S747.648 85.333333 512
|
||||
85.333333z m198.698667 625.365334a42.666667
|
||||
42.666667 0 0 1-60.330667
|
||||
0L512 572.330667l-138.368 138.368a42.666667 42.666667 0 0 1-60.330667-60.330667L451.669333 512 313.301333 373.632a42.666667 42.666667 0 0 1 60.330667-60.330667L512 451.669333l138.368-138.368a42.624 42.624 0 1 1 60.330667 60.330667L572.330667 512l138.368 138.368a42.666667 42.666667 0 0 1 0 60.330667z"
|
||||
fill="#8e0707" p-id="10051">
|
||||
</path></svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1766247454540" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9049"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
|
||||
<path d="M512 1024C230.4 1024 0 793.6 0 512S230.4 0 512 0s512 230.4 512 512-230.4 512-512 512z m0-938.666667C277.333333 85.333333 85.333333 277.333333 85.333333 512s192 426.666667 426.666667 426.666667 426.666667-192 426.666667-426.666667S746.666667 85.333333 512 85.333333z" p-id="9050">
|
||||
</path>
|
||||
<path d="M708.266667 375.466667c-17.066667-17.066667-44.8-17.066667-59.733334 0l-181.333333 181.333333-91.733333-91.733333c-14.933333-14.933333-40.533333-14.933333-55.466667 0l-6.4 4.266666c-14.933333 14.933333-14.933333 40.533333 0 55.466667l125.866667 125.866667c14.933333 14.933333 40.533333 14.933333 55.466666 0l4.266667-4.266667 209.066667-209.066667c17.066667-17.066667 17.066667-44.8 0-61.866666z" p-id="9051">
|
||||
</path></svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1766136633767" class="icon" viewBox="0 0 1028 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7819" width="32.125" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M1018.319924 112.117535q4.093748 9.210934 6.652341 21.492179t2.558593 25.585928-5.117186 26.609365-16.374994 25.585928q-12.281245 12.281245-22.003898 21.492179t-16.886712 16.374994q-8.187497 8.187497-15.351557 14.32812l-191.382739-191.382739q12.281245-11.257808 29.167958-27.121083t28.144521-25.074209q14.32812-11.257808 29.679676-15.863275t30.191395-4.093748 28.656239 4.605467 24.050772 9.210934q21.492179 11.257808 47.589826 39.402329t40.425766 58.847634zM221.062416 611.554845q6.140623-6.140623 28.656239-29.167958t56.289041-56.80076l74.710909-74.710909 82.898406-82.898406 220.038979-220.038979 191.382739 192.406177-220.038979 220.038979-81.874969 82.898406q-40.937484 39.914047-73.687472 73.175753t-54.242167 54.753885-25.585928 24.562491q-10.234371 9.210934-23.539054 19.445305t-27.632802 16.374994q-14.32812 7.16406-41.960921 17.398431t-57.824197 19.957024-57.312478 16.886712-40.425766 9.210934q-27.632802 3.070311-36.843736-8.187497t-5.117186-37.867173q2.046874-14.32812 9.722653-41.449203t16.374994-56.289041 16.886712-53.730448 13.304682-33.773425q6.140623-14.32812 13.816401-26.097646t22.003898-26.097646z" p-id="7820" fill="#000000"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1769048650684" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6190" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M874.058005 149.941995a510.06838 510.06838 0 1 0 109.740156 162.738976 511.396369 511.396369 0 0 0-109.740156-162.738976z m66.278708 362.178731A428.336713 428.336713 0 1 1 512 83.663287a428.698892 428.698892 0 0 1 428.336713 428.336713z" fill="#36404f" p-id="6191"></path><path d="M417.954256 281.533601a41.046923 41.046923 0 0 0-41.77128 40.201839v385.116718a41.892007 41.892007 0 0 0 83.663287 0v-385.116718a41.167649 41.167649 0 0 0-41.892007-40.201839zM606.045744 281.533601a41.046923 41.046923 0 0 0-41.77128 40.201839v385.116718a41.892007 41.892007 0 0 0 83.663287 0v-385.116718a41.167649 41.167649 0 0 0-41.892007-40.201839z" fill="#36404f" p-id="6192"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1004 B |
@@ -1,5 +1 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1761736278335" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5885"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
|
||||
<path d="M226.592 896C167.616 896 128 850.48 128 782.736V241.264C128 173.52 167.616 128 226.592 128c20.176 0 41.136 5.536 62.288 16.464l542.864 280.432C887.648 453.792 896 491.872 896 512s-8.352 58.208-64.272 87.088L288.864 879.536C267.712 890.464 246.768 896 226.592 896z m0-704.304c-31.008 0-34.368 34.656-34.368 49.568v541.472c0 14.896 3.344 49.568 34.368 49.568 9.6 0 20.88-3.2 32.608-9.248l542.864-280.432c21.904-11.328 29.712-23.232 29.712-30.608s-7.808-19.28-29.712-30.592L259.2 200.96c-11.728-6.048-23.008-9.264-32.608-9.264z" p-id="5886"></path></svg>
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1761736278335" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5885" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M226.592 896C167.616 896 128 850.48 128 782.736V241.264C128 173.52 167.616 128 226.592 128c20.176 0 41.136 5.536 62.288 16.464l542.864 280.432C887.648 453.792 896 491.872 896 512s-8.352 58.208-64.272 87.088L288.864 879.536C267.712 890.464 246.768 896 226.592 896z m0-704.304c-31.008 0-34.368 34.656-34.368 49.568v541.472c0 14.896 3.344 49.568 34.368 49.568 9.6 0 20.88-3.2 32.608-9.248l542.864-280.432c21.904-11.328 29.712-23.232 29.712-30.608s-7.808-19.28-29.712-30.592L259.2 200.96c-11.728-6.048-23.008-9.264-32.608-9.264z" p-id="5886"></path></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 890 B After Width: | Height: | Size: 886 B |
@@ -1 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1767084779395" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="21382" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M304.79872 108.70784c-118.75328 2.56-194.5856 51.74784-259.92192 144.73216-69.6832 99.1232-37.41696 233.31328-37.41696 233.31328s31.7696-100.06528 88.81664-147.74784c50.53952-42.22976 99.60448-78.11072 208.52224-82.97984v54.68672l141.69088-126.94016-141.69088-129.024v53.95968zM719.19616 915.25632c118.75328-2.56512 194.5856-51.712 259.96288-144.6912 69.64224-99.1232 37.37088-233.31328 37.37088-233.31328s-31.7696 100.06528-88.81152 147.74784c-50.51904 42.20928-99.6096 78.08512-208.52736 82.95936v-54.66624l-141.66528 126.93504 141.66528 129.024v-53.99552zM794.82368 304.37376h-88.64256c-72.77056 0-131.712 58.96192-131.712 131.712v110.96064c54.29248 22.21056 113.75616 34.49856 176.06656 34.49856 62.26944 0 121.728-12.288 176.02048-34.49856V436.08064c-0.00512-72.75008-58.96192-131.70688-131.73248-131.70688zM863.34464 167.58272c0 62.336-50.49856 112.87552-112.80896 112.87552-62.37696 0-112.87552-50.53952-112.87552-112.87552s50.49856-112.83456 112.87552-112.83456c62.30528 0 112.80896 50.49856 112.80896 112.83456z" fill="#ffffff" p-id="21383"></path><path d="M333.27616 692.08576H244.6336c-72.77056 0-131.73248 58.9824-131.73248 131.73248v110.94016c54.30784 22.23104 113.75104 34.49856 176.06656 34.49856 62.28992 0 121.74848-12.26752 176.02048-34.49856v-110.94016c0-72.75008-58.96192-131.73248-131.712-131.73248zM401.80224 555.31008c0 62.31552-50.47808 112.88064-112.83456 112.88064-62.37696 0-112.88064-50.56512-112.88064-112.88064 0-62.35136 50.50368-112.85504 112.88064-112.85504 62.35648 0 112.83456 50.5088 112.83456 112.85504z" fill="#ffffff" p-id="21384"></path></svg>
|
||||
<?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>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.8 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1766289101374" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2562" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M801.796646 348.507817c-6.599924 7.099918-9.599889 16.799806-8.299904 26.399694 1.499983 10.899874 2.399972 21.799748 2.699969 32.299627 1.399984 51.499404-11.099872 96.098888-37.09957 132.698464-45.099478 63.199269-117.398641 83.499034-170.098032 98.298863-35.799586 9.999884-61.599287 12.599854-82.399047 14.599831-22.799736 2.299973-34.299603 3.399961-50.599414 12.799851-3.199963 1.899978-6.399926 3.899955-9.49989 6.09993-29.499659 21.199755-46.399463 55.699355-46.399463 91.998935v28.599669c0 10.699876 5.499936 20.599762 14.399833 26.599692 30.299649 20.399764 50.199419 55.199361 49.599426 94.598906-0.799991 60.299302-49.999421 109.598732-110.398722 110.398722C291.002557 1024.89999 240.003148 974.400574 240.003148 912.001296c0-38.89955 19.799771-73.199153 49.899422-93.198921 8.799898-5.899932 14.099837-15.899816 14.099837-26.499694V231.60917c0-10.699876-5.499936-20.599762-14.399833-26.599692-30.299649-20.399764-50.199419-55.199361-49.599426-94.598906C240.803138 50.11127 290.002569 0.911839 350.40187 0.01185 413.001146-0.88814 464.000555 49.611276 464.000555 112.010554c0 38.89955-19.799771 73.199153-49.899422 93.198921-8.799898 5.899932-14.099837 15.899816-14.099837 26.499694v346.095994c0 4.099953 4.399949 6.599924 7.999908 4.599947h0.099998c34.299603-19.699772 62.099281-22.399741 88.99897-25.099709 18.799782-1.799979 38.299557-3.799956 65.899238-11.499867 43.299499-12.09986 92.398931-25.8997 117.898635-61.599287 16.999803-23.799725 20.599762-53.399382 19.099779-79.599079-0.699992-12.599854-8.699899-23.499728-20.399763-28.099675-42.099513-16.299811-71.899168-57.299337-71.599172-105.298781 0.399995-61.699286 51.299406-111.698707 112.998692-111.198714 61.399289 0.499994 110.998715 50.499416 110.998716 111.998704 0 29.599657-11.499867 56.499346-30.199651 76.499115z" p-id="2563" fill="#ffffff"></path></svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB |
@@ -1,6 +1 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1761204835005" class="icon" viewBox="0 0 1171 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="5692" xmlns:xlink="http://www.w3.org/1999/xlink" width="228.7109375" height="200">
|
||||
<path d="M502.237757 1024 644.426501 829.679301 502.237757 788.716444 502.237757 1024 502.237757
|
||||
1024ZM0 566.713817 403.967637 689.088066 901.485385 266.66003 515.916344 721.68034 947.825442 855.099648 1170.285714 0 0 566.713817 0 566.713817Z" p-id="5693"></path></svg>
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1761204835005" class="icon" viewBox="0 0 1171 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5692" xmlns:xlink="http://www.w3.org/1999/xlink" width="228.7109375" height="200"><path d="M502.237757 1024 644.426501 829.679301 502.237757 788.716444 502.237757 1024 502.237757 1024ZM0 566.713817 403.967637 689.088066 901.485385 266.66003 515.916344 721.68034 947.825442 855.099648 1170.285714 0 0 566.713817 0 566.713817Z" p-id="5693"></path></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 610 B After Width: | Height: | Size: 603 B |
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1768992484327" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="10530" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M512 853.333333c-187.733333 0-341.333333-153.6-341.333333-341.333333s153.6-341.333333 341.333333-341.333333
|
||||
341.333333 153.6 341.333333 341.333333-153.6 341.333333-341.333333 341.333333z m0-85.333333c140.8 0 256-115.2 256-256s-115.2-256-256-256-256
|
||||
115.2-256 256 115.2 256 256 256z m-85.333333-341.333333h170.666666v170.666666h-170.666666v-170.666666z" fill="#ffffff" p-id="10531"></path></svg>
|
||||
|
Before Width: | Height: | Size: 709 B |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1769048534610" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4890" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M527.984 1001.6a480 480 0 1 1 480-480 480.384 480.384 0 0 1-480 480z m0-883.696A403.696 403.696 0 1 0 931.68 521.6 403.84 403.84 0 0 0 527.984 117.904z" fill="#36404f" p-id="4891"></path><path d="M473.136 729.6a47.088 47.088 0 0 1-18.112-3.888 38.768 38.768 0 0 1-23.056-34.992V384.384a39.632 39.632 0 0 1 23.056-34.992 46.016 46.016 0 0 1 43.632 3.888l211.568 153.168a38.72 38.72 0 0 1 16.464 31.104 37.632 37.632 0 0 1-16.464 31.104l-211.568 153.168a44.56 44.56 0 0 1-25.52 7.776z m41.168-266.704v149.296l102.896-74.64z" fill="#36404f" p-id="4892"></path></svg>
|
||||
|
Before Width: | Height: | Size: 894 B |
@@ -1,11 +1,5 @@
|
||||
<template>
|
||||
<el-tooltip
|
||||
:disabled="!isOverflow"
|
||||
effect="light"
|
||||
placement="top"
|
||||
:content="text"
|
||||
popper-class="multi-line-tooltip-popper"
|
||||
>
|
||||
<el-tooltip :disabled="!isOverflow" effect="light" placement="top" :content="text">
|
||||
<div
|
||||
ref="containerRef"
|
||||
class="multi-line-ellipsis"
|
||||
@@ -34,7 +28,7 @@ interface Props {
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
text: '',
|
||||
lines: 3,
|
||||
maxWidth: '100%'
|
||||
maxWidth: '100%',
|
||||
})
|
||||
|
||||
const isOverflow = ref(false)
|
||||
@@ -51,8 +45,8 @@ const containerStyle = computed(
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
lineHeight: '1.5',
|
||||
wordBreak: 'break-all'
|
||||
} as HTMLAttributes['style'])
|
||||
wordBreak: 'break-all',
|
||||
}) as HTMLAttributes['style'],
|
||||
)
|
||||
|
||||
// 检查文字是否溢出
|
||||
@@ -97,9 +91,3 @@ onMounted(() => {
|
||||
white-space: normal;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.multi-line-tooltip-popper {
|
||||
max-width: 400px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
<template>
|
||||
<teleport to="body">
|
||||
<div class="notification-container">
|
||||
<transition-group
|
||||
name="notification"
|
||||
tag="div"
|
||||
class="notification-list"
|
||||
>
|
||||
<div
|
||||
v-for="notification in notifications"
|
||||
:key="notification.id"
|
||||
:class="[
|
||||
'notification-item',
|
||||
`notification-${notification.type || 'info'}`
|
||||
]"
|
||||
:style="{ zIndex: notification.zIndex || 1000 }"
|
||||
>
|
||||
<div class="notification-content">
|
||||
<div class="notification-icon">
|
||||
<component :is="getIcon(notification.type)" />
|
||||
</div>
|
||||
<div class="notification-message">
|
||||
<div class="notification-title">{{ notification.title }}</div>
|
||||
<div v-if="notification.detailTitle" class="notification-detail-title">
|
||||
{{ notification.detailTitle }}
|
||||
</div>
|
||||
<div v-if="notification.detailMessage" class="notification-detail-desc">
|
||||
{{ notification.detailMessage }}
|
||||
</div>
|
||||
<div v-else-if="notification.message" class="notification-desc">
|
||||
{{ notification.message }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="notification-close" @click="close(notification.id)">
|
||||
<Close />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="notification.showProgress" class="notification-progress">
|
||||
<div
|
||||
class="progress-bar"
|
||||
:style="{ width: `${notification.progress || 0}%` }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</transition-group>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import {
|
||||
Close,
|
||||
SuccessFilled as IconSuccess,
|
||||
WarningFilled as IconWarning,
|
||||
CircleCloseFilled,
|
||||
InfoFilled
|
||||
} from '@element-plus/icons-vue'
|
||||
import type { NotificationItem } from '@/composables/useNotification'
|
||||
|
||||
const props = defineProps<{
|
||||
notifications: NotificationItem[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [id: string]
|
||||
}>()
|
||||
|
||||
const close = (id: string) => {
|
||||
emit('close', id)
|
||||
}
|
||||
|
||||
const getIcon = (type?: string) => {
|
||||
switch (type) {
|
||||
case 'success':
|
||||
return IconSuccess
|
||||
case 'warning':
|
||||
return IconWarning
|
||||
case 'error':
|
||||
return IconWarning
|
||||
default:
|
||||
return InfoFilled
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.notification-container {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.notification-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.notification-item {
|
||||
pointer-events: auto;
|
||||
min-width: 300px;
|
||||
max-width: 450px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
overflow: hidden;
|
||||
border-left: 4px solid #409eff;
|
||||
}
|
||||
|
||||
.notification-success {
|
||||
border-left-color: #67c23a;
|
||||
}
|
||||
|
||||
.notification-warning {
|
||||
border-left-color: #e6a23c;
|
||||
}
|
||||
|
||||
.notification-error {
|
||||
border-left-color: #f56c6c;
|
||||
}
|
||||
|
||||
.notification-content {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 12px 16px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.notification-icon {
|
||||
flex-shrink: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.notification-icon .success {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.notification-icon .warning {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.notification-icon .error {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.notification-icon .info {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.notification-message {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.notification-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.notification-detail-title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #409eff;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.notification-detail-desc {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.notification-desc {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.notification-close {
|
||||
flex-shrink: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.notification-close:hover {
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.notification-progress {
|
||||
height: 2px;
|
||||
background: #f0f2f5;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: #409eff;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
/* 进入动画 */
|
||||
.notification-enter-active {
|
||||
animation: slideInRight 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* 离开动画 */
|
||||
.notification-leave-active {
|
||||
animation: slideOutRight 0.3s ease-in;
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideOutRight {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
/* 列表项移动动画 */
|
||||
.notification-move,
|
||||
.notification-enter-active,
|
||||
.notification-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.notification-leave-active {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,163 +0,0 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
export interface NotificationItem {
|
||||
id: string
|
||||
title: string
|
||||
message?: string
|
||||
type?: 'success' | 'warning' | 'info' | 'error'
|
||||
duration?: number
|
||||
showProgress?: boolean
|
||||
progress?: number
|
||||
zIndex?: number
|
||||
onClose?: () => void
|
||||
// 详细进度信息
|
||||
detailTitle?: string
|
||||
detailMessage?: string
|
||||
}
|
||||
|
||||
const notifications = ref<NotificationItem[]>([])
|
||||
let notificationIdCounter = 0
|
||||
let zIndexCounter = 1000
|
||||
|
||||
export function useNotification() {
|
||||
const addNotification = (notification: Omit<NotificationItem, 'id' | 'zIndex'>) => {
|
||||
const id = `notification-${notificationIdCounter++}`
|
||||
const newNotification: NotificationItem = {
|
||||
...notification,
|
||||
id,
|
||||
zIndex: ++zIndexCounter,
|
||||
}
|
||||
|
||||
notifications.value.push(newNotification)
|
||||
|
||||
// 自动关闭
|
||||
if (notification.duration && notification.duration > 0) {
|
||||
setTimeout(() => {
|
||||
removeNotification(id)
|
||||
}, notification.duration)
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
const removeNotification = (id: string) => {
|
||||
const index = notifications.value.findIndex((n) => n.id === id)
|
||||
if (index !== -1) {
|
||||
const notification = notifications.value[index]
|
||||
notifications.value.splice(index, 1)
|
||||
notification.onClose?.()
|
||||
}
|
||||
}
|
||||
|
||||
const success = (title: string, message?: string, options?: Partial<NotificationItem>) => {
|
||||
return addNotification({
|
||||
title,
|
||||
message,
|
||||
type: 'success',
|
||||
duration: 3000,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
const warning = (title: string, message?: string, options?: Partial<NotificationItem>) => {
|
||||
return addNotification({
|
||||
title,
|
||||
message,
|
||||
type: 'warning',
|
||||
duration: 3000,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
const info = (title: string, message?: string, options?: Partial<NotificationItem>) => {
|
||||
return addNotification({
|
||||
title,
|
||||
message,
|
||||
type: 'info',
|
||||
duration: 3000,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
const error = (title: string, message?: string, options?: Partial<NotificationItem>) => {
|
||||
return addNotification({
|
||||
title,
|
||||
message,
|
||||
type: 'error',
|
||||
duration: 5000,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
const progress = (
|
||||
title: string,
|
||||
current: number,
|
||||
total: number,
|
||||
options?: Partial<NotificationItem>,
|
||||
) => {
|
||||
const progressPercent = Math.round((current / total) * 100)
|
||||
return addNotification({
|
||||
title,
|
||||
message: `${current}/${total}`,
|
||||
type: 'info',
|
||||
showProgress: true,
|
||||
progress: progressPercent,
|
||||
duration: 0, // 不自动关闭
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
const updateProgress = (id: string, current: number, total: number) => {
|
||||
const notification = notifications.value.find((n) => n.id === id)
|
||||
if (notification) {
|
||||
notification.progress = Math.round((current / total) * 100)
|
||||
notification.message = `${current}/${total}`
|
||||
}
|
||||
}
|
||||
|
||||
const updateProgressDetail = (
|
||||
id: string,
|
||||
detailTitle: string,
|
||||
detailMessage: string,
|
||||
current?: number,
|
||||
total?: number
|
||||
) => {
|
||||
const notification = notifications.value.find((n) => n.id === id)
|
||||
if (notification) {
|
||||
notification.detailTitle = detailTitle
|
||||
notification.detailMessage = detailMessage
|
||||
if (current !== undefined && total !== undefined) {
|
||||
notification.progress = Math.round((current / total) * 100)
|
||||
notification.message = `${current}/${total}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新通知的主标题
|
||||
const updateNotificationTitle = (id: string, title: string) => {
|
||||
const notification = notifications.value.find((n) => n.id === id)
|
||||
if (notification) {
|
||||
notification.title = title
|
||||
}
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
notifications.value.forEach((n) => n.onClose?.())
|
||||
notifications.value = []
|
||||
}
|
||||
|
||||
return {
|
||||
notifications,
|
||||
addNotification,
|
||||
removeNotification,
|
||||
success,
|
||||
warning,
|
||||
info,
|
||||
error,
|
||||
progress,
|
||||
updateProgress,
|
||||
updateProgressDetail,
|
||||
updateNotificationTitle,
|
||||
clear,
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed, reactive, nextTick } 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 websocket from '@/utils/websocket'
|
||||
import { changeBriefs } from '@/utils/collaboration_Brief_FrontEnd.ts'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import AssignmentButton from './TaskTemplate/TaskSyllabus/components/AssignmentButton.vue'
|
||||
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
|
||||
(e: 'search', value: string): void
|
||||
@@ -17,12 +20,9 @@ const configStore = useConfigStore()
|
||||
const searchValue = ref('')
|
||||
const triggerOnFocus = ref(true)
|
||||
const isFocus = ref(false)
|
||||
const hasAutoSearched = ref(false)
|
||||
const isExpanded = ref(false)
|
||||
// 添加一个状态来跟踪是否正在填充步骤数据
|
||||
const isFillingSteps = ref(false)
|
||||
// 存储当前填充任务的取消函数
|
||||
const currentStepAbortController = ref<{ cancel: () => void } | null>(null)
|
||||
const hasAutoSearched = ref(false) // 防止重复自动搜索
|
||||
const agentAllocationVisible = ref(false) // 智能体分配弹窗是否可见
|
||||
const isExpanded = ref(false) // 控制搜索框是否展开
|
||||
|
||||
// 解析URL参数
|
||||
function getUrlParam(param: string): string | null {
|
||||
@@ -30,12 +30,6 @@ function getUrlParam(param: string): string | null {
|
||||
return urlParams.get(param)
|
||||
}
|
||||
|
||||
const planReady = computed(() => {
|
||||
return agentsStore.agentRawPlan.data !== undefined
|
||||
})
|
||||
const openAgentAllocationDialog = () => {
|
||||
agentsStore.openAgentAllocationDialog()
|
||||
}
|
||||
// 自动搜索函数
|
||||
async function autoSearchFromUrl() {
|
||||
const query = getUrlParam('q')
|
||||
@@ -52,152 +46,33 @@ async function autoSearchFromUrl() {
|
||||
}
|
||||
}
|
||||
|
||||
// 智能体分配
|
||||
// 处理智能体分配点击事件
|
||||
function handleAgentAllocation() {
|
||||
agentAllocationVisible.value = true
|
||||
}
|
||||
|
||||
// 关闭智能体分配弹窗
|
||||
function handleAgentAllocationClose() {
|
||||
agentAllocationVisible.value = false
|
||||
}
|
||||
|
||||
// 处理获取焦点事件
|
||||
function handleFocus() {
|
||||
isFocus.value = true
|
||||
isExpanded.value = true // 搜索框展开
|
||||
}
|
||||
|
||||
const taskContainerRef = ref<HTMLDivElement | null>(null)
|
||||
|
||||
// 处理失去焦点事件
|
||||
function handleBlur() {
|
||||
isFocus.value = false
|
||||
// 延迟收起搜索框,以便点击按钮等操作
|
||||
setTimeout(() => {
|
||||
isExpanded.value = false
|
||||
// 强制重置文本区域高度到最小行数
|
||||
resetTextareaHeight()
|
||||
}, 200)
|
||||
}
|
||||
|
||||
// 🆕 预加载所有任务的智能体评分数据(顺序加载,确保任务详情已填充)
|
||||
async function preloadAllTaskAgentScores(outlineData: any, goal: string) {
|
||||
const tasks = outlineData['Collaboration Process'] || []
|
||||
|
||||
if (tasks.length === 0) {
|
||||
console.log('ℹ️ 没有任务需要预加载评分数据')
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`🚀 开始预加载 ${tasks.length} 个任务的智能体评分数据...`)
|
||||
|
||||
// 🆕 顺序预加载:等待每个任务详情填充完成后再预加载其评分
|
||||
for (const task of tasks) {
|
||||
// 确保任务有 Id
|
||||
if (!task.Id) {
|
||||
task.Id = `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
||||
}
|
||||
|
||||
const taskId = task.Id
|
||||
|
||||
// 检查是否已有缓存数据
|
||||
if (agentsStore.hasTaskScoreData(taskId)) {
|
||||
console.log(`⏭️ 任务 "${task.StepName}" (${taskId}) 已有缓存数据,跳过`)
|
||||
continue
|
||||
}
|
||||
|
||||
// 🆕 等待任务详情填充完成(通过检查 AgentSelection 是否存在)
|
||||
// 最多等待 60 秒,超时则跳过该任务
|
||||
let waitCount = 0
|
||||
const maxWait = 60 // 60 * 500ms = 30秒
|
||||
while (!task.AgentSelection && waitCount < maxWait) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
waitCount++
|
||||
}
|
||||
|
||||
if (!task.AgentSelection) {
|
||||
console.warn(`⚠️ 任务 "${task.StepName}" (${taskId}) 详情未填充完成,跳过评分预加载`)
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
// 调用初始化接口获取评分数据
|
||||
const agentScores = await api.agentSelectModifyInit({
|
||||
goal: goal,
|
||||
stepTask: {
|
||||
StepName: task.StepName,
|
||||
TaskContent: task.TaskContent,
|
||||
InputObject_List: task.InputObject_List,
|
||||
OutputObject: task.OutputObject
|
||||
}
|
||||
})
|
||||
|
||||
// 提取维度列表
|
||||
const firstAgent = Object.keys(agentScores)[0]
|
||||
const aspectList = firstAgent ? Object.keys(agentScores[firstAgent] || {}) : []
|
||||
|
||||
// 存储到 store(按任务ID存储)
|
||||
agentsStore.setTaskScoreData(taskId, {
|
||||
aspectList,
|
||||
agentScores
|
||||
})
|
||||
|
||||
console.log(`✅ 任务 "${task.StepName}" (${taskId}) 的评分数据预加载完成,维度数: ${aspectList.length}`)
|
||||
} catch (error) {
|
||||
console.error(`❌ 任务 "${task.StepName}" (${taskId}) 的评分数据预加载失败:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`🎉 所有 ${tasks.length} 个任务的智能体评分数据预加载完成(或已跳过)`)
|
||||
}
|
||||
|
||||
// 重置文本区域高度到最小行数
|
||||
function resetTextareaHeight() {
|
||||
nextTick(() => {
|
||||
// 获取textarea元素
|
||||
const textarea =
|
||||
document.querySelector('#task-container .el-textarea__inner') ||
|
||||
document.querySelector('#task-container textarea')
|
||||
|
||||
if (textarea instanceof HTMLElement) {
|
||||
// 强制设置最小高度
|
||||
textarea.style.height = 'auto'
|
||||
textarea.style.minHeight = '56px'
|
||||
textarea.style.overflowY = 'hidden'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 停止填充数据的处理函数
|
||||
async function handleStop() {
|
||||
try {
|
||||
// 通过 WebSocket 发送停止信号
|
||||
if (websocket.connected) {
|
||||
await websocket.send('stop_generation', {
|
||||
goal: searchValue.value
|
||||
})
|
||||
ElMessage.success('已发送停止信号,正在停止生成...')
|
||||
} else {
|
||||
ElMessage.warning('WebSocket 未连接,无法停止')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('停止生成失败:', error)
|
||||
ElMessage.error('停止生成失败')
|
||||
} finally {
|
||||
// 无论后端是否成功停止,都重置状态
|
||||
isFillingSteps.value = false
|
||||
currentStepAbortController.value = null
|
||||
// 标记用户已停止填充
|
||||
agentsStore.setHasStoppedFilling(true)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理按钮点击事件
|
||||
function handleButtonClick() {
|
||||
if (isFillingSteps.value) {
|
||||
// 如果正在填充数据,点击停止
|
||||
handleStop()
|
||||
} else {
|
||||
// 否则开始搜索
|
||||
handleSearch()
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSearch() {
|
||||
// 用于标记大纲是否成功加载
|
||||
let outlineLoaded = false
|
||||
|
||||
try {
|
||||
triggerOnFocus.value = false
|
||||
if (!searchValue.value) {
|
||||
@@ -207,132 +82,18 @@ async function handleSearch() {
|
||||
emit('search-start')
|
||||
agentsStore.resetAgent()
|
||||
agentsStore.setAgentRawPlan({ loading: true })
|
||||
// 重置停止状态
|
||||
agentsStore.setHasStoppedFilling(false)
|
||||
|
||||
// 获取大纲
|
||||
const outlineData = await api.generateBasePlan({
|
||||
const data = await api.generateBasePlan({
|
||||
goal: searchValue.value,
|
||||
inputs: []
|
||||
})
|
||||
|
||||
// 检查是否已被停止
|
||||
if (!isFillingSteps.value && currentStepAbortController.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// 处理简报数据格式
|
||||
outlineData['Collaboration Process'] = changeBriefs(outlineData['Collaboration Process'])
|
||||
|
||||
// 立即显示大纲
|
||||
agentsStore.setAgentRawPlan({ data: outlineData, loading: false })
|
||||
outlineLoaded = true
|
||||
data['Collaboration Process'] = changeBriefs(data['Collaboration Process'])
|
||||
agentsStore.setAgentRawPlan({ data })
|
||||
emit('search', searchValue.value)
|
||||
|
||||
// 🆕 预加载所有任务的智能体评分数据(在后台静默执行)
|
||||
preloadAllTaskAgentScores(outlineData, searchValue.value)
|
||||
|
||||
// 开始填充步骤详情,设置状态
|
||||
isFillingSteps.value = true
|
||||
|
||||
// 并行填充所有步骤的详情
|
||||
const steps = outlineData['Collaboration Process'] || []
|
||||
|
||||
// 带重试的填充函数
|
||||
const fillStepWithRetry = async (step: any, retryCount = 0): Promise<void> => {
|
||||
const maxRetries = 2 // 最多重试2次
|
||||
|
||||
// 检查是否已停止
|
||||
if (!isFillingSteps.value) {
|
||||
console.log('检测到停止信号,跳过步骤填充')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (!step.StepName) {
|
||||
console.warn('步骤缺少 StepName,跳过填充详情')
|
||||
return
|
||||
}
|
||||
|
||||
// 使用现有的 fillStepTask API 填充每个步骤的详情
|
||||
const detailedStep = await api.fillStepTask({
|
||||
goal: searchValue.value,
|
||||
stepTask: {
|
||||
StepName: step.StepName,
|
||||
TaskContent: step.TaskContent,
|
||||
InputObject_List: step.InputObject_List,
|
||||
OutputObject: step.OutputObject
|
||||
}
|
||||
})
|
||||
|
||||
// 再次检查是否已停止(在 API 调用后)
|
||||
if (!isFillingSteps.value) {
|
||||
console.log('检测到停止信号,跳过更新步骤详情')
|
||||
return
|
||||
}
|
||||
|
||||
// 更新该步骤的详情到 store
|
||||
updateStepDetail(step.StepName, detailedStep)
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`填充步骤 ${step.StepName} 详情失败 (尝试 ${retryCount + 1}/${maxRetries + 1}):`,
|
||||
error
|
||||
)
|
||||
|
||||
// 如果未达到最大重试次数,延迟后重试
|
||||
if (retryCount < maxRetries) {
|
||||
console.log(`正在重试步骤 ${step.StepName}...`)
|
||||
// 延迟1秒后重试,避免立即重试导致同样的问题
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
return fillStepWithRetry(step, retryCount + 1)
|
||||
} else {
|
||||
console.error(`步骤 ${step.StepName} 在 ${maxRetries + 1} 次尝试后仍然失败`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // 为每个步骤并行填充详情(选人+过程)
|
||||
// const fillPromises = steps.map(step => fillStepWithRetry(step))
|
||||
// // 等待所有步骤填充完成(包括重试)
|
||||
// await Promise.all(fillPromises)
|
||||
|
||||
// 串行填充所有步骤的详情(避免字段混乱)
|
||||
for (const step of steps) {
|
||||
await fillStepWithRetry(step)
|
||||
}
|
||||
} finally {
|
||||
triggerOnFocus.value = true
|
||||
// 完成填充,重置状态
|
||||
isFillingSteps.value = false
|
||||
currentStepAbortController.value = null
|
||||
// 如果大纲加载失败,确保关闭loading
|
||||
if (!outlineLoaded) {
|
||||
agentsStore.setAgentRawPlan({ loading: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:更新单个步骤的详情
|
||||
function updateStepDetail(stepId: string, detailedStep: any) {
|
||||
const planData = agentsStore.agentRawPlan.data
|
||||
if (!planData) return
|
||||
|
||||
const collaborationProcess = planData['Collaboration Process']
|
||||
if (!collaborationProcess) return
|
||||
|
||||
const index = collaborationProcess.findIndex((s: any) => s.StepName === stepId)
|
||||
if (index !== -1 && collaborationProcess[index]) {
|
||||
// 保持响应式更新 - 使用 Vue 的响应式系统
|
||||
Object.assign(collaborationProcess[index], {
|
||||
AgentSelection: detailedStep.AgentSelection || [],
|
||||
TaskProcess: detailedStep.TaskProcess || [],
|
||||
Collaboration_Brief_frontEnd: detailedStep.Collaboration_Brief_frontEnd || {
|
||||
template: '',
|
||||
data: {}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const querySearch = (queryString: string, cb: (v: { value: string }[]) => void) => {
|
||||
const results = queryString
|
||||
@@ -348,6 +109,8 @@ const createFilter = (queryString: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
const taskContainerRef = ref<HTMLDivElement | null>(null)
|
||||
|
||||
// 组件挂载时检查URL参数
|
||||
onMounted(() => {
|
||||
autoSearchFromUrl()
|
||||
@@ -370,12 +133,10 @@ onMounted(() => {
|
||||
>
|
||||
<span class="text-[var(--color-text-task)] font-bold task-title">任务</span>
|
||||
<el-autocomplete
|
||||
ref="autocompleteRef"
|
||||
v-model.trim="searchValue"
|
||||
class="task-input"
|
||||
size="large"
|
||||
:rows="1"
|
||||
:autosize="{ minRows: 1, maxRows: 10 }"
|
||||
:rows="isFocus ? 3 : 1"
|
||||
placeholder="请输入您的任务"
|
||||
type="textarea"
|
||||
:append-to="taskContainerRef"
|
||||
@@ -383,7 +144,6 @@ onMounted(() => {
|
||||
@change="agentsStore.setSearchValue"
|
||||
:disabled="!(agentsStore.agents.length > 0)"
|
||||
:debounce="0"
|
||||
:clearable="true"
|
||||
:trigger-on-focus="triggerOnFocus"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@@ -394,27 +154,37 @@ onMounted(() => {
|
||||
class="task-button"
|
||||
color="linear-gradient(to right, #00C7D2, #315AB4)"
|
||||
size="large"
|
||||
:title="isFillingSteps ? '点击停止生成' : '点击搜索任务'"
|
||||
title="点击搜索任务"
|
||||
circle
|
||||
:loading="agentsStore.agentRawPlan.loading"
|
||||
:disabled="!searchValue"
|
||||
@click.stop="handleButtonClick"
|
||||
@click.stop="handleSearch"
|
||||
>
|
||||
<SvgIcon
|
||||
v-if="!agentsStore.agentRawPlan.loading && !isFillingSteps"
|
||||
v-if="!agentsStore.agentRawPlan.loading"
|
||||
icon-class="paper-plane"
|
||||
size="18px"
|
||||
color="#ffffff"
|
||||
/>
|
||||
<SvgIcon
|
||||
v-if="!agentsStore.agentRawPlan.loading && isFillingSteps"
|
||||
icon-class="stoprunning"
|
||||
size="30px"
|
||||
color="#ffffff"
|
||||
/>
|
||||
</el-button>
|
||||
</div>
|
||||
<AssignmentButton v-if="planReady" @click="openAgentAllocationDialog" />
|
||||
<!-- 智能体分配 -->
|
||||
<!-- <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>
|
||||
@@ -445,19 +215,6 @@ onMounted(() => {
|
||||
/* 搜索框展开时的样式 */
|
||||
&.expanded {
|
||||
box-shadow: var(--color-task-shadow);
|
||||
:deep(.el-autocomplete .el-textarea .el-textarea__inner) {
|
||||
overflow-y: auto !important;
|
||||
min-height: 56px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 非展开状态时,确保文本区域高度固定 */
|
||||
&:not(.expanded) {
|
||||
:deep(.el-textarea__inner) {
|
||||
height: 56px !important;
|
||||
overflow-y: hidden !important;
|
||||
min-height: 56px !important;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-popper) {
|
||||
@@ -471,7 +228,6 @@ onMounted(() => {
|
||||
transition: height 0s ease-in-out;
|
||||
border-top: 1px solid var(--color-border);
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
|
||||
li {
|
||||
height: 45px;
|
||||
@@ -509,11 +265,6 @@ onMounted(() => {
|
||||
resize: none;
|
||||
color: var(--color-text-taskbar);
|
||||
|
||||
/* 聚焦时的样式 */
|
||||
.expanded & {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
line-height: 1.2;
|
||||
font-size: 18px;
|
||||
@@ -610,4 +361,67 @@ onMounted(() => {
|
||||
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>
|
||||
|
||||
@@ -28,16 +28,14 @@ const agentsStore = useAgentsStore()
|
||||
:class="agentsStore.currentTask?.AgentSelection?.includes(item.Name) ? 'active-card' : ''"
|
||||
>
|
||||
<div class="flex items-center justify-between relative h-[43px]">
|
||||
<!-- 图标区域 -->
|
||||
<div
|
||||
class="w-[44px] h-[44px] rounded-full flex items-center justify-center flex-shrink-0 relative right-[2px] icon-container"
|
||||
:style="{ background: getAgentMapIcon(item.Name).color }"
|
||||
>
|
||||
<svg-icon :icon-class="getAgentMapIcon(item.Name).icon" color="#fff" size="24px" />
|
||||
</div>
|
||||
|
||||
<div class="flex-1 text-[14px] textClass flex flex-col items-end justify-start truncate ml-1">
|
||||
<div class="flex items-center justify-start gap-2 w-full">
|
||||
<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="
|
||||
@@ -98,8 +96,6 @@ const agentsStore = useAgentsStore()
|
||||
v-if="index1 !== taskProcess.filter(i => i.AgentName === item.Name).length - 1"
|
||||
class="h-[1px] w-full bg-[var(--color-border-default)] my-[8px]"
|
||||
></div>
|
||||
|
||||
<AssignmentButton />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ElNotification } from 'element-plus'
|
||||
import { pick } from 'lodash'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
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'
|
||||
@@ -17,9 +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', 'apiUrl', 'apiKey', 'apiModel']))
|
||||
)
|
||||
await api.setAgents(agentsStore.agents.map(item => pick(item, ['Name', 'Profile'])))
|
||||
})
|
||||
|
||||
// 上传agent文件
|
||||
@@ -37,77 +37,50 @@ const handleFileSelect = (event: Event) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 验证API配置:三个字段必须同时存在或同时不存在
|
||||
const validateApiConfig = (agent: any) => {
|
||||
const hasApiUrl = 'apiUrl' in agent
|
||||
const hasApiKey = 'apiKey' in agent
|
||||
const hasApiModel = 'apiModel' in agent
|
||||
|
||||
return hasApiUrl === hasApiKey && hasApiKey === hasApiModel
|
||||
}
|
||||
|
||||
const readFileContent = (file: File) => {
|
||||
const readFileContent = async (file: File) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = e => {
|
||||
try {
|
||||
const content = e.target?.result as string
|
||||
const jsonData = JSON.parse(content)
|
||||
|
||||
if (!Array.isArray(jsonData)) {
|
||||
ElMessage.error('JSON格式错误: 必须为数组格式')
|
||||
reader.onload = async e => {
|
||||
if (!e.target?.result) {
|
||||
return
|
||||
}
|
||||
|
||||
const validAgents = jsonData.filter((agent) => {
|
||||
// 验证必需字段
|
||||
if (!agent.Name || typeof agent.Name !== 'string') {
|
||||
return false
|
||||
}
|
||||
if (!agent.Icon || typeof agent.Icon !== 'string') {
|
||||
return false
|
||||
}
|
||||
if (!agent.Profile || typeof agent.Profile !== 'string') {
|
||||
return false
|
||||
}
|
||||
|
||||
// 验证API配置
|
||||
if (!validateApiConfig(agent)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
// 修改发送到后端的数据
|
||||
const processedAgents = validAgents.map(agent => ({
|
||||
Name: agent.Name,
|
||||
Profile: agent.Profile,
|
||||
Icon: agent.Icon,
|
||||
Classification: agent.Classification || '',
|
||||
apiUrl: agent.apiUrl,
|
||||
apiKey: agent.apiKey,
|
||||
apiModel: agent.apiModel
|
||||
try {
|
||||
const json = JSON.parse(e.target.result?.toString?.() ?? '{}')
|
||||
// 处理 JSON 数据
|
||||
if (Array.isArray(json)) {
|
||||
const isValid = json.every(
|
||||
item =>
|
||||
typeof item.Name === 'string' &&
|
||||
typeof item.Icon === 'string' &&
|
||||
typeof item.Profile === 'string'
|
||||
)
|
||||
if (isValid) {
|
||||
// 处理有效的 JSON 数据
|
||||
agentsStore.setAgents(
|
||||
json.map(item => ({
|
||||
Name: item.Name,
|
||||
Icon: item.Icon.replace(/\.png$/, ''),
|
||||
Profile: item.Profile,
|
||||
Classification: item.Classification
|
||||
}))
|
||||
|
||||
agentsStore.setAgents(processedAgents)
|
||||
|
||||
// 调用API
|
||||
api
|
||||
.setAgents(processedAgents)
|
||||
.then(() => {
|
||||
ElMessage.success('智能体上传成功')
|
||||
)
|
||||
await api.setAgents(json.map(item => pick(item, ['Name', 'Profile'])))
|
||||
} else {
|
||||
ElNotification.error({
|
||||
title: '错误',
|
||||
message: 'JSON 格式错误'
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage.error('智能体上传失败')
|
||||
}
|
||||
} else {
|
||||
console.error('JSON is not an array')
|
||||
ElNotification.error({
|
||||
title: '错误',
|
||||
message: 'JSON 格式错误'
|
||||
})
|
||||
} catch {
|
||||
ElMessage.error('JSON解析错误')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
reader.onerror = () => {
|
||||
ElMessage.error('文件读取错误')
|
||||
}
|
||||
|
||||
reader.readAsText(file)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { getActionTypeDisplay } from '@/layout/components/config.ts'
|
||||
import { useAgentsStore } from '@/stores'
|
||||
import BranchButton from './components/TaskButton.vue'
|
||||
|
||||
const agentsStore = useAgentsStore()
|
||||
|
||||
const props = defineProps<{
|
||||
step: {
|
||||
@@ -23,41 +19,13 @@ const emit = defineEmits<{
|
||||
(e: 'save-edit', stepId: string, processId: string, value: string): void
|
||||
}>()
|
||||
|
||||
//从 currentTask 中获取数据
|
||||
const currentTaskProcess = computed(() => {
|
||||
const currentTask = agentsStore.currentTask
|
||||
if (currentTask && currentTask.Id === props.step.Id && currentTask.TaskProcess) {
|
||||
return currentTask.TaskProcess
|
||||
}
|
||||
|
||||
//从 agentRawPlan 中获取原始数据
|
||||
const collaborationProcess = agentsStore.agentRawPlan.data?.['Collaboration Process'] || []
|
||||
const rawData = collaborationProcess.find((task: any) => task.Id === props.step.Id)
|
||||
return rawData?.TaskProcess || []
|
||||
})
|
||||
|
||||
// 当前正在编辑的process ID
|
||||
const editingProcessId = ref<string | null>(null)
|
||||
// 编辑框的值
|
||||
const editValue = ref('')
|
||||
// 鼠标悬停的process ID
|
||||
const hoverProcessId = ref<string | null>(null)
|
||||
|
||||
// 处理卡片点击事件
|
||||
function handleCardClick() {
|
||||
// 如果正在编辑,不处理点击
|
||||
if (editingProcessId.value) return
|
||||
|
||||
// 设置当前任务,与任务大纲联动
|
||||
if (props.step.Id) {
|
||||
agentsStore.setCurrentTask(props.step as any)
|
||||
}
|
||||
}
|
||||
|
||||
// 检测当前是否是深色模式
|
||||
function isDarkMode(): boolean {
|
||||
return document.documentElement.classList.contains('dark')
|
||||
}
|
||||
|
||||
// 获取颜色浅两号的函数
|
||||
function getLightColor(color: string, level: number = 2): string {
|
||||
if (!color || color.length !== 7 || color[0] !== '#') return color
|
||||
@@ -77,34 +45,6 @@ function getLightColor(color: string, level: number = 2): string {
|
||||
.padStart(2, '0')}${Math.round(newB).toString(16).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
// 获取颜色深两号的函数
|
||||
function getDarkColor(color: string, level: number = 2): string {
|
||||
if (!color || color.length !== 7 || color[0] !== '#') return color
|
||||
|
||||
const r = parseInt(color.substr(1, 2), 16)
|
||||
const g = parseInt(color.substr(3, 2), 16)
|
||||
const b = parseInt(color.substr(5, 2), 16)
|
||||
|
||||
// 降低亮度(深两号)
|
||||
const darkenAmount = level * 20
|
||||
const newR = Math.max(0, r - darkenAmount)
|
||||
const newG = Math.max(0, g - darkenAmount)
|
||||
const newB = Math.max(0, b - darkenAmount)
|
||||
|
||||
return `#${Math.round(newR).toString(16).padStart(2, '0')}${Math.round(newG)
|
||||
.toString(16)
|
||||
.padStart(2, '0')}${Math.round(newB).toString(16).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
// 根据主题模式获取调整后的颜色
|
||||
function getAdjustedColor(color: string, level: number = 2): string {
|
||||
if (isDarkMode()) {
|
||||
return getDarkColor(color, level)
|
||||
} else {
|
||||
return getLightColor(color, level)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理鼠标进入
|
||||
function handleMouseEnter(processId: string) {
|
||||
hoverProcessId.value = processId
|
||||
@@ -139,12 +79,12 @@ function handleCancel() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="process-card" @click="handleCardClick">
|
||||
<div class="process-card">
|
||||
<div class="process-content">
|
||||
<!-- 显示模式 -->
|
||||
<div class="display-content">
|
||||
<span
|
||||
v-for="process in currentTaskProcess"
|
||||
v-for="process in step.TaskProcess"
|
||||
:key="process.ID"
|
||||
class="process-segment"
|
||||
@mouseenter="handleMouseEnter(process.ID)"
|
||||
@@ -166,7 +106,6 @@ function handleCancel() {
|
||||
<!-- 编辑模式 - 修改为卡片样式 -->
|
||||
<div v-if="editingProcessId === process.ID" class="edit-container">
|
||||
<div class="edit-card">
|
||||
<div class="flex flex-col gap-3">
|
||||
<el-input
|
||||
v-model="editValue"
|
||||
type="textarea"
|
||||
@@ -174,24 +113,25 @@ function handleCancel() {
|
||||
placeholder="请输入描述内容"
|
||||
autofocus
|
||||
/>
|
||||
<div class="flex justify-end">
|
||||
<svg-icon
|
||||
icon-class="Check"
|
||||
size="20px"
|
||||
color="#328621"
|
||||
class="cursor-pointer mr-4"
|
||||
<div class="edit-buttons">
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
icon="Check"
|
||||
@click="handleSave(process.ID)"
|
||||
title="保存"
|
||||
/>
|
||||
<svg-icon
|
||||
icon-class="Cancel"
|
||||
size="20px"
|
||||
color="#8e0707"
|
||||
class="cursor-pointer mr-1"
|
||||
>
|
||||
√
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
icon="Close"
|
||||
@click="handleCancel"
|
||||
title="取消"
|
||||
/>
|
||||
</div>
|
||||
>
|
||||
×
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -205,7 +145,7 @@ function handleCancel() {
|
||||
border: `1px solid ${getActionTypeDisplay(process.ActionType)?.border}`,
|
||||
backgroundColor:
|
||||
hoverProcessId === process.ID
|
||||
? getAdjustedColor(getActionTypeDisplay(process.ActionType)?.color || '#909399')
|
||||
? getLightColor(getActionTypeDisplay(process.ActionType)?.color || '#909399')
|
||||
: 'transparent'
|
||||
}"
|
||||
@dblclick="handleDblClick(process.ID, process.Description)"
|
||||
@@ -213,28 +153,21 @@ function handleCancel() {
|
||||
{{ process.Description }}
|
||||
</span>
|
||||
|
||||
<span class="separator" v-if="process.Description && !process.Description.endsWith('。')"
|
||||
>。</span
|
||||
>
|
||||
<span class="separator" v-if="!process.Description.endsWith('。')">。</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 按钮点击不会冒泡到卡片 -->
|
||||
<BranchButton :step="step" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.process-card {
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
background: var(--color-bg-list);
|
||||
border: 1px solid var(--color-border-default);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
.process-content {
|
||||
min-height: 20px;
|
||||
@@ -262,7 +195,8 @@ function handleCancel() {
|
||||
margin-bottom: 8px;
|
||||
|
||||
.edit-card {
|
||||
//background: #f0f2f5;
|
||||
position: relative;
|
||||
background: #f0f2f5;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
@@ -279,15 +213,40 @@ function handleCancel() {
|
||||
border-radius: 4px;
|
||||
resize: vertical;
|
||||
min-height: 60px;
|
||||
color: var(--color-text-taskbar);
|
||||
}
|
||||
}
|
||||
|
||||
.edit-buttons {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
bottom: 8px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
margin-top: 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useAgentsStore, useSelectionStore } from '@/stores'
|
||||
|
||||
const agentsStore = useAgentsStore()
|
||||
const selectionStore = useSelectionStore()
|
||||
const emit = defineEmits<{
|
||||
(e: 'click'): void
|
||||
}>()
|
||||
|
||||
const props = defineProps<{
|
||||
step?: any
|
||||
}>()
|
||||
|
||||
// 获取分支数量
|
||||
const branchCount = computed(() => {
|
||||
if (!props.step?.Id) return 1
|
||||
|
||||
// 获取该任务步骤的分支数据
|
||||
const taskStepId = props.step.Id
|
||||
// 获取该任务的 agent 组合
|
||||
const agents = props.step.AgentSelection || []
|
||||
const branches = selectionStore.getTaskProcessBranches(taskStepId, agents)
|
||||
return branches.length || 1
|
||||
})
|
||||
|
||||
// 判断按钮是否可点击
|
||||
const isClickable = computed(() => {
|
||||
if (!props.step?.Id || !agentsStore.currentTask?.Id) {
|
||||
return false
|
||||
}
|
||||
return props.step.Id === agentsStore.currentTask.Id
|
||||
})
|
||||
|
||||
const handleClick = (event?: MouseEvent) => {
|
||||
// 只有可点击时才执行操作
|
||||
if (!isClickable.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// 阻止冒泡,避免触发卡片点击
|
||||
if (event) {
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
emit('click')
|
||||
// 设置当前任务
|
||||
if (props.step) {
|
||||
agentsStore.setCurrentTask(props.step)
|
||||
}
|
||||
// 触发打开任务过程探索窗口
|
||||
agentsStore.openPlanTask()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="task-button"
|
||||
:class="{ 'has-branches': branchCount > 0, 'is-disabled': !isClickable }"
|
||||
@click="handleClick"
|
||||
:title="isClickable ? `${branchCount} 个分支` : '请先在任务大纲中选中此任务'"
|
||||
>
|
||||
<!-- 流程图标 -->
|
||||
<svg-icon icon-class="branch" size="20px" class="task-icon" />
|
||||
|
||||
<!-- 分支数量显示 -->
|
||||
<span class="branch-count">
|
||||
{{ branchCount }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.task-button {
|
||||
/* 定位 - 右下角 */
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
/* 尺寸 */
|
||||
width: 36px;
|
||||
height: 32px;
|
||||
|
||||
/* 样式 */
|
||||
background-color: #43a8aa;
|
||||
border-radius: 10px 0 0 0;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
z-index: 100;
|
||||
|
||||
/* 布局 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
/* 交互 */
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
// 禁用状态
|
||||
&.is-disabled {
|
||||
background-color: #bdc3c7;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
|
||||
&:hover {
|
||||
filter: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.has-branches::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: -2px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #ff6b6b;
|
||||
border-radius: 50%;
|
||||
border: 2px solid white;
|
||||
}
|
||||
}
|
||||
|
||||
.task-icon {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.branch-count {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
bottom: 2px;
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
font-weight: 800;
|
||||
text-align: right;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -1,183 +0,0 @@
|
||||
// 模拟后端原始返回格式的 Mock 数据 - fill_stepTask_TaskProcess 接口
|
||||
// 后端返回格式: IRawStepTask { StepName, TaskContent, InputObject_List, OutputObject, AgentSelection, TaskProcess, Collaboration_Brief_frontEnd }
|
||||
|
||||
import type { IRawStepTask } from '@/stores'
|
||||
|
||||
// TaskProcess 项格式
|
||||
interface RawTaskProcessItem {
|
||||
ID: string
|
||||
ActionType: string
|
||||
AgentName: string
|
||||
Description: string
|
||||
ImportantInput: string[]
|
||||
}
|
||||
|
||||
// Collaboration_Brief_frontEnd 数据项格式
|
||||
interface RawBriefDataItem {
|
||||
text: string
|
||||
color: number[] // [h, s, l]
|
||||
}
|
||||
|
||||
// 后端返回的完整数据格式
|
||||
export interface RawAgentTaskProcessResponse {
|
||||
StepName: string
|
||||
TaskContent: string
|
||||
InputObject_List: string[]
|
||||
OutputObject: string
|
||||
AgentSelection: string[]
|
||||
TaskProcess: RawTaskProcessItem[]
|
||||
Collaboration_Brief_frontEnd?: {
|
||||
template: string
|
||||
data: Record<string, RawBriefDataItem>
|
||||
}
|
||||
}
|
||||
|
||||
// 模拟后端返回的原始数据结构(与后端缓存数据格式一致)
|
||||
// 使用与 AgentAssignmentBackendMock 相同的 agent 列表
|
||||
export const mockBackendAgentTaskProcessData: RawAgentTaskProcessResponse = {
|
||||
StepName: '腐蚀类型识别',
|
||||
TaskContent: '分析船舶制造中常见的材料腐蚀类型及其成因。',
|
||||
InputObject_List: [],
|
||||
OutputObject: '腐蚀类型及成因列表',
|
||||
AgentSelection: ['腐蚀机理研究员', '实验材料学家', '防护工程专家'],
|
||||
TaskProcess: [
|
||||
{
|
||||
ID: 'action_101',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '分析海洋环境下的腐蚀机理,确定关键防护要素',
|
||||
ImportantInput: ['海洋环境参数', '防护性能指标'],
|
||||
},
|
||||
{
|
||||
ID: 'action_102',
|
||||
ActionType: 'Critique',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '基于腐蚀机理分析结果,设计涂层材料的基础配方',
|
||||
ImportantInput: ['腐蚀机理分析结果', '涂层材料配方'],
|
||||
},
|
||||
{
|
||||
ID: 'action_103',
|
||||
ActionType: 'Improve',
|
||||
AgentName: '防护工程专家',
|
||||
Description: '筛选适用于防护涂层的二维材料,评估其性能潜力',
|
||||
ImportantInput: ['材料配方设计', '涂层材料配方'],
|
||||
},
|
||||
{
|
||||
ID: 'action_104',
|
||||
ActionType: 'Finalize',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '制定涂层材料性能测试实验方案,包括测试指标和方法',
|
||||
ImportantInput: ['二维材料筛选结果', '防护性能指标'],
|
||||
},
|
||||
{
|
||||
ID: 'action_105',
|
||||
ActionType: 'Critique',
|
||||
AgentName: '防护工程专家',
|
||||
Description: '模拟海洋流体环境对涂层材料的影响,优化涂层结构',
|
||||
ImportantInput: ['实验方案', '海洋环境参数'],
|
||||
},
|
||||
{
|
||||
ID: 'action_106',
|
||||
ActionType: 'Improve',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '综合评估涂层材料的防护性能,提出改进建议',
|
||||
ImportantInput: ['流体力学模拟结果', '实验材料学测试结果', '二维材料性能数据'],
|
||||
},
|
||||
{
|
||||
ID: 'action_107',
|
||||
ActionType: 'Improve',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '整理研发数据和测试结果,撰写完整的研发报告',
|
||||
ImportantInput: ['综合性能评估', '所有研发数据'],
|
||||
},
|
||||
],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '基于!<0>!、!<1>!和!<2>!,!<3>!、!<4>!、!<5>!和!<6>!执行!<7>!任务,以获得!<8>!。',
|
||||
data: {
|
||||
'0': {
|
||||
text: '涂层材料配方',
|
||||
color: [120, 60, 70], // hsl(120, 60%, 70%)
|
||||
},
|
||||
'1': {
|
||||
text: '海洋环境参数',
|
||||
color: [120, 60, 70], // hsl(120, 60%, 70%)
|
||||
},
|
||||
'2': {
|
||||
text: '防护性能指标',
|
||||
color: [120, 60, 70], // hsl(120, 60%, 70%)
|
||||
},
|
||||
'3': {
|
||||
text: '腐蚀机理研究员',
|
||||
color: [0, 0, 90], // hsl(0, 0%, 90%)
|
||||
},
|
||||
'4': {
|
||||
text: '先进材料研发员',
|
||||
color: [0, 0, 90], // hsl(0, 0%, 90%)
|
||||
},
|
||||
'5': {
|
||||
text: '二维材料科学家',
|
||||
color: [0, 0, 90], // hsl(0, 0%, 90%)
|
||||
},
|
||||
'6': {
|
||||
text: '实验材料学家',
|
||||
color: [0, 0, 90], // hsl(0, 0%, 90%)
|
||||
},
|
||||
'7': {
|
||||
text: '研发适用于海洋环境的耐腐蚀防护涂层材料,并进行性能测试与评估',
|
||||
color: [0, 0, 87], // hsl(0, 0%, 87%)
|
||||
},
|
||||
'8': {
|
||||
text: '防护涂层材料研发报告',
|
||||
color: [30, 100, 80], // hsl(30, 100%, 80%)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 模拟后端API调用 - fill_stepTask_TaskProcess
|
||||
export const mockBackendFillAgentTaskProcess = async (
|
||||
goal: string,
|
||||
stepTask: any,
|
||||
agents: string[],
|
||||
): Promise<RawAgentTaskProcessResponse> => {
|
||||
// 模拟网络延迟 500ms
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
|
||||
// 在真实场景中,后端会根据传入的 goal、stepTask 和 agents 生成不同的 TaskProcess
|
||||
// 这里我们直接返回预设的 Mock 数据
|
||||
// 可以根据传入的 agents 动态修改 AgentSelection 和 TaskProcess
|
||||
|
||||
// 确保 agents 数组不为空
|
||||
const safeAgents = agents.length > 0 ? agents : ['腐蚀机理研究员']
|
||||
|
||||
const responseData: RawAgentTaskProcessResponse = {
|
||||
...mockBackendAgentTaskProcessData,
|
||||
AgentSelection: agents,
|
||||
TaskProcess: mockBackendAgentTaskProcessData.TaskProcess.map((action, index) => ({
|
||||
...action,
|
||||
AgentName: safeAgents[index % safeAgents.length],
|
||||
})),
|
||||
Collaboration_Brief_frontEnd: mockBackendAgentTaskProcessData.Collaboration_Brief_frontEnd
|
||||
? {
|
||||
template: mockBackendAgentTaskProcessData.Collaboration_Brief_frontEnd.template,
|
||||
data: { ...mockBackendAgentTaskProcessData.Collaboration_Brief_frontEnd.data },
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
|
||||
// 更新 Collaboration_Brief_frontEnd.data 中的 agent 引用
|
||||
if (responseData.Collaboration_Brief_frontEnd?.data) {
|
||||
const agentCount = Math.min(safeAgents.length, 4) // 最多4个agent
|
||||
for (let i = 0; i < agentCount; i++) {
|
||||
const key = String(i + 3) // agent从索引3开始
|
||||
if (responseData.Collaboration_Brief_frontEnd.data[key]) {
|
||||
responseData.Collaboration_Brief_frontEnd.data[key] = {
|
||||
...responseData.Collaboration_Brief_frontEnd.data[key],
|
||||
text: safeAgents[i]!,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return responseData
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
<!-- AdditionalOutputCard.vue -->
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, nextTick } from 'vue'
|
||||
import { useAgentsStore } from '@/stores'
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||
|
||||
const agentsStore = useAgentsStore()
|
||||
const props = defineProps<{
|
||||
index: number
|
||||
}>()
|
||||
|
||||
// 获取产物名称
|
||||
const currentOutput = computed(() => {
|
||||
return agentsStore.additionalOutputs[props.index] || ''
|
||||
})
|
||||
|
||||
// 编辑状态
|
||||
const isEditing = ref(false)
|
||||
const inputValue = ref('')
|
||||
const originalValue = ref('')
|
||||
const inputRef = ref<HTMLElement>()
|
||||
|
||||
// 点击编辑图标
|
||||
const handleEditClick = () => {
|
||||
isEditing.value = true
|
||||
originalValue.value = inputValue.value
|
||||
|
||||
// 等待 DOM 更新后聚焦输入框
|
||||
nextTick(() => {
|
||||
if (inputRef.value) {
|
||||
const inputEl = inputRef.value.querySelector('input')
|
||||
if (inputEl) {
|
||||
inputEl.focus()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 保存编辑
|
||||
const handleSave = () => {
|
||||
if (isEditing.value) {
|
||||
isEditing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 取消编辑
|
||||
const handleCancel = () => {
|
||||
if (isEditing.value) {
|
||||
inputValue.value = originalValue.value // 恢复原始值
|
||||
isEditing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 处理键盘事件
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
handleSave()
|
||||
} else if (event.key === 'Escape') {
|
||||
event.preventDefault()
|
||||
handleCancel()
|
||||
}
|
||||
}
|
||||
|
||||
// 输入框失去焦点处理
|
||||
const handleBlur = () => {
|
||||
// 延迟处理,避免点击按钮时立即触发
|
||||
setTimeout(() => {
|
||||
if (isEditing.value) {
|
||||
handleSave()
|
||||
}
|
||||
}, 200)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 当产物存在时才显示 -->
|
||||
<div v-if="currentOutput" class="card-item">
|
||||
<el-card
|
||||
class="card-item w-full relative output-object-card"
|
||||
:shadow="true"
|
||||
:id="`additional-output-${index}`"
|
||||
>
|
||||
<!-- 显示产物名称 -->
|
||||
<div class="text-start w-[100%]">
|
||||
<div class="text-[18px] font-bold text-[var(--color-text)] mb-2">
|
||||
{{ currentOutput }}
|
||||
</div>
|
||||
<div ref="inputRef">
|
||||
<el-input
|
||||
v-model="inputValue"
|
||||
:readonly="!isEditing"
|
||||
:placeholder="isEditing ? '请输入内容...' : '点击编辑图标开始编辑...'"
|
||||
@keydown="handleKeydown"
|
||||
@blur="handleBlur"
|
||||
:class="{ editing: isEditing }"
|
||||
>
|
||||
<template #suffix>
|
||||
<!-- 只读状态:显示编辑图标 -->
|
||||
<div v-if="!isEditing" class="flex items-center">
|
||||
<svg-icon
|
||||
icon-class="Edit"
|
||||
size="20px"
|
||||
class="cursor-pointer hover:text-[#409eff] transition-colors"
|
||||
@click="handleEditClick"
|
||||
title="点击编辑"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<!-- 编辑状态下的提示 -->
|
||||
<div v-if="isEditing" class="mt-2 text-end text-xs text-gray-500">
|
||||
<svg-icon
|
||||
icon-class="Check"
|
||||
size="20px"
|
||||
color="#328621"
|
||||
class="cursor-pointer mr-4"
|
||||
@click="handleSave"
|
||||
title="保存"
|
||||
/>
|
||||
<svg-icon
|
||||
icon-class="Cancel"
|
||||
size="20px"
|
||||
color="#8e0707"
|
||||
class="cursor-pointer mr-4"
|
||||
@click="handleCancel"
|
||||
title="取消"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.card-item {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.output-object-card {
|
||||
:deep(.el-card__body) {
|
||||
min-height: 80px;
|
||||
display: flex;
|
||||
align-items: start;
|
||||
justify-content: start;
|
||||
}
|
||||
}
|
||||
|
||||
/* 输入框样式 */
|
||||
:deep(.el-input .el-input__wrapper) {
|
||||
box-shadow: none;
|
||||
background-color: var(--color-bg-three);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* 编辑状态下的输入框样式 */
|
||||
:deep(.el-input.editing .el-input__wrapper) {
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-input.editing .el-input__wrapper.is-focus) {
|
||||
border-color: #c0c4cc;
|
||||
box-shadow: 0 0 0 1px #c0c4cc;
|
||||
}
|
||||
</style>
|
||||
@@ -38,12 +38,11 @@ const data = computed<Data | null>(() => {
|
||||
if (result.NodeId === props.nodeId) {
|
||||
// LogNodeType 为 object直接渲染Content
|
||||
if (result.LogNodeType === 'object') {
|
||||
const data = {
|
||||
return {
|
||||
Description: props.nodeId,
|
||||
Content: sanitize(result.content),
|
||||
LogNodeType: result.LogNodeType
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
if (!result.ActionHistory) {
|
||||
|
||||
@@ -59,7 +59,7 @@ function handlePrev() {
|
||||
<!-- <div>{{ `${displayIndex + 1}/${data.length}` }}</div>
|
||||
<el-button type="primary" size="small" @click="handleNext">下一个</el-button> -->
|
||||
<!-- 关闭 -->
|
||||
<!-- <SvgIcon icon-class="close" size="15px" /> -->
|
||||
<SvgIcon icon-class="close" size="15px" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分割线 -->
|
||||
|
||||
@@ -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>
|
||||
@@ -1,12 +1,3 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
isAdding?: boolean
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e: 'start-add-output'): void
|
||||
}>()
|
||||
</script>
|
||||
<template>
|
||||
<div class="absolute inset-0 flex items-start gap-[14%]">
|
||||
<!-- 左侧元素 -->
|
||||
@@ -19,26 +10,10 @@ defineEmits<{
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧元素 -->
|
||||
<div class="flex-1 relative h-full flex justify-center">
|
||||
<!-- 背景那一根线 -->
|
||||
<div class="h-full bg-[var(--color-bg-flow)] w-[5px]">
|
||||
<!-- 顶部加号区域 -->
|
||||
<!-- <div
|
||||
v-if="!isAdding"
|
||||
v-dev-only
|
||||
class="plus-area mt-[35px] ml-[-15px] w-[34px] h-[34px] flex items-center justify-center cursor-pointer rounded-full"
|
||||
@click="$emit('start-add-output')"
|
||||
> -->
|
||||
<!-- 加号图标 -->
|
||||
<!-- <svg-icon
|
||||
icon-class="plus"
|
||||
color="var(--color-text)"
|
||||
size="20px"
|
||||
class="plus-icon opacity-0 transition-opacity duration-200"
|
||||
/>
|
||||
</div> -->
|
||||
<!-- 线底部的小圆球 -->
|
||||
<div
|
||||
class="absolute bottom-0 left-1/2 transform -translate-x-1/2 bg-[var(--color-bg-flow)] w-[15px] h-[15px] rounded-full"
|
||||
@@ -48,9 +23,4 @@ defineEmits<{
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.plus-area:hover .plus-icon {
|
||||
opacity: 1;
|
||||
border: 1px dashed var(--color-text);
|
||||
}
|
||||
</style>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
@@ -1,365 +0,0 @@
|
||||
<template>
|
||||
<div class="root-node-wrapper">
|
||||
<!-- 左侧连接点(输入) -->
|
||||
<Handle type="target" :position="Position.Left" id="left" />
|
||||
<!-- 右侧连接点(输出) -->
|
||||
<Handle type="source" :position="Position.Right" id="right" />
|
||||
<!-- 底部连接点(用于分支) -->
|
||||
<Handle type="source" :position="Position.Bottom" id="bottom" />
|
||||
|
||||
<el-card class="root-node-card" :shadow="true">
|
||||
<!-- 目标内容 -->
|
||||
<div class="goal-content">
|
||||
<div class="goal-label">初始目标</div>
|
||||
<div class="goal-text">{{ goal }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 底部添加按钮 -->
|
||||
<div v-if="!isAddingBranch" class="external-add-btn" @click="startAddBranch">
|
||||
<el-icon :size="20" color="#409eff">
|
||||
<CirclePlus />
|
||||
</el-icon>
|
||||
</div>
|
||||
|
||||
<!-- 取消按钮(输入框显示时) -->
|
||||
<div v-else class="external-add-btn cancel-btn" @click="cancelAddBranch">
|
||||
<el-icon :size="20" color="#f56c6c">
|
||||
<Remove />
|
||||
</el-icon>
|
||||
</div>
|
||||
|
||||
<!-- 外部输入框 -->
|
||||
<div v-if="isAddingBranch" class="external-input-container">
|
||||
<el-input
|
||||
v-model="branchInput"
|
||||
placeholder="输入分支需求..."
|
||||
size="small"
|
||||
@keydown="handleBranchKeydown"
|
||||
ref="branchInputRef"
|
||||
class="branch-input"
|
||||
>
|
||||
<template #suffix>
|
||||
<svg-icon
|
||||
icon-class="paper-plane"
|
||||
size="16px"
|
||||
color="#409eff"
|
||||
class="submit-icon"
|
||||
:class="{ 'is-disabled': !branchInput.trim() }"
|
||||
@click="submitBranch"
|
||||
/>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
import { CirclePlus, Remove } from '@element-plus/icons-vue'
|
||||
import { Handle, Position } from '@vue-flow/core'
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||
interface RootNodeData {
|
||||
goal: string
|
||||
initialInput?: string[] | string
|
||||
isRoot?: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
id: string
|
||||
data: RootNodeData
|
||||
isAddingBranch?: boolean
|
||||
[key: string]: any
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'add-branch', nodeId: string, branchContent: string): void
|
||||
(e: 'start-add-branch', nodeId: string): void
|
||||
(e: 'cancel-add-branch'): void
|
||||
}>()
|
||||
|
||||
const goal = computed(() => props.data.goal || '')
|
||||
|
||||
const initialInput = computed(() => props.data.initialInput)
|
||||
|
||||
const displayInput = computed(() => {
|
||||
if (!initialInput.value) return false
|
||||
if (Array.isArray(initialInput.value)) {
|
||||
return initialInput.value.length > 0
|
||||
}
|
||||
return initialInput.value.trim().length > 0
|
||||
})
|
||||
|
||||
// 分支添加相关状态(使用父组件传入的 prop)
|
||||
const branchInput = ref('')
|
||||
const branchInputRef = ref<InstanceType<typeof HTMLInputElement>>()
|
||||
|
||||
// 计算属性,使用父组件传入的状态
|
||||
const isAddingBranch = computed(() => props.isAddingBranch || false)
|
||||
|
||||
// 分支添加相关方法
|
||||
const startAddBranch = () => {
|
||||
emit('start-add-branch', props.id)
|
||||
branchInput.value = ''
|
||||
nextTick(() => {
|
||||
branchInputRef.value?.focus()
|
||||
})
|
||||
}
|
||||
|
||||
const cancelAddBranch = () => {
|
||||
emit('cancel-add-branch')
|
||||
branchInput.value = ''
|
||||
}
|
||||
|
||||
const submitBranch = () => {
|
||||
if (branchInput.value.trim()) {
|
||||
emit('add-branch', props.id, branchInput.value.trim())
|
||||
branchInput.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
const handleBranchKeydown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
submitBranch()
|
||||
} else if (event.key === 'Escape') {
|
||||
cancelAddBranch()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.root-node-card {
|
||||
width: 200px;
|
||||
min-height: 100px;
|
||||
background: var(--color-bg-three);
|
||||
border: 2px solid #409eff;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 6px 16px rgba(64, 158, 255, 0.3);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.root-badge {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.goal-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.goal-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.goal-label {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #409eff;
|
||||
margin-bottom: 6px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.goal-text {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-title-header);
|
||||
line-height: 1.6;
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.initial-input {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.input-divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, transparent, #409eff, transparent);
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.input-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.input-text {
|
||||
font-size: 13px;
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
word-break: break-word;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
// 节点包装器
|
||||
.root-node-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// 外部添加按钮
|
||||
.external-add-btn {
|
||||
position: absolute;
|
||||
bottom: -20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
border: 2px solid #409eff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
z-index: 10;
|
||||
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
||||
|
||||
&:hover {
|
||||
background: #409eff;
|
||||
transform: translateX(-50%) scale(1.1);
|
||||
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
|
||||
|
||||
.el-icon {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateX(-50%) scale(0.95);
|
||||
}
|
||||
|
||||
// 取消按钮样式
|
||||
&.cancel-btn {
|
||||
border-color: #f56c6c;
|
||||
box-shadow: 0 2px 8px rgba(245, 108, 108, 0.2);
|
||||
|
||||
&:hover {
|
||||
background: #f56c6c;
|
||||
box-shadow: 0 4px 12px rgba(245, 108, 108, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 外部输入容器
|
||||
.external-input-container {
|
||||
position: absolute;
|
||||
bottom: -80px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 260px;
|
||||
background: #fff;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
z-index: 100;
|
||||
animation: slideDown 0.2s ease-out;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: #fff;
|
||||
border-left: 1px solid #dcdfe6;
|
||||
border-top: 1px solid #dcdfe6;
|
||||
transform: translateX(-50%) rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-50%) translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.branch-input {
|
||||
width: 100%;
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
background: transparent;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
|
||||
&:hover {
|
||||
border-color: #c0c4cc;
|
||||
}
|
||||
|
||||
&.is-focus {
|
||||
border-color: #409eff;
|
||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
.el-input__inner {
|
||||
font-size: 13px;
|
||||
color: #000;
|
||||
padding-right: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提交图标样式
|
||||
.submit-icon {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover:not(.is-disabled) {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
&:active:not(.is-disabled) {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,518 +0,0 @@
|
||||
<template>
|
||||
<div class="task-node-wrapper">
|
||||
<!-- 左侧连接点(输入) -->
|
||||
<Handle type="target" :position="Position.Left" id="left" />
|
||||
<!-- 右侧连接点(输出) -->
|
||||
<Handle type="source" :position="Position.Right" id="right" />
|
||||
<!-- 底部连接点(用于分支) -->
|
||||
<Handle type="source" :position="Position.Bottom" id="bottom" />
|
||||
|
||||
<el-card
|
||||
class="task-node-card"
|
||||
:class="{
|
||||
'is-editing': isEditing,
|
||||
'is-active': isActive,
|
||||
'is-branch-selected': props.isBranchSelected
|
||||
}"
|
||||
:shadow="true"
|
||||
>
|
||||
<!-- 任务名称 -->
|
||||
<div class="task-name">{{ task.StepName }}</div>
|
||||
|
||||
<!-- 智能体列表 -->
|
||||
<div class="agents-container">
|
||||
<el-tooltip
|
||||
v-for="agentSelection in task.AgentSelection"
|
||||
:key="agentSelection"
|
||||
effect="light"
|
||||
placement="top"
|
||||
:show-after="500"
|
||||
popper-class="task-syllabus-tooltip-popper"
|
||||
>
|
||||
<template #content>
|
||||
<div class="agent-tooltip">
|
||||
<div class="text-[16px] font-bold">{{ agentSelection }}</div>
|
||||
<div class="h-[1px] w-full bg-[#494B51] my-[4px]"></div>
|
||||
<div class="text-[12px]">
|
||||
{{ task.TaskProcess.find(i => i.AgentName === agentSelection)?.Description }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="agent-icon" :style="{ background: getAgentIcon(agentSelection).color }">
|
||||
<svg-icon :icon-class="getAgentIcon(agentSelection).icon" color="#fff" size="20px" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 底部添加按钮(卡片外部) -->
|
||||
<div v-if="!isAddingBranch" class="external-add-btn" @click="startAddBranch">
|
||||
<el-icon :size="20" color="#409eff">
|
||||
<CirclePlus />
|
||||
</el-icon>
|
||||
</div>
|
||||
|
||||
<!-- 取消按钮(输入框显示时) -->
|
||||
<div v-else class="external-add-btn cancel-btn" @click="cancelAddBranch">
|
||||
<el-icon :size="20" color="#f56c6c">
|
||||
<Remove />
|
||||
</el-icon>
|
||||
</div>
|
||||
|
||||
<!-- 外部输入框 -->
|
||||
<div v-if="isAddingBranch" class="external-input-container">
|
||||
<el-input
|
||||
v-model="branchInput"
|
||||
placeholder="输入分支需求..."
|
||||
size="small"
|
||||
@keydown="handleBranchKeydown"
|
||||
ref="branchInputRef"
|
||||
class="branch-input"
|
||||
>
|
||||
<template #suffix>
|
||||
<svg-icon
|
||||
icon-class="paper-plane"
|
||||
size="16px"
|
||||
color="#409eff"
|
||||
class="submit-icon"
|
||||
:class="{ 'is-disabled': !branchInput.trim() }"
|
||||
@click="submitBranch"
|
||||
/>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
import { CirclePlus, Remove } from '@element-plus/icons-vue'
|
||||
import { Handle, Position } from '@vue-flow/core'
|
||||
import { type IRawStepTask } from '@/stores'
|
||||
import { getAgentMapIcon } from '@/layout/components/config'
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||
|
||||
interface TaskNodeData {
|
||||
task: IRawStepTask
|
||||
isEditing: boolean
|
||||
editingContent: string
|
||||
}
|
||||
|
||||
// 使用更宽松的类型定义来避免类型错误
|
||||
const props = defineProps<{
|
||||
id: string
|
||||
data: TaskNodeData
|
||||
isAddingBranch?: boolean
|
||||
isBranchSelected?: boolean // 是否属于选中的分支路径
|
||||
[key: string]: any
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'edit-task', nodeId: string): void
|
||||
(e: 'save-task', nodeId: string, content: string): void
|
||||
(e: 'cancel-edit', nodeId: string): void
|
||||
(e: 'add-branch', nodeId: string, branchContent: string): void
|
||||
(e: 'start-add-branch', nodeId: string): void
|
||||
(e: 'cancel-add-branch'): void
|
||||
}>()
|
||||
|
||||
const editingContent = ref(props.data.task.TaskContent || '')
|
||||
|
||||
// 分支添加相关状态(使用父组件传入的 prop)
|
||||
const branchInput = ref('')
|
||||
const branchInputRef = ref<InstanceType<typeof HTMLInputElement>>()
|
||||
|
||||
// 计算属性,使用父组件传入的状态
|
||||
const isAddingBranch = computed(() => props.isAddingBranch || false)
|
||||
|
||||
const isEditing = computed({
|
||||
get: () => props.data.isEditing,
|
||||
set: value => {
|
||||
if (!value) {
|
||||
emit('cancel-edit', props.id)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const isActive = computed(() => {
|
||||
return props.data.task.StepName === props.data.task.StepName
|
||||
})
|
||||
|
||||
const task = computed(() => props.data.task)
|
||||
|
||||
const getAgentIcon = (agentName: string) => {
|
||||
return getAgentMapIcon(agentName)
|
||||
}
|
||||
|
||||
const startEdit = () => {
|
||||
emit('edit-task', props.id)
|
||||
}
|
||||
|
||||
const saveEdit = () => {
|
||||
emit('save-task', props.id, editingContent.value)
|
||||
}
|
||||
|
||||
const cancelEdit = () => {
|
||||
emit('cancel-edit', props.id)
|
||||
}
|
||||
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
saveEdit()
|
||||
} else if (event.key === 'Escape') {
|
||||
cancelEdit()
|
||||
}
|
||||
}
|
||||
|
||||
// 分支添加相关方法
|
||||
const startAddBranch = () => {
|
||||
emit('start-add-branch', props.id)
|
||||
branchInput.value = ''
|
||||
nextTick(() => {
|
||||
branchInputRef.value?.focus()
|
||||
})
|
||||
}
|
||||
|
||||
const cancelAddBranch = () => {
|
||||
emit('cancel-add-branch')
|
||||
branchInput.value = ''
|
||||
}
|
||||
|
||||
const submitBranch = () => {
|
||||
if (branchInput.value.trim()) {
|
||||
emit('add-branch', props.id, branchInput.value.trim())
|
||||
branchInput.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
const handleBranchKeydown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
submitBranch()
|
||||
} else if (event.key === 'Escape') {
|
||||
cancelAddBranch()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.task-node-card {
|
||||
width: 150px;
|
||||
min-height: 100px;
|
||||
background-color: var(--color-card-bg-task);
|
||||
border: 1px solid var(--color-card-border-task);
|
||||
box-sizing: border-box;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-card-bg-task-hover);
|
||||
border-color: var(--color-card-border-hover);
|
||||
box-shadow: var(--color-card-shadow-hover);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
// 分支选中高亮样式(绿色)
|
||||
&.is-branch-selected {
|
||||
border-color: #67c23a;
|
||||
box-shadow: 0 0 0 3px rgba(103, 194, 58, 0.3);
|
||||
background-color: rgba(103, 194, 58, 0.05);
|
||||
|
||||
&:hover {
|
||||
border-color: #67c23a;
|
||||
box-shadow: 0 0 0 3px rgba(103, 194, 58, 0.4);
|
||||
background-color: rgba(103, 194, 58, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
&.is-editing {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.task-name {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
color: var(--color-text-title-header);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background: var(--color-border-separate);
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.task-content {
|
||||
font-size: 14px;
|
||||
color: var(--color-text-secondary);
|
||||
min-height: 40px;
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.task-content-editing {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.edit-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.agents-container {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.agent-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
.input-outputs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
font-weight: bold;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.agent-tooltip {
|
||||
padding: 8px;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
// 编辑器样式
|
||||
.task-content-editor {
|
||||
:deep(.el-textarea__inner) {
|
||||
font-size: 14px;
|
||||
color: var(--color-text-secondary);
|
||||
background: transparent;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
resize: none;
|
||||
}
|
||||
}
|
||||
|
||||
// 节点包装器(用于容纳外部按钮)
|
||||
.task-node-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// 外部添加按钮
|
||||
.external-add-btn {
|
||||
position: absolute;
|
||||
bottom: -20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
border: 2px solid #409eff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
z-index: 10;
|
||||
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
||||
|
||||
&:hover {
|
||||
background: #409eff;
|
||||
transform: translateX(-50%) scale(1.1);
|
||||
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
|
||||
|
||||
.el-icon {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateX(-50%) scale(0.95);
|
||||
}
|
||||
|
||||
// 取消按钮样式
|
||||
&.cancel-btn {
|
||||
border-color: #f56c6c;
|
||||
box-shadow: 0 2px 8px rgba(245, 108, 108, 0.2);
|
||||
|
||||
&:hover {
|
||||
background: #f56c6c;
|
||||
box-shadow: 0 4px 12px rgba(245, 108, 108, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 外部输入容器
|
||||
.external-input-container {
|
||||
position: absolute;
|
||||
bottom: -80px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 260px;
|
||||
background: #fff;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
z-index: 100;
|
||||
animation: slideDown 0.2s ease-out;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: #fff;
|
||||
border-left: 1px solid #dcdfe6;
|
||||
border-top: 1px solid #dcdfe6;
|
||||
transform: translateX(-50%) rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-50%) translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.add-branch-section {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px dashed var(--color-border-separate);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.add-branch-btn {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.branch-input-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.branch-input {
|
||||
width: 100%;
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
background: transparent;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
|
||||
&:hover {
|
||||
border-color: #c0c4cc;
|
||||
}
|
||||
|
||||
&.is-focus {
|
||||
border-color: #409eff;
|
||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
.el-input__inner {
|
||||
font-size: 13px;
|
||||
color: #000;
|
||||
padding-right: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提交图标样式
|
||||
.submit-icon {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover:not(.is-disabled) {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
&:active:not(.is-disabled) {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.is-adding-branch {
|
||||
.task-node-card {
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.task-syllabus-tooltip-popper {
|
||||
z-index: 4000 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,127 +0,0 @@
|
||||
# Mock 数据说明
|
||||
|
||||
本目录包含用于分支功能的 mock 数据,支持在开发环境中测试分支逻辑,无需调用真实后端 API。
|
||||
|
||||
## 文件说明
|
||||
|
||||
### 1. `branchPlanOutlineMock.ts`
|
||||
**用途**: 根节点级别的分支(任务大纲分支)
|
||||
|
||||
**类型**: `IApiStepTask[][]`
|
||||
|
||||
**说明**: 返回多个分支方案,每个方案是一个完整的任务流程(IApiStepTask[])
|
||||
|
||||
**示例结构**:
|
||||
```typescript
|
||||
[
|
||||
// 第一个分支方案(瀑布流开发)
|
||||
[task1, task2, ...],
|
||||
// 第二个分支方案(敏捷开发)
|
||||
[task1, task2, ...],
|
||||
// 第三个分支方案(快速原型)
|
||||
[task1, task2, ...]
|
||||
]
|
||||
```
|
||||
|
||||
### 2. `branchTaskProcessMock.ts`
|
||||
**用途**: 任务节点级别的分支(任务流程分支)
|
||||
|
||||
**类型**: `IApiAgentAction[][]`
|
||||
|
||||
**说明**: 返回多个分支方案,每个方案是一系列动作(IApiAgentAction[]),这些动作会追加到现有任务的 TaskProcess 中
|
||||
|
||||
**示例结构**:
|
||||
```typescript
|
||||
[
|
||||
// 第一个分支方案(标准开发流程)
|
||||
[action1, action2, action3, ...],
|
||||
// 第二个分支方案(快速原型流程)
|
||||
[action1, action2, ...],
|
||||
// ... 更多方案
|
||||
]
|
||||
```
|
||||
|
||||
## 如何使用
|
||||
|
||||
### 切换 Mock 数据开关
|
||||
|
||||
在 `PlanModification.vue` 文件中,找到以下配置:
|
||||
|
||||
```typescript
|
||||
// 开关:控制是否使用 mock 数据(开发时设置为 true,生产时设置为 false)
|
||||
const USE_MOCK_DATA = true
|
||||
```
|
||||
|
||||
- **开发阶段**: 设置为 `true`,使用 mock 数据
|
||||
- **生产环境**: 设置为 `false`,调用真实 API
|
||||
|
||||
### 数据转换流程
|
||||
|
||||
```
|
||||
IApiStepTask (API 格式)
|
||||
↓ convertToIRawStepTask()
|
||||
IRawStepTask (内部格式)
|
||||
↓
|
||||
更新到 agentsStore.agentRawPlan.data
|
||||
↓
|
||||
Vue Flow 流程图自动更新
|
||||
```
|
||||
|
||||
### Mock 数据特点
|
||||
|
||||
1. **模拟网络延迟**: Mock 数据调用会模拟 800ms 的网络延迟
|
||||
2. **多方案选择**: 每个分支接口提供多个备选方案(目前默认使用第一个)
|
||||
3. **完整数据结构**: Mock 数据包含完整的字段,与真实 API 返回格式一致
|
||||
4. **类型安全**: 使用 TypeScript 类型定义,确保类型正确
|
||||
|
||||
## 扩展 Mock 数据
|
||||
|
||||
### 添加新的分支方案
|
||||
|
||||
在对应的 mock 文件中添加新的数组元素:
|
||||
|
||||
```typescript
|
||||
// branchPlanOutlineMock.ts
|
||||
const mockPlanBranchData: IApiStepTask[][] = [
|
||||
// 现有方案...
|
||||
[
|
||||
// 新增方案
|
||||
{
|
||||
name: '新方案步骤1',
|
||||
content: '...',
|
||||
// ... 其他字段
|
||||
},
|
||||
{
|
||||
name: '新方案步骤2',
|
||||
content: '...',
|
||||
// ... 其他字段
|
||||
}
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
### 随机选择方案
|
||||
|
||||
修改 `PlanModification.vue` 中的方案选择逻辑:
|
||||
|
||||
```typescript
|
||||
// 当前:固定选择第一个
|
||||
const mockBranchTasks = branchPlanOutlineMock[0]
|
||||
|
||||
// 改为:随机选择一个方案
|
||||
const randomIndex = Math.floor(Math.random() * branchPlanOutlineMock.length)
|
||||
const mockBranchTasks = branchPlanOutlineMock[randomIndex]
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. ⚠️ **生产环境**: 发布前务必将 `USE_MOCK_DATA` 设置为 `false`
|
||||
2. 🔍 **调试**: 查看控制台日志,以 `[Mock]` 开头的是 mock 数据相关日志
|
||||
3. 📝 **数据一致性**: 确保 mock 数据结构与真实 API 返回格式一致
|
||||
4. 🔄 **数据持久化**: Mock 数据仅存在于前端,刷新页面后会丢失
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `PlanModification.vue`: 主要逻辑文件,包含分支添加和 mock 数据集成
|
||||
- `api/index.ts`: 真实 API 接口定义
|
||||
- `stores/modules/agents.ts`: 类型定义和数据存储
|
||||
@@ -1,246 +0,0 @@
|
||||
// branch_PlanOutline 接口的 Mock 数据和 Mock API
|
||||
// 模拟后端返回的原始数据格式(IRawPlanResponse)
|
||||
|
||||
import type { IRawPlanResponse, IRawStepTask } from '@/stores'
|
||||
|
||||
// 后端返回的数据格式
|
||||
export type BranchPlanOutlineResponse = IRawPlanResponse
|
||||
|
||||
// Mock 数据:模拟后端返回的原始分支数据(不含 Collaboration_Brief_FrontEnd)
|
||||
// 注意:这里模拟的是 branch_PlanOutline 函数返回的数据,不是前端转换后的数据
|
||||
const mockBranchDataRaw: IRawStepTask[][] = [
|
||||
// 第一个分支方案
|
||||
[
|
||||
{
|
||||
StepName: '分析用户需求',
|
||||
TaskContent: '分析用户需求,制定项目开发计划',
|
||||
InputObject_List: ['腐蚀类型及成因列表'],
|
||||
OutputObject: '项目开发计划书',
|
||||
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '',
|
||||
data: {},
|
||||
},
|
||||
TaskProcess: [],
|
||||
},
|
||||
{
|
||||
StepName: '系统设计与架构',
|
||||
TaskContent: '设计系统架构和数据库结构',
|
||||
InputObject_List: ['项目开发计划书'],
|
||||
OutputObject: '系统设计文档',
|
||||
AgentSelection: ['腐蚀机理研究员', '防护工程专家'],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '',
|
||||
data: {},
|
||||
},
|
||||
TaskProcess: [],
|
||||
},
|
||||
],
|
||||
// 第二个分支方案(快速原型方案)
|
||||
[
|
||||
{
|
||||
StepName: '需求快速原型',
|
||||
TaskContent: '构建快速原型验证核心功能',
|
||||
InputObject_List: ['腐蚀类型及成因列表'],
|
||||
OutputObject: '原型系统',
|
||||
AgentSelection: ['实验材料学家', '防护工程专家'],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '',
|
||||
data: {},
|
||||
},
|
||||
TaskProcess: [],
|
||||
},
|
||||
{
|
||||
StepName: '原型测试与优化',
|
||||
TaskContent: '测试原型并根据反馈快速迭代',
|
||||
InputObject_List: ['原型系统'],
|
||||
OutputObject: '优化后的原型',
|
||||
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '',
|
||||
data: {},
|
||||
},
|
||||
TaskProcess: [],
|
||||
},
|
||||
],
|
||||
// 第三个分支方案(质量优先方案)
|
||||
[
|
||||
{
|
||||
StepName: '需求深度分析',
|
||||
TaskContent: '深入分析用户需求和技术可行性',
|
||||
InputObject_List: ['腐蚀类型及成因列表'],
|
||||
OutputObject: '详细需求分析报告',
|
||||
AgentSelection: ['腐蚀机理研究员', '防护工程专家'],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '',
|
||||
data: {},
|
||||
},
|
||||
TaskProcess: [],
|
||||
},
|
||||
{
|
||||
StepName: '质量保障设计',
|
||||
TaskContent: '设计质量保障体系和测试方案',
|
||||
InputObject_List: ['详细需求分析报告'],
|
||||
OutputObject: '质量保障方案',
|
||||
AgentSelection: ['实验材料学家', '防护工程专家'],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '',
|
||||
data: {},
|
||||
},
|
||||
TaskProcess: [],
|
||||
},
|
||||
{
|
||||
StepName: '系统开发与测试',
|
||||
TaskContent: '按质量标准进行系统开发和测试',
|
||||
InputObject_List: ['质量保障方案'],
|
||||
OutputObject: '经过完整测试的系统',
|
||||
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '',
|
||||
data: {},
|
||||
},
|
||||
TaskProcess: [],
|
||||
},
|
||||
],
|
||||
// 第四个分支方案(敏捷开发方案)
|
||||
[
|
||||
{
|
||||
StepName: '迭代规划',
|
||||
TaskContent: '制定敏捷开发迭代计划',
|
||||
InputObject_List: ['腐蚀类型及成因列表'],
|
||||
OutputObject: '迭代计划',
|
||||
AgentSelection: ['防护工程专家', '腐蚀机理研究员'],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '',
|
||||
data: {},
|
||||
},
|
||||
TaskProcess: [],
|
||||
},
|
||||
{
|
||||
StepName: '快速开发与验证',
|
||||
TaskContent: '快速开发并持续验证功能',
|
||||
InputObject_List: ['迭代计划'],
|
||||
OutputObject: '可运行的功能模块',
|
||||
AgentSelection: ['实验材料学家', '防护工程专家'],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '',
|
||||
data: {},
|
||||
},
|
||||
TaskProcess: [],
|
||||
},
|
||||
],
|
||||
]
|
||||
|
||||
/**
|
||||
* 模拟后端的 Add_Collaboration_Brief_FrontEnd 函数
|
||||
* 为每个任务添加前端协作简报(Collaboration_Brief_frontEnd)
|
||||
*/
|
||||
function Add_Collaboration_Brief_FrontEnd(branchList: IRawStepTask[][]): IRawStepTask[][] {
|
||||
return branchList.map((tasks) =>
|
||||
tasks.map((task) => ({
|
||||
...task,
|
||||
Collaboration_Brief_frontEnd: generateCollaborationBrief(task),
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成协作简报(Collaboration_Brief_frontEnd)
|
||||
* 根据 StepName、TaskContent、AgentSelection 生成模板和数据
|
||||
*/
|
||||
function generateCollaborationBrief(task: IRawStepTask): {
|
||||
template: string
|
||||
data: Record<string, any>
|
||||
} {
|
||||
const agents = task.AgentSelection || []
|
||||
const stepName = task.StepName || ''
|
||||
|
||||
// 为每个 agent 生成颜色
|
||||
const colors = [
|
||||
'hsl(210, 70%, 50%)',
|
||||
'hsl(30, 70%, 50%)',
|
||||
'hsl(120, 70%, 50%)',
|
||||
'hsl(270, 70%, 50%)',
|
||||
]
|
||||
|
||||
// 生成 data 对象
|
||||
const data: Record<string, any> = {}
|
||||
agents.forEach((agent, index) => {
|
||||
data[agent] = {
|
||||
text: agent,
|
||||
style: { background: colors[index % colors.length] },
|
||||
}
|
||||
})
|
||||
|
||||
// 生成 template(简化版本,实际应根据任务内容生成)
|
||||
const template =
|
||||
agents.length > 0
|
||||
? agents.map((agent, i) => `!<${agent}>!负责!<${stepName}-${i}>!`).join(',')
|
||||
: ''
|
||||
|
||||
return {
|
||||
template,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock API:模拟后端 branch_PlanOutline 接口调用
|
||||
*
|
||||
* @param branch_Number - 分支数量
|
||||
* @param Modification_Requirement - 修改需求
|
||||
* @param Existing_Steps - 现有步骤列表(包含完整信息的对象数组)
|
||||
* @param Baseline_Completion - 基线完成度
|
||||
* @param InitialObject_List - 初始对象列表
|
||||
* @param General_Goal - 总体目标
|
||||
* @returns Promise<IRawPlanResponse> - 返回包含 'Collaboration Process' 的响应
|
||||
*/
|
||||
export const mockBranchPlanOutlineAPI = async (params: {
|
||||
branch_Number: number
|
||||
Modification_Requirement: string
|
||||
Existing_Steps: any[] // 临时使用 any[],因为这里没有 IRawStepTask 类型
|
||||
Baseline_Completion: number
|
||||
InitialObject_List: string[]
|
||||
General_Goal: string
|
||||
}): Promise<IRawPlanResponse> => {
|
||||
// 模拟网络延迟 800ms
|
||||
await new Promise((resolve) => setTimeout(resolve, 800))
|
||||
|
||||
console.log('[Mock API] branch_PlanOutline 调用参数:', params)
|
||||
|
||||
// 🆕 使用轮询方式选择分支方案(依次循环使用所有分支方案)
|
||||
const totalBranches = mockBranchDataRaw.length
|
||||
const sessionKey = `branch-plan-outline-index-${params.General_Goal || 'default'}`
|
||||
|
||||
// 获取上一次的选择索引
|
||||
let lastIndex = parseInt(sessionStorage.getItem(sessionKey) || '0')
|
||||
|
||||
// 计算本次的选择索引(轮询到下一个分支)
|
||||
const selectedBranchIndex = (lastIndex + 1) % totalBranches
|
||||
|
||||
// 保存本次的选择索引
|
||||
sessionStorage.setItem(sessionKey, selectedBranchIndex.toString())
|
||||
|
||||
const rawBranchData = mockBranchDataRaw[selectedBranchIndex] || []
|
||||
|
||||
console.log(
|
||||
'[Mock API] branch_PlanOutline 选择分支方案:',
|
||||
selectedBranchIndex + 1,
|
||||
'/',
|
||||
totalBranches,
|
||||
)
|
||||
|
||||
// 模拟后端处理:添加 Collaboration_Brief_FrontEnd
|
||||
const processedBranches = Add_Collaboration_Brief_FrontEnd([rawBranchData])
|
||||
|
||||
// 构造响应数据(符合后端返回格式)
|
||||
const response: IRawPlanResponse = {
|
||||
'Collaboration Process': processedBranches[0] || [],
|
||||
}
|
||||
|
||||
console.log('[Mock API] branch_PlanOutline 返回数据:', response)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
export default mockBranchPlanOutlineAPI
|
||||
@@ -1,155 +0,0 @@
|
||||
// branch_TaskProcess 接口的 Mock 数据和 Mock API
|
||||
export interface BranchAction {
|
||||
ID: string
|
||||
ActionType: string
|
||||
AgentName: string
|
||||
Description: string
|
||||
ImportantInput: string[]
|
||||
}
|
||||
|
||||
export type BranchTaskProcessResponse = BranchAction[][]
|
||||
|
||||
// Mock 数据:模拟后端返回的原始任务流程数据(2D 数组)
|
||||
// 格式:[[action1, action2], [action3, action4]]
|
||||
const mockBranchTaskProcessDataRaw: BranchAction[][] = [
|
||||
// 第一个任务分支方案
|
||||
[
|
||||
{
|
||||
ID: 'agent3',
|
||||
ActionType: 'Critique',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '详细分析用户需求文档',
|
||||
ImportantInput: ['agent2'],
|
||||
},
|
||||
{
|
||||
ID: 'agent4',
|
||||
ActionType: 'Critique',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '设计系统整体架构',
|
||||
ImportantInput: ['agent3'],
|
||||
},
|
||||
{
|
||||
ID: 'agent5',
|
||||
ActionType: 'Improve',
|
||||
AgentName: '防护工程专家',
|
||||
Description: '实现系统核心功能',
|
||||
ImportantInput: ['agent4'],
|
||||
},
|
||||
{
|
||||
ID: 'agent6',
|
||||
ActionType: 'Finalize',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '进行系统集成测试',
|
||||
ImportantInput: ['agent5'],
|
||||
},
|
||||
],
|
||||
// 第二个任务分支方案
|
||||
[
|
||||
{
|
||||
ID: 'agent7',
|
||||
ActionType: 'Critique',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '深入分析用户需求和技术约束',
|
||||
ImportantInput: ['agent2'],
|
||||
},
|
||||
{
|
||||
ID: 'agent8',
|
||||
ActionType: 'Critique',
|
||||
AgentName: '防护工程专家',
|
||||
Description: '设计系统技术架构和数据流',
|
||||
ImportantInput: ['agent8'],
|
||||
},
|
||||
{
|
||||
ID: 'agent9',
|
||||
ActionType: 'Improve',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '评估系统安全性',
|
||||
ImportantInput: ['agent4'],
|
||||
},
|
||||
{
|
||||
ID: 'agent10',
|
||||
ActionType: 'Finalize',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '完成系统安全测试',
|
||||
ImportantInput: ['agent9'],
|
||||
},
|
||||
],
|
||||
|
||||
//第三个任务分支方案
|
||||
[
|
||||
{
|
||||
ID: 'agent12',
|
||||
ActionType: 'Critique',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '设计系统整体架构',
|
||||
ImportantInput: ['agent11'],
|
||||
},
|
||||
{
|
||||
ID: 'agent13',
|
||||
ActionType: 'Improve',
|
||||
AgentName: '防护工程专家',
|
||||
Description: '实现系统核心功能',
|
||||
ImportantInput: ['agent12'],
|
||||
},
|
||||
{
|
||||
ID: 'agent14',
|
||||
ActionType: 'Finalize',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '进行系统集成测试',
|
||||
ImportantInput: ['agent13'],
|
||||
},
|
||||
],
|
||||
]
|
||||
|
||||
/**
|
||||
* Mock API:模拟后端 branch_TaskProcess 接口调用
|
||||
*
|
||||
* @param branch_Number - 分支数量
|
||||
* @param Modification_Requirement - 修改需求
|
||||
* @param Existing_Steps - 现有步骤列表(包含完整信息的对象数组)
|
||||
* @param Baseline_Completion - 基线完成度
|
||||
* @param stepTaskExisting - 现有任务
|
||||
* @param General_Goal - 总体目标
|
||||
* @returns Promise<BranchAction[][]> - 返回 2D 数组,与后端格式完全一致
|
||||
*/
|
||||
export const mockBranchTaskProcessAPI = async (params: {
|
||||
branch_Number: number
|
||||
Modification_Requirement: string
|
||||
Existing_Steps: BranchAction[]
|
||||
Baseline_Completion: number
|
||||
stepTaskExisting: any
|
||||
General_Goal: string
|
||||
}): Promise<BranchAction[][]> => {
|
||||
// 模拟网络延迟 800ms
|
||||
await new Promise((resolve) => setTimeout(resolve, 800))
|
||||
|
||||
console.log('[Mock API] branch_TaskProcess 调用参数:', params)
|
||||
|
||||
// 🆕 使用轮询方式选择分支方案(依次循环使用所有分支方案)
|
||||
const totalBranches = mockBranchTaskProcessDataRaw.length
|
||||
const sessionKey = `branch-task-process-index-${params.stepTaskExisting?.Id || 'default'}`
|
||||
|
||||
// 获取上一次的选择索引
|
||||
let lastIndex = parseInt(sessionStorage.getItem(sessionKey) || '0')
|
||||
|
||||
// 计算本次的选择索引(轮询到下一个分支)
|
||||
const selectedBranchIndex = (lastIndex + 1) % totalBranches
|
||||
|
||||
// 保存本次的选择索引
|
||||
sessionStorage.setItem(sessionKey, selectedBranchIndex.toString())
|
||||
|
||||
const rawBranchData = mockBranchTaskProcessDataRaw[selectedBranchIndex] || []
|
||||
|
||||
console.log(
|
||||
'[Mock API] branch_TaskProcess 选择分支方案:',
|
||||
selectedBranchIndex + 1,
|
||||
'/',
|
||||
totalBranches,
|
||||
)
|
||||
console.log('[Mock API] branch_TaskProcess 返回数据:', rawBranchData)
|
||||
|
||||
// 直接返回 2D 数组,与后端格式完全一致
|
||||
return [rawBranchData]
|
||||
}
|
||||
|
||||
export default mockBranchTaskProcessAPI
|
||||
@@ -1,617 +0,0 @@
|
||||
// fill_stepTask 接口的 Mock API
|
||||
// 简化版本:硬编码多个流程卡片的填充内容(支持多个分支方案)
|
||||
|
||||
import type { IRawStepTask } from '@/stores'
|
||||
|
||||
// 后端返回的数据格式
|
||||
export type FillStepTaskResponse = IRawStepTask
|
||||
|
||||
/**
|
||||
* Mock API:模拟后端 fill_stepTask 接口调用
|
||||
*
|
||||
* 根据 stepTask 的 StepName 返回对应的填充内容
|
||||
* 支持多个分支方案的轮询选择
|
||||
*
|
||||
* @param General_Goal - 总体目标
|
||||
* @param stepTask - 待填充的任务(包含基本字段)
|
||||
* @returns Promise<IRawStepTask> - 返回填充了 AgentSelection 和 TaskProcess 的完整任务
|
||||
*/
|
||||
export const mockFillStepTaskAPI = async (params: {
|
||||
General_Goal: string
|
||||
stepTask: any
|
||||
}): Promise<IRawStepTask> => {
|
||||
// 模拟网络延迟 300ms
|
||||
await new Promise((resolve) => setTimeout(resolve, 300))
|
||||
|
||||
console.log('[Mock API] fill_stepTask 调用参数:', params)
|
||||
|
||||
const stepName = params.stepTask.StepName
|
||||
|
||||
// 🆕 使用轮询方式选择填充方案(依次循环使用所有分支方案)
|
||||
const branchIndexKey = `fill-step-task-index-${params.stepTask?.Id || stepName || 'default'}`
|
||||
let lastIndex = parseInt(sessionStorage.getItem(branchIndexKey) || '0')
|
||||
const totalBranches = 4 // 总共4个分支方案
|
||||
const selectedBranchIndex = (lastIndex + 1) % totalBranches
|
||||
sessionStorage.setItem(branchIndexKey, selectedBranchIndex.toString())
|
||||
|
||||
console.log('[Mock API] fill_stepTask 选择分支方案:', selectedBranchIndex + 1, '/', totalBranches)
|
||||
|
||||
// ==================== 第一个任务组 ====================
|
||||
if (stepName === '分析用户需求' || stepName === '需求快速原型' || stepName === '需求深度分析' || stepName === '迭代规划') {
|
||||
let filledTask: IRawStepTask
|
||||
|
||||
if (selectedBranchIndex === 0) {
|
||||
// 第一个分支方案:分析用户需求
|
||||
filledTask = {
|
||||
Id: params.stepTask.Id || 'task-1',
|
||||
StepName: '分析用户需求',
|
||||
TaskContent: '分析用户需求,制定项目开发计划',
|
||||
InputObject_List: ['用户需求文档', '技术规范'],
|
||||
OutputObject: '项目开发计划书',
|
||||
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '!腐蚀机理研究员!负责!腐蚀类型识别-0!,!实验材料学家!负责!腐蚀类型识别-1!',
|
||||
data: {
|
||||
腐蚀机理研究员: {
|
||||
text: '腐蚀机理研究员',
|
||||
style: { background: 'hsl(210, 70%, 50%)' },
|
||||
},
|
||||
实验材料学家: {
|
||||
text: '实验材料学家',
|
||||
style: { background: 'hsl(120, 70%, 50%)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
TaskProcess: [
|
||||
{
|
||||
ID: 'action-1-1',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '分析腐蚀环境和介质特征,识别潜在腐蚀类型',
|
||||
ImportantInput: ['InputObject:腐蚀类型及成因列表'],
|
||||
},
|
||||
{
|
||||
ID: 'action-1-2',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '设计腐蚀实验方案,确定测试参数',
|
||||
ImportantInput: ['ActionResult:action-1-1'],
|
||||
},
|
||||
{
|
||||
ID: 'action-1-3',
|
||||
ActionType: 'Critique',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '评估实验方案的可行性',
|
||||
ImportantInput: [
|
||||
'ActionResult:action-1-2',
|
||||
'ActionResult:action-1-1',
|
||||
'InputObject:腐蚀类型及成因列表',
|
||||
],
|
||||
},
|
||||
{
|
||||
ID: 'action-1-4',
|
||||
ActionType: 'Improve',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '优化实验设计,完善测试流程',
|
||||
ImportantInput: ['ActionResult:action-1-3'],
|
||||
},
|
||||
{
|
||||
ID: 'action-1-5',
|
||||
ActionType: 'Finalize',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '确认最终的腐蚀类型识别方案',
|
||||
ImportantInput: ['ActionResult:action-1-4', 'ActionResult:action-1-3'],
|
||||
},
|
||||
],
|
||||
}
|
||||
} else if (selectedBranchIndex === 1) {
|
||||
// 第二个分支方案:需求快速原型
|
||||
filledTask = {
|
||||
Id: params.stepTask.Id || 'task-1',
|
||||
StepName: '需求快速原型',
|
||||
TaskContent: '构建快速原型验证核心功能',
|
||||
InputObject_List: ['腐蚀类型及成因列表'],
|
||||
OutputObject: '原型系统',
|
||||
AgentSelection: ['实验材料学家', '防护工程专家'],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '!实验材料学家!负责!需求快速原型-0!,!防护工程专家!负责!需求快速原型-1!',
|
||||
data: {
|
||||
实验材料学家: {
|
||||
text: '实验材料学家',
|
||||
style: { background: 'hsl(120, 70%, 50%)' },
|
||||
},
|
||||
防护工程专家: {
|
||||
text: '防护工程专家',
|
||||
style: { background: 'hsl(30, 70%, 50%)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
TaskProcess: [
|
||||
{
|
||||
ID: 'action-1-1-branch2',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '基于腐蚀类型列表,设计快速原型验证方案',
|
||||
ImportantInput: ['InputObject:腐蚀类型及成因列表'],
|
||||
},
|
||||
{
|
||||
ID: 'action-1-2-branch2',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '防护工程专家',
|
||||
Description: '实现原型核心功能和用户界面',
|
||||
ImportantInput: ['ActionResult:action-1-1-branch2'],
|
||||
},
|
||||
{
|
||||
ID: 'action-1-3-branch2',
|
||||
ActionType: 'Critique',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '评估原型功能完整性和用户体验',
|
||||
ImportantInput: ['ActionResult:action-1-2-branch2'],
|
||||
},
|
||||
{
|
||||
ID: 'action-1-4-branch2',
|
||||
ActionType: 'Improve',
|
||||
AgentName: '防护工程专家',
|
||||
Description: '根据反馈优化原型功能和界面',
|
||||
ImportantInput: ['ActionResult:action-1-3-branch2'],
|
||||
},
|
||||
{
|
||||
ID: 'action-1-5-branch2',
|
||||
ActionType: 'Finalize',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '确认最终版本的原型系统',
|
||||
ImportantInput: ['ActionResult:action-1-4-branch2'],
|
||||
},
|
||||
],
|
||||
}
|
||||
} else if (selectedBranchIndex === 2) {
|
||||
// 第三个分支方案:需求深度分析
|
||||
filledTask = {
|
||||
Id: params.stepTask.Id || 'task-1',
|
||||
StepName: '需求深度分析',
|
||||
TaskContent: '深入分析用户需求和技术可行性',
|
||||
InputObject_List: ['腐蚀类型及成因列表'],
|
||||
OutputObject: '详细需求分析报告',
|
||||
AgentSelection: ['腐蚀机理研究员', '防护工程专家'],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '!腐蚀机理研究员!负责!需求深度分析-0!,!防护工程专家!负责!需求深度分析-1!',
|
||||
data: {
|
||||
腐蚀机理研究员: {
|
||||
text: '腐蚀机理研究员',
|
||||
style: { background: 'hsl(210, 70%, 50%)' },
|
||||
},
|
||||
防护工程专家: {
|
||||
text: '防护工程专家',
|
||||
style: { background: 'hsl(30, 70%, 50%)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
TaskProcess: [
|
||||
{
|
||||
ID: 'action-1-1-branch3',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '全面分析腐蚀环境和技术约束条件',
|
||||
ImportantInput: ['InputObject:腐蚀类型及成因列表'],
|
||||
},
|
||||
{
|
||||
ID: 'action-1-2-branch3',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '防护工程专家',
|
||||
Description: '评估技术可行性和防护方案',
|
||||
ImportantInput: ['ActionResult:action-1-1-branch3'],
|
||||
},
|
||||
{
|
||||
ID: 'action-1-3-branch3',
|
||||
ActionType: 'Critique',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '评审需求分析的完整性和准确性',
|
||||
ImportantInput: ['ActionResult:action-1-2-branch3', 'ActionResult:action-1-1-branch3'],
|
||||
},
|
||||
{
|
||||
ID: 'action-1-4-branch3',
|
||||
ActionType: 'Improve',
|
||||
AgentName: '防护工程专家',
|
||||
Description: '补充技术风险评估和应对策略',
|
||||
ImportantInput: ['ActionResult:action-1-3-branch3'],
|
||||
},
|
||||
{
|
||||
ID: 'action-1-5-branch3',
|
||||
ActionType: 'Finalize',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '确认详细需求分析报告',
|
||||
ImportantInput: ['ActionResult:action-1-4-branch3', 'ActionResult:action-1-3-branch3'],
|
||||
},
|
||||
],
|
||||
}
|
||||
} else {
|
||||
// 第四个分支方案:迭代规划
|
||||
filledTask = {
|
||||
Id: params.stepTask.Id || 'task-1',
|
||||
StepName: '迭代规划',
|
||||
TaskContent: '制定敏捷开发迭代计划',
|
||||
InputObject_List: ['腐蚀类型及成因列表'],
|
||||
OutputObject: '迭代计划',
|
||||
AgentSelection: ['防护工程专家', '腐蚀机理研究员'],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '!防护工程专家!负责!迭代规划-0!,!腐蚀机理研究员!负责!迭代规划-1!',
|
||||
data: {
|
||||
防护工程专家: {
|
||||
text: '防护工程专家',
|
||||
style: { background: 'hsl(30, 70%, 50%)' },
|
||||
},
|
||||
腐蚀机理研究员: {
|
||||
text: '腐蚀机理研究员',
|
||||
style: { background: 'hsl(210, 70%, 50%)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
TaskProcess: [
|
||||
{
|
||||
ID: 'action-1-1-branch4',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '防护工程专家',
|
||||
Description: '制定敏捷开发总体框架和迭代周期',
|
||||
ImportantInput: ['InputObject:腐蚀类型及成因列表'],
|
||||
},
|
||||
{
|
||||
ID: 'action-1-2-branch4',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '规划各迭代阶段的技术验证重点',
|
||||
ImportantInput: ['ActionResult:action-1-1-branch4'],
|
||||
},
|
||||
{
|
||||
ID: 'action-1-3-branch4',
|
||||
ActionType: 'Critique',
|
||||
AgentName: '防护工程专家',
|
||||
Description: '评审迭代计划的合理性和可执行性',
|
||||
ImportantInput: ['ActionResult:action-1-2-branch4'],
|
||||
},
|
||||
{
|
||||
ID: 'action-1-4-branch4',
|
||||
ActionType: 'Improve',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '优化迭代节奏和里程碑设置',
|
||||
ImportantInput: ['ActionResult:action-1-3-branch4'],
|
||||
},
|
||||
{
|
||||
ID: 'action-1-5-branch4',
|
||||
ActionType: 'Finalize',
|
||||
AgentName: '防护工程专家',
|
||||
Description: '确认最终迭代计划',
|
||||
ImportantInput: ['ActionResult:action-1-4-branch4', 'ActionResult:action-1-3-branch4'],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[Mock API] fill_stepTask 返回数据:', filledTask)
|
||||
return filledTask
|
||||
}
|
||||
// ==================== 第二个任务组 ====================
|
||||
else if (stepName === '系统设计与架构' || stepName === '原型测试与优化' || stepName === '质量保障设计' || stepName === '快速开发与验证') {
|
||||
let filledTask: IRawStepTask
|
||||
|
||||
if (selectedBranchIndex === 0) {
|
||||
// 第一个分支方案:系统设计与架构
|
||||
filledTask = {
|
||||
Id: params.stepTask.Id || 'task-2',
|
||||
StepName: '系统设计与架构',
|
||||
TaskContent: '设计系统架构和数据库结构',
|
||||
InputObject_List: ['项目开发计划书'],
|
||||
OutputObject: '系统设计文档',
|
||||
AgentSelection: ['腐蚀机理研究员', '防护工程专家'],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '!腐蚀机理研究员!负责!系统设计与架构-0!,!防护工程专家!负责!系统设计与架构-1!',
|
||||
data: {
|
||||
腐蚀机理研究员: {
|
||||
text: '腐蚀机理研究员',
|
||||
style: { background: 'hsl(210, 70%, 50%)' },
|
||||
},
|
||||
防护工程专家: {
|
||||
text: '防护工程专家',
|
||||
style: { background: 'hsl(30, 70%, 50%)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
TaskProcess: [
|
||||
{
|
||||
ID: 'action-2-1',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '分析系统功能需求,提出整体架构方案',
|
||||
ImportantInput: ['InputObject:项目开发计划书'],
|
||||
},
|
||||
{
|
||||
ID: 'action-2-2',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '防护工程专家',
|
||||
Description: '设计防护系统架构和数据库模型',
|
||||
ImportantInput: ['ActionResult:action-2-1'],
|
||||
},
|
||||
{
|
||||
ID: 'action-2-3',
|
||||
ActionType: 'Critique',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '评审架构设计的合理性和可扩展性',
|
||||
ImportantInput: ['ActionResult:action-2-2', 'InputObject:项目开发计划书'],
|
||||
},
|
||||
{
|
||||
ID: 'action-2-4',
|
||||
ActionType: 'Improve',
|
||||
AgentName: '防护工程专家',
|
||||
Description: '优化架构设计,补充性能和安全方案',
|
||||
ImportantInput: ['ActionResult:action-2-3', 'ActionResult:action-2-2'],
|
||||
},
|
||||
{
|
||||
ID: 'action-2-5',
|
||||
ActionType: 'Finalize',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '确认最终系统架构设计文档',
|
||||
ImportantInput: ['ActionResult:action-2-4', 'ActionResult:action-2-3'],
|
||||
},
|
||||
],
|
||||
}
|
||||
} else if (selectedBranchIndex === 1) {
|
||||
// 第二个分支方案:原型测试与优化
|
||||
filledTask = {
|
||||
Id: params.stepTask.Id || 'task-2',
|
||||
StepName: '原型测试与优化',
|
||||
TaskContent: '测试原型并根据反馈快速迭代',
|
||||
InputObject_List: ['原型系统'],
|
||||
OutputObject: '优化后的原型',
|
||||
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '!腐蚀机理研究员!负责!原型测试与优化-0!,!实验材料学家!负责!原型测试与优化-1!',
|
||||
data: {
|
||||
腐蚀机理研究员: {
|
||||
text: '腐蚀机理研究员',
|
||||
style: { background: 'hsl(210, 70%, 50%)' },
|
||||
},
|
||||
实验材料学家: {
|
||||
text: '实验材料学家',
|
||||
style: { background: 'hsl(120, 70%, 50%)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
TaskProcess: [
|
||||
{
|
||||
ID: 'action-2-1-branch2',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '制定原型测试计划和验证标准',
|
||||
ImportantInput: ['InputObject:原型系统'],
|
||||
},
|
||||
{
|
||||
ID: 'action-2-2-branch2',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '执行原型测试并收集性能数据',
|
||||
ImportantInput: ['ActionResult:action-2-1-branch2'],
|
||||
},
|
||||
{
|
||||
ID: 'action-2-3-branch2',
|
||||
ActionType: 'Critique',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '分析测试结果和用户反馈',
|
||||
ImportantInput: ['ActionResult:action-2-2-branch2'],
|
||||
},
|
||||
{
|
||||
ID: 'action-2-4-branch2',
|
||||
ActionType: 'Improve',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '根据测试结果优化原型功能和性能',
|
||||
ImportantInput: ['ActionResult:action-2-3-branch2'],
|
||||
},
|
||||
{
|
||||
ID: 'action-2-5-branch2',
|
||||
ActionType: 'Finalize',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '确认优化后的原型版本',
|
||||
ImportantInput: ['ActionResult:action-2-4-branch2', 'ActionResult:action-2-3-branch2'],
|
||||
},
|
||||
],
|
||||
}
|
||||
} else if (selectedBranchIndex === 2) {
|
||||
// 第三个分支方案:质量保障设计
|
||||
filledTask = {
|
||||
Id: params.stepTask.Id || 'task-2',
|
||||
StepName: '质量保障设计',
|
||||
TaskContent: '设计质量保障体系和测试方案',
|
||||
InputObject_List: ['详细需求分析报告'],
|
||||
OutputObject: '质量保障方案',
|
||||
AgentSelection: ['实验材料学家', '防护工程专家'],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '!实验材料学家!负责!质量保障设计-0!,!防护工程专家!负责!质量保障设计-1!',
|
||||
data: {
|
||||
实验材料学家: {
|
||||
text: '实验材料学家',
|
||||
style: { background: 'hsl(120, 70%, 50%)' },
|
||||
},
|
||||
防护工程专家: {
|
||||
text: '防护工程专家',
|
||||
style: { background: 'hsl(30, 70%, 50%)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
TaskProcess: [
|
||||
{
|
||||
ID: 'action-2-1-branch3',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '制定质量标准和测试指标体系',
|
||||
ImportantInput: ['InputObject:详细需求分析报告'],
|
||||
},
|
||||
{
|
||||
ID: 'action-2-2-branch3',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '防护工程专家',
|
||||
Description: '设计质量保障流程和验证方案',
|
||||
ImportantInput: ['ActionResult:action-2-1-branch3'],
|
||||
},
|
||||
{
|
||||
ID: 'action-2-3-branch3',
|
||||
ActionType: 'Critique',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '评审质量保障方案的完整性',
|
||||
ImportantInput: ['ActionResult:action-2-2-branch3'],
|
||||
},
|
||||
{
|
||||
ID: 'action-2-4-branch3',
|
||||
ActionType: 'Improve',
|
||||
AgentName: '防护工程专家',
|
||||
Description: '完善质量保障方案,补充风险控制',
|
||||
ImportantInput: ['ActionResult:action-2-3-branch3'],
|
||||
},
|
||||
{
|
||||
ID: 'action-2-5-branch3',
|
||||
ActionType: 'Finalize',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '确认最终质量保障方案',
|
||||
ImportantInput: ['ActionResult:action-2-4-branch3', 'ActionResult:action-2-3-branch3'],
|
||||
},
|
||||
],
|
||||
}
|
||||
} else {
|
||||
// 第四个分支方案:快速开发与验证
|
||||
filledTask = {
|
||||
Id: params.stepTask.Id || 'task-2',
|
||||
StepName: '快速开发与验证',
|
||||
TaskContent: '快速开发并持续验证功能',
|
||||
InputObject_List: ['迭代计划'],
|
||||
OutputObject: '可运行的功能模块',
|
||||
AgentSelection: ['实验材料学家', '防护工程专家'],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '!实验材料学家!负责!快速开发与验证-0!,!防护工程专家!负责!快速开发与验证-1!',
|
||||
data: {
|
||||
实验材料学家: {
|
||||
text: '实验材料学家',
|
||||
style: { background: 'hsl(120, 70%, 50%)' },
|
||||
},
|
||||
防护工程专家: {
|
||||
text: '防护工程专家',
|
||||
style: { background: 'hsl(30, 70%, 50%)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
TaskProcess: [
|
||||
{
|
||||
ID: 'action-2-1-branch4',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '根据迭代计划快速开发功能模块',
|
||||
ImportantInput: ['InputObject:迭代计划'],
|
||||
},
|
||||
{
|
||||
ID: 'action-2-2-branch4',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '防护工程专家',
|
||||
Description: '实现功能防护机制和验证逻辑',
|
||||
ImportantInput: ['ActionResult:action-2-1-branch4'],
|
||||
},
|
||||
{
|
||||
ID: 'action-2-3-branch4',
|
||||
ActionType: 'Critique',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '验证功能模块的正确性和性能',
|
||||
ImportantInput: ['ActionResult:action-2-2-branch4'],
|
||||
},
|
||||
{
|
||||
ID: 'action-2-4-branch4',
|
||||
ActionType: 'Improve',
|
||||
AgentName: '防护工程专家',
|
||||
Description: '根据验证结果优化功能实现',
|
||||
ImportantInput: ['ActionResult:action-2-3-branch4'],
|
||||
},
|
||||
{
|
||||
ID: 'action-2-5-branch4',
|
||||
ActionType: 'Finalize',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '确认可运行的功能模块',
|
||||
ImportantInput: ['ActionResult:action-2-4-branch4', 'ActionResult:action-2-3-branch4'],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[Mock API] fill_stepTask 返回数据:', filledTask)
|
||||
return filledTask
|
||||
}
|
||||
// ==================== 第三个任务组(仅第三个分支方案) ====================
|
||||
else if (stepName === '系统开发与测试') {
|
||||
const filledTask: IRawStepTask = {
|
||||
Id: params.stepTask.Id || 'task-3',
|
||||
StepName: '系统开发与测试',
|
||||
TaskContent: '按质量标准进行系统开发和测试',
|
||||
InputObject_List: ['质量保障方案'],
|
||||
OutputObject: '经过完整测试的系统',
|
||||
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
|
||||
Collaboration_Brief_frontEnd: {
|
||||
template: '!腐蚀机理研究员!负责!系统开发与测试-0!,!实验材料学家!负责!系统开发与测试-1!',
|
||||
data: {
|
||||
腐蚀机理研究员: {
|
||||
text: '腐蚀机理研究员',
|
||||
style: { background: 'hsl(210, 70%, 50%)' },
|
||||
},
|
||||
实验材料学家: {
|
||||
text: '实验材料学家',
|
||||
style: { background: 'hsl(120, 70%, 50%)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
TaskProcess: [
|
||||
{
|
||||
ID: 'action-3-1',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '根据质量标准制定系统开发计划',
|
||||
ImportantInput: ['InputObject:质量保障方案'],
|
||||
},
|
||||
{
|
||||
ID: 'action-3-2',
|
||||
ActionType: 'Propose',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '按标准实施系统开发和单元测试',
|
||||
ImportantInput: ['ActionResult:action-3-1'],
|
||||
},
|
||||
{
|
||||
ID: 'action-3-3',
|
||||
ActionType: 'Critique',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '评审系统开发质量和测试覆盖率',
|
||||
ImportantInput: ['ActionResult:action-3-2'],
|
||||
},
|
||||
{
|
||||
ID: 'action-3-4',
|
||||
ActionType: 'Improve',
|
||||
AgentName: '实验材料学家',
|
||||
Description: '根据评审结果完善系统功能和测试',
|
||||
ImportantInput: ['ActionResult:action-3-3'],
|
||||
},
|
||||
{
|
||||
ID: 'action-3-5',
|
||||
ActionType: 'Finalize',
|
||||
AgentName: '腐蚀机理研究员',
|
||||
Description: '确认经过完整测试的系统版本',
|
||||
ImportantInput: ['ActionResult:action-3-4', 'ActionResult:action-3-3'],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
console.log('[Mock API] fill_stepTask 返回数据:', filledTask)
|
||||
return filledTask
|
||||
}
|
||||
// ==================== 其他任务 ====================
|
||||
else {
|
||||
// 其他任务,返回空 TaskProcess
|
||||
console.warn('[Mock API] 未知的 StepName:', stepName)
|
||||
return {
|
||||
...params.stepTask,
|
||||
AgentSelection: [],
|
||||
TaskProcess: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default mockFillStepTaskAPI
|
||||
@@ -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>
|
||||
@@ -1,115 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||
import { useAgentsStore } from '@/stores'
|
||||
|
||||
const agentsStore = useAgentsStore()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'click'): void
|
||||
}>()
|
||||
|
||||
// 判断是否禁用 - 必须先点击任务大纲中的卡片
|
||||
const isDisabled = computed(() => {
|
||||
return !agentsStore.currentTask
|
||||
})
|
||||
|
||||
// 获取agent组合卡片数量
|
||||
const agentGroupCount = computed(() => {
|
||||
if (!agentsStore.currentTask?.Id) return 1
|
||||
|
||||
// 获取该任务的已确认agent组合
|
||||
const confirmedGroups = agentsStore.getConfirmedAgentGroups(agentsStore.currentTask.Id)
|
||||
|
||||
// 当前任务agents(1) + 已确认的agent组合数量
|
||||
return confirmedGroups.length || 1
|
||||
})
|
||||
|
||||
const handleClick = () => {
|
||||
if (isDisabled.value) return
|
||||
emit('click')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="assignment-button"
|
||||
:class="{ 'is-disabled': isDisabled, 'has-groups': agentGroupCount > 1 }"
|
||||
@click="handleClick"
|
||||
:title="isDisabled ? '请先点击任务大纲中的任务卡片' : `${agentGroupCount} 个agent组合`"
|
||||
>
|
||||
<!-- 智能体分配图标 -->
|
||||
<SvgIcon icon-class="agent-change" size="24px" color="#fff" />
|
||||
|
||||
<!-- agent组合数量显示 -->
|
||||
<span class="agent-group-count">
|
||||
{{ agentGroupCount }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.assignment-button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 40px;
|
||||
height: 36px;
|
||||
background-color: #43a8aa;
|
||||
border-radius: 10px 0 0 0;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
&:active {
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
|
||||
// 多个agent组合时显示红点指示器
|
||||
&.has-groups::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: -2px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #ff6b6b;
|
||||
border-radius: 50%;
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
// 禁用状态
|
||||
&.is-disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
filter: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.agent-group-count {
|
||||
position: absolute;
|
||||
right: 1px;
|
||||
bottom: 2px;
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
font-weight: 800;
|
||||
text-align: right;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -1,99 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useAgentsStore, useSelectionStore } from '@/stores'
|
||||
|
||||
const agentsStore = useAgentsStore()
|
||||
const selectionStore = useSelectionStore()
|
||||
const emit = defineEmits<{
|
||||
(e: 'click'): void
|
||||
}>()
|
||||
|
||||
// 获取分支数量
|
||||
const branchCount = computed(() => {
|
||||
// flowBranches 包含所有通过 Branch 创建的分支
|
||||
const extraBranches = selectionStore.flowBranches?.length || 1
|
||||
|
||||
return extraBranches
|
||||
})
|
||||
|
||||
const handleClick = () => {
|
||||
emit('click')
|
||||
// 触发打开分支窗口
|
||||
agentsStore.openPlanModification()
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="branch-button"
|
||||
:class="{ 'has-branches': branchCount > 0 }"
|
||||
@click="handleClick"
|
||||
:title="`${branchCount} 个分支`"
|
||||
>
|
||||
<!-- 分支图标 -->
|
||||
<svg-icon icon-class="branch" size="24px" class="branch-icon" />
|
||||
|
||||
<!-- 分支数量显示 -->
|
||||
<span class="branch-count">
|
||||
{{ branchCount }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.branch-button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
/* 尺寸 */
|
||||
width: 36px;
|
||||
height: 32px;
|
||||
|
||||
/* 样式 */
|
||||
background-color: #43a8aa;
|
||||
border-radius: 10px 0 0 0;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
z-index: 100;
|
||||
|
||||
/* 布局 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
|
||||
/* 交互 */
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
&.has-branches::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: -2px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #ff6b6b;
|
||||
border-radius: 50%;
|
||||
border: 2px solid white;
|
||||
}
|
||||
}
|
||||
|
||||
.branch-icon {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.branch-count {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
bottom: 2px;
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
font-weight: 800;
|
||||
text-align: right;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -1,446 +0,0 @@
|
||||
<template>
|
||||
<div ref="rndContainer" class="rnd-container" :style="containerStyle">
|
||||
<!-- 标题栏 -->
|
||||
<div class="float-window-header" @mousedown="handleMouseDown">
|
||||
<div v-if="typeof title === 'string'" class="header-title">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div v-else class="header-title-custom">
|
||||
<slot name="title">
|
||||
{{ title }}
|
||||
</slot>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button v-if="onClose" class="close-btn" @click="handleClose">
|
||||
<svg-icon icon-class="close" size="20px" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div
|
||||
ref="contentContainer"
|
||||
class="float-window-content"
|
||||
@pointerenter="setResizeable(false)"
|
||||
@pointerleave="setResizeable(true)"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<!-- 调整大小的手柄 -->
|
||||
<div
|
||||
v-for="handle in resizeHandles"
|
||||
:key="handle"
|
||||
:class="`resize-handle resize-handle-${handle}`"
|
||||
@mousedown="e => startResize(e, handle)"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { debounce } from 'lodash-es'
|
||||
export interface IFloatingWindowProps {
|
||||
title?: string | any
|
||||
onClose?: () => void
|
||||
onResize?: () => void
|
||||
minWidth?: number
|
||||
minHeight?: number
|
||||
defaultSize?: {
|
||||
x: number
|
||||
y: number
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<IFloatingWindowProps>(), {
|
||||
title: '',
|
||||
minWidth: 150,
|
||||
minHeight: 60
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:position': [value: { x: number; y: number }]
|
||||
'update:size': [value: { width: number; height: number }]
|
||||
}>()
|
||||
|
||||
// 响应式状态
|
||||
const position = ref({ x: 0, y: 0 })
|
||||
const size = ref({ width: 400, height: 300 })
|
||||
const isDragging = ref(false)
|
||||
const isResizing = ref(false)
|
||||
const resizeDirection = ref<string | null>(null)
|
||||
const resizeable = ref(true)
|
||||
const resizeStart = ref({ x: 0, y: 0, width: 0, height: 0, right: 0, bottom: 0 })
|
||||
const dragStart = ref({ x: 0, y: 0 })
|
||||
const containerRef = ref<HTMLElement>()
|
||||
|
||||
// 窗口管理
|
||||
let windowsArrange: HTMLElement[] = []
|
||||
|
||||
const focusWindow = (element: HTMLElement) => {
|
||||
if (!element) return
|
||||
|
||||
// 过滤掉已经不存在的元素
|
||||
windowsArrange = windowsArrange.filter(ele => ele.isConnected && element !== ele)
|
||||
|
||||
// 将当前窗口移到最前面
|
||||
windowsArrange.push(element)
|
||||
|
||||
// 更新所有窗口的z-index
|
||||
windowsArrange.forEach((ele, index) => {
|
||||
ele.style.zIndex = `${index + 3000}`
|
||||
})
|
||||
}
|
||||
|
||||
// 计算默认尺寸
|
||||
const defaultSize = computed(() => {
|
||||
if (props.defaultSize) return props.defaultSize
|
||||
|
||||
const width = Math.min(1280, window.innerWidth - 20)
|
||||
const height = Math.min(600, window.innerHeight - 20)
|
||||
const x = (window.innerWidth - width) / 2
|
||||
const y = (window.innerHeight - height) / 2
|
||||
return { x, y, width, height }
|
||||
})
|
||||
|
||||
// 容器样式
|
||||
const containerStyle = computed(() => ({
|
||||
position: 'fixed' as const,
|
||||
left: `${position.value.x}px`,
|
||||
top: `${position.value.y}px`,
|
||||
width: `${size.value.width}px`,
|
||||
height: `${size.value.height}px`,
|
||||
border: '3px solid #43A8AA',
|
||||
boxShadow: '3px 3px 20px rgba(0, 0, 0, 0.3)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column' as const,
|
||||
zIndex: '3000',
|
||||
backgroundColor: 'var(--color-bg-three)',
|
||||
overflow: 'hidden',
|
||||
userSelect: (isDragging.value || isResizing.value ? 'none' : 'auto') as any
|
||||
}))
|
||||
|
||||
// 调整大小的手柄位置
|
||||
const resizeHandles = ['n', 'e', 's', 'w', 'ne', 'nw', 'se', 'sw']
|
||||
|
||||
// 事件处理
|
||||
const handleMouseDown = (e: MouseEvent) => {
|
||||
if (!resizeable.value || isResizing.value) return
|
||||
|
||||
isDragging.value = true
|
||||
dragStart.value = {
|
||||
x: e.clientX - position.value.x,
|
||||
y: e.clientY - position.value.y
|
||||
}
|
||||
|
||||
if (containerRef.value) {
|
||||
focusWindow(containerRef.value)
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', handleDragMove)
|
||||
document.addEventListener('mouseup', handleDragEnd)
|
||||
}
|
||||
|
||||
const handleDragMove = (e: MouseEvent) => {
|
||||
if (!isDragging.value) return
|
||||
|
||||
let newX = e.clientX - dragStart.value.x
|
||||
let newY = e.clientY - dragStart.value.y
|
||||
|
||||
// 边界检查
|
||||
newX = Math.max(0, Math.min(newX, window.innerWidth - size.value.width))
|
||||
newY = Math.max(0, Math.min(newY, window.innerHeight - size.value.height))
|
||||
|
||||
position.value = { x: newX, y: newY }
|
||||
emit('update:position', position.value)
|
||||
}
|
||||
|
||||
const handleDragEnd = () => {
|
||||
isDragging.value = false
|
||||
document.removeEventListener('mousemove', handleDragMove)
|
||||
document.removeEventListener('mouseup', handleDragEnd)
|
||||
}
|
||||
|
||||
const startResize = (e: MouseEvent, direction: string) => {
|
||||
if (!resizeable.value) return
|
||||
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
|
||||
isResizing.value = true
|
||||
resizeDirection.value = direction
|
||||
resizeStart.value = {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
width: size.value.width,
|
||||
height: size.value.height,
|
||||
right: position.value.x + size.value.width,
|
||||
bottom: position.value.y + size.value.height
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', handleResizeMove)
|
||||
document.addEventListener('mouseup', handleResizeEnd)
|
||||
}
|
||||
|
||||
const handleResizeMove = (e: MouseEvent) => {
|
||||
if (!isResizing.value || !resizeDirection.value) return
|
||||
|
||||
const deltaX = e.clientX - resizeStart.value.x
|
||||
const deltaY = e.clientY - resizeStart.value.y
|
||||
|
||||
let newWidth = resizeStart.value.width
|
||||
let newHeight = resizeStart.value.height
|
||||
let newX = position.value.x
|
||||
let newY = position.value.y
|
||||
|
||||
// 根据调整方向计算新尺寸和位置
|
||||
switch (resizeDirection.value) {
|
||||
// 右边调整 - 固定左边
|
||||
case 'e':
|
||||
newWidth = Math.max(props.minWidth, resizeStart.value.width + deltaX)
|
||||
break
|
||||
|
||||
// 下边调整 - 固定上边
|
||||
case 's':
|
||||
newHeight = Math.max(props.minHeight, resizeStart.value.height + deltaY)
|
||||
break
|
||||
|
||||
// 左边调整 - 固定右边
|
||||
case 'w':
|
||||
newWidth = Math.max(props.minWidth, resizeStart.value.width - deltaX)
|
||||
newX = resizeStart.value.right - newWidth
|
||||
break
|
||||
|
||||
// 上边调整 - 固定下边
|
||||
case 'n':
|
||||
newHeight = Math.max(props.minHeight, resizeStart.value.height - deltaY)
|
||||
newY = resizeStart.value.bottom - newHeight
|
||||
break
|
||||
|
||||
// 右上角调整 - 固定左下角
|
||||
case 'ne':
|
||||
newWidth = Math.max(props.minWidth, resizeStart.value.width + deltaX)
|
||||
newHeight = Math.max(props.minHeight, resizeStart.value.height - deltaY)
|
||||
newY = resizeStart.value.bottom - newHeight
|
||||
break
|
||||
|
||||
// 左上角调整 - 固定右下角
|
||||
case 'nw':
|
||||
newWidth = Math.max(props.minWidth, resizeStart.value.width - deltaX)
|
||||
newHeight = Math.max(props.minHeight, resizeStart.value.height - deltaY)
|
||||
newX = resizeStart.value.right - newWidth
|
||||
newY = resizeStart.value.bottom - newHeight
|
||||
break
|
||||
|
||||
// 左下角调整 - 固定右上角
|
||||
case 'sw':
|
||||
newWidth = Math.max(props.minWidth, resizeStart.value.width - deltaX)
|
||||
newHeight = Math.max(props.minHeight, resizeStart.value.height + deltaY)
|
||||
newX = resizeStart.value.right - newWidth
|
||||
break
|
||||
|
||||
// 右下角调整 - 固定左上角
|
||||
case 'se':
|
||||
default:
|
||||
newWidth = Math.max(props.minWidth, resizeStart.value.width + deltaX)
|
||||
newHeight = Math.max(props.minHeight, resizeStart.value.height + deltaY)
|
||||
break
|
||||
}
|
||||
|
||||
// 边界检查
|
||||
newX = Math.max(0, Math.min(newX, window.innerWidth - newWidth))
|
||||
newY = Math.max(0, Math.min(newY, window.innerHeight - newHeight))
|
||||
newWidth = Math.min(newWidth, window.innerWidth - newX)
|
||||
newHeight = Math.min(newHeight, window.innerHeight - newY)
|
||||
|
||||
size.value = { width: newWidth, height: newHeight }
|
||||
position.value = { x: newX, y: newY }
|
||||
|
||||
emit('update:size', size.value)
|
||||
emit('update:position', position.value)
|
||||
}
|
||||
|
||||
const handleResizeEnd = debounce(() => {
|
||||
isResizing.value = false
|
||||
resizeDirection.value = null
|
||||
document.removeEventListener('mousemove', handleResizeMove)
|
||||
document.removeEventListener('mouseup', handleResizeEnd)
|
||||
|
||||
props.onResize?.()
|
||||
}, 50)
|
||||
|
||||
const setResizeable = (value: boolean) => {
|
||||
resizeable.value = value
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
if (props.onClose) {
|
||||
props.onClose()
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
position.value = { x: defaultSize.value.x, y: defaultSize.value.y }
|
||||
size.value = { width: defaultSize.value.width, height: defaultSize.value.height }
|
||||
|
||||
nextTick(() => {
|
||||
if (containerRef.value) {
|
||||
focusWindow(containerRef.value)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// 清理事件监听
|
||||
document.removeEventListener('mousemove', handleDragMove)
|
||||
document.removeEventListener('mouseup', handleDragEnd)
|
||||
document.removeEventListener('mousemove', handleResizeMove)
|
||||
document.removeEventListener('mouseup', handleResizeEnd)
|
||||
|
||||
// 从窗口管理数组中移除
|
||||
if (containerRef.value) {
|
||||
windowsArrange = windowsArrange.filter(ele => ele !== containerRef.value)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rnd-container {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.float-window-header {
|
||||
background-color: #1976d2;
|
||||
color: white;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 5px;
|
||||
user-select: none;
|
||||
cursor: move;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.header-title-custom {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.close-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.float-window-content {
|
||||
flex-grow: 1;
|
||||
background: var(--color-card);
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 调整大小的手柄样式 */
|
||||
.resize-handle {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.resize-handle-n,
|
||||
.resize-handle-s {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
||||
.resize-handle-e,
|
||||
.resize-handle-w {
|
||||
width: 6px;
|
||||
height: 100%;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.resize-handle-ne,
|
||||
.resize-handle-nw,
|
||||
.resize-handle-se,
|
||||
.resize-handle-sw {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.resize-handle-n {
|
||||
top: -3px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.resize-handle-s {
|
||||
bottom: -3px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.resize-handle-e {
|
||||
top: 0;
|
||||
right: -3px;
|
||||
}
|
||||
|
||||
.resize-handle-w {
|
||||
top: 0;
|
||||
left: -3px;
|
||||
}
|
||||
|
||||
.resize-handle-ne {
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
cursor: ne-resize;
|
||||
}
|
||||
|
||||
.resize-handle-nw {
|
||||
top: -6px;
|
||||
left: -6px;
|
||||
cursor: nw-resize;
|
||||
}
|
||||
|
||||
.resize-handle-se {
|
||||
bottom: -6px;
|
||||
right: -6px;
|
||||
cursor: se-resize;
|
||||
}
|
||||
|
||||
.resize-handle-sw {
|
||||
bottom: -6px;
|
||||
left: -6px;
|
||||
cursor: sw-resize;
|
||||
}
|
||||
</style>
|
||||
@@ -1,116 +0,0 @@
|
||||
// Mock数据 - 用于agentSelectModifyAddAspect接口
|
||||
// 模拟用户输入新维度后,所有agent在该维度上的评分数据
|
||||
|
||||
import { vueAgentList } from './AgentAssignmentMock'
|
||||
|
||||
// 类型定义
|
||||
export interface NewDimensionScore {
|
||||
score: number
|
||||
reason: string
|
||||
}
|
||||
|
||||
export type NewDimensionScoreData = Record<string, NewDimensionScore>
|
||||
|
||||
// 模拟接口返回的数据结构
|
||||
export interface AgentAddAspectResponse {
|
||||
aspectName: string // 新添加的维度名称
|
||||
agentScores: NewDimensionScoreData // 所有agent在该维度上的评分
|
||||
}
|
||||
|
||||
// 生成指定维度名称的mock评分数据
|
||||
export const generateMockDimensionScores = (dimensionName: string): AgentAddAspectResponse => {
|
||||
const agentScores: NewDimensionScoreData = {}
|
||||
|
||||
vueAgentList.forEach((agent) => {
|
||||
// 随机生成1-5的评分
|
||||
const score = Math.floor(Math.random() * 5) + 1
|
||||
|
||||
// 根据评分生成不同的原因描述
|
||||
let reason = ''
|
||||
switch (score) {
|
||||
case 5:
|
||||
reason = `在"${dimensionName}"方面表现卓越,展现出杰出的能力和深刻的理解`
|
||||
break
|
||||
case 4:
|
||||
reason = `在"${dimensionName}"方面表现优秀,具有良好的专业能力和执行力`
|
||||
break
|
||||
case 3:
|
||||
reason = `在"${dimensionName}"方面表现合格,能够完成相关任务`
|
||||
break
|
||||
case 2:
|
||||
reason = `在"${dimensionName}"方面表现一般,仍有提升空间`
|
||||
break
|
||||
case 1:
|
||||
reason = `在"${dimensionName}"方面需要加强,建议进一步提升相关能力`
|
||||
break
|
||||
}
|
||||
|
||||
agentScores[agent] = { score, reason }
|
||||
})
|
||||
|
||||
return {
|
||||
aspectName: dimensionName,
|
||||
agentScores,
|
||||
}
|
||||
}
|
||||
|
||||
// 预设的一些常用维度及其评分数据
|
||||
export const presetDimensionScores: Record<string, AgentAddAspectResponse> = {
|
||||
创新性: generateMockDimensionScores('创新性'),
|
||||
技术能力: generateMockDimensionScores('技术能力'),
|
||||
沟通技巧: generateMockDimensionScores('沟通技巧'),
|
||||
问题解决: generateMockDimensionScores('问题解决'),
|
||||
团队协作: generateMockDimensionScores('团队协作'),
|
||||
学习能力: generateMockDimensionScores('学习能力'),
|
||||
执行力: generateMockDimensionScores('执行力'),
|
||||
责任心: generateMockDimensionScores('责任心'),
|
||||
适应性: generateMockDimensionScores('适应性'),
|
||||
领导力: generateMockDimensionScores('领导力'),
|
||||
}
|
||||
|
||||
// 模拟API调用函数(用于前端测试)
|
||||
export const mockAgentAddAspectApi = async (
|
||||
aspectList: string[],
|
||||
): Promise<AgentAddAspectResponse[]> => {
|
||||
// 获取新增的维度(最后一个)
|
||||
const newAspect = aspectList[aspectList.length - 1]
|
||||
|
||||
// 模拟网络延迟 500ms
|
||||
await new Promise((resolve) => setTimeout(resolve, 20000))
|
||||
|
||||
// 如果是预设维度,返回预设数据
|
||||
if (presetDimensionScores[newAspect]) {
|
||||
return [presetDimensionScores[newAspect]]
|
||||
}
|
||||
|
||||
// 否则动态生成新的评分数据
|
||||
return [generateMockDimensionScores(newAspect)]
|
||||
}
|
||||
|
||||
// Vue Composition API 兼容的hook
|
||||
export const useAgentAddAspectMock = () => {
|
||||
const addNewDimension = async (dimensionName: string) => {
|
||||
const response = await mockAgentAddAspectApi([dimensionName])
|
||||
return response[0]
|
||||
}
|
||||
|
||||
const getMultipleDimensions = async (dimensionNames: string[]) => {
|
||||
const responses: AgentAddAspectResponse[] = []
|
||||
|
||||
for (const dimension of dimensionNames) {
|
||||
if (presetDimensionScores[dimension]) {
|
||||
responses.push(presetDimensionScores[dimension])
|
||||
} else {
|
||||
responses.push(generateMockDimensionScores(dimension))
|
||||
}
|
||||
}
|
||||
|
||||
return responses
|
||||
}
|
||||
|
||||
return {
|
||||
addNewDimension,
|
||||
getMultipleDimensions,
|
||||
generateMockDimensionScores,
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
// 模拟后端原始返回格式的Mock数据 - 维度 -> agent -> { Reason, Score }
|
||||
import { vueAgentList, vueAspectList } from './AgentAssignmentMock'
|
||||
|
||||
// 后端返回的评分项格式
|
||||
export interface BackendScoreItem {
|
||||
Reason: string
|
||||
Score: number
|
||||
}
|
||||
|
||||
// 后端返回的完整数据格式
|
||||
export type BackendAgentScoreResponse = Record<string, Record<string, BackendScoreItem>>
|
||||
|
||||
// 模拟后端返回的原始数据结构(维度 -> agent -> { Reason, Score })
|
||||
export const mockBackendAgentScoreData: BackendAgentScoreResponse = {
|
||||
能力: {
|
||||
船舶设计师: { Reason: '展现出卓越的创造力和创新思维', Score: 4 },
|
||||
防护工程专家: { Reason: '展现出杰出的创造性问题解决能力', Score: 5 },
|
||||
病理生理学家: { Reason: '具有中等创造技能,有待提升', Score: 3 },
|
||||
药物化学家: { Reason: '在大多数情况下展现较强的创造性思维', Score: 4 },
|
||||
制剂工程师: { Reason: '展现出胜任的创造能力', Score: 3 },
|
||||
监管事务专家: { Reason: '具有较强的创造性表达能力', Score: 4 },
|
||||
物理学家: { Reason: '擅长创新性思维方法', Score: 5 },
|
||||
实验材料学家: { Reason: '展现出卓越的创造性思维和创新能力', Score: 5 },
|
||||
计算模拟专家: { Reason: '展现出良好的创造性问题解决能力', Score: 4 },
|
||||
腐蚀机理研究员: { Reason: '展现出卓越的创造性问题解决能力', Score: 5 },
|
||||
先进材料研发员: { Reason: '展现出平衡的创造能力', Score: 4 },
|
||||
肾脏病学家: { Reason: '展现出卓越的创造天赋', Score: 5 },
|
||||
临床研究协调员: { Reason: '展现出胜任的创造性思维', Score: 3 },
|
||||
中医药专家: { Reason: '展现出较强的创造性主动性', Score: 4 },
|
||||
药物安全专家: { Reason: '具有发展中的创造技能', Score: 3 },
|
||||
二维材料科学家: { Reason: '展现出卓越的创造愿景', Score: 5 },
|
||||
光电物理学家: { Reason: '展现出卓越的创造性执行力', Score: 4 },
|
||||
机器学习专家: { Reason: '具有较强的创造性问题解决能力', Score: 4 },
|
||||
流体动力学专家: { Reason: '展现出胜任的创造能力', Score: 3 },
|
||||
},
|
||||
可用性: {
|
||||
船舶设计师: { Reason: '展现出卓越的共情能力和社会意识', Score: 5 },
|
||||
防护工程专家: { Reason: '具有较强的情绪调节和人际交往技能', Score: 4 },
|
||||
病理生理学家: { Reason: '展现出卓越的情感智力', Score: 5 },
|
||||
药物化学家: { Reason: '在大多数情况下展现平均的情感智力', Score: 3 },
|
||||
制剂工程师: { Reason: '具有良好的情绪意识和沟通能力', Score: 4 },
|
||||
监管事务专家: { Reason: '在情绪意识方面偶尔表现不足', Score: 3 },
|
||||
物理学家: { Reason: '具有较强的情绪理解能力', Score: 4 },
|
||||
实验材料学家: { Reason: '展现出卓越的共情能力和社交技能', Score: 5 },
|
||||
计算模拟专家: { Reason: '具有良好的情绪调节能力', Score: 4 },
|
||||
腐蚀机理研究员: { Reason: '展现出卓越的情感智力和社会意识', Score: 5 },
|
||||
先进材料研发员: { Reason: '具有发展中的情绪意识', Score: 3 },
|
||||
肾脏病学家: { Reason: '擅长人际交往和建立关系', Score: 5 },
|
||||
临床研究协调员: { Reason: '展现出平衡的情感智力', Score: 4 },
|
||||
中医药专家: { Reason: '具有基本的情绪理解能力', Score: 3 },
|
||||
药物安全专家: { Reason: '展现出良好的情绪调节能力', Score: 4 },
|
||||
二维材料科学家: { Reason: '展现出卓越的社会意识', Score: 5 },
|
||||
光电物理学家: { Reason: '在情感智力方面需要提升', Score: 3 },
|
||||
机器学习专家: { Reason: '具有较强的共情能力', Score: 4 },
|
||||
流体动力学专家: { Reason: '具有良好的情绪沟通技能', Score: 4 },
|
||||
},
|
||||
专业性: {
|
||||
船舶设计师: { Reason: '展现出胜任的哲学推理技能', Score: 3 },
|
||||
防护工程专家: { Reason: '展现出卓越的逻辑推理和分析能力', Score: 5 },
|
||||
病理生理学家: { Reason: '展现出深刻的哲学洞察力和批判性思维', Score: 2 },
|
||||
药物化学家: { Reason: '展现出良好的哲学理解能力', Score: 1 },
|
||||
制剂工程师: { Reason: '具有基础哲学推理能力,存在一些局限', Score: 3 },
|
||||
监管事务专家: { Reason: '展现出较强的分析思维能力', Score: 4 },
|
||||
物理学家: { Reason: '展现出卓越的哲学深度', Score: 5 },
|
||||
实验材料学家: { Reason: '展现出卓越的专业分析和推理能力', Score: 5 },
|
||||
计算模拟专家: { Reason: '具有良好的批判性思维能力', Score: 4 },
|
||||
腐蚀机理研究员: { Reason: '具有较强的专业性分析和推理能力', Score: 4 },
|
||||
先进材料研发员: { Reason: '展现出卓越的逻辑推理能力', Score: 5 },
|
||||
肾脏病学家: { Reason: '具有基础的哲学理解能力', Score: 3 },
|
||||
临床研究协调员: { Reason: '展现出平衡的哲学推理能力', Score: 4 },
|
||||
中医药专家: { Reason: '需要在哲学思维方面发展', Score: 3 },
|
||||
药物安全专家: { Reason: '展现出良好的分析技能', Score: 4 },
|
||||
二维材料科学家: { Reason: '具有较强的哲学洞察力', Score: 4 },
|
||||
光电物理学家: { Reason: '擅长批判性思维和分析', Score: 5 },
|
||||
机器学习专家: { Reason: '具有基础哲学推理能力', Score: 3 },
|
||||
流体动力学专家: { Reason: '展现出卓越的哲学才能', Score: 5 },
|
||||
},
|
||||
效率: {
|
||||
船舶设计师: { Reason: '在任务完成方面展现出卓越的效率', Score: 4 },
|
||||
防护工程专家: { Reason: '擅长高效的工作流程管理', Score: 5 },
|
||||
病理生理学家: { Reason: '展现出平均的效率,有提升空间', Score: 3 },
|
||||
药物化学家: { Reason: '具有良好的时间管理技能', Score: 4 },
|
||||
制剂工程师: { Reason: '展现出胜任的效率', Score: 3 },
|
||||
监管事务专家: { Reason: '展现出卓越的生产力', Score: 5 },
|
||||
物理学家: { Reason: '具有强大的任务执行能力', Score: 4 },
|
||||
实验材料学家: { Reason: '具有良好的工作效率和时间管理', Score: 4 },
|
||||
计算模拟专家: { Reason: '展现出良好的工作流程优化能力', Score: 4 },
|
||||
腐蚀机理研究员: { Reason: '展现出卓越的效率和工作流程管理', Score: 5 },
|
||||
先进材料研发员: { Reason: '展现出足够的效率', Score: 3 },
|
||||
肾脏病学家: { Reason: '擅长快速完成任务', Score: 5 },
|
||||
临床研究协调员: { Reason: '展现出良好的生产力', Score: 4 },
|
||||
中医药专家: { Reason: '具有中等的效率水平', Score: 3 },
|
||||
药物安全专家: { Reason: '具有较强的任务效率', Score: 4 },
|
||||
二维材料科学家: { Reason: '具有良好的执行速度', Score: 4 },
|
||||
光电物理学家: { Reason: '展现出卓越的效率', Score: 5 },
|
||||
机器学习专家: { Reason: '展现出平均的生产力', Score: 3 },
|
||||
流体动力学专家: { Reason: '在执行方面具有良好的效率', Score: 4 },
|
||||
},
|
||||
准确性: {
|
||||
船舶设计师: { Reason: '展现出卓越的细节关注度', Score: 5 },
|
||||
防护工程专家: { Reason: '展现出卓越的准确性和精确度', Score: 5 },
|
||||
病理生理学家: { Reason: '展现出卓越的精确度', Score: 5 },
|
||||
药物化学家: { Reason: '展现出良好的准确性', Score: 4 },
|
||||
制剂工程师: { Reason: '展现出中等的准确性,有提升空间', Score: 3 },
|
||||
监管事务专家: { Reason: '具有较强的细节关注度', Score: 4 },
|
||||
物理学家: { Reason: '在精确度和准确性方面表现卓越', Score: 5 },
|
||||
实验材料学家: { Reason: '展现出卓越的细节导向和精确度', Score: 5 },
|
||||
计算模拟专家: { Reason: '展现出平均的准确性', Score: 3 },
|
||||
腐蚀机理研究员: { Reason: '展现出卓越的准确性和精确技能', Score: 5 },
|
||||
先进材料研发员: { Reason: '展现出卓越的准确性', Score: 5 },
|
||||
肾脏病学家: { Reason: '展现出较强的精确度', Score: 4 },
|
||||
临床研究协调员: { Reason: '展现出中等的准确性', Score: 3 },
|
||||
中医药专家: { Reason: '具有良好的细节导向能力', Score: 4 },
|
||||
药物安全专家: { Reason: '在准确性和精确度方面表现卓越', Score: 5 },
|
||||
二维材料科学家: { Reason: '展现出较强的细节关注度', Score: 4 },
|
||||
光电物理学家: { Reason: '展现出平均的准确性水平', Score: 3 },
|
||||
机器学习专家: { Reason: '在工作中具有良好的精确度', Score: 4 },
|
||||
流体动力学专家: { Reason: '展现出卓越的准确性', Score: 5 },
|
||||
},
|
||||
协作性: {
|
||||
船舶设计师: { Reason: '展现出卓越的协作技能', Score: 4 },
|
||||
防护工程专家: { Reason: '在团队合作和协作方面表现卓越', Score: 5 },
|
||||
病理生理学家: { Reason: '具有较强的协作能力', Score: 4 },
|
||||
药物化学家: { Reason: '具有中等的协作技能', Score: 3 },
|
||||
制剂工程师: { Reason: '展现出良好的团队合作精神', Score: 4 },
|
||||
监管事务专家: { Reason: '具有较强的合作能力', Score: 4 },
|
||||
物理学家: { Reason: '展现出平均的协作技能', Score: 3 },
|
||||
实验材料学家: { Reason: '在团队协作方面表现卓越', Score: 5 },
|
||||
计算模拟专家: { Reason: '展现出良好的合作工作能力', Score: 4 },
|
||||
腐蚀机理研究员: { Reason: '在团队协作和合作方面表现卓越', Score: 5 },
|
||||
先进材料研发员: { Reason: '具有中等的协作水平', Score: 3 },
|
||||
肾脏病学家: { Reason: '展现出良好的协作技能', Score: 4 },
|
||||
临床研究协调员: { Reason: '在协调和团队合作方面表现卓越', Score: 5 },
|
||||
中医药专家: { Reason: '具有较强的合作能力', Score: 4 },
|
||||
药物安全专家: { Reason: '展现出平均的协作水平', Score: 3 },
|
||||
二维材料科学家: { Reason: '展现出良好的团队合作精神', Score: 4 },
|
||||
光电物理学家: { Reason: '具有较强的协作技能', Score: 4 },
|
||||
机器学习专家: { Reason: '在团队协作方面表现卓越', Score: 5 },
|
||||
流体动力学专家: { Reason: '具有中等的协作能力', Score: 3 },
|
||||
},
|
||||
}
|
||||
|
||||
// 模拟后端API调用 - agentSelectModifyInit
|
||||
export const mockBackendAgentSelectModifyInit = async (): Promise<BackendAgentScoreResponse> => {
|
||||
// 模拟网络延迟 300ms
|
||||
await new Promise(resolve => setTimeout(resolve, 300))
|
||||
return mockBackendAgentScoreData
|
||||
}
|
||||
|
||||
// 模拟后端API调用 - agentSelectModifyAddAspect(添加新维度)
|
||||
export const mockBackendAgentSelectModifyAddAspect = async (
|
||||
aspectList: string[]
|
||||
): Promise<BackendAgentScoreResponse> => {
|
||||
// 模拟网络延迟 500ms
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
|
||||
// 获取新添加的维度(最后一个)
|
||||
const newAspect = aspectList[aspectList.length - 1]
|
||||
if (!newAspect) {
|
||||
return {}
|
||||
}
|
||||
|
||||
// 生成该维度下所有agent的评分
|
||||
const aspectData: Record<string, BackendScoreItem> = {}
|
||||
|
||||
vueAgentList.forEach(agent => {
|
||||
const score = Math.floor(Math.random() * 5) + 1
|
||||
let reason = ''
|
||||
switch (score) {
|
||||
case 5:
|
||||
reason = `在"${newAspect}"方面表现卓越,展现出杰出的能力和深刻的理解`
|
||||
break
|
||||
case 4:
|
||||
reason = `在"${newAspect}"方面表现优秀,具有良好的专业能力和执行力`
|
||||
break
|
||||
case 3:
|
||||
reason = `在"${newAspect}"方面表现合格,能够完成相关任务`
|
||||
break
|
||||
case 2:
|
||||
reason = `在"${newAspect}"方面表现一般,仍有提升空间`
|
||||
break
|
||||
case 1:
|
||||
reason = `在"${newAspect}"方面需要加强,建议进一步提升相关能力`
|
||||
break
|
||||
}
|
||||
aspectData[agent] = { Reason: reason, Score: score }
|
||||
})
|
||||
|
||||
return {
|
||||
[newAspect]: aspectData
|
||||
}
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
// Vue兼容的mock数据 - 6个维度,19个智能体
|
||||
export const vueAgentList = [
|
||||
'船舶设计师',
|
||||
'防护工程专家',
|
||||
'病理生理学家',
|
||||
'药物化学家',
|
||||
'制剂工程师',
|
||||
'监管事务专家',
|
||||
'物理学家',
|
||||
'实验材料学家',
|
||||
'计算模拟专家',
|
||||
'腐蚀机理研究员',
|
||||
'先进材料研发员',
|
||||
'肾脏病学家',
|
||||
'临床研究协调员',
|
||||
'中医药专家',
|
||||
'药物安全专家',
|
||||
'二维材料科学家',
|
||||
'光电物理学家',
|
||||
'机器学习专家',
|
||||
'流体动力学专家',
|
||||
]
|
||||
|
||||
export const vueAspectList = ['能力', '可用性', '专业性', '效率', '准确性', '协作性']
|
||||
|
||||
// 类型定义
|
||||
export type AgentName = (typeof vueAgentList)[number]
|
||||
export type AspectName = (typeof vueAspectList)[number]
|
||||
|
||||
export interface AgentScore {
|
||||
score: number
|
||||
reason: string
|
||||
}
|
||||
|
||||
export type IAgentSelectModifyAddRequest = Record<AspectName, Record<AgentName, AgentScore>>
|
||||
|
||||
// Vue友好的数据结构 - agent -> 维度 -> 评分(与后端返回格式一致)
|
||||
export const vueAgentScoreData: Record<AgentName, Record<AspectName, AgentScore>> = {
|
||||
船舶设计师: {
|
||||
能力: { score: 4, reason: '展现出卓越的创造力和创新思维' },
|
||||
可用性: { score: 5, reason: '展现出卓越的共情能力和社会意识' },
|
||||
专业性: { score: 3, reason: '展现出胜任的哲学推理技能' },
|
||||
效率: { score: 4, reason: '在任务完成方面展现出卓越的效率' },
|
||||
准确性: { score: 5, reason: '展现出卓越的细节关注度' },
|
||||
协作性: { score: 4, reason: '展现出卓越的协作技能' },
|
||||
},
|
||||
防护工程专家: {
|
||||
能力: { score: 5, reason: '展现出杰出的创造性问题解决能力' },
|
||||
可用性: { score: 4, reason: '具有较强的情绪调节和人际交往技能' },
|
||||
专业性: { score: 5, reason: '展现出卓越的逻辑推理和分析能力' },
|
||||
效率: { score: 5, reason: '擅长高效的工作流程管理' },
|
||||
准确性: { score: 5, reason: '展现出卓越的准确性和精确度' },
|
||||
协作性: { score: 5, reason: '在团队合作和协作方面表现卓越' },
|
||||
},
|
||||
病理生理学家: {
|
||||
能力: { score: 3, reason: '具有中等创造技能,有待提升' },
|
||||
可用性: { score: 5, reason: '展现出卓越的情感智力' },
|
||||
专业性: { score: 2, reason: '展现出深刻的哲学洞察力和批判性思维' },
|
||||
效率: { score: 3, reason: '展现出平均的效率,有提升空间' },
|
||||
准确性: { score: 5, reason: '展现出卓越的精确度' },
|
||||
协作性: { score: 4, reason: '具有较强的协作能力' },
|
||||
},
|
||||
药物化学家: {
|
||||
能力: { score: 4, reason: '在大多数情况下展现较强的创造性思维' },
|
||||
可用性: { score: 3, reason: '在大多数情况下展现平均的情感智力' },
|
||||
专业性: { score: 1, reason: '展现出良好的哲学理解能力' },
|
||||
效率: { score: 4, reason: '具有良好的时间管理技能' },
|
||||
准确性: { score: 4, reason: '展现出良好的准确性' },
|
||||
协作性: { score: 3, reason: '具有中等的协作技能' },
|
||||
},
|
||||
制剂工程师: {
|
||||
能力: { score: 3, reason: '展现出胜任的创造能力' },
|
||||
可用性: { score: 4, reason: '具有良好的情绪意识和沟通能力' },
|
||||
专业性: { score: 3, reason: '具有基础哲学推理能力,存在一些局限' },
|
||||
效率: { score: 3, reason: '展现出胜任的效率' },
|
||||
准确性: { score: 3, reason: '展现出中等的准确性,有提升空间' },
|
||||
协作性: { score: 4, reason: '展现出良好的团队合作精神' },
|
||||
},
|
||||
监管事务专家: {
|
||||
能力: { score: 4, reason: '具有较强的创造性表达能力' },
|
||||
可用性: { score: 3, reason: '在情绪意识方面偶尔表现不足' },
|
||||
专业性: { score: 4, reason: '展现出较强的分析思维能力' },
|
||||
效率: { score: 5, reason: '展现出卓越的生产力' },
|
||||
准确性: { score: 4, reason: '具有较强的细节关注度' },
|
||||
协作性: { score: 4, reason: '具有较强的合作能力' },
|
||||
},
|
||||
物理学家: {
|
||||
能力: { score: 5, reason: '擅长创新性思维方法' },
|
||||
可用性: { score: 4, reason: '具有较强的情绪理解能力' },
|
||||
专业性: { score: 5, reason: '展现出卓越的哲学深度' },
|
||||
效率: { score: 4, reason: '具有强大的任务执行能力' },
|
||||
准确性: { score: 5, reason: '在精确度和准确性方面表现卓越' },
|
||||
协作性: { score: 3, reason: '展现出平均的协作技能' },
|
||||
},
|
||||
实验材料学家: {
|
||||
能力: { score: 5, reason: '展现出卓越的创造性思维和创新能力' },
|
||||
可用性: { score: 5, reason: '展现出卓越的共情能力和社交技能' },
|
||||
专业性: { score: 5, reason: '展现出卓越的专业分析和推理能力' },
|
||||
效率: { score: 4, reason: '具有良好的工作效率和时间管理' },
|
||||
准确性: { score: 5, reason: '展现出卓越的细节导向和精确度' },
|
||||
协作性: { score: 5, reason: '在团队协作方面表现卓越' },
|
||||
},
|
||||
计算模拟专家: {
|
||||
能力: { score: 4, reason: '展现出良好的创造性问题解决能力' },
|
||||
可用性: { score: 4, reason: '具有良好的情绪调节能力' },
|
||||
专业性: { score: 4, reason: '具有良好的批判性思维能力' },
|
||||
效率: { score: 4, reason: '展现出良好的工作流程优化能力' },
|
||||
准确性: { score: 3, reason: '展现出平均的准确性' },
|
||||
协作性: { score: 4, reason: '展现出良好的合作工作能力' },
|
||||
},
|
||||
腐蚀机理研究员: {
|
||||
能力: { score: 5, reason: '展现出卓越的创造性问题解决能力' },
|
||||
可用性: { score: 5, reason: '展现出卓越的情感智力和社会意识' },
|
||||
专业性: { score: 4, reason: '具有较强的专业性分析和推理能力' },
|
||||
效率: { score: 5, reason: '展现出卓越的效率和工作流程管理' },
|
||||
准确性: { score: 5, reason: '展现出卓越的准确性和精确技能' },
|
||||
协作性: { score: 5, reason: '在团队协作和合作方面表现卓越' },
|
||||
},
|
||||
先进材料研发员: {
|
||||
能力: { score: 4, reason: '展现出平衡的创造能力' },
|
||||
可用性: { score: 3, reason: '具有发展中的情绪意识' },
|
||||
专业性: { score: 5, reason: '展现出卓越的逻辑推理能力' },
|
||||
效率: { score: 3, reason: '展现出足够的效率' },
|
||||
准确性: { score: 5, reason: '展现出卓越的准确性' },
|
||||
协作性: { score: 3, reason: '具有中等的协作水平' },
|
||||
},
|
||||
肾脏病学家: {
|
||||
能力: { score: 5, reason: '展现出卓越的创造天赋' },
|
||||
可用性: { score: 5, reason: '擅长人际交往和建立关系' },
|
||||
专业性: { score: 3, reason: '具有基础的哲学理解能力' },
|
||||
效率: { score: 5, reason: '擅长快速完成任务' },
|
||||
准确性: { score: 4, reason: '展现出较强的精确度' },
|
||||
协作性: { score: 4, reason: '展现出良好的协作技能' },
|
||||
},
|
||||
临床研究协调员: {
|
||||
能力: { score: 3, reason: '展现出胜任的创造性思维' },
|
||||
可用性: { score: 4, reason: '展现出平衡的情感智力' },
|
||||
专业性: { score: 4, reason: '展现出平衡的哲学推理能力' },
|
||||
效率: { score: 4, reason: '展现出良好的生产力' },
|
||||
准确性: { score: 3, reason: '展现出中等的准确性' },
|
||||
协作性: { score: 5, reason: '在协调和团队合作方面表现卓越' },
|
||||
},
|
||||
中医药专家: {
|
||||
能力: { score: 4, reason: '展现出较强的创造性主动性' },
|
||||
可用性: { score: 3, reason: '具有基本的情绪理解能力' },
|
||||
专业性: { score: 3, reason: '需要在哲学思维方面发展' },
|
||||
效率: { score: 3, reason: '具有中等的效率水平' },
|
||||
准确性: { score: 4, reason: '具有良好的细节导向能力' },
|
||||
协作性: { score: 4, reason: '具有较强的合作能力' },
|
||||
},
|
||||
药物安全专家: {
|
||||
能力: { score: 3, reason: '具有发展中的创造技能' },
|
||||
可用性: { score: 4, reason: '展现出良好的情绪调节能力' },
|
||||
专业性: { score: 4, reason: '展现出良好的分析技能' },
|
||||
效率: { score: 4, reason: '具有较强的任务效率' },
|
||||
准确性: { score: 5, reason: '在准确性和精确度方面表现卓越' },
|
||||
协作性: { score: 3, reason: '展现出平均的协作水平' },
|
||||
},
|
||||
二维材料科学家: {
|
||||
能力: { score: 5, reason: '展现出卓越的创造愿景' },
|
||||
可用性: { score: 5, reason: '展现出卓越的社会意识' },
|
||||
专业性: { score: 4, reason: '具有较强的哲学洞察力' },
|
||||
效率: { score: 4, reason: '具有良好的执行速度' },
|
||||
准确性: { score: 4, reason: '展现出较强的细节关注度' },
|
||||
协作性: { score: 4, reason: '展现出良好的团队合作精神' },
|
||||
},
|
||||
光电物理学家: {
|
||||
能力: { score: 4, reason: '展现出卓越的创造性执行力' },
|
||||
可用性: { score: 3, reason: '在情感智力方面需要提升' },
|
||||
专业性: { score: 5, reason: '擅长批判性思维和分析' },
|
||||
效率: { score: 5, reason: '展现出卓越的效率' },
|
||||
准确性: { score: 3, reason: '展现出平均的准确性水平' },
|
||||
协作性: { score: 4, reason: '具有较强的协作技能' },
|
||||
},
|
||||
机器学习专家: {
|
||||
能力: { score: 4, reason: '具有较强的创造性问题解决能力' },
|
||||
可用性: { score: 4, reason: '具有较强的共情能力' },
|
||||
专业性: { score: 3, reason: '具有基础哲学推理能力' },
|
||||
效率: { score: 3, reason: '展现出平均的生产力' },
|
||||
准确性: { score: 4, reason: '在工作中具有良好的精确度' },
|
||||
协作性: { score: 5, reason: '在团队协作方面表现卓越' },
|
||||
},
|
||||
流体动力学专家: {
|
||||
能力: { score: 3, reason: '展现出胜任的创造能力' },
|
||||
可用性: { score: 4, reason: '具有良好的情绪沟通技能' },
|
||||
专业性: { score: 5, reason: '展现出卓越的哲学才能' },
|
||||
效率: { score: 4, reason: '在执行方面具有良好的效率' },
|
||||
准确性: { score: 5, reason: '展现出卓越的准确性' },
|
||||
协作性: { score: 3, reason: '具有中等的协作能力' },
|
||||
},
|
||||
}
|
||||
|
||||
// Vue友好的智能体选择配置
|
||||
export const vueAgentSelections = {
|
||||
balanced: { agents: ['船舶设计师', '防护工程专家', '病理生理学家'] },
|
||||
creative: { agents: ['防护工程专家', '物理学家', '二维材料科学家'] },
|
||||
emotional: { agents: ['船舶设计师', '病理生理学家', '实验材料学家'] },
|
||||
philosophical: { agents: ['病理生理学家', '物理学家', '光电物理学家'] },
|
||||
mixed: { agents: ['药物化学家', '先进材料研发员', '肾脏病学家', '机器学习专家'] },
|
||||
}
|
||||
|
||||
export const vueCurrentAgentSelection = 'balanced'
|
||||
|
||||
// Vue兼容的工具函数
|
||||
export const vueCalculateAgentAverages = () => {
|
||||
const averages: Record<string, number> = {}
|
||||
|
||||
vueAgentList.forEach((agent) => {
|
||||
let total = 0
|
||||
let count = 0
|
||||
|
||||
vueAspectList.forEach((aspect) => {
|
||||
// 数据结构:agentScores[agent][aspect]
|
||||
const scoreData = vueAgentScoreData[agent]?.[aspect]
|
||||
if (scoreData) {
|
||||
total += scoreData.score
|
||||
count++
|
||||
}
|
||||
})
|
||||
|
||||
averages[agent] = count > 0 ? Number((total / count).toFixed(2)) : 0
|
||||
})
|
||||
|
||||
return averages
|
||||
}
|
||||
|
||||
// 获取按平均分排序的智能体列表
|
||||
export const vueGetSortedAgentsByAverage = () => {
|
||||
const averages = vueCalculateAgentAverages()
|
||||
return [...vueAgentList].sort((a, b) => {
|
||||
return averages[b] - averages[a]
|
||||
})
|
||||
}
|
||||
|
||||
// Vue Composition API 兼容的hook
|
||||
export const useAgentMockData = () => {
|
||||
// 在Vue中可以使用ref或reactive包装数据
|
||||
const agentScores = vueAgentScoreData
|
||||
const agentSelections = vueAgentSelections
|
||||
const currentSelection = vueCurrentAgentSelection
|
||||
|
||||
// 计算平均分的响应式函数
|
||||
const calculateAverages = () => {
|
||||
return vueCalculateAgentAverages()
|
||||
}
|
||||
|
||||
// 获取特定维度的评分(返回该维度下所有智能体的评分)
|
||||
const getScoresByAspect = (aspect: AspectName) => {
|
||||
const result: Record<AgentName, AgentScore> = {}
|
||||
// 数据结构:agentScores[agent][aspect],需要遍历所有agent提取指定aspect
|
||||
vueAgentList.forEach((agent) => {
|
||||
if (agentScores[agent]?.[aspect]) {
|
||||
result[agent] = agentScores[agent][aspect]!
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
// 获取特定智能体的所有维度评分
|
||||
const getScoresByAgent = (agent: AgentName) => {
|
||||
// 数据结构:agentScores[agent][aspect],直接返回agent的所有维度评分
|
||||
return agentScores[agent] || {}
|
||||
}
|
||||
|
||||
return {
|
||||
agentScores,
|
||||
agentSelections,
|
||||
currentSelection,
|
||||
calculateAverages,
|
||||
getScoresByAspect,
|
||||
getScoresByAgent,
|
||||
getSortedAgents: vueGetSortedAgentsByAverage,
|
||||
}
|
||||
}
|
||||
|
||||
// Vue 2.x 兼容的选项式API版本
|
||||
export const vueMockMixin = {
|
||||
data() {
|
||||
return {
|
||||
vueAgentScores: vueAgentScoreData,
|
||||
vueAgentSelections: vueAgentSelections,
|
||||
vueCurrentSelection: vueCurrentAgentSelection,
|
||||
vueAgentList: vueAgentList,
|
||||
vueAspectList: vueAspectList,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
vueAgentAverages() {
|
||||
return vueCalculateAgentAverages()
|
||||
},
|
||||
vueSortedAgents() {
|
||||
return vueGetSortedAgentsByAverage()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
vueGetScoresByAspect(aspect: AspectName) {
|
||||
const agentScores = (this as any).vueAgentScores
|
||||
const agentList = (this as any).vueAgentList
|
||||
// 数据结构:agentScores[agent][aspect],遍历所有agent提取指定aspect
|
||||
const result: Record<string, { score: number; reason: string }> = {}
|
||||
agentList.forEach((agent: string) => {
|
||||
if (agentScores[agent]?.[aspect]) {
|
||||
result[agent] = agentScores[agent][aspect]
|
||||
}
|
||||
})
|
||||
return result
|
||||
},
|
||||
vueGetScoresByAgent(agent: AgentName) {
|
||||
const agentScores = (this as any).vueAgentScores
|
||||
// 数据结构:agentScores[agent][aspect],直接返回agent的所有维度评分
|
||||
return agentScores[agent] || {}
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -3,29 +3,14 @@ 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, ref, nextTick, watch, onMounted } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { AnchorLocations } from '@jsplumb/browser-ui'
|
||||
import { Loading } from '@element-plus/icons-vue'
|
||||
import MultiLineTooltip from '@/components/MultiLineTooltip/index.vue'
|
||||
import Bg from './Bg.vue'
|
||||
import BranchButton from './components/BranchButton.vue'
|
||||
import Notification from '@/components/Notification/Notification.vue'
|
||||
import { useNotification } from '@/composables/useNotification'
|
||||
|
||||
// 判断计划是否就绪
|
||||
const planReady = computed(() => {
|
||||
return agentsStore.agentRawPlan.data !== undefined
|
||||
})
|
||||
|
||||
const openPlanModification = () => {
|
||||
agentsStore.openPlanModification()
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
(el: 'resetAgentRepoLine'): void
|
||||
(el: 'setCurrentTask', task: IRawStepTask): void
|
||||
(el: 'add-output', outputName: string): void
|
||||
(el: 'click-branch'): void
|
||||
}>()
|
||||
|
||||
const jsplumb = new Jsplumb('task-syllabus')
|
||||
@@ -40,152 +25,14 @@ const collaborationProcess = computed(() => {
|
||||
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
|
||||
})
|
||||
|
||||
// 检测是否正在填充详情(有步骤但没有 AgentSelection)
|
||||
const isFillingDetails = computed(() => {
|
||||
const process = agentsStore.agentRawPlan.data?.['Collaboration Process'] || []
|
||||
return (
|
||||
process.length > 0 &&
|
||||
process.some(step => !step.AgentSelection || step.AgentSelection.length === 0)
|
||||
)
|
||||
})
|
||||
|
||||
// 计算填充进度
|
||||
const completedSteps = computed(() => {
|
||||
const process = agentsStore.agentRawPlan.data?.['Collaboration Process'] || []
|
||||
return process.filter(step => step.AgentSelection && step.AgentSelection.length > 0).length
|
||||
})
|
||||
|
||||
const totalSteps = computed(() => {
|
||||
return agentsStore.agentRawPlan.data?.['Collaboration Process']?.length || 0
|
||||
})
|
||||
|
||||
// Notification system
|
||||
const { notifications, progress: showProgress, updateProgressDetail, removeNotification } = useNotification()
|
||||
const fillingProgressNotificationId = ref<string | null>(null)
|
||||
|
||||
// 监听填充进度,显示通知
|
||||
watch(
|
||||
[isFillingDetails, completedSteps, totalSteps, () => agentsStore.hasStoppedFilling],
|
||||
([filling, completed, total, hasStopped]) => {
|
||||
// 如果用户已停止,关闭进度通知
|
||||
if (hasStopped && fillingProgressNotificationId.value) {
|
||||
removeNotification(fillingProgressNotificationId.value)
|
||||
fillingProgressNotificationId.value = null
|
||||
return
|
||||
}
|
||||
|
||||
if (filling && total > 0) {
|
||||
if (!fillingProgressNotificationId.value) {
|
||||
// 创建进度通知
|
||||
fillingProgressNotificationId.value = showProgress(
|
||||
'生成协作流程',
|
||||
completed,
|
||||
total
|
||||
)
|
||||
updateProgressDetail(
|
||||
fillingProgressNotificationId.value,
|
||||
`${completed}/${total}`,
|
||||
'正在分配智能体...',
|
||||
completed,
|
||||
total
|
||||
)
|
||||
} else {
|
||||
// 更新进度通知
|
||||
updateProgressDetail(
|
||||
fillingProgressNotificationId.value,
|
||||
`${completed}/${total}`,
|
||||
'正在分配智能体...',
|
||||
completed,
|
||||
total
|
||||
)
|
||||
}
|
||||
} else if (fillingProgressNotificationId.value && !filling) {
|
||||
// 填充完成,关闭进度通知
|
||||
removeNotification(fillingProgressNotificationId.value)
|
||||
fillingProgressNotificationId.value = null
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 编辑状态管理
|
||||
const editingTaskId = ref<string | null>(null)
|
||||
const editingContent = ref('')
|
||||
|
||||
// 添加新产物状态管理
|
||||
const isAddingOutput = ref(false)
|
||||
const newOutputInputRef = ref<HTMLElement>()
|
||||
const showAddOutputForm = ref(false)
|
||||
const newOutputName = ref('')
|
||||
|
||||
// 处理加号点击
|
||||
const handleAddOutputClick = () => {
|
||||
isAddingOutput.value = true
|
||||
newOutputName.value = ''
|
||||
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
if (newOutputInputRef.value) {
|
||||
newOutputInputRef.value?.focus()
|
||||
}
|
||||
jsplumb.instance.repaintEverything()
|
||||
}, 50)
|
||||
})
|
||||
}
|
||||
|
||||
// 保存新产物
|
||||
const saveNewOutput = () => {
|
||||
if (newOutputName.value.trim()) {
|
||||
const outputName = newOutputName.value.trim()
|
||||
const success = agentsStore.addNewOutput(outputName)
|
||||
if (success) {
|
||||
emit('add-output', outputName)
|
||||
isAddingOutput.value = false
|
||||
newOutputName.value = ''
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
jsplumb.instance.repaintEverything()
|
||||
}, 50)
|
||||
})
|
||||
console.log('添加新产物成功', outputName)
|
||||
} else {
|
||||
// 退出编辑状态
|
||||
isAddingOutput.value = false
|
||||
newOutputName.value = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 取消添加产物
|
||||
const cancelAddOutput = () => {
|
||||
isAddingOutput.value = false
|
||||
newOutputName.value = ''
|
||||
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
jsplumb.instance.repaintEverything()
|
||||
}, 50)
|
||||
})
|
||||
}
|
||||
|
||||
// 处理新产物的键盘事件
|
||||
const handleNewOutputKeydown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
saveNewOutput()
|
||||
} else if (event.key === 'Escape') {
|
||||
cancelAddOutput()
|
||||
}
|
||||
}
|
||||
|
||||
// 新产物输入框失去焦点处理
|
||||
const handleNewOutputBlur = () => {
|
||||
setTimeout(() => {
|
||||
if (newOutputName.value.trim() === '') {
|
||||
cancelAddOutput()
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
// 开始编辑
|
||||
const startEditing = (task: IRawStepTask) => {
|
||||
if (!task.Id) {
|
||||
@@ -201,23 +48,7 @@ const saveEditing = () => {
|
||||
if (editingTaskId.value && editingContent.value.trim()) {
|
||||
const taskToUpdate = collaborationProcess.value.find(item => item.Id === editingTaskId.value)
|
||||
if (taskToUpdate) {
|
||||
// 保存旧值用于比较
|
||||
const oldValue = taskToUpdate.TaskContent
|
||||
const newValue = editingContent.value.trim()
|
||||
|
||||
// 只有内容真正变化时才更新和记录修改
|
||||
if (newValue !== oldValue) {
|
||||
taskToUpdate.TaskContent = newValue
|
||||
|
||||
// 记录修改过的步骤索引到 store(用于重新执行)
|
||||
const stepIndex = collaborationProcess.value.findIndex(item => item.Id === editingTaskId.value)
|
||||
if (stepIndex >= 0) {
|
||||
agentsStore.addModifiedStep(stepIndex)
|
||||
console.log(`📝 步骤 ${stepIndex + 1} (${taskToUpdate.StepName}) 的 TaskContent 已被修改,将从该步骤重新执行`)
|
||||
}
|
||||
} else {
|
||||
console.log(`ℹ️ 步骤 ${taskToUpdate.StepName} 的 TaskContent 未发生变化,不记录修改`)
|
||||
}
|
||||
taskToUpdate.TaskContent = editingContent.value.trim()
|
||||
}
|
||||
}
|
||||
editingTaskId.value = null
|
||||
@@ -240,6 +71,44 @@ const handleKeydown = (event: KeyboardEvent) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 显示添加产物表单
|
||||
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[] = [
|
||||
@@ -288,51 +157,6 @@ function clear() {
|
||||
jsplumb.reset()
|
||||
}
|
||||
|
||||
// 封装连线重绘方法
|
||||
const redrawConnections = () => {
|
||||
// 等待 DOM 更新完成
|
||||
nextTick(() => {
|
||||
// 清除旧连线
|
||||
jsplumb.reset()
|
||||
|
||||
// 等待 DOM 稳定后重新绘制
|
||||
setTimeout(() => {
|
||||
const arr: ConnectArg[] = []
|
||||
const currentTaskId = agentsStore.currentTask?.Id
|
||||
|
||||
// 重新绘制所有连线
|
||||
collaborationProcess.value.forEach(item => {
|
||||
arr.push(...handleCurrentTask(item, item.Id !== currentTaskId))
|
||||
})
|
||||
|
||||
jsplumb.connects(arr)
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
// 监听 collaborationProcess 变化,自动重绘连线
|
||||
watch(
|
||||
() => collaborationProcess,
|
||||
() => {
|
||||
redrawConnections()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// 组件挂载后初始化连线
|
||||
onMounted(() => {
|
||||
// 初始化时绘制连线
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
const arr: ConnectArg[] = []
|
||||
collaborationProcess.value.forEach(item => {
|
||||
arr.push(...handleCurrentTask(item, true))
|
||||
})
|
||||
jsplumb.connects(arr)
|
||||
}, 100)
|
||||
})
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
changeTask,
|
||||
clear
|
||||
@@ -341,12 +165,6 @@ defineExpose({
|
||||
|
||||
<template>
|
||||
<div class="h-full flex flex-col">
|
||||
<!-- Notification 通知系统 -->
|
||||
<Notification
|
||||
:notifications="notifications"
|
||||
@close="(id) => removeNotification(id)"
|
||||
/>
|
||||
|
||||
<div class="text-[18px] font-bold mb-[18px] text-[var(--color-text-title-header)]">
|
||||
任务大纲
|
||||
</div>
|
||||
@@ -361,7 +179,7 @@ defineExpose({
|
||||
class="w-full relative min-h-full"
|
||||
id="task-syllabus"
|
||||
>
|
||||
<Bg :is-adding="isAddingOutput" @start-add-output="handleAddOutputClick" />
|
||||
<Bg />
|
||||
|
||||
<div class="w-full flex items-center gap-[14%] mb-[35px]">
|
||||
<div class="flex-1 flex justify-center">
|
||||
@@ -380,51 +198,11 @@ defineExpose({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 添加新产物卡片 -->
|
||||
<div
|
||||
v-if="isAddingOutput"
|
||||
class="card-it w-full flex items-center gap-[14%] bg-[var(--color-card-bg)] add-output-form mb-[100px]"
|
||||
>
|
||||
<!-- 左侧空白的流程卡片占位 -->
|
||||
<div class="w-[43%] relative z-99" style="height: 20px"></div>
|
||||
|
||||
<!-- 右侧可编辑的产物卡片 -->
|
||||
<el-card
|
||||
class="w-[43%] relative task-syllabus-output-object-card border-dashed border-2 border-[var(--color-primary)]"
|
||||
>
|
||||
<div class="h-full flex items-center justify-center">
|
||||
<!-- 输入框 -->
|
||||
<el-input
|
||||
ref="newOutputInputRef"
|
||||
v-model="newOutputName"
|
||||
placeholder="Enter保存,ESC取消"
|
||||
@keydown="handleNewOutputKeydown"
|
||||
@blur="handleNewOutputBlur"
|
||||
size="large"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 显示临时产物卡片 -->
|
||||
<div
|
||||
v-for="output in agentsStore.additionalOutputs"
|
||||
:key="output"
|
||||
class="card-it w-full flex items-center gap-[14%] bg-[var(--color-card-bg)] mb-[100px]"
|
||||
>
|
||||
<!-- 左侧空白的流程卡片占位 -->
|
||||
<div class="w-[43%] relative z-99" style="height: 100px"></div>
|
||||
|
||||
<!-- 右侧产物卡片 -->
|
||||
<el-card class="w-[43%] relative task-syllabus-output-object-card" :shadow="true">
|
||||
<div class="text-[18px] font-bold text-center">{{ output }}</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<div
|
||||
v-for="item in collaborationProcess"
|
||||
:key="item.Id"
|
||||
class="card-it w-full flex items-center gap-[14%] bg-[var(--color-card-bg)] mb-[100px]"
|
||||
class="card-it w-full flex items-center gap-[14%] bg-[var(--color-card-bg)]"
|
||||
>
|
||||
<!-- 流程卡片 -->
|
||||
<el-card
|
||||
@@ -437,7 +215,7 @@ defineExpose({
|
||||
<MultiLineTooltip placement="right" :text="item.StepName" :lines="2">
|
||||
<div class="text-[18px] font-bold text-center">{{ item.StepName }}</div>
|
||||
</MultiLineTooltip>
|
||||
<div class="h-[1px] w-full bg-[var(--color-border-separate)] my-[8px]"></div>
|
||||
<div class="h-[1px] w-full bg-[#d6d6d6] my-[8px]"></div>
|
||||
|
||||
<!-- 任务内容区域 - 支持双击编辑 -->
|
||||
<div v-if="editingTaskId === item.Id" class="w-full">
|
||||
@@ -451,23 +229,11 @@ defineExpose({
|
||||
class="task-content-editor"
|
||||
size="small"
|
||||
/>
|
||||
<div class="flex justify-end">
|
||||
<svg-icon
|
||||
icon-class="Check"
|
||||
size="20px"
|
||||
color="#328621"
|
||||
class="cursor-pointer mr-4"
|
||||
@click="saveEditing"
|
||||
title="保存"
|
||||
/>
|
||||
<svg-icon
|
||||
icon-class="Cancel"
|
||||
size="20px"
|
||||
color="#8e0707"
|
||||
class="cursor-pointer mr-1"
|
||||
@click="cancelEditing"
|
||||
title="取消"
|
||||
/>
|
||||
<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>
|
||||
</div>
|
||||
@@ -479,30 +245,15 @@ defineExpose({
|
||||
</MultiLineTooltip>
|
||||
</div>
|
||||
|
||||
<div class="h-[1px] w-full bg-[var(--color-border-separate)] my-[8px]"></div>
|
||||
<div class="h-[1px] w-full bg-[#d6d6d6] my-[8px]"></div>
|
||||
<div
|
||||
class="flex items-center gap-2 flex-wrap relative w-full"
|
||||
:class="!item.AgentSelection || item.AgentSelection.length === 0 ? 'min-h-[40px]' : 'overflow-y-auto max-h-[72px]'"
|
||||
class="flex items-center gap-2 overflow-y-auto flex-wrap relative w-full max-h-[72px]"
|
||||
>
|
||||
<!-- 连接到智能体库的连接点 -->
|
||||
<div
|
||||
class="absolute left-[-10px] top-1/2 transform -translate-y-1/2"
|
||||
:id="`task-syllabus-flow-agents-${item.Id}`"
|
||||
></div>
|
||||
|
||||
<!-- 未填充智能体时显示Loading -->
|
||||
<div
|
||||
v-if="(!item.AgentSelection || item.AgentSelection.length === 0) && !agentsStore.hasStoppedFilling"
|
||||
class="flex items-center gap-2 text-[var(--color-text-secondary)] text-[14px]"
|
||||
>
|
||||
<el-icon class="is-loading" :size="20">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
<span>正在分配智能体...</span>
|
||||
</div>
|
||||
|
||||
<!-- 已填充智能体时显示智能体列表 -->
|
||||
<template v-else>
|
||||
<el-tooltip
|
||||
v-for="agentSelection in item.AgentSelection"
|
||||
:key="agentSelection"
|
||||
@@ -514,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>
|
||||
@@ -531,7 +280,6 @@ defineExpose({
|
||||
/>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</div>
|
||||
</el-card>
|
||||
<!-- 产物卡片 -->
|
||||
@@ -544,9 +292,74 @@ defineExpose({
|
||||
<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>
|
||||
<BranchButton v-if="planReady" @click="openPlanModification" />
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
@@ -555,6 +368,8 @@ defineExpose({
|
||||
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);
|
||||
@@ -575,6 +390,7 @@ defineExpose({
|
||||
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);
|
||||
@@ -633,27 +449,6 @@ defineExpose({
|
||||
|
||||
.add-output-form {
|
||||
animation: slideDown 0.3s ease-out;
|
||||
:deep(.el-card__body) {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
:deep(.el-input__wrapper) {
|
||||
border: 1px solid var(--color-text);
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
|
||||
&.is-focus {
|
||||
border-color: var(--color-text);
|
||||
box-shadow: 0 0 0 1px var(--color-primary-light);
|
||||
}
|
||||
:deep(.el-input__inner) {
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
@@ -667,6 +462,11 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
|
||||
// 添加产物表单样式
|
||||
:deep(.el-card__body) {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
// 输入框样式
|
||||
:deep(.el-input__wrapper) {
|
||||
background: transparent;
|
||||
|
||||
@@ -71,11 +71,6 @@ function clear() {
|
||||
taskResultJsplumb.repaintEverything()
|
||||
}
|
||||
|
||||
const additionalOutputs = ref<string[]>([])
|
||||
const handleAddOutput = (outputName: string) => {
|
||||
additionalOutputs.value.unshift(outputName)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
changeTask,
|
||||
resetAgentRepoLine,
|
||||
@@ -99,7 +94,6 @@ defineExpose({
|
||||
ref="taskSyllabusRef"
|
||||
@resetAgentRepoLine="resetAgentRepoLine"
|
||||
@set-current-task="handleTaskSyllabusCurrentTask"
|
||||
@add-output="handleAddOutput"
|
||||
/>
|
||||
</div>
|
||||
<!-- 执行结果 -->
|
||||
@@ -108,7 +102,6 @@ defineExpose({
|
||||
ref="taskResultRef"
|
||||
@refresh-line="taskResultJsplumb.repaintEverything"
|
||||
@set-current-task="handleTaskResultCurrentTask"
|
||||
:additional-outputs="additionalOutputs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,13 +2,9 @@
|
||||
import Header from './components/Header.vue'
|
||||
import Main from './components/Main/index.vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import FloatWindow from './components/Main/TaskTemplate/TaskSyllabus/components/FloatWindow.vue'
|
||||
import PlanModification from './components/Main/TaskTemplate/TaskSyllabus/Branch/PlanModification.vue'
|
||||
import { useAgentsStore } from '@/stores/modules/agents'
|
||||
import PlanTask from './components/Main/TaskTemplate/TaskProcess/components/PlanTask.vue'
|
||||
import AgentAllocation from './components/Main/TaskTemplate/TaskSyllabus/components/AgentAllocation.vue'
|
||||
|
||||
const isDarkMode = ref(false)
|
||||
const agentsStore = useAgentsStore()
|
||||
|
||||
// 初始化主题
|
||||
const initTheme = () => {
|
||||
const savedTheme = localStorage.getItem('theme')
|
||||
@@ -84,29 +80,5 @@ onMounted(() => {
|
||||
|
||||
<Header />
|
||||
<Main />
|
||||
|
||||
<FloatWindow
|
||||
v-if="agentsStore.planModificationWindow"
|
||||
title="任务大纲探索"
|
||||
@close="agentsStore.closePlanModification"
|
||||
>
|
||||
<PlanModification />
|
||||
</FloatWindow>
|
||||
|
||||
<FloatWindow
|
||||
v-if="agentsStore.planTaskWindow"
|
||||
title="任务过程探索"
|
||||
@close="agentsStore.closePlanTask"
|
||||
>
|
||||
<PlanTask />
|
||||
</FloatWindow>
|
||||
|
||||
<FloatWindow
|
||||
v-if="agentsStore.agentAllocationDialog"
|
||||
title="智能体探索"
|
||||
@close="agentsStore.closeAgentAllocationDialog"
|
||||
>
|
||||
<AgentAllocation />
|
||||
</FloatWindow>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -7,38 +7,14 @@ import './styles/tailwindcss.css'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
import 'virtual:svg-icons-register'
|
||||
import { initService } from '@/utils/request.ts'
|
||||
import websocket from '@/utils/websocket'
|
||||
import { setupStore, useConfigStore } from '@/stores'
|
||||
import { setupDirective } from '@/ directive'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
|
||||
async function init() {
|
||||
const app = createApp(App)
|
||||
setupStore(app)
|
||||
initService()
|
||||
const configStore = useConfigStore()
|
||||
await configStore.initConfig()
|
||||
const app = createApp(App)
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component)
|
||||
}
|
||||
setupStore(app)
|
||||
setupDirective(app)
|
||||
initService()
|
||||
|
||||
// 初始化WebSocket连接
|
||||
try {
|
||||
// WebSocket需要直接连接到后端,不能通过代理
|
||||
const apiBaseUrl = configStore.config.apiBaseUrl || `${import.meta.env.BASE_URL || '/'}api`
|
||||
// 移除 /api 后缀,如果是相对路径则构造完整URL
|
||||
let wsUrl = apiBaseUrl.replace(/\/api$/, '')
|
||||
// 如果是相对路径(以/开头),使用当前host
|
||||
if (wsUrl.startsWith('/')) {
|
||||
wsUrl = `${window.location.protocol}//${window.location.host}${wsUrl}`
|
||||
}
|
||||
console.log('🔌 Connecting to WebSocket at:', wsUrl)
|
||||
await websocket.connect(wsUrl)
|
||||
console.log('✅ WebSocket initialized successfully')
|
||||
} catch (error) {
|
||||
console.warn('⚠️ WebSocket connection failed, will use REST API fallback:', error)
|
||||
}
|
||||
|
||||
document.title = configStore.config.centerTitle
|
||||
app.use(router)
|
||||
app.mount('#app')
|
||||
|
||||
@@ -10,4 +10,3 @@ export function setupStore(app: App<Element>) {
|
||||
|
||||
export * from './modules/agents.ts'
|
||||
export * from './modules/config.ts'
|
||||
export * from './modules/selection.ts'
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { getAgentMapIcon } from '@/layout/components/config'
|
||||
|
||||
import { store } from '../index'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import type { IExecuteRawResponse } from '@/api'
|
||||
import { useConfigStore } from '@/stores/modules/config.ts'
|
||||
|
||||
export interface Agent {
|
||||
Name: string
|
||||
Profile: string
|
||||
Icon: string
|
||||
Classification: string
|
||||
apiUrl?: string
|
||||
apiKey?: string
|
||||
apiModel?: string
|
||||
}
|
||||
|
||||
type HslColorVector = [number, number, number]
|
||||
@@ -45,51 +43,10 @@ export interface IRawStepTask {
|
||||
InputObject_List?: string[]
|
||||
OutputObject?: string
|
||||
AgentSelection?: string[]
|
||||
Collaboration_Brief_frontEnd: IRichText
|
||||
Collaboration_Brief_FrontEnd: IRichText
|
||||
TaskProcess: TaskProcess[]
|
||||
}
|
||||
|
||||
export interface IApiAgentAction {
|
||||
id: string
|
||||
type: string
|
||||
agent: string
|
||||
description: string
|
||||
inputs: string[]
|
||||
}
|
||||
|
||||
export interface IApiStepTask {
|
||||
name: string
|
||||
content: string
|
||||
inputs: string[]
|
||||
output: string
|
||||
agents: string[]
|
||||
brief: IRichText
|
||||
process: IApiAgentAction[]
|
||||
}
|
||||
|
||||
// 智能体评分数据类型
|
||||
export interface IScoreItem {
|
||||
score: number
|
||||
reason: string
|
||||
}
|
||||
|
||||
// 单个任务的评分数据结构
|
||||
export interface ITaskScoreData {
|
||||
aspectList: string[] // 维度列表
|
||||
agentScores: Record<string, Record<string, IScoreItem>> // agent -> 维度 -> 评分
|
||||
timestamp: number // 缓存时间戳
|
||||
}
|
||||
|
||||
export interface IAgentSelectModifyAddRequest {
|
||||
aspectList: string[] // 维度列表
|
||||
agentScores: Record<string, Record<string, IScoreItem>> // agent -> 维度 -> 评分(与后端返回格式一致)
|
||||
}
|
||||
|
||||
export interface IAgentSelectModifyInitRequest {
|
||||
goal: string
|
||||
stepTask: IApiStepTask
|
||||
}
|
||||
|
||||
export interface IRawPlanResponse {
|
||||
'Initial Input Object'?: string[] | string
|
||||
'General Goal'?: string
|
||||
@@ -110,185 +67,7 @@ export const useAgentsStore = defineStore('agents', () => {
|
||||
const agents = useStorage<Agent[]>(`${storageKey}-repository`, [])
|
||||
function setAgents(agent: Agent[]) {
|
||||
agents.value = agent
|
||||
}
|
||||
|
||||
// 🆕 新的按任务ID存储的评分数据
|
||||
const taskScoreDataMap = useStorage<Record<string, ITaskScoreData>>(
|
||||
`${storageKey}-task-score-data`,
|
||||
{},
|
||||
)
|
||||
|
||||
// 🆕 预加载状态追踪(用于避免重复预加载)
|
||||
const preloadingTaskIds = ref<Set<string>>(new Set())
|
||||
|
||||
// 🆕 获取指定任务的评分数据(按任务ID获取)
|
||||
function getTaskScoreData(taskId: string): IAgentSelectModifyAddRequest | null {
|
||||
if (!taskId) {
|
||||
console.warn('⚠️ getTaskScoreData: taskId 为空')
|
||||
return null
|
||||
}
|
||||
|
||||
const taskScoreData = taskScoreDataMap.value[taskId]
|
||||
if (taskScoreData) {
|
||||
console.log(`✅ 使用任务 ${taskId} 的缓存评分数据,维度数: ${taskScoreData.aspectList.length}`)
|
||||
return {
|
||||
aspectList: taskScoreData.aspectList,
|
||||
agentScores: taskScoreData.agentScores,
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`ℹ️ 任务 ${taskId} 没有缓存的评分数据`)
|
||||
return null
|
||||
}
|
||||
|
||||
// 🆕 设置指定任务的评分数据
|
||||
function setTaskScoreData(taskId: string, data: IAgentSelectModifyAddRequest) {
|
||||
if (!taskId) {
|
||||
console.warn('⚠️ setTaskScoreData: taskId 为空,无法存储')
|
||||
return
|
||||
}
|
||||
taskScoreDataMap.value = {
|
||||
...taskScoreDataMap.value,
|
||||
[taskId]: {
|
||||
...data,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
}
|
||||
console.log(`✅ 任务 ${taskId} 的评分数据已存储`, {
|
||||
aspectCount: data.aspectList.length,
|
||||
agentCount: Object.keys(data.agentScores).length,
|
||||
})
|
||||
}
|
||||
|
||||
// 🆕 检查指定任务是否有评分数据
|
||||
function hasTaskScoreData(taskId: string): boolean {
|
||||
if (!taskId) return false
|
||||
return !!taskScoreDataMap.value[taskId]?.aspectList?.length
|
||||
}
|
||||
|
||||
// 🆕 获取所有已预加载的任务ID列表
|
||||
function getPreloadedTaskIds(): string[] {
|
||||
return Object.keys(taskScoreDataMap.value)
|
||||
}
|
||||
|
||||
// 🆕 清除指定任务的评分数据
|
||||
function clearTaskScoreData(taskId: string) {
|
||||
if (taskId && taskScoreDataMap.value[taskId]) {
|
||||
const newMap = { ...taskScoreDataMap.value }
|
||||
delete newMap[taskId]
|
||||
taskScoreDataMap.value = newMap
|
||||
console.log(`✅ 任务 ${taskId} 的评分数据已清除`)
|
||||
}
|
||||
}
|
||||
|
||||
// 🆕 清除所有任务的评分数据
|
||||
function clearAllTaskScoreData() {
|
||||
taskScoreDataMap.value = {}
|
||||
preloadingTaskIds.value.clear()
|
||||
console.log('✅ 所有任务的评分数据已清除')
|
||||
}
|
||||
|
||||
// 🆕 标记任务正在预加载中
|
||||
function markTaskPreloading(taskId: string) {
|
||||
preloadingTaskIds.value.add(taskId)
|
||||
}
|
||||
|
||||
// 🆕 标记任务预加载完成
|
||||
function markTaskPreloadComplete(taskId: string) {
|
||||
preloadingTaskIds.value.delete(taskId)
|
||||
}
|
||||
|
||||
// 🆕 检查任务是否正在预加载
|
||||
function isTaskPreloading(taskId: string): boolean {
|
||||
return preloadingTaskIds.value.has(taskId)
|
||||
}
|
||||
|
||||
// 兼容旧版本:全局评分数据存储(单任务模式,已废弃,保留用于兼容)
|
||||
const IAgentSelectModifyAddRequest = useStorage<IAgentSelectModifyAddRequest | null>(
|
||||
`${storageKey}-score-data`,
|
||||
null,
|
||||
)
|
||||
|
||||
// 设置智能体评分数据(兼容旧版本)
|
||||
function setAgentScoreData(data: IAgentSelectModifyAddRequest) {
|
||||
IAgentSelectModifyAddRequest.value = data
|
||||
}
|
||||
|
||||
// 添加新维度的评分数据(追加模式)
|
||||
function addAgentScoreAspect(aspectName: string, scores: Record<string, IScoreItem>) {
|
||||
if (!IAgentSelectModifyAddRequest.value) {
|
||||
IAgentSelectModifyAddRequest.value = {
|
||||
aspectList: [],
|
||||
agentScores: {},
|
||||
}
|
||||
}
|
||||
|
||||
// 检查维度是否已存在
|
||||
if (!IAgentSelectModifyAddRequest.value.aspectList.includes(aspectName)) {
|
||||
IAgentSelectModifyAddRequest.value.aspectList.push(aspectName)
|
||||
}
|
||||
|
||||
// 添加该维度的评分数据
|
||||
for (const [agentName, scoreItem] of Object.entries(scores)) {
|
||||
if (!IAgentSelectModifyAddRequest.value.agentScores[agentName]) {
|
||||
IAgentSelectModifyAddRequest.value.agentScores[agentName] = {}
|
||||
}
|
||||
IAgentSelectModifyAddRequest.value.agentScores[agentName][aspectName] = scoreItem
|
||||
}
|
||||
}
|
||||
|
||||
// 清除智能体评分数据(兼容旧版本)
|
||||
function clearAgentScoreData() {
|
||||
IAgentSelectModifyAddRequest.value = null
|
||||
}
|
||||
|
||||
// 智能体分配确认的组合列表存储 - 按任务ID分别存储(不持久化到localStorage)
|
||||
const confirmedAgentGroupsMap = ref<Map<string, string[][]>>(new Map())
|
||||
|
||||
// 获取指定任务的确认的agent组合列表
|
||||
function getConfirmedAgentGroups(taskId: string): string[][] {
|
||||
return confirmedAgentGroupsMap.value.get(taskId) || []
|
||||
}
|
||||
|
||||
// 添加确认的agent组合到指定任务
|
||||
function addConfirmedAgentGroup(taskId: string, group: string[]) {
|
||||
const groups = confirmedAgentGroupsMap.value.get(taskId) || []
|
||||
groups.push(group)
|
||||
confirmedAgentGroupsMap.value.set(taskId, groups)
|
||||
}
|
||||
|
||||
// 清除指定任务的确认的agent组合列表
|
||||
function clearConfirmedAgentGroups(taskId: string) {
|
||||
confirmedAgentGroupsMap.value.delete(taskId)
|
||||
}
|
||||
|
||||
// 清除所有任务的确认的agent组合列表
|
||||
function clearAllConfirmedAgentGroups() {
|
||||
confirmedAgentGroupsMap.value.clear()
|
||||
}
|
||||
|
||||
const planModificationWindow = ref(false)
|
||||
const planTaskWindow = ref(false)
|
||||
const agentAllocationDialog = ref(false)
|
||||
|
||||
function openPlanModification() {
|
||||
planModificationWindow.value = true
|
||||
}
|
||||
function closePlanModification() {
|
||||
planModificationWindow.value = false
|
||||
}
|
||||
function openPlanTask() {
|
||||
planTaskWindow.value = true
|
||||
}
|
||||
function closePlanTask() {
|
||||
planTaskWindow.value = false
|
||||
}
|
||||
function openAgentAllocationDialog() {
|
||||
agentAllocationDialog.value = true
|
||||
}
|
||||
function closeAgentAllocationDialog() {
|
||||
agentAllocationDialog.value = false
|
||||
}
|
||||
};
|
||||
|
||||
// 任务搜索的内容
|
||||
const searchValue = useStorage<string>(`${storageKey}-search-value`, '')
|
||||
@@ -296,10 +75,7 @@ export const useAgentsStore = defineStore('agents', () => {
|
||||
searchValue.value = value
|
||||
}
|
||||
|
||||
const storageVersionIdentifier = useStorage<string>(
|
||||
`${storageKey}-storage-version-identifier`,
|
||||
'',
|
||||
)
|
||||
const storageVersionIdentifier = useStorage<string>(`${storageKey}-storage-version-identifier`, '')
|
||||
// 监听 configStore.config.agentRepository.storageVersionIdentifier 改变
|
||||
watch(
|
||||
() => configStore.config.agentRepository.storageVersionIdentifier,
|
||||
@@ -312,132 +88,13 @@ export const useAgentsStore = defineStore('agents', () => {
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// 当前的展示的任务流程
|
||||
const currentTask = ref<IRawStepTask>()
|
||||
|
||||
// 记录用户修改过的步骤索引(用于重新执行)
|
||||
const modifiedSteps = ref<Set<number>>(new Set())
|
||||
function addModifiedStep(stepIndex: number) {
|
||||
modifiedSteps.value.add(stepIndex)
|
||||
}
|
||||
function clearModifiedSteps() {
|
||||
modifiedSteps.value.clear()
|
||||
}
|
||||
function hasModifiedSteps() {
|
||||
return modifiedSteps.value.size > 0
|
||||
}
|
||||
|
||||
function setCurrentTask(task: IRawStepTask) {
|
||||
const existingTask = currentTask.value
|
||||
|
||||
// 🆕 智能判断:如果是同一个任务,保留用户修改过的数据(AgentSelection、TaskProcess、Collaboration_Brief_frontEnd)
|
||||
if (existingTask && existingTask.Id === task.Id) {
|
||||
currentTask.value = {
|
||||
...task,
|
||||
AgentSelection: existingTask.AgentSelection || task.AgentSelection,
|
||||
TaskProcess: existingTask.TaskProcess || task.TaskProcess,
|
||||
Collaboration_Brief_frontEnd:
|
||||
existingTask.Collaboration_Brief_frontEnd || task.Collaboration_Brief_frontEnd,
|
||||
}
|
||||
|
||||
// 🆕 同步更新主流程数据(让执行结果卡片和任务大纲都能联动)
|
||||
syncCurrentTaskToMainProcess(currentTask.value)
|
||||
|
||||
console.log('🔄 setCurrentTask: 保留同一任务的分支数据', {
|
||||
taskId: task.Id,
|
||||
taskName: task.StepName,
|
||||
preservedAgentSelection: currentTask.value.AgentSelection,
|
||||
preservedTaskProcessLength: currentTask.value.TaskProcess?.length || 0,
|
||||
})
|
||||
} else {
|
||||
// 不同任务:使用新任务数据
|
||||
currentTask.value = task
|
||||
console.log('🔄 setCurrentTask: 切换到不同任务', {
|
||||
taskId: task.Id,
|
||||
taskName: task.StepName,
|
||||
agentSelection: task.AgentSelection,
|
||||
taskProcessLength: task.TaskProcess?.length || 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 🆕 同步 currentTask 到主流程数据
|
||||
function syncCurrentTaskToMainProcess(updatedTask: IRawStepTask) {
|
||||
const collaborationProcess = agentRawPlan.value.data?.['Collaboration Process']
|
||||
if (!collaborationProcess) {
|
||||
console.warn('⚠️ syncCurrentTaskToMainProcess: collaborationProcess 不存在')
|
||||
return
|
||||
}
|
||||
|
||||
const taskIndex = collaborationProcess.findIndex((t) => t.Id === updatedTask.Id)
|
||||
if (taskIndex === -1) {
|
||||
console.warn('⚠️ syncCurrentTaskToMainProcess: 未找到对应任务', updatedTask.Id)
|
||||
return
|
||||
}
|
||||
|
||||
// 使用 splice 确保 Vue 响应式更新
|
||||
collaborationProcess.splice(taskIndex, 1, {
|
||||
...collaborationProcess[taskIndex]!,
|
||||
AgentSelection: updatedTask.AgentSelection || [],
|
||||
TaskProcess: updatedTask.TaskProcess || [],
|
||||
Collaboration_Brief_frontEnd: updatedTask.Collaboration_Brief_frontEnd,
|
||||
})
|
||||
|
||||
console.log('✅ syncCurrentTaskToMainProcess: 已同步到主流程', {
|
||||
taskName: collaborationProcess[taskIndex]!.StepName,
|
||||
agentSelection: collaborationProcess[taskIndex]!.AgentSelection,
|
||||
taskProcessLength: collaborationProcess[taskIndex]!.TaskProcess?.length || 0,
|
||||
})
|
||||
}
|
||||
|
||||
// 🆕 设置当前任务的 TaskProcess 数据(用于切换分支时更新显示)
|
||||
function setCurrentTaskProcess(taskProcess: TaskProcess[]) {
|
||||
if (currentTask.value) {
|
||||
// 创建新的对象引用,确保响应式更新
|
||||
currentTask.value = {
|
||||
...currentTask.value,
|
||||
TaskProcess: JSON.parse(JSON.stringify(taskProcess)),
|
||||
}
|
||||
|
||||
// 🆕 同步到主流程数据(让执行结果卡片和任务大纲都能联动)
|
||||
syncCurrentTaskToMainProcess(currentTask.value)
|
||||
|
||||
console.log('✅ 已更新 currentTask.TaskProcess 并同步到主流程:', {
|
||||
taskId: currentTask.value.Id,
|
||||
agent数量: taskProcess.length,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 🆕 更新当前任务的 AgentSelection 和 TaskProcess(用于在 AgentAllocation 中切换 agent 组合)
|
||||
// 此函数专门用于强制更新,不会被 setCurrentTask 的"智能保留"逻辑阻止
|
||||
function updateCurrentAgentSelection(
|
||||
agentSelection: string[],
|
||||
taskProcess: TaskProcess[],
|
||||
collaborationBrief: any,
|
||||
) {
|
||||
if (currentTask.value) {
|
||||
// 直接更新 currentTask,不保留旧数据
|
||||
currentTask.value = {
|
||||
...currentTask.value,
|
||||
AgentSelection: agentSelection,
|
||||
TaskProcess: taskProcess,
|
||||
Collaboration_Brief_frontEnd: collaborationBrief,
|
||||
}
|
||||
|
||||
// 同步到主流程数据
|
||||
syncCurrentTaskToMainProcess(currentTask.value)
|
||||
|
||||
console.log('✅ 已强制更新 currentTask 的 AgentSelection 并同步到主流程:', {
|
||||
taskId: currentTask.value.Id,
|
||||
taskName: currentTask.value.StepName,
|
||||
newAgentSelection: agentSelection,
|
||||
taskProcessLength: taskProcess.length,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const agentRawPlan = ref<{ data?: IRawPlanResponse; loading?: boolean }>({ loading: false })
|
||||
@@ -446,8 +103,7 @@ export const useAgentsStore = defineStore('agents', () => {
|
||||
if (plan.data) {
|
||||
plan.data['Collaboration Process'] = plan.data['Collaboration Process']?.map((item) => ({
|
||||
...item,
|
||||
// ✅ 只在任务没有 Id 时才生成新的 ID,保持已存在任务的 Id 不变
|
||||
Id: item.Id || uuidv4(),
|
||||
Id: uuidv4(),
|
||||
}))
|
||||
}
|
||||
agentRawPlan.value = {
|
||||
@@ -468,43 +124,18 @@ export const useAgentsStore = defineStore('agents', () => {
|
||||
}
|
||||
currentTask.value = undefined
|
||||
executePlan.value = []
|
||||
hasStoppedFilling.value = false
|
||||
}
|
||||
|
||||
// 额外的产物列表
|
||||
const additionalOutputs = ref<string[]>([])
|
||||
|
||||
// 用户在 Assignment 中选择的 agent 组合(按任务ID分别存储)
|
||||
const selectedAgentGroupMap = ref<Map<string, string[]>>(new Map())
|
||||
|
||||
// 设置指定任务的选择的 agent 组合
|
||||
function setSelectedAgentGroup(taskId: string, agents: string[]) {
|
||||
selectedAgentGroupMap.value.set(taskId, agents)
|
||||
console.log('💾 保存任务选择的 agent 组合:', { taskId, agents })
|
||||
}
|
||||
|
||||
// 获取指定任务的选择的 agent 组合
|
||||
function getSelectedAgentGroup(taskId: string): string[] | undefined {
|
||||
return selectedAgentGroupMap.value.get(taskId)
|
||||
}
|
||||
|
||||
// 清除指定任务的选择的 agent 组合
|
||||
function clearSelectedAgentGroup(taskId: string) {
|
||||
selectedAgentGroupMap.value.delete(taskId)
|
||||
}
|
||||
|
||||
// 清除所有任务的选择的 agent 组合
|
||||
function clearAllSelectedAgentGroups() {
|
||||
selectedAgentGroupMap.value.clear()
|
||||
}
|
||||
|
||||
// 添加新产物
|
||||
function addNewOutput(outputObject: string) {
|
||||
if (!outputObject.trim()) return false
|
||||
|
||||
const trimmedOutput = outputObject.trim()
|
||||
if (!additionalOutputs.value.includes(trimmedOutput)) {
|
||||
additionalOutputs.value.unshift(trimmedOutput)
|
||||
additionalOutputs.value.push(trimmedOutput)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -525,14 +156,6 @@ export const useAgentsStore = defineStore('agents', () => {
|
||||
additionalOutputs.value = []
|
||||
}
|
||||
|
||||
// 标记是否用户已停止智能体分配过程
|
||||
const hasStoppedFilling = ref(false)
|
||||
|
||||
// 设置停止状态
|
||||
function setHasStoppedFilling(value: boolean) {
|
||||
hasStoppedFilling.value = value
|
||||
}
|
||||
|
||||
return {
|
||||
agents,
|
||||
setAgents,
|
||||
@@ -540,9 +163,6 @@ export const useAgentsStore = defineStore('agents', () => {
|
||||
setSearchValue,
|
||||
currentTask,
|
||||
setCurrentTask,
|
||||
setCurrentTaskProcess, // 🆕 设置当前任务的 TaskProcess
|
||||
updateCurrentAgentSelection, // 🆕 强制更新 AgentSelection(用于 AgentAllocation 切换组合)
|
||||
syncCurrentTaskToMainProcess, // 🆕 同步 currentTask 到主流程
|
||||
agentRawPlan,
|
||||
setAgentRawPlan,
|
||||
executePlan,
|
||||
@@ -552,52 +172,6 @@ export const useAgentsStore = defineStore('agents', () => {
|
||||
addNewOutput,
|
||||
removeAdditionalOutput,
|
||||
clearAdditionalOutputs,
|
||||
// 用户选择的 agent 组合
|
||||
selectedAgentGroupMap,
|
||||
setSelectedAgentGroup,
|
||||
getSelectedAgentGroup,
|
||||
clearSelectedAgentGroup,
|
||||
clearAllSelectedAgentGroups,
|
||||
planModificationWindow,
|
||||
openPlanModification,
|
||||
closePlanModification,
|
||||
planTaskWindow,
|
||||
openPlanTask,
|
||||
closePlanTask,
|
||||
agentAllocationDialog,
|
||||
openAgentAllocationDialog,
|
||||
closeAgentAllocationDialog,
|
||||
// 智能体评分数据
|
||||
IAgentSelectModifyAddRequest,
|
||||
setAgentScoreData,
|
||||
addAgentScoreAspect,
|
||||
clearAgentScoreData,
|
||||
// 🆕 按任务ID存储的评分数据
|
||||
taskScoreDataMap,
|
||||
getTaskScoreData,
|
||||
setTaskScoreData,
|
||||
hasTaskScoreData,
|
||||
getPreloadedTaskIds,
|
||||
clearTaskScoreData,
|
||||
clearAllTaskScoreData,
|
||||
preloadingTaskIds,
|
||||
markTaskPreloading,
|
||||
markTaskPreloadComplete,
|
||||
isTaskPreloading,
|
||||
// 确认的agent组合列表(按任务ID分别存储)
|
||||
confirmedAgentGroupsMap,
|
||||
getConfirmedAgentGroups,
|
||||
addConfirmedAgentGroup,
|
||||
clearConfirmedAgentGroups,
|
||||
clearAllConfirmedAgentGroups,
|
||||
// 停止填充状态
|
||||
hasStoppedFilling,
|
||||
setHasStoppedFilling,
|
||||
// 重新执行相关
|
||||
modifiedSteps,
|
||||
addModifiedStep,
|
||||
clearModifiedSteps,
|
||||
hasModifiedSteps,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -11,25 +11,15 @@ export interface Config {
|
||||
agentRepository: {
|
||||
storageVersionIdentifier: string
|
||||
}
|
||||
// 是否是开发环境
|
||||
dev?: boolean
|
||||
// api base url
|
||||
apiBaseUrl?: string
|
||||
}
|
||||
|
||||
const defaultConfig: Config = {
|
||||
apiBaseUrl: `${import.meta.env.BASE_URL || '/'}api`
|
||||
} as Config
|
||||
|
||||
export const useConfigStore = defineStore('config', () => {
|
||||
const config = ref<Config>({} as Config)
|
||||
|
||||
// 异步调用readConfig
|
||||
async function initConfig() {
|
||||
const data = await readConfig<Config>('config.json')
|
||||
config.value = {
|
||||
...defaultConfig,
|
||||
...data
|
||||
}
|
||||
config.value = await readConfig<Config>('config.json')
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,463 +0,0 @@
|
||||
import { ref } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { store } from '../index'
|
||||
import type { IRawStepTask, IApiStepTask } from './agents'
|
||||
import type { Node, Edge } from '@vue-flow/core'
|
||||
|
||||
/**
|
||||
* 分支数据接口
|
||||
* @description 用于存储流程图中的分支节点和边数据
|
||||
*/
|
||||
export interface IBranchData {
|
||||
/** 分支唯一 ID */
|
||||
id: string
|
||||
/** 父节点 ID(根节点或任务节点) */
|
||||
parentNodeId: string
|
||||
/** 分支需求内容 */
|
||||
branchContent: string
|
||||
/** 分支类型 */
|
||||
branchType: 'root' | 'task'
|
||||
/** 分支包含的所有节点 */
|
||||
nodes: Node[]
|
||||
/** 分支包含的所有边 */
|
||||
edges: Edge[]
|
||||
/** 分支的任务数据 */
|
||||
tasks: IRawStepTask[]
|
||||
/** 创建时间 */
|
||||
createdAt: number
|
||||
}
|
||||
|
||||
export const useSelectionStore = defineStore('selection', () => {
|
||||
// ==================== 任务大纲探索分支数据存储 ====================
|
||||
|
||||
/** 流程图分支列表 */
|
||||
const flowBranches = ref<IBranchData[]>([])
|
||||
|
||||
/**
|
||||
* 添加流程图分支
|
||||
* @param data 分支数据
|
||||
* @returns 分支 ID
|
||||
*/
|
||||
function addFlowBranch(data: {
|
||||
parentNodeId: string
|
||||
branchContent: string
|
||||
branchType: 'root' | 'task'
|
||||
nodes: Node[]
|
||||
edges: Edge[]
|
||||
tasks: IRawStepTask[]
|
||||
}): string {
|
||||
const branchId = `flow-branch-${uuidv4()}`
|
||||
const newBranch: IBranchData = {
|
||||
id: branchId,
|
||||
parentNodeId: data.parentNodeId,
|
||||
branchContent: data.branchContent,
|
||||
branchType: data.branchType,
|
||||
nodes: data.nodes,
|
||||
edges: data.edges,
|
||||
tasks: data.tasks,
|
||||
createdAt: Date.now(),
|
||||
}
|
||||
flowBranches.value.push(newBranch)
|
||||
return branchId
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有流程图分支
|
||||
* @returns 所有流程图分支数据
|
||||
*/
|
||||
function getAllFlowBranches(): IBranchData[] {
|
||||
return flowBranches.value
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据父节点 ID 获取流程图分支
|
||||
* @param parentNodeId 父节点 ID
|
||||
* @returns 匹配的流程图分支列表
|
||||
*/
|
||||
function getFlowBranchesByParent(parentNodeId: string): IBranchData[] {
|
||||
return flowBranches.value.filter((branch) => branch.parentNodeId === parentNodeId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除流程图分支
|
||||
* @param branchId 分支 ID
|
||||
* @returns 是否删除成功
|
||||
*/
|
||||
function removeFlowBranch(branchId: string): boolean {
|
||||
const index = flowBranches.value.findIndex((branch) => branch.id === branchId)
|
||||
if (index > -1) {
|
||||
flowBranches.value.splice(index, 1)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/** 清除所有流程图分支 */
|
||||
function clearFlowBranches() {
|
||||
flowBranches.value = []
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据父节点 ID 清除流程图分支
|
||||
* @param parentNodeId 父节点 ID
|
||||
*/
|
||||
function clearFlowBranchesByParent(parentNodeId: string) {
|
||||
flowBranches.value = flowBranches.value.filter((branch) => branch.parentNodeId !== parentNodeId)
|
||||
}
|
||||
|
||||
// ==================== 任务过程探索分支数据存储 ====================
|
||||
/**
|
||||
* 任务过程分支数据映射
|
||||
* @description 存储结构: Map<taskStepId, Map<agentGroupKey, IBranchData[]>>
|
||||
* - taskStepId: 任务步骤 ID
|
||||
* - agentGroupKey: agent 组合的唯一标识(排序后的 JSON 字符串)
|
||||
* - IBranchData[]: 该 agent 组合在该任务下的所有分支数据
|
||||
*/
|
||||
const taskProcessBranchesMap = ref<Map<string, Map<string, IBranchData[]>>>(new Map())
|
||||
|
||||
/**
|
||||
* 添加任务过程分支
|
||||
* @param taskStepId 任务步骤 ID
|
||||
* @param agents Agent 列表
|
||||
* @param data 分支数据
|
||||
* @returns 分支 ID
|
||||
*/
|
||||
function addTaskProcessBranch(
|
||||
taskStepId: string,
|
||||
agents: string[],
|
||||
data: {
|
||||
parentNodeId: string
|
||||
branchContent: string
|
||||
branchType: 'root' | 'task'
|
||||
nodes: Node[]
|
||||
edges: Edge[]
|
||||
tasks: IRawStepTask[]
|
||||
},
|
||||
): string {
|
||||
const branchId = `task-process-branch-${uuidv4()}`
|
||||
const agentGroupKey = getAgentGroupKey(agents)
|
||||
|
||||
const newBranch: IBranchData = {
|
||||
id: branchId,
|
||||
parentNodeId: data.parentNodeId,
|
||||
branchContent: data.branchContent,
|
||||
branchType: data.branchType,
|
||||
nodes: data.nodes,
|
||||
edges: data.edges,
|
||||
tasks: data.tasks,
|
||||
createdAt: Date.now(),
|
||||
}
|
||||
|
||||
// 获取或创建该任务步骤的 Map
|
||||
if (!taskProcessBranchesMap.value.has(taskStepId)) {
|
||||
taskProcessBranchesMap.value.set(taskStepId, new Map())
|
||||
}
|
||||
|
||||
// 获取或创建该 agent 组合的分支列表
|
||||
const agentMap = taskProcessBranchesMap.value.get(taskStepId)!
|
||||
if (!agentMap.has(agentGroupKey)) {
|
||||
agentMap.set(agentGroupKey, [])
|
||||
}
|
||||
agentMap.get(agentGroupKey)!.push(newBranch)
|
||||
|
||||
return branchId
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定任务步骤和 agent 组合的所有分支
|
||||
* @param taskStepId 任务步骤 ID
|
||||
* @param agents Agent 列表
|
||||
* @returns 匹配的任务过程分支列表
|
||||
*/
|
||||
function getTaskProcessBranches(taskStepId: string, agents: string[]): IBranchData[] {
|
||||
const agentGroupKey = getAgentGroupKey(agents)
|
||||
return taskProcessBranchesMap.value.get(taskStepId)?.get(agentGroupKey) || []
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有任务过程分支
|
||||
* @returns 所有任务过程分支数据映射
|
||||
*/
|
||||
function getAllTaskProcessBranches(): Map<string, Map<string, IBranchData[]>> {
|
||||
return taskProcessBranchesMap.value
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据父节点 ID 获取任务过程分支
|
||||
* @param taskStepId 任务步骤 ID
|
||||
* @param agents Agent 列表
|
||||
* @param parentNodeId 父节点 ID
|
||||
* @returns 匹配的任务过程分支列表
|
||||
*/
|
||||
function getTaskProcessBranchesByParent(
|
||||
taskStepId: string,
|
||||
agents: string[],
|
||||
parentNodeId: string,
|
||||
): IBranchData[] {
|
||||
const agentGroupKey = getAgentGroupKey(agents)
|
||||
const branches = taskProcessBranchesMap.value.get(taskStepId)?.get(agentGroupKey) || []
|
||||
return branches.filter((branch) => branch.parentNodeId === parentNodeId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除任务过程分支
|
||||
* @param taskStepId 任务步骤 ID
|
||||
* @param agents Agent 列表
|
||||
* @param branchId 分支 ID
|
||||
* @returns 是否删除成功
|
||||
*/
|
||||
function removeTaskProcessBranch(
|
||||
taskStepId: string,
|
||||
agents: string[],
|
||||
branchId: string,
|
||||
): boolean {
|
||||
const agentGroupKey = getAgentGroupKey(agents)
|
||||
const branches = taskProcessBranchesMap.value.get(taskStepId)?.get(agentGroupKey)
|
||||
if (branches) {
|
||||
const index = branches.findIndex((branch) => branch.id === branchId)
|
||||
if (index > -1) {
|
||||
branches.splice(index, 1)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定任务步骤和 agent 组合的所有分支
|
||||
* @param taskStepId 任务步骤 ID
|
||||
* @param agents Agent 列表(可选,不传则清除该任务步骤的所有分支)
|
||||
*/
|
||||
function clearTaskProcessBranches(taskStepId: string, agents?: string[]) {
|
||||
if (agents) {
|
||||
// 清除指定 agent 组合的分支
|
||||
const agentGroupKey = getAgentGroupKey(agents)
|
||||
const agentMap = taskProcessBranchesMap.value.get(taskStepId)
|
||||
if (agentMap) {
|
||||
agentMap.delete(agentGroupKey)
|
||||
}
|
||||
} else {
|
||||
// 清除该任务步骤的所有分支(所有 agent 组合)
|
||||
taskProcessBranchesMap.value.delete(taskStepId)
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Agent 组合 TaskProcess 数据存储 ====================
|
||||
/**
|
||||
* Agent 组合 TaskProcess 数据映射
|
||||
* @description 用于存储 fill_stepTask_TaskProcess 接口返回的数据
|
||||
* 存储结构: Map<taskId, Map<agentGroupKey, IApiStepTask>>
|
||||
* - taskId: 任务 ID
|
||||
* - agentGroupKey: agent 组合的唯一标识(排序后的 JSON 字符串)
|
||||
* - IApiStepTask: 该 agent 组合的完整 TaskProcess 数据
|
||||
*/
|
||||
const agentTaskProcessMap = ref<Map<string, Map<string, IApiStepTask>>>(new Map())
|
||||
|
||||
/**
|
||||
* 生成 agent 组合的唯一 key
|
||||
* @description 排序后保证一致性
|
||||
* @param agents Agent 列表
|
||||
* @returns Agent 组合的唯一标识
|
||||
*/
|
||||
function getAgentGroupKey(agents: string[]): string {
|
||||
// 处理 undefined 或 null 的情况
|
||||
if (!agents || !Array.isArray(agents)) {
|
||||
return JSON.stringify([])
|
||||
}
|
||||
return JSON.stringify([...agents].sort())
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储 agent 组合的 TaskProcess 数据
|
||||
* @param taskId 任务 ID
|
||||
* @param agents Agent 列表
|
||||
* @param taskProcess TaskProcess 数据
|
||||
*/
|
||||
function setAgentTaskProcess(taskId: string, agents: string[], taskProcess: IApiStepTask) {
|
||||
const groupKey = getAgentGroupKey(agents)
|
||||
|
||||
// 获取或创建该任务的 Map
|
||||
if (!agentTaskProcessMap.value.has(taskId)) {
|
||||
agentTaskProcessMap.value.set(taskId, new Map())
|
||||
}
|
||||
|
||||
// 存储该 agent 组合的 TaskProcess 数据
|
||||
agentTaskProcessMap.value.get(taskId)!.set(groupKey, taskProcess)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 agent 组合的 TaskProcess 数据
|
||||
* @param taskId 任务 ID
|
||||
* @param agents Agent 列表
|
||||
* @returns TaskProcess 数据
|
||||
*/
|
||||
function getAgentTaskProcess(taskId: string, agents: string[]): IApiStepTask | undefined {
|
||||
const groupKey = getAgentGroupKey(agents)
|
||||
return agentTaskProcessMap.value.get(taskId)?.get(groupKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 agent 组合是否已有 TaskProcess 数据
|
||||
* @param taskId 任务 ID
|
||||
* @param agents Agent 列表
|
||||
* @returns 是否存在数据
|
||||
*/
|
||||
function hasAgentTaskProcess(taskId: string, agents: string[]): boolean {
|
||||
const groupKey = getAgentGroupKey(agents)
|
||||
return agentTaskProcessMap.value.get(taskId)?.has(groupKey) || false
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定任务的所有 agent 组合 TaskProcess 数据
|
||||
* @param taskId 任务 ID
|
||||
*/
|
||||
function clearAgentTaskProcess(taskId: string) {
|
||||
agentTaskProcessMap.value.delete(taskId)
|
||||
}
|
||||
|
||||
/** 清除所有任务的 agent 组合 TaskProcess 数据 */
|
||||
function clearAllAgentTaskProcess() {
|
||||
agentTaskProcessMap.value.clear()
|
||||
}
|
||||
|
||||
// ==================== 当前生效的任务过程分支 ====================
|
||||
/**
|
||||
* 当前生效的任务过程分支映射
|
||||
* @description 记录每个任务步骤和 agent 组合当前生效的分支 ID(持久化选中状态)
|
||||
* 存储结构: Map<taskStepId, Map<agentGroupKey, branchId>>
|
||||
* - taskStepId: 任务步骤 ID
|
||||
* - agentGroupKey: agent 组合的唯一标识
|
||||
* - branchId: 当前选中的分支 ID
|
||||
*/
|
||||
const activeTaskProcessBranchMap = ref<Map<string, Map<string, string>>>(new Map())
|
||||
|
||||
/**
|
||||
* 当前生效的 TaskProcess 数据映射
|
||||
* @description 用于外部组件显示职责分配
|
||||
* 存储结构: Map<taskStepId, Map<agentGroupKey, TaskProcess[]>>
|
||||
*/
|
||||
const activeTaskProcessDataMap = ref<Map<string, Map<string, any[]>>>(new Map())
|
||||
|
||||
/**
|
||||
* 设置当前生效的分支
|
||||
* @param taskStepId 任务步骤 ID
|
||||
* @param agents Agent 列表
|
||||
* @param branchId 分支 ID
|
||||
*/
|
||||
function setActiveTaskProcessBranch(taskStepId: string, agents: string[], branchId: string) {
|
||||
const agentGroupKey = getAgentGroupKey(agents)
|
||||
|
||||
// 获取或创建该任务步骤的 Map
|
||||
if (!activeTaskProcessBranchMap.value.has(taskStepId)) {
|
||||
activeTaskProcessBranchMap.value.set(taskStepId, new Map())
|
||||
}
|
||||
|
||||
activeTaskProcessBranchMap.value.get(taskStepId)!.set(agentGroupKey, branchId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前生效的 TaskProcess 数据
|
||||
* @param taskStepId 任务步骤 ID
|
||||
* @param agents Agent 列表
|
||||
* @param taskProcess TaskProcess 数据
|
||||
*/
|
||||
function setActiveTaskProcessData(taskStepId: string, agents: string[], taskProcess: any[]) {
|
||||
const agentGroupKey = getAgentGroupKey(agents)
|
||||
|
||||
// 获取或创建该任务步骤的 Map
|
||||
if (!activeTaskProcessDataMap.value.has(taskStepId)) {
|
||||
activeTaskProcessDataMap.value.set(taskStepId, new Map())
|
||||
}
|
||||
|
||||
activeTaskProcessDataMap.value.get(taskStepId)!.set(agentGroupKey, taskProcess)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前生效的分支 ID
|
||||
* @param taskStepId 任务步骤 ID
|
||||
* @param agents Agent 列表
|
||||
* @returns 分支 ID
|
||||
*/
|
||||
function getActiveTaskProcessBranch(taskStepId: string, agents: string[]): string | undefined {
|
||||
const agentGroupKey = getAgentGroupKey(agents)
|
||||
return activeTaskProcessBranchMap.value.get(taskStepId)?.get(agentGroupKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前生效的 TaskProcess 数据
|
||||
* @param taskStepId 任务步骤 ID
|
||||
* @param agents Agent 列表
|
||||
* @returns TaskProcess 数据
|
||||
*/
|
||||
function getActiveTaskProcessData(taskStepId: string, agents: string[]): any[] | undefined {
|
||||
const agentGroupKey = getAgentGroupKey(agents)
|
||||
return activeTaskProcessDataMap.value.get(taskStepId)?.get(agentGroupKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除生效分支
|
||||
* @param taskStepId 任务步骤 ID
|
||||
* @param agents Agent 列表(可选,不传则清除该任务步骤的所有生效分支)
|
||||
*/
|
||||
function clearActiveTaskProcessBranch(taskStepId: string, agents?: string[]) {
|
||||
if (agents) {
|
||||
// 清除指定 agent 组合的生效分支
|
||||
const agentGroupKey = getAgentGroupKey(agents)
|
||||
activeTaskProcessBranchMap.value.get(taskStepId)?.delete(agentGroupKey)
|
||||
activeTaskProcessDataMap.value.get(taskStepId)?.delete(agentGroupKey)
|
||||
} else {
|
||||
// 清除该任务步骤的所有生效分支(所有 agent 组合)
|
||||
activeTaskProcessBranchMap.value.delete(taskStepId)
|
||||
activeTaskProcessDataMap.value.delete(taskStepId)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// ==================== 状态 ====================
|
||||
flowBranches,
|
||||
taskProcessBranchesMap,
|
||||
agentTaskProcessMap,
|
||||
activeTaskProcessBranchMap,
|
||||
activeTaskProcessDataMap,
|
||||
|
||||
// ==================== 任务大纲分支管理方法 ====================
|
||||
addFlowBranch,
|
||||
getAllFlowBranches,
|
||||
getFlowBranchesByParent,
|
||||
removeFlowBranch,
|
||||
clearFlowBranches,
|
||||
clearFlowBranchesByParent,
|
||||
|
||||
// ==================== 任务过程分支管理方法 ====================
|
||||
addTaskProcessBranch,
|
||||
getTaskProcessBranches,
|
||||
getAllTaskProcessBranches,
|
||||
getTaskProcessBranchesByParent,
|
||||
removeTaskProcessBranch,
|
||||
clearTaskProcessBranches,
|
||||
|
||||
// ==================== 任务过程分支生效状态管理方法 ====================
|
||||
setActiveTaskProcessBranch,
|
||||
setActiveTaskProcessData,
|
||||
getActiveTaskProcessBranch,
|
||||
getActiveTaskProcessData,
|
||||
clearActiveTaskProcessBranch,
|
||||
|
||||
// ==================== Agent 组合 TaskProcess 数据管理方法 ====================
|
||||
getAgentGroupKey,
|
||||
setAgentTaskProcess,
|
||||
getAgentTaskProcess,
|
||||
hasAgentTaskProcess,
|
||||
clearAgentTaskProcess,
|
||||
clearAllAgentTaskProcess,
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 用于在组件外部使用 Selection Store
|
||||
* @returns Selection Store 实例
|
||||
*/
|
||||
export function useSelectionStoreHook() {
|
||||
return useSelectionStore(store)
|
||||
}
|
||||
@@ -100,13 +100,6 @@
|
||||
--color-text-result-detail: rgba(0,0,0,0.6);
|
||||
// 结果卡片运行后颜色
|
||||
--color-text-result-detail-run: #000;
|
||||
// 分割线
|
||||
--color-border-separate: #D6D6D6;
|
||||
// url颜色
|
||||
--color-text-url: #FF0000;
|
||||
// model颜色
|
||||
--color-text-model: #0580ff;
|
||||
|
||||
}
|
||||
|
||||
// 深色模式
|
||||
@@ -211,12 +204,4 @@ html.dark {
|
||||
--color-text-result-detail: #6c6e72;
|
||||
// 结果卡片运行后颜色
|
||||
--color-text-result-detail-run: #fff;
|
||||
// 分割线
|
||||
--color-border-separate: rgba(255,255,255,0.18);
|
||||
// url颜色
|
||||
--color-text-url: #2cd235;
|
||||
// model颜色
|
||||
--color-text-model: #00c8d2;
|
||||
|
||||
--el-fill-color-blank: transparent !important;
|
||||
}
|
||||
|
||||
@@ -92,10 +92,6 @@ $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);
|
||||
// url和model颜色
|
||||
$text-url: var(--color-text-url);
|
||||
$text-model: var(--color-text-model);
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -151,6 +147,4 @@ $text-model: var(--color-text-model);
|
||||
text-taskbar: $text-taskbar;
|
||||
agent-list-selected-bg: $agent-list-selected-bg;
|
||||
text-result-detail: $text-result-detail;
|
||||
text-url: $text-url;
|
||||
text-model: $text-model;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export function changeBriefs(task?: IRawStepTask[]): IRawStepTask[] {
|
||||
return task.map((item) => {
|
||||
const record = {
|
||||
...item,
|
||||
Collaboration_Brief_frontEnd: changeBrief(item),
|
||||
Collaboration_Brief_FrontEnd: changeBrief(item),
|
||||
}
|
||||
return record
|
||||
})
|
||||
@@ -30,10 +30,10 @@ function changeBrief(task: IRawStepTask): IRichText {
|
||||
// 如果不存在AgentSelection直接返回
|
||||
const agents = task.AgentSelection ?? []
|
||||
if (agents.length === 0) {
|
||||
return task.Collaboration_Brief_frontEnd
|
||||
return task.Collaboration_Brief_FrontEnd
|
||||
}
|
||||
const data: IRichText['data'] = {}
|
||||
let indexOffset = 0
|
||||
const data: IRichText['data'] = {};
|
||||
let indexOffset = 0;
|
||||
|
||||
// 根据InputObject_List修改
|
||||
const inputs = task.InputObject_List ?? []
|
||||
@@ -41,62 +41,63 @@ function changeBrief(task: IRawStepTask): IRichText {
|
||||
data[(index + indexOffset).toString()] = {
|
||||
text,
|
||||
style: { background: '#ACDBA0' },
|
||||
}
|
||||
return `!<${index + indexOffset}>!`
|
||||
})
|
||||
const inputSentence = nameJoin(inputPlaceHolders)
|
||||
indexOffset += inputs.length
|
||||
};
|
||||
return `!<${index + indexOffset}>!`;
|
||||
});
|
||||
const inputSentence = nameJoin(inputPlaceHolders);
|
||||
indexOffset += inputs.length;
|
||||
|
||||
// 根据AgentSelection修改
|
||||
const namePlaceholders = agents.map((text, index) => {
|
||||
data[(index + indexOffset).toString()] = {
|
||||
text,
|
||||
style: { background: '#E5E5E5', boxShadow: '1px 1px 4px 1px #0003' },
|
||||
}
|
||||
return `!<${index + indexOffset}>!`
|
||||
})
|
||||
const nameSentence = nameJoin(namePlaceholders)
|
||||
indexOffset += agents.length
|
||||
};
|
||||
return `!<${index + indexOffset}>!`;
|
||||
});
|
||||
const nameSentence = nameJoin(namePlaceholders);
|
||||
indexOffset += agents.length;
|
||||
|
||||
let actionSentence = task.TaskContent ?? ''
|
||||
|
||||
let actionSentence = task.TaskContent ?? '';
|
||||
|
||||
// delete the last '.' of actionSentence
|
||||
if (actionSentence[actionSentence.length - 1] === '.') {
|
||||
actionSentence = actionSentence.slice(0, -1)
|
||||
actionSentence = actionSentence.slice(0, -1);
|
||||
}
|
||||
const actionIndex = indexOffset++
|
||||
const actionIndex = indexOffset++;
|
||||
|
||||
data[actionIndex.toString()] = {
|
||||
text: actionSentence,
|
||||
style: { background: '#DDD', border: '1.5px solid #ddd' },
|
||||
}
|
||||
};
|
||||
|
||||
let outputSentence = ''
|
||||
const output = task.OutputObject ?? ''
|
||||
let outputSentence = '';
|
||||
const output = task.OutputObject ?? '';
|
||||
if (output) {
|
||||
data[indexOffset.toString()] = {
|
||||
text: output,
|
||||
style: { background: '#FFCA8C' },
|
||||
}
|
||||
outputSentence = `得到 !<${indexOffset}>!`
|
||||
};
|
||||
outputSentence = `得到 !<${indexOffset}>!`;
|
||||
}
|
||||
|
||||
// Join them togeter
|
||||
let content = inputSentence
|
||||
let content = inputSentence;
|
||||
if (content) {
|
||||
content = `基于${content}, ${nameSentence} 执行任务 !<${actionIndex}>!`
|
||||
content = `基于${content}, ${nameSentence} 执行任务 !<${actionIndex}>!`;
|
||||
} else {
|
||||
content = `${nameSentence} 执行任务 !<${actionIndex}>!`
|
||||
content = `${nameSentence} 执行任务 !<${actionIndex}>!`;
|
||||
}
|
||||
if (outputSentence) {
|
||||
content = `${content}, ${outputSentence}.`
|
||||
content = `${content}, ${outputSentence}.`;
|
||||
} else {
|
||||
content = `${content}.`
|
||||
content = `${content}.`;
|
||||
}
|
||||
content = content.trim()
|
||||
content = content.trim();
|
||||
|
||||
return {
|
||||
template: content,
|
||||
data,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
import type {
|
||||
AxiosError,
|
||||
AxiosInstance,
|
||||
AxiosRequestConfig,
|
||||
AxiosResponse,
|
||||
InternalAxiosRequestConfig,
|
||||
} from 'axios'
|
||||
import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
|
||||
import axios from 'axios'
|
||||
import qs from 'qs'
|
||||
import type { Ref } from 'vue'
|
||||
import { ElNotification } from 'element-plus'
|
||||
import { ref } from 'vue'
|
||||
import { useConfigStoreHook } from '@/stores'
|
||||
|
||||
// 创建 axios 实例
|
||||
let service: AxiosInstance
|
||||
@@ -21,9 +14,9 @@ export interface AxiosResponseData {
|
||||
}
|
||||
|
||||
export function initService() {
|
||||
const configStore = useConfigStoreHook()
|
||||
service = axios.create({
|
||||
baseURL: configStore.config.apiBaseUrl,
|
||||
baseURL: '/api',
|
||||
timeout: 50000,
|
||||
headers: { 'Content-Type': 'application/json;charset=utf-8' },
|
||||
paramsSerializer: (params) => {
|
||||
return qs.stringify(params)
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
/**
|
||||
* WebSocket 客户端封装
|
||||
* 基于 socket.io-client 实现
|
||||
*/
|
||||
|
||||
import { io, Socket } from 'socket.io-client'
|
||||
|
||||
interface WebSocketConfig {
|
||||
url?: string
|
||||
reconnectionAttempts?: number
|
||||
reconnectionDelay?: number
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
interface RequestMessage {
|
||||
id: string
|
||||
action: string
|
||||
data: any
|
||||
}
|
||||
|
||||
interface ResponseMessage {
|
||||
id: string
|
||||
status: 'success' | 'error' | 'streaming' | 'complete'
|
||||
data?: any
|
||||
error?: string
|
||||
stage?: string
|
||||
message?: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
interface StreamProgressCallback {
|
||||
(data: any): void
|
||||
}
|
||||
|
||||
type RequestHandler = {
|
||||
resolve: (value: any) => void
|
||||
reject: (error: Error) => void
|
||||
timer?: ReturnType<typeof setTimeout>
|
||||
onProgress?: StreamProgressCallback
|
||||
}
|
||||
|
||||
class WebSocketClient {
|
||||
private socket: Socket | null = null
|
||||
private requestHandlers = new Map<string, RequestHandler>()
|
||||
private streamHandlers = new Map<string, StreamProgressCallback>()
|
||||
private config: Required<WebSocketConfig>
|
||||
private isConnected = false
|
||||
|
||||
constructor() {
|
||||
this.config = {
|
||||
url: '',
|
||||
reconnectionAttempts: 5,
|
||||
reconnectionDelay: 1000,
|
||||
timeout: 300000, // 5分钟超时
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接到WebSocket服务器
|
||||
*/
|
||||
connect(url?: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const wsUrl = url || this.config.url || window.location.origin
|
||||
|
||||
this.socket = io(wsUrl, {
|
||||
transports: ['websocket', 'polling'],
|
||||
reconnection: true,
|
||||
reconnectionAttempts: this.config.reconnectionAttempts,
|
||||
reconnectionDelay: this.config.reconnectionDelay,
|
||||
})
|
||||
|
||||
this.socket.on('connect', () => {
|
||||
this.isConnected = true
|
||||
resolve()
|
||||
})
|
||||
|
||||
this.socket.on('connect_error', (error) => {
|
||||
reject(error)
|
||||
})
|
||||
|
||||
this.socket.on('disconnect', (reason) => {
|
||||
this.isConnected = false
|
||||
})
|
||||
|
||||
this.socket.on('connected', (data) => {
|
||||
// Server connected message
|
||||
})
|
||||
|
||||
// 监听响应消息
|
||||
this.socket.on('response', (response: ResponseMessage) => {
|
||||
const { id, status, data, error } = response
|
||||
const handler = this.requestHandlers.get(id)
|
||||
|
||||
if (handler) {
|
||||
// 清除超时定时器
|
||||
if (handler.timer) {
|
||||
clearTimeout(handler.timer)
|
||||
}
|
||||
|
||||
if (status === 'success') {
|
||||
handler.resolve(data)
|
||||
} else {
|
||||
handler.reject(new Error(error || 'Unknown error'))
|
||||
}
|
||||
|
||||
// 删除处理器
|
||||
this.requestHandlers.delete(id)
|
||||
}
|
||||
})
|
||||
|
||||
// 监听流式进度消息
|
||||
this.socket.on('progress', (response: ResponseMessage) => {
|
||||
const { id, status, data, error } = response
|
||||
|
||||
// 首先检查是否有对应的流式处理器
|
||||
const streamCallback = this.streamHandlers.get(id)
|
||||
if (streamCallback) {
|
||||
if (status === 'streaming') {
|
||||
// 解析 data 字段(JSON 字符串)并传递给回调
|
||||
try {
|
||||
const parsedData = typeof data === 'string' ? JSON.parse(data) : data
|
||||
streamCallback(parsedData)
|
||||
} catch (e) {
|
||||
// Failed to parse progress data
|
||||
}
|
||||
} else if (status === 'complete') {
|
||||
this.streamHandlers.delete(id)
|
||||
streamCallback({ type: 'complete' })
|
||||
} else if (status === 'error') {
|
||||
this.streamHandlers.delete(id)
|
||||
streamCallback({ type: 'error', error })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否有对应的普通请求处理器(支持send()方法的进度回调)
|
||||
const requestHandler = this.requestHandlers.get(id)
|
||||
if (requestHandler && requestHandler.onProgress) {
|
||||
// 解析 data 字段并传递给进度回调
|
||||
try {
|
||||
const parsedData = typeof data === 'string' ? JSON.parse(data) : data
|
||||
requestHandler.onProgress(parsedData)
|
||||
} catch (e) {
|
||||
// Failed to parse progress data
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 心跳检测
|
||||
this.socket.on('pong', () => {
|
||||
// Pong received
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送请求(双向通信,支持可选的进度回调)
|
||||
*/
|
||||
send(action: string, data: any, timeout?: number, onProgress?: StreamProgressCallback): Promise<any> {
|
||||
if (!this.socket || !this.isConnected) {
|
||||
return Promise.reject(new Error('WebSocket未连接'))
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const requestId = `${action}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
||||
|
||||
// 设置超时
|
||||
const timeoutMs = timeout || this.config.timeout
|
||||
const timer = setTimeout(() => {
|
||||
if (this.requestHandlers.has(requestId)) {
|
||||
this.requestHandlers.delete(requestId)
|
||||
reject(new Error(`Request timeout: ${action}`))
|
||||
}
|
||||
}, timeoutMs)
|
||||
|
||||
// 保存处理器(包含可选的进度回调)
|
||||
this.requestHandlers.set(requestId, { resolve, reject, timer, onProgress })
|
||||
|
||||
// 发送消息
|
||||
this.socket!.emit(action, {
|
||||
id: requestId,
|
||||
action,
|
||||
data,
|
||||
} as RequestMessage)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅流式数据
|
||||
*/
|
||||
subscribe(
|
||||
action: string,
|
||||
data: any,
|
||||
onProgress: StreamProgressCallback,
|
||||
onComplete?: () => void,
|
||||
onError?: (error: Error) => void,
|
||||
): void {
|
||||
if (!this.socket || !this.isConnected) {
|
||||
onError?.(new Error('WebSocket未连接'))
|
||||
return
|
||||
}
|
||||
|
||||
const requestId = `${action}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
||||
|
||||
// 保存流式处理器
|
||||
const wrappedCallback = (progressData: any) => {
|
||||
if (progressData?.type === 'complete') {
|
||||
this.streamHandlers.delete(requestId)
|
||||
onComplete?.()
|
||||
} else {
|
||||
onProgress(progressData)
|
||||
}
|
||||
}
|
||||
|
||||
this.streamHandlers.set(requestId, wrappedCallback)
|
||||
|
||||
// 发送订阅请求
|
||||
this.socket.emit(action, {
|
||||
id: requestId,
|
||||
action,
|
||||
data,
|
||||
} as RequestMessage)
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送心跳
|
||||
*/
|
||||
ping(): void {
|
||||
if (this.socket && this.isConnected) {
|
||||
this.socket.emit('ping')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
*/
|
||||
disconnect(): void {
|
||||
if (this.socket) {
|
||||
// 清理所有处理器
|
||||
this.requestHandlers.forEach((handler) => {
|
||||
if (handler.timer) {
|
||||
clearTimeout(handler.timer)
|
||||
}
|
||||
})
|
||||
this.requestHandlers.clear()
|
||||
this.streamHandlers.clear()
|
||||
|
||||
this.socket.disconnect()
|
||||
this.socket = null
|
||||
this.isConnected = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接状态
|
||||
*/
|
||||
get connected(): boolean {
|
||||
return this.isConnected && this.socket?.connected === true
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Socket ID
|
||||
*/
|
||||
get id(): string | undefined {
|
||||
return this.socket?.id
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
export default new WebSocketClient()
|
||||
@@ -14,7 +14,6 @@ const pathSrc = resolve(__dirname, 'src')
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
base: '',
|
||||
plugins: [
|
||||
vue(),
|
||||
tailwindcss(),
|
||||
@@ -39,7 +38,7 @@ export default defineConfig({
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
},
|
||||
},
|
||||
server: {
|
||||
@@ -47,14 +46,13 @@ export default defineConfig({
|
||||
'/api': {
|
||||
changeOrigin: true,
|
||||
// 接口地址
|
||||
// target: 'http://82.157.183.212:21092',
|
||||
target: 'http://82.157.183.212:21097',
|
||||
// target: 'http://localhost:8000',
|
||||
// rewrite: (path: string) => path.replace(/^\/api/, ''),
|
||||
// configure: (proxy, options) => {
|
||||
// console.log('Proxy configured:', options)
|
||||
// },
|
||||
},
|
||||
target: 'http://82.157.183.212:21092',
|
||||
// rewrite: (path: string) =>
|
||||
// path.replace(/^\/api/, ''),
|
||||
configure: (proxy, options) => {
|
||||
console.log('Proxy configured:', options)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||