Compare commits
35 Commits
c00c0072b8
...
web
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3e6c7a618 | ||
|
|
1749ae4f1e | ||
|
|
418b2e5f8f | ||
|
|
641d70033d | ||
|
|
b287867069 | ||
|
|
5699635d1a | ||
|
|
ac035d1237 | ||
|
|
53add0431e | ||
|
|
786c674d21 | ||
|
|
1c8036adf1 | ||
|
|
45314b7be6 | ||
|
|
c5848410c1 | ||
|
|
571b5101ff | ||
|
|
029df6b5a5 | ||
|
|
edb39d4c1f | ||
|
|
0e87777ae8 | ||
|
|
244deceb91 | ||
|
|
69587c0481 | ||
|
|
e0cc11647f | ||
|
|
59fd94e783 | ||
|
|
3ff70463ca | ||
|
|
82e92f12aa | ||
|
|
920588b063 | ||
|
|
5847365eee | ||
|
|
d42554ce03 | ||
|
|
bcc0c53ba1 | ||
|
|
7da5e82d40 | ||
|
|
cc22655a1e | ||
|
|
f0db3c88e4 | ||
|
|
b987fe70ad | ||
|
|
b42ab5aedd | ||
|
|
5ef86c6fa9 | ||
|
|
907310365a | ||
|
|
5dace5f788 | ||
|
|
77530c49f8 |
@@ -1,5 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from openai import OpenAI, AsyncOpenAI
|
|
||||||
|
import httpx
|
||||||
|
from openai import OpenAI, AsyncOpenAI, max_retries
|
||||||
import yaml
|
import yaml
|
||||||
from termcolor import colored
|
from termcolor import colored
|
||||||
import os
|
import os
|
||||||
@@ -21,6 +23,9 @@ OPENAI_API_BASE = os.getenv("OPENAI_API_BASE") or yaml_data.get(
|
|||||||
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") or yaml_data.get(
|
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") or yaml_data.get(
|
||||||
"OPENAI_API_KEY", ""
|
"OPENAI_API_KEY", ""
|
||||||
)
|
)
|
||||||
|
OPENAI_API_MODEL = os.getenv("OPENAI_API_MODEL") or yaml_data.get(
|
||||||
|
"OPENAI_API_MODEL", ""
|
||||||
|
)
|
||||||
|
|
||||||
# Initialize OpenAI clients
|
# Initialize OpenAI clients
|
||||||
client = OpenAI(api_key=OPENAI_API_KEY, base_url=OPENAI_API_BASE)
|
client = OpenAI(api_key=OPENAI_API_KEY, base_url=OPENAI_API_BASE)
|
||||||
@@ -41,8 +46,11 @@ MISTRAL_API_KEY = os.getenv("MISTRAL_API_KEY") or yaml_data.get(
|
|||||||
|
|
||||||
# for LLM completion
|
# for LLM completion
|
||||||
def LLM_Completion(
|
def LLM_Completion(
|
||||||
messages: list[dict], stream: bool = True, useGroq: bool = True
|
messages: list[dict], stream: bool = True, useGroq: bool = True,model_config: dict = None
|
||||||
) -> str:
|
) -> 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:
|
if not useGroq or not FAST_DESIGN_MODE:
|
||||||
force_gpt4 = True
|
force_gpt4 = True
|
||||||
useGroq = False
|
useGroq = False
|
||||||
@@ -75,6 +83,97 @@ def LLM_Completion(
|
|||||||
return _chat_completion(messages=messages)
|
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:
|
async def _achat_completion_stream_groq(messages: list[dict]) -> str:
|
||||||
from groq import AsyncGroq
|
from groq import AsyncGroq
|
||||||
groq_client = AsyncGroq(api_key=GROQ_API_KEY)
|
groq_client = AsyncGroq(api_key=GROQ_API_KEY)
|
||||||
@@ -100,7 +199,16 @@ async def _achat_completion_stream_groq(messages: list[dict]) -> str:
|
|||||||
else:
|
else:
|
||||||
raise Exception("failed")
|
raise Exception("failed")
|
||||||
|
|
||||||
|
# 检查响应是否有效
|
||||||
|
if not response.choices or len(response.choices) == 0:
|
||||||
|
raise Exception("Groq API returned empty response")
|
||||||
|
if not response.choices[0] or not response.choices[0].message:
|
||||||
|
raise Exception("Groq API returned invalid response format")
|
||||||
|
|
||||||
full_reply_content = response.choices[0].message.content
|
full_reply_content = response.choices[0].message.content
|
||||||
|
if full_reply_content is None:
|
||||||
|
raise Exception("Groq API returned None content")
|
||||||
|
|
||||||
print(colored(full_reply_content, "blue", "on_white"), end="")
|
print(colored(full_reply_content, "blue", "on_white"), end="")
|
||||||
print()
|
print()
|
||||||
return full_reply_content
|
return full_reply_content
|
||||||
@@ -133,7 +241,16 @@ async def _achat_completion_stream_mixtral(messages: list[dict]) -> str:
|
|||||||
else:
|
else:
|
||||||
raise Exception("failed")
|
raise Exception("failed")
|
||||||
|
|
||||||
|
# 检查响应是否有效
|
||||||
|
if not stream.choices or len(stream.choices) == 0:
|
||||||
|
raise Exception("Mistral API returned empty response")
|
||||||
|
if not stream.choices[0] or not stream.choices[0].message:
|
||||||
|
raise Exception("Mistral API returned invalid response format")
|
||||||
|
|
||||||
full_reply_content = stream.choices[0].message.content
|
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(colored(full_reply_content, "blue", "on_white"), end="")
|
||||||
print()
|
print()
|
||||||
return full_reply_content
|
return full_reply_content
|
||||||
@@ -144,7 +261,7 @@ async def _achat_completion_stream_gpt35(messages: list[dict]) -> str:
|
|||||||
messages=messages,
|
messages=messages,
|
||||||
max_tokens=4096,
|
max_tokens=4096,
|
||||||
temperature=0.3,
|
temperature=0.3,
|
||||||
timeout=30,
|
timeout=600,
|
||||||
model="gpt-3.5-turbo-16k",
|
model="gpt-3.5-turbo-16k",
|
||||||
stream=True,
|
stream=True,
|
||||||
)
|
)
|
||||||
@@ -156,8 +273,9 @@ async def _achat_completion_stream_gpt35(messages: list[dict]) -> str:
|
|||||||
async for chunk in response:
|
async for chunk in response:
|
||||||
collected_chunks.append(chunk) # save the event response
|
collected_chunks.append(chunk) # save the event response
|
||||||
choices = chunk.choices
|
choices = chunk.choices
|
||||||
if len(choices) > 0:
|
if len(choices) > 0 and choices[0] is not None:
|
||||||
chunk_message = chunk.choices[0].delta
|
chunk_message = choices[0].delta
|
||||||
|
if chunk_message is not None:
|
||||||
collected_messages.append(chunk_message) # save the message
|
collected_messages.append(chunk_message) # save the message
|
||||||
if chunk_message.content:
|
if chunk_message.content:
|
||||||
print(
|
print(
|
||||||
@@ -169,19 +287,23 @@ async def _achat_completion_stream_gpt35(messages: list[dict]) -> str:
|
|||||||
full_reply_content = "".join(
|
full_reply_content = "".join(
|
||||||
[m.content or "" for m in collected_messages if m is not None]
|
[m.content or "" for m in collected_messages if m is not None]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 检查最终结果是否为空
|
||||||
|
if not full_reply_content or full_reply_content.strip() == "":
|
||||||
|
raise Exception("Stream API (gpt-3.5) returned empty content")
|
||||||
|
|
||||||
return full_reply_content
|
return full_reply_content
|
||||||
|
|
||||||
|
|
||||||
async def _achat_completion_json(messages: list[dict]) -> str:
|
def _achat_completion_json(messages: list[dict] ) -> str:
|
||||||
max_attempts = 5
|
max_attempts = 5
|
||||||
|
|
||||||
for attempt in range(max_attempts):
|
for attempt in range(max_attempts):
|
||||||
try:
|
try:
|
||||||
response = await async_client.chat.completions.create(
|
response = async_client.chat.completions.create(
|
||||||
messages=messages,
|
messages=messages,
|
||||||
max_tokens=4096,
|
max_tokens=4096,
|
||||||
temperature=0.3,
|
temperature=0.3,
|
||||||
timeout=30,
|
timeout=600,
|
||||||
model=MODEL,
|
model=MODEL,
|
||||||
response_format={"type": "json_object"},
|
response_format={"type": "json_object"},
|
||||||
)
|
)
|
||||||
@@ -192,7 +314,16 @@ async def _achat_completion_json(messages: list[dict]) -> str:
|
|||||||
else:
|
else:
|
||||||
raise Exception("failed")
|
raise Exception("failed")
|
||||||
|
|
||||||
|
# 检查响应是否有效
|
||||||
|
if not response.choices or len(response.choices) == 0:
|
||||||
|
raise Exception("OpenAI API returned empty response")
|
||||||
|
if not response.choices[0] or not response.choices[0].message:
|
||||||
|
raise Exception("OpenAI API returned invalid response format")
|
||||||
|
|
||||||
full_reply_content = response.choices[0].message.content
|
full_reply_content = response.choices[0].message.content
|
||||||
|
if full_reply_content is None:
|
||||||
|
raise Exception("OpenAI API returned None content")
|
||||||
|
|
||||||
print(colored(full_reply_content, "blue", "on_white"), end="")
|
print(colored(full_reply_content, "blue", "on_white"), end="")
|
||||||
print()
|
print()
|
||||||
return full_reply_content
|
return full_reply_content
|
||||||
@@ -211,8 +342,9 @@ async def _achat_completion_stream(messages: list[dict]) -> str:
|
|||||||
async for chunk in response:
|
async for chunk in response:
|
||||||
collected_chunks.append(chunk) # save the event response
|
collected_chunks.append(chunk) # save the event response
|
||||||
choices = chunk.choices
|
choices = chunk.choices
|
||||||
if len(choices) > 0:
|
if len(choices) > 0 and choices[0] is not None:
|
||||||
chunk_message = chunk.choices[0].delta
|
chunk_message = choices[0].delta
|
||||||
|
if chunk_message is not None:
|
||||||
collected_messages.append(chunk_message) # save the message
|
collected_messages.append(chunk_message) # save the message
|
||||||
if chunk_message.content:
|
if chunk_message.content:
|
||||||
print(
|
print(
|
||||||
@@ -224,6 +356,11 @@ async def _achat_completion_stream(messages: list[dict]) -> str:
|
|||||||
full_reply_content = "".join(
|
full_reply_content = "".join(
|
||||||
[m.content or "" for m in collected_messages if m is not None]
|
[m.content or "" for m in collected_messages if m is not None]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 检查最终结果是否为空
|
||||||
|
if not full_reply_content or full_reply_content.strip() == "":
|
||||||
|
raise Exception("Stream API returned empty content")
|
||||||
|
|
||||||
return full_reply_content
|
return full_reply_content
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print_colored(f"OpenAI API error in _achat_completion_stream: {str(e)}", "red")
|
print_colored(f"OpenAI API error in _achat_completion_stream: {str(e)}", "red")
|
||||||
@@ -233,7 +370,17 @@ async def _achat_completion_stream(messages: list[dict]) -> str:
|
|||||||
def _chat_completion(messages: list[dict]) -> str:
|
def _chat_completion(messages: list[dict]) -> str:
|
||||||
try:
|
try:
|
||||||
rsp = client.chat.completions.create(**_cons_kwargs(messages))
|
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
|
content = rsp.choices[0].message.content
|
||||||
|
if content is None:
|
||||||
|
raise Exception("OpenAI API returned None content")
|
||||||
|
|
||||||
return content
|
return content
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print_colored(f"OpenAI API error in _chat_completion: {str(e)}", "red")
|
print_colored(f"OpenAI API error in _chat_completion: {str(e)}", "red")
|
||||||
@@ -245,7 +392,7 @@ def _cons_kwargs(messages: list[dict]) -> dict:
|
|||||||
"messages": messages,
|
"messages": messages,
|
||||||
"max_tokens": 2000,
|
"max_tokens": 2000,
|
||||||
"temperature": 0.3,
|
"temperature": 0.3,
|
||||||
"timeout": 15,
|
"timeout": 600,
|
||||||
}
|
}
|
||||||
kwargs_mode = {"model": MODEL}
|
kwargs_mode = {"model": MODEL}
|
||||||
kwargs.update(kwargs_mode)
|
kwargs.update(kwargs_mode)
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ PROMPT_ABILITY_REQUIREMENT_GENERATION = """
|
|||||||
## Instruction
|
## 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.
|
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 (The general goal for the collaboration plan, "Current Task" is just one of its substep)
|
||||||
{General_Goal}
|
{General_Goal}
|
||||||
|
|
||||||
@@ -49,6 +51,8 @@ PROMPT_AGENT_ABILITY_SCORING = """
|
|||||||
## Instruction
|
## 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.
|
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
|
## AgentBoard
|
||||||
{Agent_Board}
|
{Agent_Board}
|
||||||
|
|
||||||
@@ -133,5 +137,6 @@ def AgentSelectModify_init(stepTask, General_Goal, Agent_Board):
|
|||||||
|
|
||||||
|
|
||||||
def AgentSelectModify_addAspect(aspectList, Agent_Board):
|
def AgentSelectModify_addAspect(aspectList, Agent_Board):
|
||||||
scoreTable = agentAbilityScoring(Agent_Board, aspectList)
|
newAspect = aspectList[-1]
|
||||||
|
scoreTable = agentAbilityScoring(Agent_Board, [newAspect])
|
||||||
return scoreTable
|
return scoreTable
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ PROMPT_AGENT_SELECTION_GENERATION = """
|
|||||||
## Instruction
|
## 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".
|
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 (Specify the general goal for the collaboration plan)
|
||||||
{General_Goal}
|
{General_Goal}
|
||||||
|
|
||||||
|
|||||||
@@ -1,55 +1,53 @@
|
|||||||
from AgentCoord.PlanEngine.planOutline_Generator import generate_PlanOutline
|
from AgentCoord.PlanEngine.planOutline_Generator import generate_PlanOutline
|
||||||
from AgentCoord.PlanEngine.AgentSelection_Generator import (
|
# from AgentCoord.PlanEngine.AgentSelection_Generator import (
|
||||||
generate_AgentSelection,
|
# generate_AgentSelection,
|
||||||
)
|
# )
|
||||||
from AgentCoord.PlanEngine.taskProcess_Generator import generate_TaskProcess
|
|
||||||
import AgentCoord.util as util
|
|
||||||
|
|
||||||
|
|
||||||
def generate_basePlan(
|
def generate_basePlan(
|
||||||
General_Goal, Agent_Board, AgentProfile_Dict, InitialObject_List
|
General_Goal, Agent_Board, AgentProfile_Dict, InitialObject_List
|
||||||
):
|
):
|
||||||
basePlan = {
|
"""
|
||||||
"Initial Input Object": InitialObject_List,
|
优化模式:生成大纲 + 智能体选择,但不生成任务流程
|
||||||
"Collaboration Process": [],
|
优化用户体验:
|
||||||
}
|
1. 快速生成大纲和分配智能体
|
||||||
|
2. 用户可以看到完整的大纲和智能体图标
|
||||||
|
3. TaskProcess由前端通过 fillStepTask API 异步填充
|
||||||
|
|
||||||
|
"""
|
||||||
|
# 参数保留以保持接口兼容性
|
||||||
|
_ = AgentProfile_Dict
|
||||||
PlanOutline = generate_PlanOutline(
|
PlanOutline = generate_PlanOutline(
|
||||||
InitialObject_List=[], General_Goal=General_Goal
|
InitialObject_List=InitialObject_List, General_Goal=General_Goal
|
||||||
)
|
)
|
||||||
|
|
||||||
|
basePlan = {
|
||||||
|
"General Goal": General_Goal,
|
||||||
|
"Initial Input Object": InitialObject_List,
|
||||||
|
"Collaboration Process": []
|
||||||
|
}
|
||||||
|
|
||||||
for stepItem in PlanOutline:
|
for stepItem in PlanOutline:
|
||||||
Current_Task = {
|
# # 为每个步骤分配智能体
|
||||||
"TaskName": stepItem["StepName"],
|
# Current_Task = {
|
||||||
"InputObject_List": stepItem["InputObject_List"],
|
# "TaskName": stepItem["StepName"],
|
||||||
"OutputObject": stepItem["OutputObject"],
|
# "InputObject_List": stepItem["InputObject_List"],
|
||||||
"TaskContent": stepItem["TaskContent"],
|
# "OutputObject": stepItem["OutputObject"],
|
||||||
|
# "TaskContent": stepItem["TaskContent"],
|
||||||
|
# }
|
||||||
|
# AgentSelection = generate_AgentSelection(
|
||||||
|
# General_Goal=General_Goal,
|
||||||
|
# Current_Task=Current_Task,
|
||||||
|
# Agent_Board=Agent_Board,
|
||||||
|
# )
|
||||||
|
|
||||||
|
# 添加智能体选择,但不添加任务流程
|
||||||
|
stepItem["AgentSelection"] = []
|
||||||
|
stepItem["TaskProcess"] = [] # 空数组,由前端异步填充
|
||||||
|
stepItem["Collaboration_Brief_frontEnd"] = {
|
||||||
|
"template": "",
|
||||||
|
"data": {}
|
||||||
}
|
}
|
||||||
AgentSelection = generate_AgentSelection(
|
|
||||||
General_Goal=General_Goal,
|
|
||||||
Current_Task=Current_Task,
|
|
||||||
Agent_Board=Agent_Board,
|
|
||||||
)
|
|
||||||
Current_Task_Description = {
|
|
||||||
"TaskName": stepItem["StepName"],
|
|
||||||
"AgentInvolved": [
|
|
||||||
{"Name": name, "Profile": AgentProfile_Dict[name]}
|
|
||||||
for name in AgentSelection
|
|
||||||
],
|
|
||||||
"InputObject_List": stepItem["InputObject_List"],
|
|
||||||
"OutputObject": stepItem["OutputObject"],
|
|
||||||
"CurrentTaskDescription": util.generate_template_sentence_for_CollaborationBrief(
|
|
||||||
stepItem["InputObject_List"],
|
|
||||||
stepItem["OutputObject"],
|
|
||||||
AgentSelection,
|
|
||||||
stepItem["TaskContent"],
|
|
||||||
),
|
|
||||||
}
|
|
||||||
TaskProcess = generate_TaskProcess(
|
|
||||||
General_Goal=General_Goal,
|
|
||||||
Current_Task_Description=Current_Task_Description,
|
|
||||||
)
|
|
||||||
# add the generated AgentSelection and TaskProcess to the stepItem
|
|
||||||
stepItem["AgentSelection"] = AgentSelection
|
|
||||||
stepItem["TaskProcess"] = TaskProcess
|
|
||||||
basePlan["Collaboration Process"].append(stepItem)
|
basePlan["Collaboration Process"].append(stepItem)
|
||||||
basePlan["General Goal"] = General_Goal
|
|
||||||
return basePlan
|
return basePlan
|
||||||
@@ -9,6 +9,8 @@ PROMPT_PLAN_OUTLINE_BRANCHING = """
|
|||||||
Based on "Existing Steps", your task is to comeplete the "Remaining Steps" for the plan for "General Goal".
|
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.
|
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 (Specify the general goal for the plan)
|
||||||
{General_Goal}
|
{General_Goal}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ PROMPT_TASK_PROCESS_BRANCHING = """
|
|||||||
Based on "Existing Steps", your task is to comeplete the "Remaining Steps" for the "Task for Current Step".
|
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.
|
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 (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}
|
{General_Goal}
|
||||||
|
|
||||||
@@ -55,27 +57,40 @@ Note: "Modification Requirement" specifies how to modify the "Baseline Completio
|
|||||||
"ID": "Action4",
|
"ID": "Action4",
|
||||||
"ActionType": "Propose",
|
"ActionType": "Propose",
|
||||||
"AgentName": "Mia",
|
"AgentName": "Mia",
|
||||||
"Description": "Propose psychological theories on love and attachment that could be applied to AI's emotional development.",
|
"Description": "提议关于人工智能情感发展的心理学理论,重点关注爱与依恋的概念。",
|
||||||
"ImportantInput": [
|
"ImportantInput": [
|
||||||
"InputObject:Story Outline"
|
"InputObject:Story Outline"
|
||||||
]
|
]
|
||||||
}},
|
}},
|
||||||
{{
|
{{
|
||||||
"ID": "Action5",
|
"ID": "Action5",
|
||||||
"ActionType": "Propose",
|
"ActionType": "Critique",
|
||||||
"AgentName": "Noah",
|
"AgentName": "Noah",
|
||||||
"Description": "Propose ethical considerations and philosophical questions regarding AI's capacity for love.",
|
"Description": "对Mia提出的心理学理论进行批判性评估,分析其在AI情感发展场景中的适用性和局限性。",
|
||||||
"ImportantInput": []
|
"ImportantInput": [
|
||||||
|
"ActionResult:Action4"
|
||||||
|
]
|
||||||
}},
|
}},
|
||||||
{{
|
{{
|
||||||
"ID": "Action6",
|
"ID": "Action6",
|
||||||
"ActionType": "Finalize",
|
"ActionType": "Improve",
|
||||||
"AgentName": "Liam",
|
"AgentName": "Liam",
|
||||||
"Description": "Combine the poetic elements and ethical considerations into a cohesive set of core love elements for the story.",
|
"Description": "基于Noah的批判性反馈,改进和完善心理学理论框架,使其更贴合AI情感发展的实际需求。",
|
||||||
"ImportantInput": [
|
"ImportantInput": [
|
||||||
"ActionResult:Action1",
|
"ActionResult:Action4",
|
||||||
"ActionResult:Action5"
|
"ActionResult:Action5"
|
||||||
]
|
]
|
||||||
|
}},
|
||||||
|
{{
|
||||||
|
"ID": "Action7",
|
||||||
|
"ActionType": "Finalize",
|
||||||
|
"AgentName": "Mia",
|
||||||
|
"Description": "综合所有提议、批判和改进意见,整合并提交最终的AI情感发展心理学理论框架。",
|
||||||
|
"ImportantInput": [
|
||||||
|
"ActionResult:Action4",
|
||||||
|
"ActionResult:Action5",
|
||||||
|
"ActionResult:Action6"
|
||||||
|
]
|
||||||
}}
|
}}
|
||||||
]
|
]
|
||||||
}}
|
}}
|
||||||
@@ -84,7 +99,12 @@ Note: "Modification Requirement" specifies how to modify the "Baseline Completio
|
|||||||
ImportantInput: Specify if there is any previous result that should be taken special consideration during the execution the action. Should be of format "InputObject:xx" or "ActionResult:xx".
|
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.
|
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".
|
AgentName: Specify the agent who will perform the action, You CAN ONLY USE THE NAME APPEARS IN "AgentInvolved".
|
||||||
ActionType: Specify the type of action, note that only the last action can be of type "Finalize", and the last action must be "Finalize".
|
ActionType: Specify the type of action. **CRITICAL REQUIREMENTS:**
|
||||||
|
1. The "Remaining Steps" MUST include ALL FOUR action types in the following order: Propose -> Critique -> Improve -> Finalize
|
||||||
|
2. Each action type (Propose, Critique, Improve, Finalize) MUST appear at least once
|
||||||
|
3. The actions must follow the sequence: Propose actions first, then Critique actions, then Improve actions, and Finalize must be the last action
|
||||||
|
4. Even if only one agent is involved in a phase, that phase must still have its corresponding action type
|
||||||
|
5. The last action must ALWAYS be of type "Finalize"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ PROMPT_PLAN_OUTLINE_GENERATION = """
|
|||||||
## Instruction
|
## Instruction
|
||||||
Based on "Output Format Example", "General Goal", and "Initial Key Object List", output a formatted "Plan_Outline".
|
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)
|
## 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}
|
{InitialObject_List}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ PROMPT_TASK_PROCESS_GENERATION = """
|
|||||||
## Instruction
|
## 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".
|
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 (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}
|
{General_Goal}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ You are within a multi-agent collaboration for the "Current Task".
|
|||||||
Now it's your turn to take action. Read the "Context Information" and take your action following "Instruction for Your Current Action".
|
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*
|
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
|
## Context Information
|
||||||
|
|
||||||
### General Goal (The "Current Task" is indeed a substep of the general goal)
|
### General Goal (The "Current Task" is indeed a substep of the general goal)
|
||||||
@@ -81,16 +83,33 @@ class BaseAction():
|
|||||||
action_Record += PROMPT_TEMPLATE_ACTION_RECORD.format(AgentName = actionInfo["AgentName"], Action_Description = actionInfo["AgentName"], Action_Result = actionInfo["Action_Result"], Important_Mark = 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
|
# Handle missing agent profiles gracefully
|
||||||
|
model_config = None
|
||||||
if agentName not in AgentProfile_Dict:
|
if agentName not in AgentProfile_Dict:
|
||||||
print_colored(text=f"Warning: Agent '{agentName}' not found in AgentProfile_Dict. Using default profile.", text_color="yellow")
|
print_colored(text=f"Warning: Agent '{agentName}' not found in AgentProfile_Dict. Using default profile.", text_color="yellow")
|
||||||
agentProfile = f"AI Agent named {agentName}"
|
agentProfile = f"AI Agent named {agentName}"
|
||||||
else:
|
else:
|
||||||
agentProfile = AgentProfile_Dict[agentName]
|
# agentProfile = AgentProfile_Dict[agentName]
|
||||||
|
agent_config = AgentProfile_Dict[agentName]
|
||||||
prompt = PROMPT_TEMPLATE_TAKE_ACTION_BASE.format(agentName = agentName, agentProfile = agentProfile, General_Goal = General_Goal, Current_Task_Description = TaskDescription, Input_Objects = inputObject_Record, History_Action = action_Record, Action_Description = self.info["Description"], Action_Custom_Note = self.Action_Custom_Note)
|
agentProfile = agent_config.get("profile",f"AI Agent named {agentName}")
|
||||||
|
if agent_config.get("useCustomAPI",False):
|
||||||
|
model_config = {
|
||||||
|
"apiModel":agent_config.get("apiModel"),
|
||||||
|
"apiUrl":agent_config.get("apiUrl"),
|
||||||
|
"apiKey":agent_config.get("apiKey"),
|
||||||
|
}
|
||||||
|
prompt = PROMPT_TEMPLATE_TAKE_ACTION_BASE.format(
|
||||||
|
agentName = agentName,
|
||||||
|
agentProfile = agentProfile,
|
||||||
|
General_Goal = General_Goal,
|
||||||
|
Current_Task_Description = TaskDescription,
|
||||||
|
Input_Objects = inputObject_Record,
|
||||||
|
History_Action = action_Record,
|
||||||
|
Action_Description = self.info["Description"],
|
||||||
|
Action_Custom_Note = self.Action_Custom_Note
|
||||||
|
)
|
||||||
print_colored(text = prompt, text_color="red")
|
print_colored(text = prompt, text_color="red")
|
||||||
messages = [{"role":"system", "content": prompt}]
|
messages = [{"role":"system", "content": prompt}]
|
||||||
ActionResult = LLM_Completion(messages,True,False)
|
ActionResult = LLM_Completion(messages,True,False,model_config=model_config)
|
||||||
ActionInfo_with_Result = copy.deepcopy(self.info)
|
ActionInfo_with_Result = copy.deepcopy(self.info)
|
||||||
ActionInfo_with_Result["Action_Result"] = ActionResult
|
ActionInfo_with_Result["Action_Result"] = ActionResult
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from AgentCoord.RehearsalEngine_V2.Action import BaseAction
|
from AgentCoord.RehearsalEngine_V2.Action import BaseAction
|
||||||
|
|
||||||
ACTION_CUSTOM_NOTE = '''
|
ACTION_CUSTOM_NOTE = '''
|
||||||
Note: Since you are in a conversation, your critique must be concise, clear and easy to read, don't overwhelm others. If you want to list some points, list at most 2 points.
|
注意:由于你在对话中,你的批评必须简洁、清晰且易于阅读,不要让人感到压力过大。如果你要列出一些观点,最多列出2点。
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ from AgentCoord.util.converter import read_outputObject_content
|
|||||||
from AgentCoord.RehearsalEngine_V2.Action import BaseAction
|
from AgentCoord.RehearsalEngine_V2.Action import BaseAction
|
||||||
|
|
||||||
ACTION_CUSTOM_NOTE = '''
|
ACTION_CUSTOM_NOTE = '''
|
||||||
Note: You can say something before you give the final content of {OutputName}. When you decide to give the final content of {OutputName}, it should be enclosed like this:
|
注意:你可以在给出{OutputName}的最终内容之前先说一些话。当你决定给出{OutputName}的最终内容时,应该这样包含:
|
||||||
```{OutputName}
|
```{OutputName}
|
||||||
(the content of {OutputName})
|
({OutputName}的内容)
|
||||||
```
|
```
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from AgentCoord.RehearsalEngine_V2.Action import BaseAction
|
from AgentCoord.RehearsalEngine_V2.Action import BaseAction
|
||||||
|
|
||||||
ACTION_CUSTOM_NOTE = '''
|
ACTION_CUSTOM_NOTE = '''
|
||||||
Note: You can say something before you provide the improved version of the content.
|
注意:你可以在提供改进版本的内容之前先说一些话。
|
||||||
The improved version you provide must be a completed version (e.g. if you provide a improved story, you should give completed story content, rather than just reporting where you have improved).
|
你提供的改进版本必须是完整的版本(例如,如果你提供改进的故事,你应该给出完整的故事内容,而不仅仅是报告你在哪里改进了)。
|
||||||
When you decide to give the improved version of the content, it should be start like this:
|
当你决定提供内容的改进版本时,应该这样开始:
|
||||||
|
|
||||||
## Improved version of xxx
|
## xxx的改进版本
|
||||||
(the improved version of the content)
|
(改进版本的内容)
|
||||||
```
|
```
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|||||||
608
backend/AgentCoord/RehearsalEngine_V2/ExecutePlan_Optimized.py
Normal file
@@ -0,0 +1,608 @@
|
|||||||
|
"""
|
||||||
|
优化版执行计划 - 支持动态追加步骤
|
||||||
|
在执行过程中可以接收新的步骤并追加到执行队列
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from typing import List, Dict, Set, Generator, Any
|
||||||
|
import AgentCoord.RehearsalEngine_V2.Action as Action
|
||||||
|
import AgentCoord.util as util
|
||||||
|
from termcolor import colored
|
||||||
|
from AgentCoord.RehearsalEngine_V2.execution_state import execution_state_manager
|
||||||
|
from AgentCoord.RehearsalEngine_V2.dynamic_execution_manager import dynamic_execution_manager
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== 配置参数 ====================
|
||||||
|
# 最大并发请求数
|
||||||
|
MAX_CONCURRENT_REQUESTS = 2
|
||||||
|
|
||||||
|
# 批次之间的延迟
|
||||||
|
BATCH_DELAY = 1.0
|
||||||
|
|
||||||
|
# 429错误重试次数和延迟
|
||||||
|
MAX_RETRIES = 3
|
||||||
|
RETRY_DELAY = 5.0
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== 限流器 ====================
|
||||||
|
class RateLimiter:
|
||||||
|
"""
|
||||||
|
异步限流器,控制并发请求数量
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, max_concurrent: int = MAX_CONCURRENT_REQUESTS):
|
||||||
|
self.semaphore = asyncio.Semaphore(max_concurrent)
|
||||||
|
self.max_concurrent = max_concurrent
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
await self.semaphore.acquire()
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, *args):
|
||||||
|
self.semaphore.release()
|
||||||
|
|
||||||
|
|
||||||
|
# 全局限流器实例
|
||||||
|
rate_limiter = RateLimiter()
|
||||||
|
|
||||||
|
|
||||||
|
def build_action_dependency_graph(TaskProcess: List[Dict]) -> Dict[int, List[int]]:
|
||||||
|
"""
|
||||||
|
构建动作依赖图
|
||||||
|
|
||||||
|
Args:
|
||||||
|
TaskProcess: 任务流程列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
依赖映射字典 {action_index: [dependent_action_indices]}
|
||||||
|
"""
|
||||||
|
dependency_map = {i: [] for i in range(len(TaskProcess))}
|
||||||
|
|
||||||
|
for i, action in enumerate(TaskProcess):
|
||||||
|
important_inputs = action.get('ImportantInput', [])
|
||||||
|
if not important_inputs:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查是否依赖其他动作的ActionResult
|
||||||
|
for j, prev_action in enumerate(TaskProcess):
|
||||||
|
if i == j:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 判断是否依赖前一个动作的结果
|
||||||
|
if any(
|
||||||
|
inp.startswith('ActionResult:') and
|
||||||
|
inp == f'ActionResult:{prev_action["ID"]}'
|
||||||
|
for inp in important_inputs
|
||||||
|
):
|
||||||
|
dependency_map[i].append(j)
|
||||||
|
|
||||||
|
return dependency_map
|
||||||
|
|
||||||
|
|
||||||
|
def get_parallel_batches(TaskProcess: List[Dict], dependency_map: Dict[int, List[int]]) -> List[List[int]]:
|
||||||
|
"""
|
||||||
|
将动作分为多个批次,每批内部可以并行执行
|
||||||
|
|
||||||
|
Args:
|
||||||
|
TaskProcess: 任务流程列表
|
||||||
|
dependency_map: 依赖图
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
批次列表 [[batch1_indices], [batch2_indices], ...]
|
||||||
|
"""
|
||||||
|
batches = []
|
||||||
|
completed: Set[int] = set()
|
||||||
|
|
||||||
|
while len(completed) < len(TaskProcess):
|
||||||
|
# 找出所有依赖已满足的动作
|
||||||
|
ready_to_run = [
|
||||||
|
i for i in range(len(TaskProcess))
|
||||||
|
if i not in completed and
|
||||||
|
all(dep in completed for dep in dependency_map[i])
|
||||||
|
]
|
||||||
|
|
||||||
|
if not ready_to_run:
|
||||||
|
# 避免死循环
|
||||||
|
remaining = [i for i in range(len(TaskProcess)) if i not in completed]
|
||||||
|
if remaining:
|
||||||
|
print(colored(f"警告: 检测到循环依赖,强制串行执行: {remaining}", "yellow"))
|
||||||
|
ready_to_run = remaining[:1]
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
batches.append(ready_to_run)
|
||||||
|
completed.update(ready_to_run)
|
||||||
|
|
||||||
|
return batches
|
||||||
|
|
||||||
|
|
||||||
|
async def execute_single_action_async(
|
||||||
|
ActionInfo: Dict,
|
||||||
|
General_Goal: str,
|
||||||
|
TaskDescription: str,
|
||||||
|
OutputName: str,
|
||||||
|
KeyObjects: Dict,
|
||||||
|
ActionHistory: List,
|
||||||
|
agentName: str,
|
||||||
|
AgentProfile_Dict: Dict,
|
||||||
|
InputName_List: List[str]
|
||||||
|
) -> Dict:
|
||||||
|
"""
|
||||||
|
异步执行单个动作
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ActionInfo: 动作信息
|
||||||
|
General_Goal: 总体目标
|
||||||
|
TaskDescription: 任务描述
|
||||||
|
OutputName: 输出对象名称
|
||||||
|
KeyObjects: 关键对象字典
|
||||||
|
ActionHistory: 动作历史
|
||||||
|
agentName: 智能体名称
|
||||||
|
AgentProfile_Dict: 智能体配置字典
|
||||||
|
InputName_List: 输入名称列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
动作执行结果
|
||||||
|
"""
|
||||||
|
actionType = ActionInfo["ActionType"]
|
||||||
|
|
||||||
|
# 创建动作实例
|
||||||
|
if actionType in Action.customAction_Dict:
|
||||||
|
currentAction = Action.customAction_Dict[actionType](
|
||||||
|
info=ActionInfo,
|
||||||
|
OutputName=OutputName,
|
||||||
|
KeyObjects=KeyObjects,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
currentAction = Action.BaseAction(
|
||||||
|
info=ActionInfo,
|
||||||
|
OutputName=OutputName,
|
||||||
|
KeyObjects=KeyObjects,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 在线程池中运行,避免阻塞事件循环
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
ActionInfo_with_Result = await loop.run_in_executor(
|
||||||
|
None,
|
||||||
|
lambda: currentAction.run(
|
||||||
|
General_Goal=General_Goal,
|
||||||
|
TaskDescription=TaskDescription,
|
||||||
|
agentName=agentName,
|
||||||
|
AgentProfile_Dict=AgentProfile_Dict,
|
||||||
|
InputName_List=InputName_List,
|
||||||
|
OutputName=OutputName,
|
||||||
|
KeyObjects=KeyObjects,
|
||||||
|
ActionHistory=ActionHistory,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return ActionInfo_with_Result
|
||||||
|
|
||||||
|
|
||||||
|
async def execute_step_async_streaming(
|
||||||
|
stepDescrip: Dict,
|
||||||
|
General_Goal: str,
|
||||||
|
AgentProfile_Dict: Dict,
|
||||||
|
KeyObjects: Dict,
|
||||||
|
step_index: int,
|
||||||
|
total_steps: int
|
||||||
|
) -> Generator[Dict, None, None]:
|
||||||
|
"""
|
||||||
|
异步执行单个步骤,支持流式返回
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stepDescrip: 步骤描述
|
||||||
|
General_Goal: 总体目标
|
||||||
|
AgentProfile_Dict: 智能体配置字典
|
||||||
|
KeyObjects: 关键对象字典
|
||||||
|
step_index: 步骤索引
|
||||||
|
total_steps: 总步骤数
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
执行事件字典
|
||||||
|
"""
|
||||||
|
# 准备步骤信息
|
||||||
|
StepName = (
|
||||||
|
util.camel_case_to_normal(stepDescrip["StepName"])
|
||||||
|
if util.is_camel_case(stepDescrip["StepName"])
|
||||||
|
else stepDescrip["StepName"]
|
||||||
|
)
|
||||||
|
TaskContent = stepDescrip["TaskContent"]
|
||||||
|
InputName_List = (
|
||||||
|
[
|
||||||
|
(
|
||||||
|
util.camel_case_to_normal(obj)
|
||||||
|
if util.is_camel_case(obj)
|
||||||
|
else obj
|
||||||
|
)
|
||||||
|
for obj in stepDescrip["InputObject_List"]
|
||||||
|
]
|
||||||
|
if stepDescrip["InputObject_List"] is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
OutputName = (
|
||||||
|
util.camel_case_to_normal(stepDescrip["OutputObject"])
|
||||||
|
if util.is_camel_case(stepDescrip["OutputObject"])
|
||||||
|
else stepDescrip["OutputObject"]
|
||||||
|
)
|
||||||
|
Agent_List = stepDescrip["AgentSelection"]
|
||||||
|
TaskProcess = stepDescrip["TaskProcess"]
|
||||||
|
|
||||||
|
TaskDescription = (
|
||||||
|
util.converter.generate_template_sentence_for_CollaborationBrief(
|
||||||
|
input_object_list=InputName_List,
|
||||||
|
output_object=OutputName,
|
||||||
|
agent_list=Agent_List,
|
||||||
|
step_task=TaskContent,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 初始化日志节点
|
||||||
|
inputObject_Record = [
|
||||||
|
{InputName: KeyObjects[InputName]} for InputName in InputName_List
|
||||||
|
]
|
||||||
|
stepLogNode = {
|
||||||
|
"LogNodeType": "step",
|
||||||
|
"NodeId": StepName,
|
||||||
|
"InputName_List": InputName_List,
|
||||||
|
"OutputName": OutputName,
|
||||||
|
"chatLog": [],
|
||||||
|
"inputObject_Record": inputObject_Record,
|
||||||
|
}
|
||||||
|
objectLogNode = {
|
||||||
|
"LogNodeType": "object",
|
||||||
|
"NodeId": OutputName,
|
||||||
|
"content": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 返回步骤开始事件
|
||||||
|
yield {
|
||||||
|
"type": "step_start",
|
||||||
|
"step_index": step_index,
|
||||||
|
"total_steps": total_steps,
|
||||||
|
"step_name": StepName,
|
||||||
|
"task_description": TaskDescription,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 构建动作依赖图
|
||||||
|
dependency_map = build_action_dependency_graph(TaskProcess)
|
||||||
|
batches = get_parallel_batches(TaskProcess, dependency_map)
|
||||||
|
|
||||||
|
ActionHistory = []
|
||||||
|
total_actions = len(TaskProcess)
|
||||||
|
completed_actions = 0
|
||||||
|
|
||||||
|
util.print_colored(
|
||||||
|
f"📋 步骤 {step_index + 1}/{total_steps}: {StepName} ({total_actions} 个动作, 分 {len(batches)} 批并行执行)",
|
||||||
|
text_color="cyan"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 分批执行动作
|
||||||
|
for batch_index, batch_indices in enumerate(batches):
|
||||||
|
# 在每个批次执行前检查暂停状态
|
||||||
|
should_continue = await execution_state_manager.async_check_pause()
|
||||||
|
if not should_continue:
|
||||||
|
util.print_colored("🛑 用户请求停止执行", "red")
|
||||||
|
return
|
||||||
|
|
||||||
|
batch_size = len(batch_indices)
|
||||||
|
|
||||||
|
if batch_size > 1:
|
||||||
|
util.print_colored(
|
||||||
|
f"🚦 批次 {batch_index + 1}/{len(batches)}: 并行执行 {batch_size} 个动作",
|
||||||
|
text_color="blue"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
util.print_colored(
|
||||||
|
f"🔄 动作 {completed_actions + 1}/{total_actions}: 串行执行",
|
||||||
|
text_color="yellow"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 并行执行当前批次的所有动作
|
||||||
|
tasks = [
|
||||||
|
execute_single_action_async(
|
||||||
|
TaskProcess[i],
|
||||||
|
General_Goal=General_Goal,
|
||||||
|
TaskDescription=TaskDescription,
|
||||||
|
OutputName=OutputName,
|
||||||
|
KeyObjects=KeyObjects,
|
||||||
|
ActionHistory=ActionHistory,
|
||||||
|
agentName=TaskProcess[i]["AgentName"],
|
||||||
|
AgentProfile_Dict=AgentProfile_Dict,
|
||||||
|
InputName_List=InputName_List
|
||||||
|
)
|
||||||
|
for i in batch_indices
|
||||||
|
]
|
||||||
|
|
||||||
|
# 等待当前批次完成
|
||||||
|
batch_results = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
# 逐个返回结果
|
||||||
|
for i, result in enumerate(batch_results):
|
||||||
|
action_index_in_batch = batch_indices[i]
|
||||||
|
completed_actions += 1
|
||||||
|
|
||||||
|
util.print_colored(
|
||||||
|
f"✅ 动作 {completed_actions}/{total_actions} 完成: {result['ActionType']} by {result['AgentName']}",
|
||||||
|
text_color="green"
|
||||||
|
)
|
||||||
|
|
||||||
|
ActionHistory.append(result)
|
||||||
|
|
||||||
|
# 立即返回该动作结果
|
||||||
|
yield {
|
||||||
|
"type": "action_complete",
|
||||||
|
"step_index": step_index,
|
||||||
|
"step_name": StepName,
|
||||||
|
"action_index": action_index_in_batch,
|
||||||
|
"total_actions": total_actions,
|
||||||
|
"completed_actions": completed_actions,
|
||||||
|
"action_result": result,
|
||||||
|
"batch_info": {
|
||||||
|
"batch_index": batch_index,
|
||||||
|
"batch_size": batch_size,
|
||||||
|
"is_parallel": batch_size > 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 步骤完成
|
||||||
|
objectLogNode["content"] = KeyObjects[OutputName]
|
||||||
|
stepLogNode["ActionHistory"] = ActionHistory
|
||||||
|
|
||||||
|
yield {
|
||||||
|
"type": "step_complete",
|
||||||
|
"step_index": step_index,
|
||||||
|
"step_name": StepName,
|
||||||
|
"step_log_node": stepLogNode,
|
||||||
|
"object_log_node": objectLogNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def executePlan_streaming_dynamic(
|
||||||
|
plan: Dict,
|
||||||
|
num_StepToRun: int,
|
||||||
|
RehearsalLog: List,
|
||||||
|
AgentProfile_Dict: Dict,
|
||||||
|
existingKeyObjects: Dict = None,
|
||||||
|
execution_id: str = None
|
||||||
|
) -> Generator[str, None, None]:
|
||||||
|
"""
|
||||||
|
动态执行计划,支持在执行过程中追加新步骤
|
||||||
|
|
||||||
|
Args:
|
||||||
|
plan: 执行计划
|
||||||
|
num_StepToRun: 要运行的步骤数
|
||||||
|
RehearsalLog: 已执行的历史记录
|
||||||
|
AgentProfile_Dict: 智能体配置
|
||||||
|
existingKeyObjects: 已存在的KeyObjects
|
||||||
|
execution_id: 执行ID(用于动态追加步骤)
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
SSE格式的事件字符串
|
||||||
|
"""
|
||||||
|
# 初始化执行状态
|
||||||
|
general_goal = plan.get("General Goal", "")
|
||||||
|
execution_state_manager.start_execution(general_goal)
|
||||||
|
|
||||||
|
print(colored(f"⏸️ 执行状态管理器已启动,支持暂停/恢复", "green"))
|
||||||
|
|
||||||
|
# 准备执行
|
||||||
|
KeyObjects = existingKeyObjects.copy() if existingKeyObjects else {}
|
||||||
|
finishedStep_index = -1
|
||||||
|
|
||||||
|
for logNode in RehearsalLog:
|
||||||
|
if logNode["LogNodeType"] == "step":
|
||||||
|
finishedStep_index += 1
|
||||||
|
if logNode["LogNodeType"] == "object":
|
||||||
|
KeyObjects[logNode["NodeId"]] = logNode["content"]
|
||||||
|
|
||||||
|
if existingKeyObjects:
|
||||||
|
print(colored(f"📦 使用已存在的 KeyObjects: {list(existingKeyObjects.keys())}", "cyan"))
|
||||||
|
|
||||||
|
# 确定要运行的步骤范围
|
||||||
|
if num_StepToRun is None:
|
||||||
|
run_to = len(plan["Collaboration Process"])
|
||||||
|
else:
|
||||||
|
run_to = (finishedStep_index + 1) + num_StepToRun
|
||||||
|
|
||||||
|
steps_to_run = plan["Collaboration Process"][(finishedStep_index + 1): run_to]
|
||||||
|
|
||||||
|
# 使用动态执行管理器
|
||||||
|
if execution_id:
|
||||||
|
# 初始化执行管理器,使用传入的execution_id
|
||||||
|
actual_execution_id = dynamic_execution_manager.start_execution(general_goal, steps_to_run, execution_id)
|
||||||
|
print(colored(f"🚀 开始执行计划(动态模式),共 {len(steps_to_run)} 个步骤,执行ID: {actual_execution_id}", "cyan"))
|
||||||
|
else:
|
||||||
|
print(colored(f"🚀 开始执行计划(流式推送),共 {len(steps_to_run)} 个步骤", "cyan"))
|
||||||
|
|
||||||
|
total_steps = len(steps_to_run)
|
||||||
|
|
||||||
|
# 使用队列实现流式推送
|
||||||
|
async def produce_events(queue: asyncio.Queue):
|
||||||
|
"""异步生产者"""
|
||||||
|
try:
|
||||||
|
step_index = 0
|
||||||
|
|
||||||
|
if execution_id:
|
||||||
|
# 动态模式:循环获取下一个步骤
|
||||||
|
# 等待新步骤的最大次数(避免无限等待)
|
||||||
|
max_empty_wait_cycles = 5 # 最多等待60次,每次等待1秒
|
||||||
|
empty_wait_count = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# 检查暂停状态
|
||||||
|
should_continue = await execution_state_manager.async_check_pause()
|
||||||
|
if not should_continue:
|
||||||
|
print(colored("🛑 用户请求停止执行", "red"))
|
||||||
|
await queue.put({
|
||||||
|
"type": "error",
|
||||||
|
"message": "执行已被用户停止"
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
|
# 获取下一个步骤
|
||||||
|
stepDescrip = dynamic_execution_manager.get_next_step(execution_id)
|
||||||
|
|
||||||
|
if stepDescrip is None:
|
||||||
|
# 没有更多步骤了,检查是否应该继续等待
|
||||||
|
empty_wait_count += 1
|
||||||
|
|
||||||
|
# 获取执行信息
|
||||||
|
execution_info = dynamic_execution_manager.get_execution_info(execution_id)
|
||||||
|
|
||||||
|
if execution_info:
|
||||||
|
queue_total_steps = execution_info.get("total_steps", 0)
|
||||||
|
completed_steps = execution_info.get("completed_steps", 0)
|
||||||
|
|
||||||
|
# 如果没有步骤在队列中(queue_total_steps为0),立即退出
|
||||||
|
if queue_total_steps == 0:
|
||||||
|
print(colored(f"⚠️ 没有步骤在队列中,退出执行", "yellow"))
|
||||||
|
break
|
||||||
|
|
||||||
|
# 如果所有步骤都已完成,等待可能的新步骤
|
||||||
|
if completed_steps >= queue_total_steps:
|
||||||
|
if empty_wait_count >= max_empty_wait_cycles:
|
||||||
|
# 等待超时,退出执行
|
||||||
|
print(colored(f"✅ 所有步骤执行完成,等待超时", "green"))
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# 等待新步骤追加
|
||||||
|
print(colored(f"⏳ 等待新步骤追加... ({empty_wait_count}/{max_empty_wait_cycles})", "cyan"))
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# 还有步骤未完成,继续尝试获取
|
||||||
|
print(colored(f"⏳ 等待步骤就绪... ({completed_steps}/{queue_total_steps})", "cyan"))
|
||||||
|
await asyncio.sleep(0.5)
|
||||||
|
empty_wait_count = 0 # 重置等待计数
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# 执行信息不存在,退出
|
||||||
|
print(colored(f"⚠️ 执行信息不存在,退出执行", "yellow"))
|
||||||
|
break
|
||||||
|
|
||||||
|
# 重置等待计数
|
||||||
|
empty_wait_count = 0
|
||||||
|
|
||||||
|
# 获取最新的总步骤数(用于显示)
|
||||||
|
execution_info = dynamic_execution_manager.get_execution_info(execution_id)
|
||||||
|
current_total_steps = execution_info.get("total_steps", total_steps) if execution_info else total_steps
|
||||||
|
|
||||||
|
# 执行步骤
|
||||||
|
async for event in execute_step_async_streaming(
|
||||||
|
stepDescrip,
|
||||||
|
plan["General Goal"],
|
||||||
|
AgentProfile_Dict,
|
||||||
|
KeyObjects,
|
||||||
|
step_index,
|
||||||
|
current_total_steps # 使用动态更新的总步骤数
|
||||||
|
):
|
||||||
|
if execution_state_manager.is_stopped():
|
||||||
|
await queue.put({
|
||||||
|
"type": "error",
|
||||||
|
"message": "执行已被用户停止"
|
||||||
|
})
|
||||||
|
return
|
||||||
|
|
||||||
|
await queue.put(event)
|
||||||
|
|
||||||
|
# 标记步骤完成
|
||||||
|
dynamic_execution_manager.mark_step_completed(execution_id)
|
||||||
|
|
||||||
|
# 更新KeyObjects
|
||||||
|
OutputName = stepDescrip.get("OutputObject", "")
|
||||||
|
if OutputName and OutputName in KeyObjects:
|
||||||
|
# 对象日志节点会在step_complete中发送
|
||||||
|
pass
|
||||||
|
|
||||||
|
step_index += 1
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 非动态模式:按顺序执行所有步骤
|
||||||
|
for step_index, stepDescrip in enumerate(steps_to_run):
|
||||||
|
should_continue = await execution_state_manager.async_check_pause()
|
||||||
|
if not should_continue:
|
||||||
|
print(colored("🛑 用户请求停止执行", "red"))
|
||||||
|
await queue.put({
|
||||||
|
"type": "error",
|
||||||
|
"message": "执行已被用户停止"
|
||||||
|
})
|
||||||
|
return
|
||||||
|
|
||||||
|
async for event in execute_step_async_streaming(
|
||||||
|
stepDescrip,
|
||||||
|
plan["General Goal"],
|
||||||
|
AgentProfile_Dict,
|
||||||
|
KeyObjects,
|
||||||
|
step_index,
|
||||||
|
total_steps
|
||||||
|
):
|
||||||
|
if execution_state_manager.is_stopped():
|
||||||
|
await queue.put({
|
||||||
|
"type": "error",
|
||||||
|
"message": "执行已被用户停止"
|
||||||
|
})
|
||||||
|
return
|
||||||
|
|
||||||
|
await queue.put(event)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await queue.put({
|
||||||
|
"type": "error",
|
||||||
|
"message": f"执行出错: {str(e)}"
|
||||||
|
})
|
||||||
|
finally:
|
||||||
|
await queue.put(None)
|
||||||
|
|
||||||
|
# 运行异步任务并实时yield
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
|
||||||
|
try:
|
||||||
|
queue = asyncio.Queue(maxsize=10)
|
||||||
|
producer_task = loop.create_task(produce_events(queue))
|
||||||
|
|
||||||
|
while True:
|
||||||
|
event = loop.run_until_complete(queue.get())
|
||||||
|
if event is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
# 立即转换为SSE格式并发送
|
||||||
|
event_str = json.dumps(event, ensure_ascii=False)
|
||||||
|
yield f"data: {event_str}\n\n"
|
||||||
|
|
||||||
|
loop.run_until_complete(producer_task)
|
||||||
|
|
||||||
|
if not execution_state_manager.is_stopped():
|
||||||
|
complete_event = json.dumps({
|
||||||
|
"type": "execution_complete",
|
||||||
|
"total_steps": total_steps
|
||||||
|
}, ensure_ascii=False)
|
||||||
|
yield f"data: {complete_event}\n\n"
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# 在关闭事件循环之前先清理执行记录
|
||||||
|
if execution_id:
|
||||||
|
# 清理执行记录
|
||||||
|
dynamic_execution_manager.cleanup(execution_id)
|
||||||
|
|
||||||
|
if 'producer_task' in locals():
|
||||||
|
if not producer_task.done():
|
||||||
|
producer_task.cancel()
|
||||||
|
|
||||||
|
# 确保所有任务都完成后再关闭事件循环
|
||||||
|
try:
|
||||||
|
pending = asyncio.all_tasks(loop)
|
||||||
|
for task in pending:
|
||||||
|
task.cancel()
|
||||||
|
loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
|
||||||
|
except Exception:
|
||||||
|
pass # 忽略清理过程中的错误
|
||||||
|
|
||||||
|
loop.close()
|
||||||
|
|
||||||
|
|
||||||
|
# 保留旧版本函数以保持兼容性
|
||||||
|
executePlan_streaming = executePlan_streaming_dynamic
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
"""
|
||||||
|
动态执行管理器
|
||||||
|
用于在任务执行过程中动态追加新步骤
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from typing import Dict, List, Optional, Any
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicExecutionManager:
|
||||||
|
"""
|
||||||
|
动态执行管理器
|
||||||
|
管理正在执行的任务,支持动态追加新步骤
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# 执行状态: goal -> execution_info
|
||||||
|
self._executions: Dict[str, Dict] = {}
|
||||||
|
|
||||||
|
# 线程锁
|
||||||
|
self._lock = Lock()
|
||||||
|
|
||||||
|
# 步骤队列: goal -> List[step]
|
||||||
|
self._step_queues: Dict[str, List] = {}
|
||||||
|
|
||||||
|
# 已执行的步骤索引: goal -> Set[step_index]
|
||||||
|
self._executed_steps: Dict[str, set] = {}
|
||||||
|
|
||||||
|
# 待执行的步骤索引: goal -> List[step_index]
|
||||||
|
self._pending_steps: Dict[str, List[int]] = {}
|
||||||
|
|
||||||
|
def start_execution(self, goal: str, initial_steps: List[Dict], execution_id: str = None) -> str:
|
||||||
|
"""
|
||||||
|
开始执行一个新的任务
|
||||||
|
|
||||||
|
Args:
|
||||||
|
goal: 任务目标
|
||||||
|
initial_steps: 初始步骤列表
|
||||||
|
execution_id: 执行ID,如果不提供则自动生成
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
执行ID
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
# 如果未提供execution_id,则生成一个
|
||||||
|
if execution_id is None:
|
||||||
|
execution_id = f"{goal}_{asyncio.get_event_loop().time()}"
|
||||||
|
|
||||||
|
self._executions[execution_id] = {
|
||||||
|
"goal": goal,
|
||||||
|
"status": "running",
|
||||||
|
"total_steps": len(initial_steps),
|
||||||
|
"completed_steps": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# 初始化步骤队列
|
||||||
|
self._step_queues[execution_id] = initial_steps.copy()
|
||||||
|
|
||||||
|
# 初始化已执行步骤集合
|
||||||
|
self._executed_steps[execution_id] = set()
|
||||||
|
|
||||||
|
# 初始化待执行步骤索引
|
||||||
|
self._pending_steps[execution_id] = list(range(len(initial_steps)))
|
||||||
|
|
||||||
|
print(f"🚀 启动执行: {execution_id}")
|
||||||
|
print(f"📊 初始步骤数: {len(initial_steps)}")
|
||||||
|
print(f"📋 待执行步骤索引: {self._pending_steps[execution_id]}")
|
||||||
|
|
||||||
|
return execution_id
|
||||||
|
|
||||||
|
def add_steps(self, execution_id: str, new_steps: List[Dict]) -> int:
|
||||||
|
"""
|
||||||
|
向执行中追加新步骤
|
||||||
|
|
||||||
|
Args:
|
||||||
|
execution_id: 执行ID
|
||||||
|
new_steps: 新步骤列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
追加的步骤数量
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
if execution_id not in self._step_queues:
|
||||||
|
print(f"⚠️ 警告: 执行ID {execution_id} 不存在,无法追加步骤")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
current_count = len(self._step_queues[execution_id])
|
||||||
|
|
||||||
|
# 追加新步骤到队列
|
||||||
|
self._step_queues[execution_id].extend(new_steps)
|
||||||
|
|
||||||
|
# 添加新步骤的索引到待执行列表
|
||||||
|
new_indices = list(range(current_count, current_count + len(new_steps)))
|
||||||
|
self._pending_steps[execution_id].extend(new_indices)
|
||||||
|
|
||||||
|
# 更新总步骤数
|
||||||
|
old_total = self._executions[execution_id]["total_steps"]
|
||||||
|
self._executions[execution_id]["total_steps"] = len(self._step_queues[execution_id])
|
||||||
|
new_total = self._executions[execution_id]["total_steps"]
|
||||||
|
|
||||||
|
print(f"➕ 追加了 {len(new_steps)} 个步骤到 {execution_id}")
|
||||||
|
print(f"📊 步骤总数: {old_total} -> {new_total}")
|
||||||
|
print(f"📋 待执行步骤索引: {self._pending_steps[execution_id]}")
|
||||||
|
|
||||||
|
return len(new_steps)
|
||||||
|
|
||||||
|
def get_next_step(self, execution_id: str) -> Optional[Dict]:
|
||||||
|
"""
|
||||||
|
获取下一个待执行的步骤
|
||||||
|
|
||||||
|
Args:
|
||||||
|
execution_id: 执行ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
下一个步骤,如果没有则返回None
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
if execution_id not in self._pending_steps:
|
||||||
|
print(f"⚠️ 警告: 执行ID {execution_id} 不存在")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 获取第一个待执行步骤的索引
|
||||||
|
if not self._pending_steps[execution_id]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
step_index = self._pending_steps[execution_id].pop(0)
|
||||||
|
|
||||||
|
# 从队列中获取步骤
|
||||||
|
if step_index >= len(self._step_queues[execution_id]):
|
||||||
|
print(f"⚠️ 警告: 步骤索引 {step_index} 超出范围")
|
||||||
|
return None
|
||||||
|
|
||||||
|
step = self._step_queues[execution_id][step_index]
|
||||||
|
|
||||||
|
# 标记为已执行
|
||||||
|
self._executed_steps[execution_id].add(step_index)
|
||||||
|
|
||||||
|
step_name = step.get("StepName", "未知")
|
||||||
|
print(f"🎯 获取下一个步骤: {step_name} (索引: {step_index})")
|
||||||
|
print(f"📋 剩余待执行步骤: {len(self._pending_steps[execution_id])}")
|
||||||
|
|
||||||
|
return step
|
||||||
|
|
||||||
|
def mark_step_completed(self, execution_id: str):
|
||||||
|
"""
|
||||||
|
标记一个步骤完成
|
||||||
|
|
||||||
|
Args:
|
||||||
|
execution_id: 执行ID
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
if execution_id in self._executions:
|
||||||
|
self._executions[execution_id]["completed_steps"] += 1
|
||||||
|
completed = self._executions[execution_id]["completed_steps"]
|
||||||
|
total = self._executions[execution_id]["total_steps"]
|
||||||
|
print(f"📊 步骤完成进度: {completed}/{total}")
|
||||||
|
else:
|
||||||
|
print(f"⚠️ 警告: 执行ID {execution_id} 不存在")
|
||||||
|
|
||||||
|
def get_execution_info(self, execution_id: str) -> Optional[Dict]:
|
||||||
|
"""
|
||||||
|
获取执行信息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
execution_id: 执行ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
执行信息字典
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
return self._executions.get(execution_id)
|
||||||
|
|
||||||
|
def get_pending_count(self, execution_id: str) -> int:
|
||||||
|
"""
|
||||||
|
获取待执行步骤数量
|
||||||
|
|
||||||
|
Args:
|
||||||
|
execution_id: 执行ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
待执行步骤数量
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
if execution_id not in self._pending_steps:
|
||||||
|
return 0
|
||||||
|
return len(self._pending_steps[execution_id])
|
||||||
|
|
||||||
|
def has_more_steps(self, execution_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
检查是否还有更多步骤待执行
|
||||||
|
|
||||||
|
Args:
|
||||||
|
execution_id: 执行ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否还有待执行步骤
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
if execution_id not in self._pending_steps:
|
||||||
|
return False
|
||||||
|
return len(self._pending_steps[execution_id]) > 0
|
||||||
|
|
||||||
|
def finish_execution(self, execution_id: str):
|
||||||
|
"""
|
||||||
|
完成执行
|
||||||
|
|
||||||
|
Args:
|
||||||
|
execution_id: 执行ID
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
if execution_id in self._executions:
|
||||||
|
self._executions[execution_id]["status"] = "completed"
|
||||||
|
|
||||||
|
def cancel_execution(self, execution_id: str):
|
||||||
|
"""
|
||||||
|
取消执行
|
||||||
|
|
||||||
|
Args:
|
||||||
|
execution_id: 执行ID
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
if execution_id in self._executions:
|
||||||
|
self._executions[execution_id]["status"] = "cancelled"
|
||||||
|
|
||||||
|
def cleanup(self, execution_id: str):
|
||||||
|
"""
|
||||||
|
清理执行记录
|
||||||
|
|
||||||
|
Args:
|
||||||
|
execution_id: 执行ID
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
self._executions.pop(execution_id, None)
|
||||||
|
self._step_queues.pop(execution_id, None)
|
||||||
|
self._executed_steps.pop(execution_id, None)
|
||||||
|
self._pending_steps.pop(execution_id, None)
|
||||||
|
|
||||||
|
|
||||||
|
# 全局单例
|
||||||
|
dynamic_execution_manager = DynamicExecutionManager()
|
||||||
190
backend/AgentCoord/RehearsalEngine_V2/execution_state.py
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
"""
|
||||||
|
全局执行状态管理器
|
||||||
|
用于支持任务的暂停、恢复和停止功能
|
||||||
|
使用轮询检查机制,确保线程安全
|
||||||
|
"""
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import asyncio
|
||||||
|
import time
|
||||||
|
from typing import Optional
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class ExecutionStatus(Enum):
|
||||||
|
"""执行状态枚举"""
|
||||||
|
RUNNING = "running" # 正在运行
|
||||||
|
PAUSED = "paused" # 已暂停
|
||||||
|
STOPPED = "stopped" # 已停止
|
||||||
|
IDLE = "idle" # 空闲
|
||||||
|
|
||||||
|
|
||||||
|
class ExecutionStateManager:
|
||||||
|
"""
|
||||||
|
全局执行状态管理器(单例模式)
|
||||||
|
|
||||||
|
功能:
|
||||||
|
- 管理任务执行状态(运行/暂停/停止)
|
||||||
|
- 使用轮询检查机制,避免异步事件的线程问题
|
||||||
|
- 提供线程安全的状态查询和修改接口
|
||||||
|
"""
|
||||||
|
|
||||||
|
_instance: Optional['ExecutionStateManager'] = None
|
||||||
|
_lock = threading.Lock()
|
||||||
|
|
||||||
|
def __new__(cls):
|
||||||
|
"""单例模式"""
|
||||||
|
if cls._instance is None:
|
||||||
|
with cls._lock:
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super().__new__(cls)
|
||||||
|
cls._instance._initialized = False
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""初始化状态管理器"""
|
||||||
|
if self._initialized:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._initialized = True
|
||||||
|
self._status = ExecutionStatus.IDLE
|
||||||
|
self._current_goal: Optional[str] = None # 当前执行的任务目标
|
||||||
|
# 使用简单的布尔标志,而不是 asyncio.Event
|
||||||
|
self._should_pause = False
|
||||||
|
self._should_stop = False
|
||||||
|
|
||||||
|
def get_status(self) -> ExecutionStatus:
|
||||||
|
"""获取当前执行状态"""
|
||||||
|
with self._lock:
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
def set_goal(self, goal: str):
|
||||||
|
"""设置当前执行的任务目标"""
|
||||||
|
with self._lock:
|
||||||
|
self._current_goal = goal
|
||||||
|
|
||||||
|
def get_goal(self) -> Optional[str]:
|
||||||
|
"""获取当前执行的任务目标"""
|
||||||
|
with self._lock:
|
||||||
|
return self._current_goal
|
||||||
|
|
||||||
|
def start_execution(self, goal: str):
|
||||||
|
"""开始执行"""
|
||||||
|
with self._lock:
|
||||||
|
self._status = ExecutionStatus.RUNNING
|
||||||
|
self._current_goal = goal
|
||||||
|
self._should_pause = False
|
||||||
|
self._should_stop = False
|
||||||
|
print(f"🚀 [DEBUG] start_execution: 状态设置为 RUNNING, goal={goal}")
|
||||||
|
|
||||||
|
def pause_execution(self) -> bool:
|
||||||
|
"""
|
||||||
|
暂停执行
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功暂停
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
if self._status != ExecutionStatus.RUNNING:
|
||||||
|
print(f"⚠️ [DEBUG] pause_execution: 当前状态不是RUNNING,而是 {self._status}")
|
||||||
|
return False
|
||||||
|
self._status = ExecutionStatus.PAUSED
|
||||||
|
self._should_pause = True
|
||||||
|
print(f"⏸️ [DEBUG] pause_execution: 状态设置为PAUSED, should_pause=True")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def resume_execution(self) -> bool:
|
||||||
|
"""
|
||||||
|
恢复执行
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功恢复
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
if self._status != ExecutionStatus.PAUSED:
|
||||||
|
print(f"⚠️ [DEBUG] resume_execution: 当前状态不是PAUSED,而是 {self._status}")
|
||||||
|
return False
|
||||||
|
self._status = ExecutionStatus.RUNNING
|
||||||
|
self._should_pause = False
|
||||||
|
print(f"▶️ [DEBUG] resume_execution: 状态设置为RUNNING, should_pause=False")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def stop_execution(self) -> bool:
|
||||||
|
"""
|
||||||
|
停止执行
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功停止
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
if self._status in [ExecutionStatus.IDLE, ExecutionStatus.STOPPED]:
|
||||||
|
return False
|
||||||
|
self._status = ExecutionStatus.STOPPED
|
||||||
|
self._should_stop = True
|
||||||
|
self._should_pause = False
|
||||||
|
print(f"🛑 [DEBUG] stop_execution: 状态设置为STOPPED")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""重置状态为空闲"""
|
||||||
|
with self._lock:
|
||||||
|
self._status = ExecutionStatus.IDLE
|
||||||
|
self._current_goal = None
|
||||||
|
self._should_pause = False
|
||||||
|
self._should_stop = False
|
||||||
|
print(f"🔄 [DEBUG] reset: 状态重置为IDLE")
|
||||||
|
|
||||||
|
async def async_check_pause(self):
|
||||||
|
"""
|
||||||
|
异步检查是否需要暂停(轮询方式)
|
||||||
|
|
||||||
|
如果处于暂停状态,会阻塞当前协程直到恢复或停止
|
||||||
|
应该在执行循环的关键点调用此方法
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 如果返回True表示应该继续执行,False表示应该停止
|
||||||
|
"""
|
||||||
|
# 使用轮询检查,避免异步事件问题
|
||||||
|
while True:
|
||||||
|
# 检查停止标志
|
||||||
|
if self._should_stop:
|
||||||
|
print("🛑 [DEBUG] async_check_pause: 检测到停止信号")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查暂停状态
|
||||||
|
if self._should_pause:
|
||||||
|
# 处于暂停状态,等待恢复
|
||||||
|
print("⏸️ [DEBUG] async_check_pause: 检测到暂停,等待恢复...")
|
||||||
|
await asyncio.sleep(0.1) # 短暂睡眠,避免占用CPU
|
||||||
|
|
||||||
|
# 如果恢复,继续执行
|
||||||
|
if not self._should_pause:
|
||||||
|
print("▶️ [DEBUG] async_check_pause: 从暂停中恢复!")
|
||||||
|
continue
|
||||||
|
# 如果停止了,返回
|
||||||
|
if self._should_stop:
|
||||||
|
return False
|
||||||
|
# 继续等待
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 既没有停止也没有暂停,可以继续执行
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_paused(self) -> bool:
|
||||||
|
"""检查是否处于暂停状态"""
|
||||||
|
with self._lock:
|
||||||
|
return self._status == ExecutionStatus.PAUSED
|
||||||
|
|
||||||
|
def is_running(self) -> bool:
|
||||||
|
"""检查是否正在运行"""
|
||||||
|
with self._lock:
|
||||||
|
return self._status == ExecutionStatus.RUNNING
|
||||||
|
|
||||||
|
def is_stopped(self) -> bool:
|
||||||
|
"""检查是否已停止"""
|
||||||
|
with self._lock:
|
||||||
|
return self._status == ExecutionStatus.STOPPED
|
||||||
|
|
||||||
|
|
||||||
|
# 全局单例实例
|
||||||
|
execution_state_manager = ExecutionStateManager()
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
## config for default LLM
|
## config for default LLM
|
||||||
OPENAI_API_BASE: ""
|
OPENAI_API_BASE: "https://ai.gitee.com/v1"
|
||||||
OPENAI_API_KEY: ""
|
OPENAI_API_KEY: "HYCNGM39GGFNSB1F8MBBMI9QYJR3P1CRSYS2PV1A"
|
||||||
OPENAI_API_MODEL: "gpt-4-turbo-preview"
|
OPENAI_API_MODEL: "DeepSeek-V3"
|
||||||
|
|
||||||
## config for fast mode
|
## config for fast mode
|
||||||
FAST_DESIGN_MODE: True
|
FAST_DESIGN_MODE: False
|
||||||
GROQ_API_KEY: ""
|
GROQ_API_KEY: ""
|
||||||
MISTRAL_API_KEY: ""
|
MISTRAL_API_KEY: ""
|
||||||
|
|
||||||
|
|||||||
@@ -4,4 +4,7 @@ PyYAML==6.0.1
|
|||||||
termcolor==2.4.0
|
termcolor==2.4.0
|
||||||
groq==0.4.2
|
groq==0.4.2
|
||||||
mistralai==0.1.6
|
mistralai==0.1.6
|
||||||
socksio==1.0.0
|
flask-socketio==5.3.6
|
||||||
|
python-socketio==5.11.0
|
||||||
|
simple-websocket==1.0.0
|
||||||
|
eventlet==0.40.4
|
||||||
1454
backend/server.py
1
frontend/.env
Normal file
@@ -0,0 +1 @@
|
|||||||
|
API_BASE=http://127.0.0.1:8000
|
||||||
7
frontend/components.d.ts
vendored
@@ -16,9 +16,16 @@ declare module 'vue' {
|
|||||||
ElCard: typeof import('element-plus/es')['ElCard']
|
ElCard: typeof import('element-plus/es')['ElCard']
|
||||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||||
|
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||||
|
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||||
|
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||||
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
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']
|
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||||
MultiLineTooltip: typeof import('./src/components/MultiLineTooltip/index.vue')['default']
|
MultiLineTooltip: typeof import('./src/components/MultiLineTooltip/index.vue')['default']
|
||||||
|
Notification: typeof import('./src/components/Notification/Notification.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
|
SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<html lang="">
|
<html lang="">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/logo.jpg">
|
<link rel="icon" href="/logo.png">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>多智能体协同平台</title>
|
<title>多智能体协同平台</title>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -17,8 +17,13 @@
|
|||||||
"format": "prettier --write src/"
|
"format": "prettier --write src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@element-plus/icons-vue": "^2.3.2",
|
||||||
"@jsplumb/browser-ui": "^6.2.10",
|
"@jsplumb/browser-ui": "^6.2.10",
|
||||||
"@types/markdown-it": "^14.1.2",
|
"@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",
|
"@vueuse/core": "^14.0.0",
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
"dompurify": "^3.3.0",
|
"dompurify": "^3.3.0",
|
||||||
@@ -27,6 +32,7 @@
|
|||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"qs": "^6.14.0",
|
"qs": "^6.14.0",
|
||||||
|
"socket.io-client": "^4.8.3",
|
||||||
"uuid": "^13.0.0",
|
"uuid": "^13.0.0",
|
||||||
"vue": "^3.5.22",
|
"vue": "^3.5.22",
|
||||||
"vue-router": "^4.6.3"
|
"vue-router": "^4.6.3"
|
||||||
|
|||||||
9113
frontend/pnpm-lock.yaml
generated
@@ -4,7 +4,7 @@
|
|||||||
"centerTitle": "多智能体协同平台",
|
"centerTitle": "多智能体协同平台",
|
||||||
"taskPromptWords": [
|
"taskPromptWords": [
|
||||||
"如何快速筛选慢性肾脏病药物潜在受试者?",
|
"如何快速筛选慢性肾脏病药物潜在受试者?",
|
||||||
"如何补充“丹芍活血胶囊”不良反应数据?",
|
"如何补充\"丹芍活血胶囊\"不良反应数据?",
|
||||||
"如何快速研发用于战场失血性休克的药物?",
|
"如何快速研发用于战场失血性休克的药物?",
|
||||||
"二维材料的光电性质受哪些关键因素影响?",
|
"二维材料的光电性质受哪些关键因素影响?",
|
||||||
"如何通过AI模拟的方法分析材料的微观结构?",
|
"如何通过AI模拟的方法分析材料的微观结构?",
|
||||||
@@ -15,5 +15,7 @@
|
|||||||
],
|
],
|
||||||
"agentRepository": {
|
"agentRepository": {
|
||||||
"storageVersionIdentifier": "1"
|
"storageVersionIdentifier": "1"
|
||||||
}
|
},
|
||||||
|
"dev": true,
|
||||||
|
"apiBaseUrl": "http://localhost:8000"
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
frontend/public/logo.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
42
frontend/src/ directive/devOnly/index.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import type { Directive, DirectiveBinding } from 'vue'
|
||||||
|
import { useConfigStoreHook } from '@/stores'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开发模式专用指令
|
||||||
|
* 只在开发模式下显示元素,生产模式下会移除该元素
|
||||||
|
*
|
||||||
|
* \@param binding.value - 是否开启该功能,默认为 true
|
||||||
|
* @example
|
||||||
|
* \!-- 默认开启,开发模式显示 --
|
||||||
|
* \!div v-dev-only开发模式内容</div>
|
||||||
|
*
|
||||||
|
* \!-- 传入参数控制 --
|
||||||
|
* <div v-dev-only="true">开启指令</div>
|
||||||
|
* <div v-dev-only="false">取消指令</div>
|
||||||
|
*/
|
||||||
|
export const devOnly: Directive = {
|
||||||
|
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||||
|
checkAndRemoveElement(el, binding)
|
||||||
|
},
|
||||||
|
|
||||||
|
updated(el: HTMLElement, binding: DirectiveBinding) {
|
||||||
|
checkAndRemoveElement(el, binding)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const configStore = useConfigStoreHook()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查并移除元素的逻辑
|
||||||
|
*/
|
||||||
|
function checkAndRemoveElement(el: HTMLElement, binding: DirectiveBinding) {
|
||||||
|
const isDev = typeof configStore.config.dev === 'boolean' ? configStore.config.dev : import.meta.env.DEV
|
||||||
|
// 默认值为 true,如果没有传值或者传值为 true 都启用
|
||||||
|
const shouldEnable = binding.value !== false
|
||||||
|
// 如果不是开发模式或者明确禁用,移除该元素
|
||||||
|
if (!isDev && shouldEnable) {
|
||||||
|
if (el.parentNode) {
|
||||||
|
el.parentNode.removeChild(el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
frontend/src/ directive/index.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import type { App } from 'vue'
|
||||||
|
|
||||||
|
import { devOnly } from './devOnly'
|
||||||
|
|
||||||
|
|
||||||
|
// 全局注册 directive
|
||||||
|
export function setupDirective(app: App<Element>) {
|
||||||
|
app.directive('dev-only', devOnly)
|
||||||
|
}
|
||||||
@@ -1,15 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from 'vue'
|
|
||||||
|
|
||||||
import Layout from './layout/index.vue'
|
import Layout from './layout/index.vue'
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
document.documentElement.classList.add('dark')
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<layout />
|
<Layout />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -29,11 +23,12 @@ onMounted(() => {
|
|||||||
.el-card {
|
.el-card {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: var(--color-bg-tertiary);
|
background: var(--color-bg-tertiary);
|
||||||
border: 2px solid var(--color-bg-tertiary);
|
border: 2px solid var(--color-card-border);
|
||||||
|
box-shadow: var(--color-card-border-hover);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #171B22;
|
background: var(--color-bg-content-hover);
|
||||||
box-shadow: none !important;
|
box-shadow: none;
|
||||||
transition: background-color 0.3s ease-in-out;
|
transition: background-color 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,14 +45,16 @@ onMounted(() => {
|
|||||||
.active-card {
|
.active-card {
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
$bg: var(--el-input-bg-color, var(--el-fill-color-blank));
|
$bg: var(--el-input-bg-color, var(--el-fill-color-blank));
|
||||||
background:
|
background: linear-gradient(
|
||||||
linear-gradient(var(--color-bg-tertiary), var(--color-bg-tertiary)) padding-box,
|
var(--color-agent-list-selected-bg),
|
||||||
|
var(--color-agent-list-selected-bg)
|
||||||
|
)
|
||||||
|
padding-box,
|
||||||
linear-gradient(to right, #00c8d2, #315ab4) border-box;
|
linear-gradient(to right, #00c8d2, #315ab4) border-box;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* 1. 定义流动动画:让虚线沿路径移动 */
|
/* 1. 定义流动动画:让虚线沿路径移动 */
|
||||||
@keyframes flowAnimation {
|
@keyframes flowAnimation {
|
||||||
to {
|
to {
|
||||||
@@ -71,14 +68,13 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* 2. 为jsPlumb连线绑定动画:作用于SVG的path元素 */
|
/* 2. 为jsPlumb连线绑定动画:作用于SVG的path元素 */
|
||||||
/* jtk-connector是jsPlumb连线的默认SVG类,path是实际的线条元素 */
|
/* jtk-connector是jsPlumb连线的默认SVG类,path是实际的线条元素 */
|
||||||
.jtk-connector-output path {
|
.jtk-connector-output path {
|
||||||
/* 定义虚线规则:线段长度5px + 间隙3px(总长度8px,与动画偏移量匹配) */
|
/* 定义虚线规则:线段长度5px + 间隙3px(总长度8px,与动画偏移量匹配) */
|
||||||
stroke-dasharray: 5 3;
|
stroke-dasharray: 5 3;
|
||||||
/* 应用动画:名称+时长+线性速度+无限循环 */
|
/* 应用动画:名称+时长+线性速度+无限循环 */
|
||||||
animation: flowAnimationReverse .5s linear infinite;
|
animation: flowAnimationReverse 0.5s linear infinite;
|
||||||
/* 可选:设置线条基础样式(颜色、宽度) */
|
/* 可选:设置线条基础样式(颜色、宽度) */
|
||||||
stroke-width: 2;
|
stroke-width: 2;
|
||||||
}
|
}
|
||||||
@@ -89,7 +85,7 @@ onMounted(() => {
|
|||||||
/* 定义虚线规则:线段长度5px + 间隙3px(总长度8px,与动画偏移量匹配) */
|
/* 定义虚线规则:线段长度5px + 间隙3px(总长度8px,与动画偏移量匹配) */
|
||||||
stroke-dasharray: 5 3;
|
stroke-dasharray: 5 3;
|
||||||
/* 应用动画:名称+时长+线性速度+无限循环 */
|
/* 应用动画:名称+时长+线性速度+无限循环 */
|
||||||
animation: flowAnimationReverse .5s linear infinite;
|
animation: flowAnimationReverse 0.5s linear infinite;
|
||||||
/* 可选:设置线条基础样式(颜色、宽度) */
|
/* 可选:设置线条基础样式(颜色、宽度) */
|
||||||
stroke-width: 2;
|
stroke-width: 2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
import type { Agent, IRawPlanResponse } from '@/stores'
|
import websocket from '@/utils/websocket'
|
||||||
|
import type { Agent, IApiStepTask, IRawPlanResponse, IRawStepTask } from '@/stores'
|
||||||
|
import {
|
||||||
|
mockBackendAgentSelectModifyInit,
|
||||||
|
mockBackendAgentSelectModifyAddAspect,
|
||||||
|
type BackendAgentScoreResponse,
|
||||||
|
} from '@/layout/components/Main/TaskTemplate/TaskSyllabus/components/mock/AgentAssignmentBackendMock'
|
||||||
|
import {
|
||||||
|
mockBackendFillAgentTaskProcess,
|
||||||
|
type RawAgentTaskProcessResponse,
|
||||||
|
} from '@/layout/components/Main/TaskTemplate/TaskProcess/components/mock/AgentTaskProcessBackendMock'
|
||||||
|
import { mockBranchPlanOutlineAPI } from '@/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/mock/branchPlanOutlineMock'
|
||||||
|
import { mockFillStepTaskAPI } from '@/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/mock/fill-step-task-mock'
|
||||||
|
import {
|
||||||
|
mockBranchTaskProcessAPI,
|
||||||
|
type BranchAction,
|
||||||
|
} from '@/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/mock/branchTaskProcessMock'
|
||||||
|
|
||||||
export interface ActionHistory {
|
export interface ActionHistory {
|
||||||
ID: string
|
ID: string
|
||||||
@@ -19,9 +35,64 @@ export type IExecuteRawResponse = {
|
|||||||
ActionHistory: ActionHistory[]
|
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 {
|
class Api {
|
||||||
// 智能体信息
|
// 默认使用WebSocket
|
||||||
setAgents = (data: Pick<Agent, 'Name' | 'Profile'>[]) => {
|
private useWebSocketDefault = true
|
||||||
|
|
||||||
|
setAgents = (data: Pick<Agent, 'Name' | 'Profile' | 'apiUrl' | 'apiKey' | 'apiModel'>[], useWebSocket: boolean = this.useWebSocketDefault) => {
|
||||||
|
// 如果启用WebSocket且已连接,使用WebSocket
|
||||||
|
if (useWebSocket && websocket.connected) {
|
||||||
|
return websocket.send('set_agents', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则使用REST API
|
||||||
return request({
|
return request({
|
||||||
url: '/setAgents',
|
url: '/setAgents',
|
||||||
data,
|
data,
|
||||||
@@ -29,13 +100,38 @@ class Api {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
generateBasePlan = (data: { goal: string; inputs: string[] }) => {
|
generateBasePlan = (data: {
|
||||||
|
goal: string
|
||||||
|
inputs: string[]
|
||||||
|
apiUrl?: string
|
||||||
|
apiKey?: string
|
||||||
|
apiModel?: string
|
||||||
|
useWebSocket?: boolean
|
||||||
|
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
|
||||||
|
}) => {
|
||||||
|
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
|
||||||
|
|
||||||
|
// 如果启用WebSocket且已连接,使用WebSocket
|
||||||
|
if (useWs && websocket.connected) {
|
||||||
|
return websocket.send('generate_base_plan', {
|
||||||
|
'General Goal': data.goal,
|
||||||
|
'Initial Input Object': data.inputs,
|
||||||
|
apiUrl: data.apiUrl,
|
||||||
|
apiKey: data.apiKey,
|
||||||
|
apiModel: data.apiModel,
|
||||||
|
}, undefined, data.onProgress)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则使用REST API
|
||||||
return request<unknown, IRawPlanResponse>({
|
return request<unknown, IRawPlanResponse>({
|
||||||
url: '/generate_basePlan',
|
url: '/generate_basePlan',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
'General Goal': data.goal,
|
'General Goal': data.goal,
|
||||||
'Initial Input Object': data.inputs,
|
'Initial Input Object': data.inputs,
|
||||||
|
apiUrl: data.apiUrl,
|
||||||
|
apiKey: data.apiKey,
|
||||||
|
apiModel: data.apiModel,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -50,14 +146,14 @@ class Api {
|
|||||||
plan: {
|
plan: {
|
||||||
'Initial Input Object': plan['Initial Input Object'],
|
'Initial Input Object': plan['Initial Input Object'],
|
||||||
'General Goal': plan['General Goal'],
|
'General Goal': plan['General Goal'],
|
||||||
'Collaboration Process': plan['Collaboration Process']?.map(step => ({
|
'Collaboration Process': plan['Collaboration Process']?.map((step) => ({
|
||||||
StepName: step.StepName,
|
StepName: step.StepName,
|
||||||
TaskContent: step.TaskContent,
|
TaskContent: step.TaskContent,
|
||||||
InputObject_List: step.InputObject_List,
|
InputObject_List: step.InputObject_List,
|
||||||
OutputObject: step.OutputObject,
|
OutputObject: step.OutputObject,
|
||||||
AgentSelection: step.AgentSelection,
|
AgentSelection: step.AgentSelection,
|
||||||
Collaboration_Brief_frontEnd: step.Collaboration_Brief_FrontEnd,
|
Collaboration_Brief_frontEnd: step.Collaboration_Brief_frontEnd,
|
||||||
TaskProcess: step.TaskProcess.map(action => ({
|
TaskProcess: step.TaskProcess.map((action) => ({
|
||||||
ActionType: action.ActionType,
|
ActionType: action.ActionType,
|
||||||
AgentName: action.AgentName,
|
AgentName: action.AgentName,
|
||||||
Description: action.Description,
|
Description: action.Description,
|
||||||
@@ -69,6 +165,742 @@ class Api {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优化版流式执行计划(支持动态追加步骤)
|
||||||
|
* 步骤级流式 + 动作级智能并行 + 动态追加步骤
|
||||||
|
*/
|
||||||
|
executePlanOptimized = (
|
||||||
|
plan: IRawPlanResponse,
|
||||||
|
onMessage: (event: StreamingEvent) => void,
|
||||||
|
onError?: (error: Error) => void,
|
||||||
|
onComplete?: () => void,
|
||||||
|
useWebSocket?: boolean,
|
||||||
|
existingKeyObjects?: Record<string, any>,
|
||||||
|
enableDynamic?: boolean,
|
||||||
|
onExecutionStarted?: (executionId: string) => void,
|
||||||
|
executionId?: string,
|
||||||
|
restartFromStepIndex?: number, // 新增:从指定步骤重新执行的索引
|
||||||
|
rehearsalLog?: any[], // 新增:传递截断后的 RehearsalLog
|
||||||
|
) => {
|
||||||
|
const useWs = useWebSocket !== undefined ? useWebSocket : this.useWebSocketDefault
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
RehearsalLog: rehearsalLog || [], // ✅ 使用传递的 RehearsalLog
|
||||||
|
num_StepToRun: null,
|
||||||
|
existingKeyObjects: existingKeyObjects || {},
|
||||||
|
enable_dynamic: enableDynamic || false,
|
||||||
|
execution_id: executionId || null,
|
||||||
|
restart_from_step_index: restartFromStepIndex ?? null, // 新增:传递重新执行索引
|
||||||
|
plan: {
|
||||||
|
'Initial Input Object': plan['Initial Input Object'],
|
||||||
|
'General Goal': plan['General Goal'],
|
||||||
|
'Collaboration Process': plan['Collaboration Process']?.map((step) => ({
|
||||||
|
StepName: step.StepName,
|
||||||
|
TaskContent: step.TaskContent,
|
||||||
|
InputObject_List: step.InputObject_List,
|
||||||
|
OutputObject: step.OutputObject,
|
||||||
|
AgentSelection: step.AgentSelection,
|
||||||
|
Collaboration_Brief_frontEnd: step.Collaboration_Brief_frontEnd,
|
||||||
|
TaskProcess: step.TaskProcess.map((action) => ({
|
||||||
|
ActionType: action.ActionType,
|
||||||
|
AgentName: action.AgentName,
|
||||||
|
Description: action.Description,
|
||||||
|
ID: action.ID,
|
||||||
|
ImportantInput: action.ImportantInput,
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果启用WebSocket且已连接,使用WebSocket
|
||||||
|
if (useWs && websocket.connected) {
|
||||||
|
websocket.subscribe(
|
||||||
|
'execute_plan_optimized',
|
||||||
|
data,
|
||||||
|
// onProgress
|
||||||
|
(progressData) => {
|
||||||
|
try {
|
||||||
|
let event: StreamingEvent
|
||||||
|
|
||||||
|
// 处理不同类型的progress数据
|
||||||
|
if (typeof progressData === 'string') {
|
||||||
|
event = JSON.parse(progressData)
|
||||||
|
} else {
|
||||||
|
event = progressData as StreamingEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理特殊事件类型
|
||||||
|
if (event && typeof event === 'object') {
|
||||||
|
// 检查是否是execution_started事件
|
||||||
|
if ('status' in event && event.status === 'execution_started') {
|
||||||
|
if ('execution_id' in event && onExecutionStarted) {
|
||||||
|
onExecutionStarted(event.execution_id as string)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessage(event)
|
||||||
|
} catch (e) {
|
||||||
|
// Failed to parse WebSocket data
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// onComplete
|
||||||
|
() => {
|
||||||
|
onComplete?.()
|
||||||
|
},
|
||||||
|
// onError
|
||||||
|
(error) => {
|
||||||
|
onError?.(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则使用原有的SSE方式
|
||||||
|
|
||||||
|
fetch('/api/executePlanOptimized', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
})
|
||||||
|
.then(async (response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = response.body?.getReader()
|
||||||
|
const decoder = new TextDecoder()
|
||||||
|
|
||||||
|
if (!reader) {
|
||||||
|
throw new Error('Response body is null')
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer = ''
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read()
|
||||||
|
|
||||||
|
if (done) {
|
||||||
|
onComplete?.()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer += decoder.decode(value, { stream: true })
|
||||||
|
|
||||||
|
const lines = buffer.split('\n')
|
||||||
|
buffer = lines.pop() || ''
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.startsWith('data: ')) {
|
||||||
|
const data = line.slice(6)
|
||||||
|
try {
|
||||||
|
const event = JSON.parse(data)
|
||||||
|
onMessage(event)
|
||||||
|
} catch (e) {
|
||||||
|
// Failed to parse SSE data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
onError?.(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分支任务大纲
|
||||||
|
*/
|
||||||
|
branchPlanOutline = (data: {
|
||||||
|
branch_Number: number
|
||||||
|
Modification_Requirement: string
|
||||||
|
Existing_Steps: IRawStepTask[]
|
||||||
|
Baseline_Completion: number
|
||||||
|
initialInputs: string[]
|
||||||
|
goal: string
|
||||||
|
useWebSocket?: boolean
|
||||||
|
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
|
||||||
|
}) => {
|
||||||
|
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
|
||||||
|
|
||||||
|
// 如果启用WebSocket且已连接,使用WebSocket
|
||||||
|
if (useWs && websocket.connected) {
|
||||||
|
return websocket.send('branch_plan_outline', {
|
||||||
|
branch_Number: data.branch_Number,
|
||||||
|
Modification_Requirement: data.Modification_Requirement,
|
||||||
|
Existing_Steps: data.Existing_Steps,
|
||||||
|
Baseline_Completion: data.Baseline_Completion,
|
||||||
|
'Initial Input Object': data.initialInputs,
|
||||||
|
'General Goal': data.goal,
|
||||||
|
}, undefined, data.onProgress)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则使用REST API
|
||||||
|
return request<unknown, IRawPlanResponse>({
|
||||||
|
url: '/branch_PlanOutline',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
branch_Number: data.branch_Number,
|
||||||
|
Modification_Requirement: data.Modification_Requirement,
|
||||||
|
Existing_Steps: data.Existing_Steps,
|
||||||
|
Baseline_Completion: data.Baseline_Completion,
|
||||||
|
'Initial Input Object': data.initialInputs,
|
||||||
|
'General Goal': data.goal,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分支任务流程
|
||||||
|
*/
|
||||||
|
branchTaskProcess = (data: {
|
||||||
|
branch_Number: number
|
||||||
|
Modification_Requirement: string
|
||||||
|
Existing_Steps: BranchAction[]
|
||||||
|
Baseline_Completion: number
|
||||||
|
stepTaskExisting: any
|
||||||
|
goal: string
|
||||||
|
useWebSocket?: boolean
|
||||||
|
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
|
||||||
|
}) => {
|
||||||
|
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
|
||||||
|
|
||||||
|
// 如果启用WebSocket且已连接,使用WebSocket
|
||||||
|
if (useWs && websocket.connected) {
|
||||||
|
return websocket.send('branch_task_process', {
|
||||||
|
branch_Number: data.branch_Number,
|
||||||
|
Modification_Requirement: data.Modification_Requirement,
|
||||||
|
Existing_Steps: data.Existing_Steps,
|
||||||
|
Baseline_Completion: data.Baseline_Completion,
|
||||||
|
stepTaskExisting: data.stepTaskExisting,
|
||||||
|
'General Goal': data.goal,
|
||||||
|
}, undefined, data.onProgress)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则使用REST API
|
||||||
|
return request<unknown, BranchAction[][]>({
|
||||||
|
url: '/branch_TaskProcess',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
branch_Number: data.branch_Number,
|
||||||
|
Modification_Requirement: data.Modification_Requirement,
|
||||||
|
Existing_Steps: data.Existing_Steps,
|
||||||
|
Baseline_Completion: data.Baseline_Completion,
|
||||||
|
stepTaskExisting: data.stepTaskExisting,
|
||||||
|
'General Goal': data.goal,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fillStepTask = async (data: {
|
||||||
|
goal: string
|
||||||
|
stepTask: any
|
||||||
|
useWebSocket?: boolean
|
||||||
|
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
|
||||||
|
}): Promise<IRawStepTask> => {
|
||||||
|
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
|
||||||
|
let response: any
|
||||||
|
|
||||||
|
// 如果启用WebSocket且已连接,使用WebSocket
|
||||||
|
if (useWs && websocket.connected) {
|
||||||
|
response = await websocket.send('fill_step_task', {
|
||||||
|
'General Goal': data.goal,
|
||||||
|
stepTask: data.stepTask,
|
||||||
|
}, undefined, data.onProgress)
|
||||||
|
} else {
|
||||||
|
// 否则使用REST API
|
||||||
|
response = await request<
|
||||||
|
{
|
||||||
|
'General Goal': string
|
||||||
|
stepTask: any
|
||||||
|
},
|
||||||
|
{
|
||||||
|
AgentSelection?: string[]
|
||||||
|
Collaboration_Brief_FrontEnd?: {
|
||||||
|
template: string
|
||||||
|
data: Record<string, { text: string; color: number[] }>
|
||||||
|
}
|
||||||
|
InputObject_List?: string[]
|
||||||
|
OutputObject?: string
|
||||||
|
StepName?: string
|
||||||
|
TaskContent?: string
|
||||||
|
TaskProcess?: Array<{
|
||||||
|
ID: string
|
||||||
|
ActionType: string
|
||||||
|
AgentName: string
|
||||||
|
Description: string
|
||||||
|
ImportantInput: string[]
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
>({
|
||||||
|
url: '/fill_stepTask',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
'General Goal': data.goal,
|
||||||
|
stepTask: data.stepTask,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const vec2Hsl = (color: number[]): string => {
|
||||||
|
const [h, s, l] = color
|
||||||
|
return `hsl(${h}, ${s}%, ${l}%)`
|
||||||
|
}
|
||||||
|
|
||||||
|
const briefData: Record<string, { text: string; style?: Record<string, string> }> = {}
|
||||||
|
if (response.Collaboration_Brief_FrontEnd?.data) {
|
||||||
|
for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) {
|
||||||
|
briefData[key] = {
|
||||||
|
text: value.text,
|
||||||
|
style: {
|
||||||
|
background: vec2Hsl(value.color),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建前端格式的 IRawStepTask
|
||||||
|
*/
|
||||||
|
return {
|
||||||
|
StepName: response.StepName || '',
|
||||||
|
TaskContent: response.TaskContent || '',
|
||||||
|
InputObject_List: response.InputObject_List || [],
|
||||||
|
OutputObject: response.OutputObject || '',
|
||||||
|
AgentSelection: response.AgentSelection || [],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: response.Collaboration_Brief_FrontEnd?.template || '',
|
||||||
|
data: briefData,
|
||||||
|
},
|
||||||
|
TaskProcess: response.TaskProcess || [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fillStepTaskTaskProcess = async (data: {
|
||||||
|
goal: string
|
||||||
|
stepTask: IApiStepTask
|
||||||
|
agents: string[]
|
||||||
|
useWebSocket?: boolean
|
||||||
|
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
|
||||||
|
}): Promise<IApiStepTask> => {
|
||||||
|
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
|
||||||
|
let response: any
|
||||||
|
|
||||||
|
// 如果启用WebSocket且已连接,使用WebSocket
|
||||||
|
if (useWs && websocket.connected) {
|
||||||
|
response = await websocket.send('fill_step_task_process', {
|
||||||
|
'General Goal': data.goal,
|
||||||
|
stepTask_lackTaskProcess: {
|
||||||
|
StepName: data.stepTask.name,
|
||||||
|
TaskContent: data.stepTask.content,
|
||||||
|
InputObject_List: data.stepTask.inputs,
|
||||||
|
OutputObject: data.stepTask.output,
|
||||||
|
AgentSelection: data.agents,
|
||||||
|
},
|
||||||
|
}, undefined, data.onProgress)
|
||||||
|
} else {
|
||||||
|
// 否则使用REST API
|
||||||
|
response = await request<
|
||||||
|
{
|
||||||
|
'General Goal': string
|
||||||
|
stepTask_lackTaskProcess: {
|
||||||
|
StepName: string
|
||||||
|
TaskContent: string
|
||||||
|
InputObject_List: string[]
|
||||||
|
OutputObject: string
|
||||||
|
AgentSelection: string[]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StepName?: string
|
||||||
|
TaskContent?: string
|
||||||
|
InputObject_List?: string[]
|
||||||
|
OutputObject?: string
|
||||||
|
AgentSelection?: string[]
|
||||||
|
TaskProcess?: Array<{
|
||||||
|
ID: string
|
||||||
|
ActionType: string
|
||||||
|
AgentName: string
|
||||||
|
Description: string
|
||||||
|
ImportantInput: string[]
|
||||||
|
}>
|
||||||
|
Collaboration_Brief_FrontEnd?: {
|
||||||
|
template: string
|
||||||
|
data: Record<string, { text: string; color: number[] }>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>({
|
||||||
|
url: '/fill_stepTask_TaskProcess',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
'General Goal': data.goal,
|
||||||
|
stepTask_lackTaskProcess: {
|
||||||
|
StepName: data.stepTask.name,
|
||||||
|
TaskContent: data.stepTask.content,
|
||||||
|
InputObject_List: data.stepTask.inputs,
|
||||||
|
OutputObject: data.stepTask.output,
|
||||||
|
AgentSelection: data.agents,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const vec2Hsl = (color: number[]): string => {
|
||||||
|
const [h, s, l] = color
|
||||||
|
return `hsl(${h}, ${s}%, ${l}%)`
|
||||||
|
}
|
||||||
|
|
||||||
|
const briefData: Record<string, { text: string; style: { background: string } }> = {}
|
||||||
|
if (response.Collaboration_Brief_FrontEnd?.data) {
|
||||||
|
for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) {
|
||||||
|
briefData[key] = {
|
||||||
|
text: value.text,
|
||||||
|
style: {
|
||||||
|
background: vec2Hsl(value.color),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const process = (response.TaskProcess || []).map((action: any) => ({
|
||||||
|
id: action.ID,
|
||||||
|
type: action.ActionType,
|
||||||
|
agent: action.AgentName,
|
||||||
|
description: action.Description,
|
||||||
|
inputs: action.ImportantInput,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: response.StepName || '',
|
||||||
|
content: response.TaskContent || '',
|
||||||
|
inputs: response.InputObject_List || [],
|
||||||
|
output: response.OutputObject || '',
|
||||||
|
agents: response.AgentSelection || [],
|
||||||
|
brief: {
|
||||||
|
template: response.Collaboration_Brief_FrontEnd?.template || '',
|
||||||
|
data: briefData,
|
||||||
|
},
|
||||||
|
process,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为每个智能体评分
|
||||||
|
*/
|
||||||
|
agentSelectModifyInit = async (data: {
|
||||||
|
goal: string
|
||||||
|
stepTask: any
|
||||||
|
useWebSocket?: boolean
|
||||||
|
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
|
||||||
|
}): Promise<Record<string, Record<string, { reason: string; score: number }>>> => {
|
||||||
|
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
|
||||||
|
let response: Record<string, Record<string, { Reason: string; Score: number }>>
|
||||||
|
|
||||||
|
// 如果启用WebSocket且已连接,使用WebSocket
|
||||||
|
if (useWs && websocket.connected) {
|
||||||
|
response = await websocket.send('agent_select_modify_init', {
|
||||||
|
'General Goal': data.goal,
|
||||||
|
stepTask: {
|
||||||
|
StepName: data.stepTask.StepName || data.stepTask.name,
|
||||||
|
TaskContent: data.stepTask.TaskContent || data.stepTask.content,
|
||||||
|
InputObject_List: data.stepTask.InputObject_List || data.stepTask.inputs,
|
||||||
|
OutputObject: data.stepTask.OutputObject || data.stepTask.output,
|
||||||
|
},
|
||||||
|
}, undefined, data.onProgress)
|
||||||
|
} else {
|
||||||
|
// 否则使用REST API
|
||||||
|
response = await request<
|
||||||
|
{
|
||||||
|
'General Goal': string
|
||||||
|
stepTask: any
|
||||||
|
},
|
||||||
|
Record<string, Record<string, { Reason: string; Score: number }>>
|
||||||
|
>({
|
||||||
|
url: '/agentSelectModify_init',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
'General Goal': data.goal,
|
||||||
|
stepTask: {
|
||||||
|
StepName: data.stepTask.StepName || data.stepTask.name,
|
||||||
|
TaskContent: data.stepTask.TaskContent || data.stepTask.content,
|
||||||
|
InputObject_List: data.stepTask.InputObject_List || data.stepTask.inputs,
|
||||||
|
OutputObject: data.stepTask.OutputObject || data.stepTask.output,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
|
||||||
|
|
||||||
|
for (const [aspect, agents] of Object.entries(response)) {
|
||||||
|
for (const [agentName, scoreInfo] of Object.entries(agents)) {
|
||||||
|
if (!transformedData[agentName]) {
|
||||||
|
transformedData[agentName] = {}
|
||||||
|
}
|
||||||
|
transformedData[agentName][aspect] = {
|
||||||
|
reason: scoreInfo.Reason,
|
||||||
|
score: scoreInfo.Score,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformedData
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加新的评估维度
|
||||||
|
*/
|
||||||
|
agentSelectModifyAddAspect = async (data: {
|
||||||
|
aspectList: string[]
|
||||||
|
useWebSocket?: boolean
|
||||||
|
onProgress?: (progress: { status: string; stage?: string; message?: string; [key: string]: any }) => void
|
||||||
|
}): Promise<{
|
||||||
|
aspectName: string
|
||||||
|
agentScores: Record<string, { score: number; reason: string }>
|
||||||
|
}> => {
|
||||||
|
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
|
||||||
|
let response: Record<string, Record<string, { Reason: string; Score: number }>>
|
||||||
|
|
||||||
|
// 如果启用WebSocket且已连接,使用WebSocket
|
||||||
|
if (useWs && websocket.connected) {
|
||||||
|
response = await websocket.send('agent_select_modify_add_aspect', {
|
||||||
|
aspectList: data.aspectList,
|
||||||
|
}, undefined, data.onProgress)
|
||||||
|
} else {
|
||||||
|
// 否则使用REST API
|
||||||
|
response = await request<
|
||||||
|
{
|
||||||
|
aspectList: string[]
|
||||||
|
},
|
||||||
|
Record<string, Record<string, { Reason: string; Score: number }>>
|
||||||
|
>({
|
||||||
|
url: '/agentSelectModify_addAspect',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
aspectList: data.aspectList,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取新添加的维度
|
||||||
|
*/
|
||||||
|
const newAspect = data.aspectList[data.aspectList.length - 1]
|
||||||
|
if (!newAspect) {
|
||||||
|
throw new Error('aspectList is empty')
|
||||||
|
}
|
||||||
|
|
||||||
|
const newAspectAgents = response[newAspect]
|
||||||
|
const agentScores: Record<string, { score: number; reason: string }> = {}
|
||||||
|
|
||||||
|
if (newAspectAgents) {
|
||||||
|
for (const [agentName, scoreInfo] of Object.entries(newAspectAgents)) {
|
||||||
|
agentScores[agentName] = {
|
||||||
|
score: scoreInfo.Score,
|
||||||
|
reason: scoreInfo.Reason,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
aspectName: newAspect,
|
||||||
|
agentScores,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* ==================== Mock API(开发阶段使用)====================
|
||||||
|
*为每个智能体评分
|
||||||
|
*/
|
||||||
|
mockAgentSelectModifyInit = async (): Promise<
|
||||||
|
Record<string, Record<string, { reason: string; score: number }>>
|
||||||
|
> => {
|
||||||
|
const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyInit()
|
||||||
|
|
||||||
|
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
|
||||||
|
|
||||||
|
for (const [aspect, agents] of Object.entries(response)) {
|
||||||
|
for (const [agentName, scoreInfo] of Object.entries(agents)) {
|
||||||
|
if (!transformedData[agentName]) {
|
||||||
|
transformedData[agentName] = {}
|
||||||
|
}
|
||||||
|
transformedData[agentName][aspect] = {
|
||||||
|
reason: scoreInfo.Reason,
|
||||||
|
score: scoreInfo.Score,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformedData
|
||||||
|
}
|
||||||
|
|
||||||
|
mockAgentSelectModifyAddAspect = async (data: {
|
||||||
|
aspectList: string[]
|
||||||
|
}): Promise<{
|
||||||
|
aspectName: string
|
||||||
|
agentScores: Record<string, { score: number; reason: string }>
|
||||||
|
}> => {
|
||||||
|
const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyAddAspect(
|
||||||
|
data.aspectList,
|
||||||
|
)
|
||||||
|
|
||||||
|
const newAspect = data.aspectList[data.aspectList.length - 1]
|
||||||
|
if (!newAspect) {
|
||||||
|
throw new Error('aspectList is empty')
|
||||||
|
}
|
||||||
|
|
||||||
|
const newAspectAgents = response[newAspect]
|
||||||
|
const agentScores: Record<string, { score: number; reason: string }> = {}
|
||||||
|
|
||||||
|
if (newAspectAgents) {
|
||||||
|
for (const [agentName, scoreInfo] of Object.entries(newAspectAgents)) {
|
||||||
|
agentScores[agentName] = {
|
||||||
|
score: scoreInfo.Score,
|
||||||
|
reason: scoreInfo.Reason,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
aspectName: newAspect,
|
||||||
|
agentScores,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mockFillStepTaskTaskProcess = async (data: {
|
||||||
|
goal: string
|
||||||
|
stepTask: IApiStepTask
|
||||||
|
agents: string[]
|
||||||
|
}): Promise<IApiStepTask> => {
|
||||||
|
const response: RawAgentTaskProcessResponse = await mockBackendFillAgentTaskProcess(
|
||||||
|
data.goal,
|
||||||
|
data.stepTask,
|
||||||
|
data.agents,
|
||||||
|
)
|
||||||
|
|
||||||
|
const vec2Hsl = (color: number[]): string => {
|
||||||
|
const [h, s, l] = color
|
||||||
|
return `hsl(${h}, ${s}%, ${l}%)`
|
||||||
|
}
|
||||||
|
|
||||||
|
const briefData: Record<string, { text: string; style: { background: string } }> = {}
|
||||||
|
if (response.Collaboration_Brief_frontEnd?.data) {
|
||||||
|
for (const [key, value] of Object.entries(response.Collaboration_Brief_frontEnd.data)) {
|
||||||
|
briefData[key] = {
|
||||||
|
text: value.text,
|
||||||
|
style: {
|
||||||
|
background: vec2Hsl(value.color),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const process = (response.TaskProcess || []).map((action) => ({
|
||||||
|
id: action.ID,
|
||||||
|
type: action.ActionType,
|
||||||
|
agent: action.AgentName,
|
||||||
|
description: action.Description,
|
||||||
|
inputs: action.ImportantInput,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: response.StepName || '',
|
||||||
|
content: response.TaskContent || '',
|
||||||
|
inputs: response.InputObject_List || [],
|
||||||
|
output: response.OutputObject || '',
|
||||||
|
agents: response.AgentSelection || [],
|
||||||
|
brief: {
|
||||||
|
template: response.Collaboration_Brief_frontEnd?.template || '',
|
||||||
|
data: briefData,
|
||||||
|
},
|
||||||
|
process,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mockBranchPlanOutline = async (data: {
|
||||||
|
branch_Number: number
|
||||||
|
Modification_Requirement: string
|
||||||
|
Existing_Steps: IRawStepTask[]
|
||||||
|
Baseline_Completion: number
|
||||||
|
initialInputs: string[]
|
||||||
|
goal: string
|
||||||
|
}): Promise<IRawPlanResponse> => {
|
||||||
|
const response = await mockBranchPlanOutlineAPI({
|
||||||
|
branch_Number: data.branch_Number,
|
||||||
|
Modification_Requirement: data.Modification_Requirement,
|
||||||
|
Existing_Steps: data.Existing_Steps,
|
||||||
|
Baseline_Completion: data.Baseline_Completion,
|
||||||
|
InitialObject_List: data.initialInputs,
|
||||||
|
General_Goal: data.goal,
|
||||||
|
})
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
mockFillStepTask = async (data: { goal: string; stepTask: any }): Promise<any> => {
|
||||||
|
const response = await mockFillStepTaskAPI({
|
||||||
|
General_Goal: data.goal,
|
||||||
|
stepTask: data.stepTask,
|
||||||
|
})
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
mockBranchTaskProcess = async (data: {
|
||||||
|
branch_Number: number
|
||||||
|
Modification_Requirement: string
|
||||||
|
Existing_Steps: BranchAction[]
|
||||||
|
Baseline_Completion: number
|
||||||
|
stepTaskExisting: any
|
||||||
|
goal: string
|
||||||
|
}): Promise<BranchAction[][]> => {
|
||||||
|
const response = await mockBranchTaskProcessAPI({
|
||||||
|
branch_Number: data.branch_Number,
|
||||||
|
Modification_Requirement: data.Modification_Requirement,
|
||||||
|
Existing_Steps: data.Existing_Steps,
|
||||||
|
Baseline_Completion: data.Baseline_Completion,
|
||||||
|
stepTaskExisting: data.stepTaskExisting,
|
||||||
|
General_Goal: data.goal,
|
||||||
|
})
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向正在执行的任务追加新步骤
|
||||||
|
* @param executionId 执行ID
|
||||||
|
* @param newSteps 新步骤列表
|
||||||
|
* @returns 追加的步骤数量
|
||||||
|
*/
|
||||||
|
addStepsToExecution = async (executionId: string, newSteps: IRawStepTask[]): Promise<number> => {
|
||||||
|
if (!websocket.connected) {
|
||||||
|
throw new Error('WebSocket未连接')
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await websocket.send('add_steps_to_execution', {
|
||||||
|
execution_id: executionId,
|
||||||
|
new_steps: newSteps.map(step => ({
|
||||||
|
StepName: step.StepName,
|
||||||
|
TaskContent: step.TaskContent,
|
||||||
|
InputObject_List: step.InputObject_List,
|
||||||
|
OutputObject: step.OutputObject,
|
||||||
|
AgentSelection: step.AgentSelection,
|
||||||
|
Collaboration_Brief_frontEnd: step.Collaboration_Brief_frontEnd,
|
||||||
|
TaskProcess: step.TaskProcess.map(action => ({
|
||||||
|
ActionType: action.ActionType,
|
||||||
|
AgentName: action.AgentName,
|
||||||
|
Description: action.Description,
|
||||||
|
ID: action.ID,
|
||||||
|
ImportantInput: action.ImportantInput,
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
}) as { added_count: number }
|
||||||
|
|
||||||
|
return response?.added_count || 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new Api()
|
export default new Api()
|
||||||
|
|||||||
20
frontend/src/assets/icons/Cancel.svg
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg t="1766247549273" class="icon"
|
||||||
|
viewBox="0 0 1024 1024" version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="10050" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
width="16" height="16">
|
||||||
|
<path d="M512 1024C229.248 1024
|
||||||
|
0 794.752 0 512S229.248 0 512 0s512
|
||||||
|
229.248 512 512-229.248 512-512 512z
|
||||||
|
m0-938.666667C276.352 85.333333 85.333333
|
||||||
|
276.352 85.333333 512s191.018667 426.666667
|
||||||
|
426.666667 426.666667 426.666667-191.018667
|
||||||
|
426.666667-426.666667S747.648 85.333333 512
|
||||||
|
85.333333z m198.698667 625.365334a42.666667
|
||||||
|
42.666667 0 0 1-60.330667
|
||||||
|
0L512 572.330667l-138.368 138.368a42.666667 42.666667 0 0 1-60.330667-60.330667L451.669333 512 313.301333 373.632a42.666667 42.666667 0 0 1 60.330667-60.330667L512 451.669333l138.368-138.368a42.624 42.624 0 1 1 60.330667 60.330667L572.330667 512l138.368 138.368a42.666667 42.666667 0 0 1 0 60.330667z"
|
||||||
|
fill="#8e0707" p-id="10051">
|
||||||
|
</path></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
7
frontend/src/assets/icons/Check.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg t="1766247454540" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9049"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
|
||||||
|
<path d="M512 1024C230.4 1024 0 793.6 0 512S230.4 0 512 0s512 230.4 512 512-230.4 512-512 512z m0-938.666667C277.333333 85.333333 85.333333 277.333333 85.333333 512s192 426.666667 426.666667 426.666667 426.666667-192 426.666667-426.666667S746.666667 85.333333 512 85.333333z" p-id="9050">
|
||||||
|
</path>
|
||||||
|
<path d="M708.266667 375.466667c-17.066667-17.066667-44.8-17.066667-59.733334 0l-181.333333 181.333333-91.733333-91.733333c-14.933333-14.933333-40.533333-14.933333-55.466667 0l-6.4 4.266666c-14.933333 14.933333-14.933333 40.533333 0 55.466667l125.866667 125.866667c14.933333 14.933333 40.533333 14.933333 55.466666 0l4.266667-4.266667 209.066667-209.066667c17.066667-17.066667 17.066667-44.8 0-61.866666z" p-id="9051">
|
||||||
|
</path></svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
1
frontend/src/assets/icons/Edit.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1766136633767" class="icon" viewBox="0 0 1028 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7819" width="32.125" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M1018.319924 112.117535q4.093748 9.210934 6.652341 21.492179t2.558593 25.585928-5.117186 26.609365-16.374994 25.585928q-12.281245 12.281245-22.003898 21.492179t-16.886712 16.374994q-8.187497 8.187497-15.351557 14.32812l-191.382739-191.382739q12.281245-11.257808 29.167958-27.121083t28.144521-25.074209q14.32812-11.257808 29.679676-15.863275t30.191395-4.093748 28.656239 4.605467 24.050772 9.210934q21.492179 11.257808 47.589826 39.402329t40.425766 58.847634zM221.062416 611.554845q6.140623-6.140623 28.656239-29.167958t56.289041-56.80076l74.710909-74.710909 82.898406-82.898406 220.038979-220.038979 191.382739 192.406177-220.038979 220.038979-81.874969 82.898406q-40.937484 39.914047-73.687472 73.175753t-54.242167 54.753885-25.585928 24.562491q-10.234371 9.210934-23.539054 19.445305t-27.632802 16.374994q-14.32812 7.16406-41.960921 17.398431t-57.824197 19.957024-57.312478 16.886712-40.425766 9.210934q-27.632802 3.070311-36.843736-8.187497t-5.117186-37.867173q2.046874-14.32812 9.722653-41.449203t16.374994-56.289041 16.886712-53.730448 13.304682-33.773425q6.140623-14.32812 13.816401-26.097646t22.003898-26.097646z" p-id="7820" fill="#000000"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
1
frontend/src/assets/icons/Pause.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1769048650684" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6190" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M874.058005 149.941995a510.06838 510.06838 0 1 0 109.740156 162.738976 511.396369 511.396369 0 0 0-109.740156-162.738976z m66.278708 362.178731A428.336713 428.336713 0 1 1 512 83.663287a428.698892 428.698892 0 0 1 428.336713 428.336713z" fill="#36404f" p-id="6191"></path><path d="M417.954256 281.533601a41.046923 41.046923 0 0 0-41.77128 40.201839v385.116718a41.892007 41.892007 0 0 0 83.663287 0v-385.116718a41.167649 41.167649 0 0 0-41.892007-40.201839zM606.045744 281.533601a41.046923 41.046923 0 0 0-41.77128 40.201839v385.116718a41.892007 41.892007 0 0 0 83.663287 0v-385.116718a41.167649 41.167649 0 0 0-41.892007-40.201839z" fill="#36404f" p-id="6192"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1004 B |
@@ -1 +1,5 @@
|
|||||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1761736278335" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5885" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M226.592 896C167.616 896 128 850.48 128 782.736V241.264C128 173.52 167.616 128 226.592 128c20.176 0 41.136 5.536 62.288 16.464l542.864 280.432C887.648 453.792 896 491.872 896 512s-8.352 58.208-64.272 87.088L288.864 879.536C267.712 890.464 246.768 896 226.592 896z m0-704.304c-31.008 0-34.368 34.656-34.368 49.568v541.472c0 14.896 3.344 49.568 34.368 49.568 9.6 0 20.88-3.2 32.608-9.248l542.864-280.432c21.904-11.328 29.712-23.232 29.712-30.608s-7.808-19.28-29.712-30.592L259.2 200.96c-11.728-6.048-23.008-9.264-32.608-9.264z" p-id="5886"></path></svg>
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg t="1761736278335" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5885"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
|
||||||
|
<path d="M226.592 896C167.616 896 128 850.48 128 782.736V241.264C128 173.52 167.616 128 226.592 128c20.176 0 41.136 5.536 62.288 16.464l542.864 280.432C887.648 453.792 896 491.872 896 512s-8.352 58.208-64.272 87.088L288.864 879.536C267.712 890.464 246.768 896 226.592 896z m0-704.304c-31.008 0-34.368 34.656-34.368 49.568v541.472c0 14.896 3.344 49.568 34.368 49.568 9.6 0 20.88-3.2 32.608-9.248l542.864-280.432c21.904-11.328 29.712-23.232 29.712-30.608s-7.808-19.28-29.712-30.592L259.2 200.96c-11.728-6.048-23.008-9.264-32.608-9.264z" p-id="5886"></path></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 886 B After Width: | Height: | Size: 890 B |
1
frontend/src/assets/icons/agent-change.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1767084779395" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="21382" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M304.79872 108.70784c-118.75328 2.56-194.5856 51.74784-259.92192 144.73216-69.6832 99.1232-37.41696 233.31328-37.41696 233.31328s31.7696-100.06528 88.81664-147.74784c50.53952-42.22976 99.60448-78.11072 208.52224-82.97984v54.68672l141.69088-126.94016-141.69088-129.024v53.95968zM719.19616 915.25632c118.75328-2.56512 194.5856-51.712 259.96288-144.6912 69.64224-99.1232 37.37088-233.31328 37.37088-233.31328s-31.7696 100.06528-88.81152 147.74784c-50.51904 42.20928-99.6096 78.08512-208.52736 82.95936v-54.66624l-141.66528 126.93504 141.66528 129.024v-53.99552zM794.82368 304.37376h-88.64256c-72.77056 0-131.712 58.96192-131.712 131.712v110.96064c54.29248 22.21056 113.75616 34.49856 176.06656 34.49856 62.26944 0 121.728-12.288 176.02048-34.49856V436.08064c-0.00512-72.75008-58.96192-131.70688-131.73248-131.70688zM863.34464 167.58272c0 62.336-50.49856 112.87552-112.80896 112.87552-62.37696 0-112.87552-50.53952-112.87552-112.87552s50.49856-112.83456 112.87552-112.83456c62.30528 0 112.80896 50.49856 112.80896 112.83456z" fill="#ffffff" p-id="21383"></path><path d="M333.27616 692.08576H244.6336c-72.77056 0-131.73248 58.9824-131.73248 131.73248v110.94016c54.30784 22.23104 113.75104 34.49856 176.06656 34.49856 62.28992 0 121.74848-12.26752 176.02048-34.49856v-110.94016c0-72.75008-58.96192-131.73248-131.712-131.73248zM401.80224 555.31008c0 62.31552-50.47808 112.88064-112.83456 112.88064-62.37696 0-112.88064-50.56512-112.88064-112.88064 0-62.35136 50.50368-112.85504 112.88064-112.85504 62.35648 0 112.83456 50.5088 112.83456 112.85504z" fill="#ffffff" p-id="21384"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
1
frontend/src/assets/icons/branch.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1766289101374" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2562" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M801.796646 348.507817c-6.599924 7.099918-9.599889 16.799806-8.299904 26.399694 1.499983 10.899874 2.399972 21.799748 2.699969 32.299627 1.399984 51.499404-11.099872 96.098888-37.09957 132.698464-45.099478 63.199269-117.398641 83.499034-170.098032 98.298863-35.799586 9.999884-61.599287 12.599854-82.399047 14.599831-22.799736 2.299973-34.299603 3.399961-50.599414 12.799851-3.199963 1.899978-6.399926 3.899955-9.49989 6.09993-29.499659 21.199755-46.399463 55.699355-46.399463 91.998935v28.599669c0 10.699876 5.499936 20.599762 14.399833 26.599692 30.299649 20.399764 50.199419 55.199361 49.599426 94.598906-0.799991 60.299302-49.999421 109.598732-110.398722 110.398722C291.002557 1024.89999 240.003148 974.400574 240.003148 912.001296c0-38.89955 19.799771-73.199153 49.899422-93.198921 8.799898-5.899932 14.099837-15.899816 14.099837-26.499694V231.60917c0-10.699876-5.499936-20.599762-14.399833-26.599692-30.299649-20.399764-50.199419-55.199361-49.599426-94.598906C240.803138 50.11127 290.002569 0.911839 350.40187 0.01185 413.001146-0.88814 464.000555 49.611276 464.000555 112.010554c0 38.89955-19.799771 73.199153-49.899422 93.198921-8.799898 5.899932-14.099837 15.899816-14.099837 26.499694v346.095994c0 4.099953 4.399949 6.599924 7.999908 4.599947h0.099998c34.299603-19.699772 62.099281-22.399741 88.99897-25.099709 18.799782-1.799979 38.299557-3.799956 65.899238-11.499867 43.299499-12.09986 92.398931-25.8997 117.898635-61.599287 16.999803-23.799725 20.599762-53.399382 19.099779-79.599079-0.699992-12.599854-8.699899-23.499728-20.399763-28.099675-42.099513-16.299811-71.899168-57.299337-71.599172-105.298781 0.399995-61.699286 51.299406-111.698707 112.998692-111.198714 61.399289 0.499994 110.998715 50.499416 110.998716 111.998704 0 29.599657-11.499867 56.499346-30.199651 76.499115z" p-id="2563" fill="#ffffff"></path></svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
1
frontend/src/assets/icons/close.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764755052115" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6234" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M886.784 746.496q29.696 30.72 43.52 56.32t-4.608 58.368q-4.096 6.144-11.264 14.848t-14.848 16.896-15.36 14.848-12.8 9.728q-25.6 15.36-60.416 8.192t-62.464-34.816l-43.008-43.008-57.344-57.344-67.584-67.584-73.728-73.728-131.072 131.072q-60.416 60.416-98.304 99.328-38.912 38.912-77.312 48.128t-68.096-17.408l-7.168-7.168-11.264-11.264-11.264-11.264q-6.144-6.144-7.168-8.192-11.264-14.336-13.312-29.184t2.56-29.184 13.824-27.648 20.48-24.576q9.216-8.192 32.768-30.72l55.296-57.344q33.792-32.768 75.264-73.728t86.528-86.016q-49.152-49.152-93.696-93.184t-79.872-78.848-57.856-56.832-27.648-27.136q-26.624-26.624-27.136-52.736t17.92-52.736q8.192-10.24 23.552-24.064t21.504-17.92q30.72-20.48 55.296-17.92t49.152 28.16l31.744 31.744q23.552 23.552 58.368 57.344t78.336 76.288 90.624 88.576q38.912-38.912 76.288-75.776t69.632-69.12 58.368-57.856 43.52-43.008q24.576-23.552 53.248-31.232t55.296 12.8q1.024 1.024 6.656 5.12t11.264 9.216 10.752 9.728 7.168 5.632q27.648 26.624 27.136 57.856t-27.136 57.856q-18.432 18.432-45.568 46.08t-60.416 60.416-70.144 69.632l-77.824 77.824q37.888 36.864 74.24 72.192t67.584 66.048 56.32 56.32 41.472 41.984z" p-id="6235"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
14
frontend/src/assets/icons/icons.svg
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<svg
|
||||||
|
className="icon"
|
||||||
|
viewBox="0 0 8960 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="9634"
|
||||||
|
width="100%"
|
||||||
|
height="60"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8960 0c-451.52 181.184-171.2 1024-992 1024H992C171.232 1024 451.392 181.184 0 0h8960z"
|
||||||
|
p-id="9635"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 336 B |
1
frontend/src/assets/icons/left.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764757574909" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9019" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M248.88888875 558.88888844l401.7777778 401.77777782c28.44444469 28.44444469 74.66666625 28.44444469 99.55555594 0 28.44444469-28.44444469 28.44444469-74.66666625 1e-8-99.555555l-352.00000031-352.00000032 352.00000031-351.99999937c28.44444469-28.44444469 28.44444469-71.11111125 0-99.55555594s-74.66666625-28.44444469-99.55555594 0L248.88888875 459.33333344c-14.22222188 14.22222188-21.33333375 31.99999969-21.33333281 49.7777775 0 17.7777778 7.11111094 35.55555562 21.33333281 49.7777775" fill="#ffffff" p-id="9020"></path></svg>
|
||||||
|
After Width: | Height: | Size: 860 B |
1
frontend/src/assets/icons/moon.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764820450888" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="23116" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M854.442667 725.376c-117.845333 204.117333-378.837333 274.048-582.954667 156.202667A425.173333 425.173333 0 0 1 133.546667 754.346667a32 32 0 0 1 15.573333-48.298667c160.725333-57.514667 246.826667-124.16 296.789333-219.562667 52.565333-100.394667 66.176-210.346667 29.397334-361.088a32 32 0 0 1 32.810666-39.552 425.002667 425.002667 0 0 1 190.165334 56.618667c204.117333 117.845333 274.048 378.837333 156.16 582.912z" fill="#ffffff" p-id="23117"></path></svg>
|
||||||
|
After Width: | Height: | Size: 793 B |
@@ -1 +1,6 @@
|
|||||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1761204835005" class="icon" viewBox="0 0 1171 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5692" xmlns:xlink="http://www.w3.org/1999/xlink" width="228.7109375" height="200"><path d="M502.237757 1024 644.426501 829.679301 502.237757 788.716444 502.237757 1024 502.237757 1024ZM0 566.713817 403.967637 689.088066 901.485385 266.66003 515.916344 721.68034 947.825442 855.099648 1170.285714 0 0 566.713817 0 566.713817Z" p-id="5693"></path></svg>
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg t="1761204835005" class="icon" viewBox="0 0 1171 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="5692" xmlns:xlink="http://www.w3.org/1999/xlink" width="228.7109375" height="200">
|
||||||
|
<path d="M502.237757 1024 644.426501 829.679301 502.237757 788.716444 502.237757 1024 502.237757
|
||||||
|
1024ZM0 566.713817 403.967637 689.088066 901.485385 266.66003 515.916344 721.68034 947.825442 855.099648 1170.285714 0 0 566.713817 0 566.713817Z" p-id="5693"></path></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 603 B After Width: | Height: | Size: 610 B |
1
frontend/src/assets/icons/process.svg
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
1
frontend/src/assets/icons/right.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764757483582" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7204" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M775.11111125 465.11111156l-401.7777778-401.77777782c-28.44444469-28.44444469-74.66666625-28.44444469-99.55555594 0-28.44444469 28.44444469-28.44444469 74.66666625-1e-8 99.555555l352.00000031 352.00000032-352.00000031 351.99999937c-28.44444469 28.44444469-28.44444469 71.11111125 0 99.55555594s74.66666625 28.44444469 99.55555594 0L775.11111125 564.66666656c14.22222188-14.22222188 21.33333375-31.99999969 21.33333281-49.7777775 0-17.7777778-7.11111094-35.55555562-21.33333281-49.7777775" fill="#d8d8d8" p-id="7205"></path></svg>
|
||||||
|
After Width: | Height: | Size: 860 B |
7
frontend/src/assets/icons/stoprunning.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg t="1768992484327" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="10530" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<path d="M512 853.333333c-187.733333 0-341.333333-153.6-341.333333-341.333333s153.6-341.333333 341.333333-341.333333
|
||||||
|
341.333333 153.6 341.333333 341.333333-153.6 341.333333-341.333333 341.333333z m0-85.333333c140.8 0 256-115.2 256-256s-115.2-256-256-256-256
|
||||||
|
115.2-256 256 115.2 256 256 256z m-85.333333-341.333333h170.666666v170.666666h-170.666666v-170.666666z" fill="#ffffff" p-id="10531"></path></svg>
|
||||||
|
After Width: | Height: | Size: 709 B |
1
frontend/src/assets/icons/sunny.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764820260653" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13095" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M152.00256 792.00256l76.00128-77.99808 60.00128 60.00128-76.00128 77.99808z m317.99808 165.99552V832h83.99872v125.99808H470.00064zM512 233.99936c141.99808 0 256 114.00192 256 256s-114.00192 256-256 256-256-114.00192-256-256 114.00192-256 256-256zM854.00064 448h128v86.00064h-128V448z m-118.00064 326.00064l60.00128-57.99936 76.00128 76.00128-60.00128 60.00128z m136.00256-584.00256l-76.00128 76.00128-60.00128-60.00128 76.00128-76.00128z m-318.0032-165.99552v125.99808H470.00064V24.00256h83.99872z m-384 423.99744v86.00064h-128V448h128zM288 205.99808L227.99872 265.99936 151.99744 189.99808 211.99872 129.9968z" p-id="13096" fill="#ffa300"></path></svg>
|
||||||
|
After Width: | Height: | Size: 985 B |
1
frontend/src/assets/icons/video-play.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1769048534610" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4890" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M527.984 1001.6a480 480 0 1 1 480-480 480.384 480.384 0 0 1-480 480z m0-883.696A403.696 403.696 0 1 0 931.68 521.6 403.84 403.84 0 0 0 527.984 117.904z" fill="#36404f" p-id="4891"></path><path d="M473.136 729.6a47.088 47.088 0 0 1-18.112-3.888 38.768 38.768 0 0 1-23.056-34.992V384.384a39.632 39.632 0 0 1 23.056-34.992 46.016 46.016 0 0 1 43.632 3.888l211.568 153.168a38.72 38.72 0 0 1 16.464 31.104 37.632 37.632 0 0 1-16.464 31.104l-211.568 153.168a44.56 44.56 0 0 1-25.52 7.776z m41.168-266.704v149.296l102.896-74.64z" fill="#36404f" p-id="4892"></path></svg>
|
||||||
|
After Width: | Height: | Size: 894 B |
@@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-tooltip :disabled="!isOverflow" effect="light" placement="top" :content="text">
|
<el-tooltip
|
||||||
|
:disabled="!isOverflow"
|
||||||
|
effect="light"
|
||||||
|
placement="top"
|
||||||
|
:content="text"
|
||||||
|
popper-class="multi-line-tooltip-popper"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
ref="containerRef"
|
ref="containerRef"
|
||||||
class="multi-line-ellipsis"
|
class="multi-line-ellipsis"
|
||||||
@@ -28,7 +34,7 @@ interface Props {
|
|||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
text: '',
|
text: '',
|
||||||
lines: 3,
|
lines: 3,
|
||||||
maxWidth: '100%',
|
maxWidth: '100%'
|
||||||
})
|
})
|
||||||
|
|
||||||
const isOverflow = ref(false)
|
const isOverflow = ref(false)
|
||||||
@@ -45,8 +51,8 @@ const containerStyle = computed(
|
|||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
lineHeight: '1.5',
|
lineHeight: '1.5',
|
||||||
wordBreak: 'break-all',
|
wordBreak: 'break-all'
|
||||||
}) as HTMLAttributes['style'],
|
} as HTMLAttributes['style'])
|
||||||
)
|
)
|
||||||
|
|
||||||
// 检查文字是否溢出
|
// 检查文字是否溢出
|
||||||
@@ -91,3 +97,9 @@ onMounted(() => {
|
|||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.multi-line-tooltip-popper {
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
260
frontend/src/components/Notification/Notification.vue
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
<template>
|
||||||
|
<teleport to="body">
|
||||||
|
<div class="notification-container">
|
||||||
|
<transition-group
|
||||||
|
name="notification"
|
||||||
|
tag="div"
|
||||||
|
class="notification-list"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="notification in notifications"
|
||||||
|
:key="notification.id"
|
||||||
|
:class="[
|
||||||
|
'notification-item',
|
||||||
|
`notification-${notification.type || 'info'}`
|
||||||
|
]"
|
||||||
|
:style="{ zIndex: notification.zIndex || 1000 }"
|
||||||
|
>
|
||||||
|
<div class="notification-content">
|
||||||
|
<div class="notification-icon">
|
||||||
|
<component :is="getIcon(notification.type)" />
|
||||||
|
</div>
|
||||||
|
<div class="notification-message">
|
||||||
|
<div class="notification-title">{{ notification.title }}</div>
|
||||||
|
<div v-if="notification.detailTitle" class="notification-detail-title">
|
||||||
|
{{ notification.detailTitle }}
|
||||||
|
</div>
|
||||||
|
<div v-if="notification.detailMessage" class="notification-detail-desc">
|
||||||
|
{{ notification.detailMessage }}
|
||||||
|
</div>
|
||||||
|
<div v-else-if="notification.message" class="notification-desc">
|
||||||
|
{{ notification.message }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="notification-close" @click="close(notification.id)">
|
||||||
|
<Close />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="notification.showProgress" class="notification-progress">
|
||||||
|
<div
|
||||||
|
class="progress-bar"
|
||||||
|
:style="{ width: `${notification.progress || 0}%` }"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition-group>
|
||||||
|
</div>
|
||||||
|
</teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import {
|
||||||
|
Close,
|
||||||
|
SuccessFilled as IconSuccess,
|
||||||
|
WarningFilled as IconWarning,
|
||||||
|
CircleCloseFilled,
|
||||||
|
InfoFilled
|
||||||
|
} from '@element-plus/icons-vue'
|
||||||
|
import type { NotificationItem } from '@/composables/useNotification'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
notifications: NotificationItem[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
close: [id: string]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const close = (id: string) => {
|
||||||
|
emit('close', id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getIcon = (type?: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'success':
|
||||||
|
return IconSuccess
|
||||||
|
case 'warning':
|
||||||
|
return IconWarning
|
||||||
|
case 'error':
|
||||||
|
return IconWarning
|
||||||
|
default:
|
||||||
|
return InfoFilled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.notification-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 9999;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-item {
|
||||||
|
pointer-events: auto;
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 450px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
overflow: hidden;
|
||||||
|
border-left: 4px solid #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-success {
|
||||||
|
border-left-color: #67c23a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-warning {
|
||||||
|
border-left-color: #e6a23c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-error {
|
||||||
|
border-left-color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 12px 16px;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-icon .success {
|
||||||
|
color: #67c23a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-icon .warning {
|
||||||
|
color: #e6a23c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-icon .error {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-icon .info {
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-message {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-detail-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #409eff;
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-detail-desc {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-desc {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #606266;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-close {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #909399;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-close:hover {
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-progress {
|
||||||
|
height: 2px;
|
||||||
|
background: #f0f2f5;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
background: #409eff;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 进入动画 */
|
||||||
|
.notification-enter-active {
|
||||||
|
animation: slideInRight 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 离开动画 */
|
||||||
|
.notification-leave-active {
|
||||||
|
animation: slideOutRight 0.3s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideOutRight {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 列表项移动动画 */
|
||||||
|
.notification-move,
|
||||||
|
.notification-enter-active,
|
||||||
|
.notification-leave-active {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
iconClass: string
|
iconClass: string
|
||||||
|
|||||||
163
frontend/src/composables/useNotification.ts
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export interface NotificationItem {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
message?: string
|
||||||
|
type?: 'success' | 'warning' | 'info' | 'error'
|
||||||
|
duration?: number
|
||||||
|
showProgress?: boolean
|
||||||
|
progress?: number
|
||||||
|
zIndex?: number
|
||||||
|
onClose?: () => void
|
||||||
|
// 详细进度信息
|
||||||
|
detailTitle?: string
|
||||||
|
detailMessage?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const notifications = ref<NotificationItem[]>([])
|
||||||
|
let notificationIdCounter = 0
|
||||||
|
let zIndexCounter = 1000
|
||||||
|
|
||||||
|
export function useNotification() {
|
||||||
|
const addNotification = (notification: Omit<NotificationItem, 'id' | 'zIndex'>) => {
|
||||||
|
const id = `notification-${notificationIdCounter++}`
|
||||||
|
const newNotification: NotificationItem = {
|
||||||
|
...notification,
|
||||||
|
id,
|
||||||
|
zIndex: ++zIndexCounter,
|
||||||
|
}
|
||||||
|
|
||||||
|
notifications.value.push(newNotification)
|
||||||
|
|
||||||
|
// 自动关闭
|
||||||
|
if (notification.duration && notification.duration > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
removeNotification(id)
|
||||||
|
}, notification.duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeNotification = (id: string) => {
|
||||||
|
const index = notifications.value.findIndex((n) => n.id === id)
|
||||||
|
if (index !== -1) {
|
||||||
|
const notification = notifications.value[index]
|
||||||
|
notifications.value.splice(index, 1)
|
||||||
|
notification.onClose?.()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const success = (title: string, message?: string, options?: Partial<NotificationItem>) => {
|
||||||
|
return addNotification({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
type: 'success',
|
||||||
|
duration: 3000,
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const warning = (title: string, message?: string, options?: Partial<NotificationItem>) => {
|
||||||
|
return addNotification({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
type: 'warning',
|
||||||
|
duration: 3000,
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = (title: string, message?: string, options?: Partial<NotificationItem>) => {
|
||||||
|
return addNotification({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
type: 'info',
|
||||||
|
duration: 3000,
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = (title: string, message?: string, options?: Partial<NotificationItem>) => {
|
||||||
|
return addNotification({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
type: 'error',
|
||||||
|
duration: 5000,
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const progress = (
|
||||||
|
title: string,
|
||||||
|
current: number,
|
||||||
|
total: number,
|
||||||
|
options?: Partial<NotificationItem>,
|
||||||
|
) => {
|
||||||
|
const progressPercent = Math.round((current / total) * 100)
|
||||||
|
return addNotification({
|
||||||
|
title,
|
||||||
|
message: `${current}/${total}`,
|
||||||
|
type: 'info',
|
||||||
|
showProgress: true,
|
||||||
|
progress: progressPercent,
|
||||||
|
duration: 0, // 不自动关闭
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateProgress = (id: string, current: number, total: number) => {
|
||||||
|
const notification = notifications.value.find((n) => n.id === id)
|
||||||
|
if (notification) {
|
||||||
|
notification.progress = Math.round((current / total) * 100)
|
||||||
|
notification.message = `${current}/${total}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateProgressDetail = (
|
||||||
|
id: string,
|
||||||
|
detailTitle: string,
|
||||||
|
detailMessage: string,
|
||||||
|
current?: number,
|
||||||
|
total?: number
|
||||||
|
) => {
|
||||||
|
const notification = notifications.value.find((n) => n.id === id)
|
||||||
|
if (notification) {
|
||||||
|
notification.detailTitle = detailTitle
|
||||||
|
notification.detailMessage = detailMessage
|
||||||
|
if (current !== undefined && total !== undefined) {
|
||||||
|
notification.progress = Math.round((current / total) * 100)
|
||||||
|
notification.message = `${current}/${total}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新通知的主标题
|
||||||
|
const updateNotificationTitle = (id: string, title: string) => {
|
||||||
|
const notification = notifications.value.find((n) => n.id === id)
|
||||||
|
if (notification) {
|
||||||
|
notification.title = title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const clear = () => {
|
||||||
|
notifications.value.forEach((n) => n.onClose?.())
|
||||||
|
notifications.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
notifications,
|
||||||
|
addNotification,
|
||||||
|
removeNotification,
|
||||||
|
success,
|
||||||
|
warning,
|
||||||
|
info,
|
||||||
|
error,
|
||||||
|
progress,
|
||||||
|
updateProgress,
|
||||||
|
updateProgressDetail,
|
||||||
|
updateNotificationTitle,
|
||||||
|
clear,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,71 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useConfigStore } from '@/stores'
|
import { useConfigStore } from '@/stores'
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'AppHeader'
|
name: 'AppHeader'
|
||||||
})
|
})
|
||||||
|
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
|
const isDark = ref(false)
|
||||||
|
|
||||||
|
// 检测当前主题
|
||||||
|
const checkDarkMode = () => {
|
||||||
|
isDark.value =
|
||||||
|
document.documentElement.classList.contains('dark') ||
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
checkDarkMode()
|
||||||
|
|
||||||
|
// 监听系统主题变化
|
||||||
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
|
mediaQuery.addEventListener('change', checkDarkMode)
|
||||||
|
|
||||||
|
// 监听类名变化
|
||||||
|
const observer = new MutationObserver(checkDarkMode)
|
||||||
|
observer.observe(document.documentElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['class']
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
mediaQuery.removeEventListener('change', checkDarkMode)
|
||||||
|
observer.disconnect()
|
||||||
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-[var(--color-bg-secondary)] h-[60px] relative pl-[27px] font-[900] text-[24px]">
|
<div
|
||||||
<div class="absolute left-0 h-full flex items-center">
|
class="bg-[var(--color-bg)] h-[60px] relative pl-[28px] font-[900] text-[24px] absolute left-1/2 transform -translate-x-1/2 w-[600px] h-[60px] dark:bg-black flex items-center justify-center transition-[top]"
|
||||||
<img class="w-[36.8px] h-[36.8px] rounded-full mr-[12px]" src="/logo.jpg" alt="logo" />
|
>
|
||||||
<span class="text-[#9E0000]">{{ configStore.config.title }}</span>{{ configStore.config.subTitle }}
|
<svg-icon
|
||||||
</div>
|
icon-class="icons"
|
||||||
<div class="text-center h-full w-full tracking-[8.5px] flex items-center justify-center">
|
class="header-icon"
|
||||||
{{ configStore.config.centerTitle }}
|
size="100% "
|
||||||
</div>
|
:style="{
|
||||||
|
filter: isDark
|
||||||
|
? 'drop-shadow(0px 0px 5px rgba(0, 0, 0, 0.5))'
|
||||||
|
: 'drop-shadow(0px 0px 5px rgba(161, 161, 161, 0.2))',
|
||||||
|
stroke: 'var(--color-border)',
|
||||||
|
strokeWidth: '1px',
|
||||||
|
strokeLinejoin: 'round'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
<h2
|
||||||
|
className="flex items-center gap-3 text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3 absolute left-1/2 transform -translate-x-1/2"
|
||||||
|
>
|
||||||
|
<img src="/logo.png" alt="logo" className="w-8" />
|
||||||
|
<p>
|
||||||
|
<span className="text-[#9e0000]">数联网</span
|
||||||
|
><span className="text-[var(--color-text-title)]">多智能体协同平台</span>
|
||||||
|
</p>
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.header-icon {
|
||||||
|
color: var(--color-header-bg);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, onMounted, computed, reactive, nextTick } from 'vue'
|
||||||
|
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||||
import { useAgentsStore, useConfigStore } from '@/stores'
|
import { useAgentsStore, useConfigStore } from '@/stores'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
|
import websocket from '@/utils/websocket'
|
||||||
import { changeBriefs } from '@/utils/collaboration_Brief_FrontEnd.ts'
|
import { changeBriefs } from '@/utils/collaboration_Brief_FrontEnd.ts'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
import AssignmentButton from './TaskTemplate/TaskSyllabus/components/AssignmentButton.vue'
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'search-start'): void
|
(e: 'search-start'): void
|
||||||
(e: 'search', value: string): void
|
(e: 'search', value: string): void
|
||||||
@@ -14,12 +14,190 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const agentsStore = useAgentsStore()
|
const agentsStore = useAgentsStore()
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
|
|
||||||
const searchValue = ref('')
|
const searchValue = ref('')
|
||||||
const triggerOnFocus = ref(true)
|
const triggerOnFocus = ref(true)
|
||||||
const isFocus = ref(false)
|
const isFocus = ref(false)
|
||||||
|
const hasAutoSearched = ref(false)
|
||||||
|
const isExpanded = ref(false)
|
||||||
|
// 添加一个状态来跟踪是否正在填充步骤数据
|
||||||
|
const isFillingSteps = ref(false)
|
||||||
|
// 存储当前填充任务的取消函数
|
||||||
|
const currentStepAbortController = ref<{ cancel: () => void } | null>(null)
|
||||||
|
|
||||||
|
// 解析URL参数
|
||||||
|
function getUrlParam(param: string): string | null {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search)
|
||||||
|
return urlParams.get(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
const planReady = computed(() => {
|
||||||
|
return agentsStore.agentRawPlan.data !== undefined
|
||||||
|
})
|
||||||
|
const openAgentAllocationDialog = () => {
|
||||||
|
agentsStore.openAgentAllocationDialog()
|
||||||
|
}
|
||||||
|
// 自动搜索函数
|
||||||
|
async function autoSearchFromUrl() {
|
||||||
|
const query = getUrlParam('q')
|
||||||
|
if (query && !hasAutoSearched.value) {
|
||||||
|
// 解码URL参数
|
||||||
|
const decodedQuery = decodeURIComponent(query)
|
||||||
|
searchValue.value = decodedQuery
|
||||||
|
hasAutoSearched.value = true
|
||||||
|
|
||||||
|
// 延迟执行搜索,确保组件已完全渲染
|
||||||
|
setTimeout(() => {
|
||||||
|
handleSearch()
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理获取焦点事件
|
||||||
|
function handleFocus() {
|
||||||
|
isFocus.value = true
|
||||||
|
isExpanded.value = true // 搜索框展开
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskContainerRef = ref<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
|
// 处理失去焦点事件
|
||||||
|
function handleBlur() {
|
||||||
|
isFocus.value = false
|
||||||
|
// 延迟收起搜索框,以便点击按钮等操作
|
||||||
|
setTimeout(() => {
|
||||||
|
isExpanded.value = false
|
||||||
|
// 强制重置文本区域高度到最小行数
|
||||||
|
resetTextareaHeight()
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🆕 预加载所有任务的智能体评分数据(顺序加载,确保任务详情已填充)
|
||||||
|
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() {
|
async function handleSearch() {
|
||||||
|
// 用于标记大纲是否成功加载
|
||||||
|
let outlineLoaded = false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
triggerOnFocus.value = false
|
triggerOnFocus.value = false
|
||||||
if (!searchValue.value) {
|
if (!searchValue.value) {
|
||||||
@@ -29,25 +207,139 @@ async function handleSearch() {
|
|||||||
emit('search-start')
|
emit('search-start')
|
||||||
agentsStore.resetAgent()
|
agentsStore.resetAgent()
|
||||||
agentsStore.setAgentRawPlan({ loading: true })
|
agentsStore.setAgentRawPlan({ loading: true })
|
||||||
const data = await api.generateBasePlan({
|
// 重置停止状态
|
||||||
|
agentsStore.setHasStoppedFilling(false)
|
||||||
|
|
||||||
|
// 获取大纲
|
||||||
|
const outlineData = await api.generateBasePlan({
|
||||||
goal: searchValue.value,
|
goal: searchValue.value,
|
||||||
inputs: [],
|
inputs: []
|
||||||
})
|
})
|
||||||
data['Collaboration Process'] = changeBriefs(data['Collaboration Process'])
|
|
||||||
agentsStore.setAgentRawPlan({ data })
|
// 检查是否已被停止
|
||||||
|
if (!isFillingSteps.value && currentStepAbortController.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理简报数据格式
|
||||||
|
outlineData['Collaboration Process'] = changeBriefs(outlineData['Collaboration Process'])
|
||||||
|
|
||||||
|
// 立即显示大纲
|
||||||
|
agentsStore.setAgentRawPlan({ data: outlineData, loading: false })
|
||||||
|
outlineLoaded = true
|
||||||
emit('search', searchValue.value)
|
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 {
|
} finally {
|
||||||
triggerOnFocus.value = true
|
triggerOnFocus.value = true
|
||||||
|
// 完成填充,重置状态
|
||||||
|
isFillingSteps.value = false
|
||||||
|
currentStepAbortController.value = null
|
||||||
|
// 如果大纲加载失败,确保关闭loading
|
||||||
|
if (!outlineLoaded) {
|
||||||
agentsStore.setAgentRawPlan({ loading: false })
|
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 querySearch = (queryString: string, cb: (v: { value: string }[]) => void) => {
|
||||||
const results = queryString
|
const results = queryString
|
||||||
? configStore.config.taskPromptWords.filter(createFilter(queryString))
|
? configStore.config.taskPromptWords.filter(createFilter(queryString))
|
||||||
: configStore.config.taskPromptWords
|
: configStore.config.taskPromptWords
|
||||||
// call callback function to return suggestions
|
// call callback function to return suggestions
|
||||||
cb(results.map((item) => ({ value: item })))
|
cb(results.map(item => ({ value: item })))
|
||||||
}
|
}
|
||||||
|
|
||||||
const createFilter = (queryString: string) => {
|
const createFilter = (queryString: string) => {
|
||||||
@@ -56,7 +348,10 @@ const createFilter = (queryString: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const taskContainerRef = ref<HTMLDivElement | null>(null)
|
// 组件挂载时检查URL参数
|
||||||
|
onMounted(() => {
|
||||||
|
autoSearchFromUrl()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -67,13 +362,20 @@ const taskContainerRef = ref<HTMLDivElement | null>(null)
|
|||||||
:disabled="agentsStore.agents.length > 0"
|
:disabled="agentsStore.agents.length > 0"
|
||||||
>
|
>
|
||||||
<div class="task-root-container">
|
<div class="task-root-container">
|
||||||
<div class="task-container" ref="taskContainerRef" id="task-container">
|
<div
|
||||||
<span class="text-[var(--color-text)] font-bold task-title">任务</span>
|
class="task-container"
|
||||||
|
ref="taskContainerRef"
|
||||||
|
id="task-container"
|
||||||
|
:class="{ expanded: isExpanded }"
|
||||||
|
>
|
||||||
|
<span class="text-[var(--color-text-task)] font-bold task-title">任务</span>
|
||||||
<el-autocomplete
|
<el-autocomplete
|
||||||
|
ref="autocompleteRef"
|
||||||
v-model.trim="searchValue"
|
v-model.trim="searchValue"
|
||||||
class="task-input"
|
class="task-input"
|
||||||
size="large"
|
size="large"
|
||||||
:rows="isFocus ? 3 : 1"
|
:rows="1"
|
||||||
|
:autosize="{ minRows: 1, maxRows: 10 }"
|
||||||
placeholder="请输入您的任务"
|
placeholder="请输入您的任务"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:append-to="taskContainerRef"
|
:append-to="taskContainerRef"
|
||||||
@@ -81,9 +383,10 @@ const taskContainerRef = ref<HTMLDivElement | null>(null)
|
|||||||
@change="agentsStore.setSearchValue"
|
@change="agentsStore.setSearchValue"
|
||||||
:disabled="!(agentsStore.agents.length > 0)"
|
:disabled="!(agentsStore.agents.length > 0)"
|
||||||
:debounce="0"
|
:debounce="0"
|
||||||
|
:clearable="true"
|
||||||
:trigger-on-focus="triggerOnFocus"
|
:trigger-on-focus="triggerOnFocus"
|
||||||
@focus="isFocus = true"
|
@focus="handleFocus"
|
||||||
@blur="isFocus = false"
|
@blur="handleBlur"
|
||||||
@select="isFocus = false"
|
@select="isFocus = false"
|
||||||
>
|
>
|
||||||
</el-autocomplete>
|
</el-autocomplete>
|
||||||
@@ -91,20 +394,27 @@ const taskContainerRef = ref<HTMLDivElement | null>(null)
|
|||||||
class="task-button"
|
class="task-button"
|
||||||
color="linear-gradient(to right, #00C7D2, #315AB4)"
|
color="linear-gradient(to right, #00C7D2, #315AB4)"
|
||||||
size="large"
|
size="large"
|
||||||
title="点击搜索任务"
|
:title="isFillingSteps ? '点击停止生成' : '点击搜索任务'"
|
||||||
circle
|
circle
|
||||||
:loading="agentsStore.agentRawPlan.loading"
|
:loading="agentsStore.agentRawPlan.loading"
|
||||||
:disabled="!searchValue"
|
:disabled="!searchValue"
|
||||||
@click.stop="handleSearch"
|
@click.stop="handleButtonClick"
|
||||||
>
|
>
|
||||||
<SvgIcon
|
<SvgIcon
|
||||||
v-if="!agentsStore.agentRawPlan.loading"
|
v-if="!agentsStore.agentRawPlan.loading && !isFillingSteps"
|
||||||
icon-class="paper-plane"
|
icon-class="paper-plane"
|
||||||
size="18px"
|
size="18px"
|
||||||
color="var(--color-text)"
|
color="#ffffff"
|
||||||
|
/>
|
||||||
|
<SvgIcon
|
||||||
|
v-if="!agentsStore.agentRawPlan.loading && isFillingSteps"
|
||||||
|
icon-class="stoprunning"
|
||||||
|
size="30px"
|
||||||
|
color="#ffffff"
|
||||||
/>
|
/>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
<AssignmentButton v-if="planReady" @click="openAgentAllocationDialog" />
|
||||||
</div>
|
</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
@@ -120,10 +430,9 @@ const taskContainerRef = ref<HTMLDivElement | null>(null)
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
$bg: var(--el-input-bg-color, var(--el-fill-color-blank));
|
$bg: var(--el-input-bg-color, var(--el-fill-color-blank));
|
||||||
background:
|
background: linear-gradient(var(--color-bg-taskbar), var(--color-bg-taskbar)) padding-box,
|
||||||
linear-gradient(var(--color-bg-tertiary), var(--color-bg-tertiary)) padding-box,
|
|
||||||
linear-gradient(to right, #00c8d2, #315ab4) border-box;
|
linear-gradient(to right, #00c8d2, #315ab4) border-box;
|
||||||
border-radius: 40px;
|
border-radius: 30px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
@@ -131,28 +440,49 @@ const taskContainerRef = ref<HTMLDivElement | null>(null)
|
|||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 0 55px 0 47px;
|
padding: 0 55px 0 47px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
/* 搜索框展开时的样式 */
|
||||||
|
&.expanded {
|
||||||
|
box-shadow: var(--color-task-shadow);
|
||||||
|
:deep(.el-autocomplete .el-textarea .el-textarea__inner) {
|
||||||
|
overflow-y: auto !important;
|
||||||
|
min-height: 56px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 非展开状态时,确保文本区域高度固定 */
|
||||||
|
&:not(.expanded) {
|
||||||
|
:deep(.el-textarea__inner) {
|
||||||
|
height: 56px !important;
|
||||||
|
overflow-y: hidden !important;
|
||||||
|
min-height: 56px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.el-popper) {
|
:deep(.el-popper) {
|
||||||
position: static !important;
|
position: static !important;
|
||||||
width: 100%;
|
width: calc(100% + 102px); /*增加左右padding的总和 */
|
||||||
min-width: 100%;
|
min-width: calc(100% + 102px); /* 确保最小宽度也增加 */
|
||||||
background: var(--color-bg-tertiary);
|
margin-left: -47px; /* 向左偏移左padding的值 */
|
||||||
box-shadow: none;
|
margin-right: -55px; /*向右偏移右padding的值 */
|
||||||
|
background: var(--color-bg-taskbar);
|
||||||
border: none;
|
border: none;
|
||||||
transition: height 0s ease-in-out;
|
transition: height 0s ease-in-out;
|
||||||
border-top: 1px solid #494B51;
|
border-top: 1px solid var(--color-border);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
height: 45px;
|
height: 45px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
line-height: 45px;
|
line-height: 45px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
padding-left: 27px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #1C1E25;
|
background: var(--color-bg-hover);
|
||||||
color: #00F3FF;
|
color: var(--color-text-hover);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,8 +505,14 @@ const taskContainerRef = ref<HTMLDivElement | null>(null)
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
padding: 18px 0;
|
padding: 18px 0 0 18px;
|
||||||
resize: none;
|
resize: none;
|
||||||
|
color: var(--color-text-taskbar);
|
||||||
|
|
||||||
|
/* 聚焦时的样式 */
|
||||||
|
.expanded & {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
@@ -192,11 +528,10 @@ const taskContainerRef = ref<HTMLDivElement | null>(null)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.task-title {
|
.task-title {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 28px;
|
top: 28px;
|
||||||
left: 10px;
|
left: 27px;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
@@ -221,4 +556,58 @@ const taskContainerRef = ref<HTMLDivElement | null>(null)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.drawer-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
.title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-list {
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
.process-item {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--color-bg-list);
|
||||||
|
border: 1px solid var(--color-border-default);
|
||||||
|
|
||||||
|
.process-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.agent-tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-text {
|
||||||
|
line-height: 1.6;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-container {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-item:hover {
|
||||||
|
border-color: var(--el-border-color);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { getActionTypeDisplay, getAgentMapIcon } from '@/layout/components/config.ts'
|
import { getActionTypeDisplay, getAgentMapIcon } from '@/layout/components/config.ts'
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||||
import { type Agent, useAgentsStore } from '@/stores'
|
import { type Agent, useAgentsStore } from '@/stores'
|
||||||
@@ -10,9 +11,9 @@ const porps = defineProps<{
|
|||||||
|
|
||||||
const taskProcess = computed(() => {
|
const taskProcess = computed(() => {
|
||||||
const list = agentsStore.currentTask?.TaskProcess ?? []
|
const list = agentsStore.currentTask?.TaskProcess ?? []
|
||||||
return list.map((item) => ({
|
return list.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
key: uuidv4(),
|
key: uuidv4()
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -26,35 +27,40 @@ const agentsStore = useAgentsStore()
|
|||||||
class="user-item"
|
class="user-item"
|
||||||
:class="agentsStore.currentTask?.AgentSelection?.includes(item.Name) ? 'active-card' : ''"
|
:class="agentsStore.currentTask?.AgentSelection?.includes(item.Name) ? 'active-card' : ''"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between relative h-[41px]">
|
<div class="flex items-center justify-between relative h-[43px]">
|
||||||
|
<!-- 图标区域 -->
|
||||||
<div
|
<div
|
||||||
class="w-[44px] h-[44px] rounded-full flex items-center justify-center flex-shrink-0 relative right-[2px] icon-container"
|
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 }"
|
:style="{ background: getAgentMapIcon(item.Name).color }"
|
||||||
>
|
>
|
||||||
<svg-icon
|
<svg-icon :icon-class="getAgentMapIcon(item.Name).icon" color="#fff" size="24px" />
|
||||||
:icon-class="getAgentMapIcon(item.Name).icon"
|
|
||||||
color="var(--color-text)"
|
|
||||||
size="24px"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 text-[14px] flex flex-col items-end justify-end truncate ml-1">
|
|
||||||
|
<div class="flex-1 text-[14px] textClass flex flex-col items-end justify-start truncate ml-1">
|
||||||
|
<div class="flex items-center justify-start gap-2 w-full">
|
||||||
<span
|
<span
|
||||||
class="w-full truncate text-right"
|
class="truncate"
|
||||||
:style="
|
:style="
|
||||||
agentsStore.currentTask?.AgentSelection?.includes(item.Name) ? 'color:#00F3FF' : ''
|
agentsStore.currentTask?.AgentSelection?.includes(item.Name)
|
||||||
|
? 'color:var(--color-accent)'
|
||||||
|
: ''
|
||||||
"
|
"
|
||||||
>{{ item.Name }}</span
|
>{{ item.Name }}</span
|
||||||
>
|
>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="agentsStore.currentTask?.AgentSelection?.includes(item.Name)"
|
v-if="agentsStore.currentTask?.AgentSelection?.includes(item.Name)"
|
||||||
class="flex items-center gap-[7px] h-[8px] mr-1"
|
class="flex items-center gap-[7px] h-[8px] mr-1"
|
||||||
>
|
>
|
||||||
<!-- 小圆点 -->
|
<!-- 小圆点 -->
|
||||||
<div
|
<div
|
||||||
v-for="item1 in taskProcess.filter((i) => i.AgentName === item.Name)"
|
v-for="item1 in taskProcess.filter(i => i.AgentName === item.Name)"
|
||||||
:key="item1.key"
|
:key="item1.key"
|
||||||
class="w-[6px] h-[6px] rounded-full"
|
class="w-[6px] h-[6px] rounded-full"
|
||||||
:style="{ background: getActionTypeDisplay(item1.ActionType)?.color }"
|
:style="{
|
||||||
|
background: getActionTypeDisplay(item1.ActionType)?.color,
|
||||||
|
border: `1px solid ${getActionTypeDisplay(item1.ActionType)?.border}`
|
||||||
|
}"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -71,7 +77,7 @@ const agentsStore = useAgentsStore()
|
|||||||
</div>
|
</div>
|
||||||
<div class="p-[8px] pt-0">
|
<div class="p-[8px] pt-0">
|
||||||
<div
|
<div
|
||||||
v-for="(item1, index1) in taskProcess.filter((i) => i.AgentName === item.Name)"
|
v-for="(item1, index1) in taskProcess.filter(i => i.AgentName === item.Name)"
|
||||||
:key="item1.key"
|
:key="item1.key"
|
||||||
class="text-[12px]"
|
class="text-[12px]"
|
||||||
>
|
>
|
||||||
@@ -89,9 +95,11 @@ const agentsStore = useAgentsStore()
|
|||||||
</div>
|
</div>
|
||||||
<!-- 分割线 -->
|
<!-- 分割线 -->
|
||||||
<div
|
<div
|
||||||
v-if="index1 !== taskProcess.filter((i) => i.AgentName === item.Name).length - 1"
|
v-if="index1 !== taskProcess.filter(i => i.AgentName === item.Name).length - 1"
|
||||||
class="h-[1px] w-full bg-[#494B51] my-[8px]"
|
class="h-[1px] w-full bg-[var(--color-border-default)] my-[8px]"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
|
<AssignmentButton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,13 +108,14 @@ const agentsStore = useAgentsStore()
|
|||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.user-item {
|
.user-item {
|
||||||
background: #1d222b;
|
background: var(--color-agent-list-bg);
|
||||||
border-radius: 40px;
|
border-radius: 40px;
|
||||||
padding-right: 12px;
|
padding-right: 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.25s ease;
|
transition: all 0.25s ease;
|
||||||
color: #969696;
|
color: var(--color-text-detail);
|
||||||
border: 2px solid transparent;
|
border: 1px solid var(--color-agent-list-border);
|
||||||
|
box-sizing: border-box;
|
||||||
.duty-info {
|
.duty-info {
|
||||||
transition: height 0.25s ease;
|
transition: height 0.25s ease;
|
||||||
height: 0;
|
height: 0;
|
||||||
@@ -118,16 +127,30 @@ const agentsStore = useAgentsStore()
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
|
box-shadow: var(--color-agent-list-hover-shadow);
|
||||||
color: #b8b8b8;
|
color: var(--color-text);
|
||||||
|
background: var(--color-agent-list-hover-bg);
|
||||||
|
border: 1px solid var(--color-agent-list-hover-border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.textClass {
|
||||||
|
color: var(--color-text-agent-list);
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-text-agent-list-hover);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.active-card {
|
.active-card {
|
||||||
background:
|
background: linear-gradient(var(--color-bg-quaternary), var(--color-bg-quaternary)) padding-box,
|
||||||
linear-gradient(#171B22, #171B22) padding-box,
|
linear-gradient(to right, var(--color-accent), var(--color-accent-secondary)) border-box;
|
||||||
linear-gradient(to right, #00c8d2, #315ab4) border-box;
|
border: 1px solid var(--color-agent-list-border);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
box-shadow: var(--color-agent-list-hover-shadow);
|
||||||
|
color: var(--color-text);
|
||||||
|
background: var(--color-agent-list-hover-bg);
|
||||||
|
border: 1px solid var(--color-agent-list-hover-border);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
.duty-info {
|
.duty-info {
|
||||||
height: auto;
|
height: auto;
|
||||||
@@ -138,4 +161,10 @@ const agentsStore = useAgentsStore()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加头像容器样式修复
|
||||||
|
.icon-container {
|
||||||
|
right: 0 !important;
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ElNotification } from 'element-plus'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { pick } from 'lodash'
|
import { pick } from 'lodash'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
import api from '@/api/index.ts'
|
import api from '@/api/index.ts'
|
||||||
|
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||||
import { agentMapDuty } from '@/layout/components/config.ts'
|
import { agentMapDuty } from '@/layout/components/config.ts'
|
||||||
import { type Agent, useAgentsStore } from '@/stores'
|
import { type Agent, useAgentsStore } from '@/stores'
|
||||||
import { onMounted } from 'vue'
|
|
||||||
import { readConfig } from '@/utils/readJson.ts'
|
import { readConfig } from '@/utils/readJson.ts'
|
||||||
import AgentRepoList from './AgentRepoList.vue'
|
import AgentRepoList from './AgentRepoList.vue'
|
||||||
|
|
||||||
@@ -19,7 +17,9 @@ onMounted(async () => {
|
|||||||
const res = await readConfig<Agent[]>('agent.json')
|
const res = await readConfig<Agent[]>('agent.json')
|
||||||
agentsStore.setAgents(res)
|
agentsStore.setAgents(res)
|
||||||
}
|
}
|
||||||
await api.setAgents(agentsStore.agents.map((item) => pick(item, ['Name', 'Profile'])))
|
await api.setAgents(
|
||||||
|
agentsStore.agents.map(item => pick(item, ['Name', 'Profile', 'apiUrl', 'apiKey', 'apiModel']))
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 上传agent文件
|
// 上传agent文件
|
||||||
@@ -37,50 +37,77 @@ const handleFileSelect = (event: Event) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const readFileContent = async (file: File) => {
|
// 验证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 reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.onload = async (e) => {
|
reader.onload = e => {
|
||||||
if (!e.target?.result) {
|
try {
|
||||||
|
const content = e.target?.result as string
|
||||||
|
const jsonData = JSON.parse(content)
|
||||||
|
|
||||||
|
if (!Array.isArray(jsonData)) {
|
||||||
|
ElMessage.error('JSON格式错误: 必须为数组格式')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
const json = JSON.parse(e.target.result?.toString?.() ?? '{}')
|
const validAgents = jsonData.filter((agent) => {
|
||||||
// 处理 JSON 数据
|
// 验证必需字段
|
||||||
if (Array.isArray(json)) {
|
if (!agent.Name || typeof agent.Name !== 'string') {
|
||||||
const isValid = json.every(
|
return false
|
||||||
(item) =>
|
}
|
||||||
typeof item.Name === 'string' &&
|
if (!agent.Icon || typeof agent.Icon !== 'string') {
|
||||||
typeof item.Icon === 'string' &&
|
return false
|
||||||
typeof item.Profile === 'string',
|
}
|
||||||
)
|
if (!agent.Profile || typeof agent.Profile !== 'string') {
|
||||||
if (isValid) {
|
return false
|
||||||
// 处理有效的 JSON 数据
|
}
|
||||||
agentsStore.setAgents(
|
|
||||||
json.map((item) => ({
|
// 验证API配置
|
||||||
Name: item.Name,
|
if (!validateApiConfig(agent)) {
|
||||||
Icon: item.Icon.replace(/\.png$/, ''),
|
return false
|
||||||
Profile: item.Profile,
|
}
|
||||||
Classification: item.Classification,
|
|
||||||
})),
|
return true
|
||||||
)
|
|
||||||
await api.setAgents(json.map((item) => pick(item, ['Name', 'Profile'])))
|
|
||||||
} else {
|
|
||||||
ElNotification.error({
|
|
||||||
title: '错误',
|
|
||||||
message: 'JSON 格式错误',
|
|
||||||
})
|
})
|
||||||
}
|
// 修改发送到后端的数据
|
||||||
} else {
|
const processedAgents = validAgents.map(agent => ({
|
||||||
console.error('JSON is not an array')
|
Name: agent.Name,
|
||||||
ElNotification.error({
|
Profile: agent.Profile,
|
||||||
title: '错误',
|
Icon: agent.Icon,
|
||||||
message: 'JSON 格式错误',
|
Classification: agent.Classification || '',
|
||||||
|
apiUrl: agent.apiUrl,
|
||||||
|
apiKey: agent.apiKey,
|
||||||
|
apiModel: agent.apiModel
|
||||||
|
}))
|
||||||
|
|
||||||
|
agentsStore.setAgents(processedAgents)
|
||||||
|
|
||||||
|
// 调用API
|
||||||
|
api
|
||||||
|
.setAgents(processedAgents)
|
||||||
|
.then(() => {
|
||||||
|
ElMessage.success('智能体上传成功')
|
||||||
})
|
})
|
||||||
}
|
.catch(() => {
|
||||||
} catch (e) {
|
ElMessage.error('智能体上传失败')
|
||||||
console.error(e)
|
})
|
||||||
|
} catch {
|
||||||
|
ElMessage.error('JSON解析错误')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reader.onerror = () => {
|
||||||
|
ElMessage.error('文件读取错误')
|
||||||
|
}
|
||||||
|
|
||||||
reader.readAsText(file)
|
reader.readAsText(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +122,7 @@ const agentList = computed(() => {
|
|||||||
if (!agentsStore.agents.length) {
|
if (!agentsStore.agents.length) {
|
||||||
return {
|
return {
|
||||||
selected,
|
selected,
|
||||||
unselected,
|
unselected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const agent of agentsStore.agents) {
|
for (const agent of agentsStore.agents) {
|
||||||
@@ -110,14 +137,14 @@ const agentList = computed(() => {
|
|||||||
obj[agent.Classification] = arr
|
obj[agent.Classification] = arr
|
||||||
unselected.push({
|
unselected.push({
|
||||||
title: agent.Classification,
|
title: agent.Classification,
|
||||||
data: arr,
|
data: arr
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selected,
|
selected,
|
||||||
unselected: unselected,
|
unselected: unselected
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -126,7 +153,7 @@ const agentList = computed(() => {
|
|||||||
<div class="agent-repo h-full flex flex-col" id="agent-repo">
|
<div class="agent-repo h-full flex flex-col" id="agent-repo">
|
||||||
<!-- 头部 -->
|
<!-- 头部 -->
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-[18px] font-bold">智能体库</span>
|
<span class="text-[18px] font-bold text-[var(--color-text-title-header)]">智能体库</span>
|
||||||
<!-- 上传文件 -->
|
<!-- 上传文件 -->
|
||||||
<input type="file" accept=".json" @change="handleFileSelect" class="hidden" ref="fileInput" />
|
<input type="file" accept=".json" @change="handleFileSelect" class="hidden" ref="fileInput" />
|
||||||
<div class="plus-button" @click="triggerFileSelect">
|
<div class="plus-button" @click="triggerFileSelect">
|
||||||
@@ -144,14 +171,22 @@ const agentList = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 底部提示栏 -->
|
<!-- 底部提示栏 -->
|
||||||
<div class="w-full grid grid-cols-3 gap-x-[10px] bg-[#1d222b] rounded-[20px] p-[8px] mt-[10px]">
|
<div
|
||||||
|
class="w-full grid grid-cols-3 gap-x-[10px] bg-[var(--color-bg-indicator)] rounded-[20px] p-[8px] mt-[10px]"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-for="item in Object.values(agentMapDuty)"
|
v-for="item in Object.values(agentMapDuty)"
|
||||||
:key="item.key"
|
:key="item.key"
|
||||||
class="flex items-center justify-center gap-x-1"
|
class="flex items-center justify-center gap-x-1"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="w-[8px] h-[8px] rounded-full"
|
||||||
|
:style="{
|
||||||
|
background: item.color,
|
||||||
|
border: `1px solid ${item.border}`
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
<span class="text-[12px]">{{ item.name }}</span>
|
<span class="text-[12px]">{{ item.name }}</span>
|
||||||
<div class="w-[8px] h-[8px] rounded-full" :style="{ background: item.color }"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -162,7 +197,7 @@ const agentList = computed(() => {
|
|||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
|
|
||||||
.plus-button {
|
.plus-button {
|
||||||
background: #1d2128;
|
background: var(--color-bg-tertiary);
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -174,7 +209,7 @@ const agentList = computed(() => {
|
|||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #374151;
|
background: var(--color-bg-quaternary);
|
||||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,324 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { getActionTypeDisplay } from '@/layout/components/config.ts'
|
||||||
|
import { useAgentsStore } from '@/stores'
|
||||||
|
import BranchButton from './components/TaskButton.vue'
|
||||||
|
|
||||||
|
const agentsStore = useAgentsStore()
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
step: {
|
||||||
|
Id?: string
|
||||||
|
TaskProcess: Array<{
|
||||||
|
ID: string
|
||||||
|
ActionType: string
|
||||||
|
AgentName: string
|
||||||
|
Description: string
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'open-edit', stepId: string, processId: string): void
|
||||||
|
(e: 'save-edit', stepId: string, processId: string, value: string): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
//从 currentTask 中获取数据
|
||||||
|
const currentTaskProcess = computed(() => {
|
||||||
|
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
|
||||||
|
|
||||||
|
const r = parseInt(color.substr(1, 2), 16)
|
||||||
|
const g = parseInt(color.substr(3, 2), 16)
|
||||||
|
const b = parseInt(color.substr(5, 2), 16)
|
||||||
|
|
||||||
|
// 增加亮度(浅两号)
|
||||||
|
const lightenAmount = level * 20
|
||||||
|
const newR = Math.min(255, r + lightenAmount)
|
||||||
|
const newG = Math.min(255, g + lightenAmount)
|
||||||
|
const newB = Math.min(255, b + lightenAmount)
|
||||||
|
|
||||||
|
return `#${Math.round(newR).toString(16).padStart(2, '0')}${Math.round(newG)
|
||||||
|
.toString(16)
|
||||||
|
.padStart(2, '0')}${Math.round(newB).toString(16).padStart(2, '0')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取颜色深两号的函数
|
||||||
|
function getDarkColor(color: string, level: number = 2): string {
|
||||||
|
if (!color || color.length !== 7 || color[0] !== '#') return color
|
||||||
|
|
||||||
|
const r = parseInt(color.substr(1, 2), 16)
|
||||||
|
const g = parseInt(color.substr(3, 2), 16)
|
||||||
|
const b = parseInt(color.substr(5, 2), 16)
|
||||||
|
|
||||||
|
// 降低亮度(深两号)
|
||||||
|
const darkenAmount = level * 20
|
||||||
|
const newR = Math.max(0, r - darkenAmount)
|
||||||
|
const newG = Math.max(0, g - darkenAmount)
|
||||||
|
const newB = Math.max(0, b - darkenAmount)
|
||||||
|
|
||||||
|
return `#${Math.round(newR).toString(16).padStart(2, '0')}${Math.round(newG)
|
||||||
|
.toString(16)
|
||||||
|
.padStart(2, '0')}${Math.round(newB).toString(16).padStart(2, '0')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据主题模式获取调整后的颜色
|
||||||
|
function getAdjustedColor(color: string, level: number = 2): string {
|
||||||
|
if (isDarkMode()) {
|
||||||
|
return getDarkColor(color, level)
|
||||||
|
} else {
|
||||||
|
return getLightColor(color, level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理鼠标进入
|
||||||
|
function handleMouseEnter(processId: string) {
|
||||||
|
hoverProcessId.value = processId
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理鼠标离开
|
||||||
|
function handleMouseLeave() {
|
||||||
|
hoverProcessId.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理双击编辑(针对单个process)
|
||||||
|
function handleDblClick(processId: string, currentDescription: string) {
|
||||||
|
editingProcessId.value = processId
|
||||||
|
editValue.value = currentDescription
|
||||||
|
emit('open-edit', props.step.Id || '', processId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理保存编辑
|
||||||
|
function handleSave(processId: string) {
|
||||||
|
if (!editingProcessId.value) return
|
||||||
|
|
||||||
|
emit('save-edit', props.step.Id || '', processId, editValue.value)
|
||||||
|
editingProcessId.value = null
|
||||||
|
editValue.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理取消编辑
|
||||||
|
function handleCancel() {
|
||||||
|
editingProcessId.value = null
|
||||||
|
editValue.value = ''
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="process-card" @click="handleCardClick">
|
||||||
|
<div class="process-content">
|
||||||
|
<!-- 显示模式 -->
|
||||||
|
<div class="display-content">
|
||||||
|
<span
|
||||||
|
v-for="process in currentTaskProcess"
|
||||||
|
:key="process.ID"
|
||||||
|
class="process-segment"
|
||||||
|
@mouseenter="handleMouseEnter(process.ID)"
|
||||||
|
@mouseleave="handleMouseLeave"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="agent-name"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: getActionTypeDisplay(process.ActionType)?.color || '#909399',
|
||||||
|
color: '#fff',
|
||||||
|
padding: '2px 6px',
|
||||||
|
borderRadius: '3px',
|
||||||
|
marginRight: '4px'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ process.AgentName }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!-- 编辑模式 - 修改为卡片样式 -->
|
||||||
|
<div v-if="editingProcessId === process.ID" class="edit-container">
|
||||||
|
<div class="edit-card">
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<el-input
|
||||||
|
v-model="editValue"
|
||||||
|
type="textarea"
|
||||||
|
:autosize="{ minRows: 3, maxRows: 6 }"
|
||||||
|
placeholder="请输入描述内容"
|
||||||
|
autofocus
|
||||||
|
/>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<svg-icon
|
||||||
|
icon-class="Check"
|
||||||
|
size="20px"
|
||||||
|
color="#328621"
|
||||||
|
class="cursor-pointer mr-4"
|
||||||
|
@click="handleSave(process.ID)"
|
||||||
|
title="保存"
|
||||||
|
/>
|
||||||
|
<svg-icon
|
||||||
|
icon-class="Cancel"
|
||||||
|
size="20px"
|
||||||
|
color="#8e0707"
|
||||||
|
class="cursor-pointer mr-1"
|
||||||
|
@click="handleCancel"
|
||||||
|
title="取消"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 显示模式 -->
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="process-description"
|
||||||
|
:class="{ hovered: hoverProcessId === process.ID }"
|
||||||
|
:style="{
|
||||||
|
border: `1px solid ${getActionTypeDisplay(process.ActionType)?.border}`,
|
||||||
|
backgroundColor:
|
||||||
|
hoverProcessId === process.ID
|
||||||
|
? getAdjustedColor(getActionTypeDisplay(process.ActionType)?.color || '#909399')
|
||||||
|
: 'transparent'
|
||||||
|
}"
|
||||||
|
@dblclick="handleDblClick(process.ID, process.Description)"
|
||||||
|
>
|
||||||
|
{{ process.Description }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="separator" v-if="process.Description && !process.Description.endsWith('。')"
|
||||||
|
>。</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 按钮点击不会冒泡到卡片 -->
|
||||||
|
<BranchButton :step="step" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.process-card {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--color-bg-list);
|
||||||
|
border: 1px solid var(--color-border-default);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
.process-content {
|
||||||
|
min-height: 20px;
|
||||||
|
|
||||||
|
.display-content {
|
||||||
|
line-height: 1.6;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
|
||||||
|
.process-segment {
|
||||||
|
display: inline;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.agent-name {
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-right: 4px;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-container {
|
||||||
|
display: block; // 改为块级元素,使其换行显示
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
.edit-card {
|
||||||
|
//background: #f0f2f5;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
|
||||||
|
:deep(.el-textarea) {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.el-textarea__inner {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 60px;
|
||||||
|
color: var(--color-text-taskbar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-description {
|
||||||
|
display: inline;
|
||||||
|
white-space: normal;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
|
&.hovered {
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child .separator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-card:hover {
|
||||||
|
border-color: var(--el-border-color);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useAgentsStore, useSelectionStore } from '@/stores'
|
||||||
|
|
||||||
|
const agentsStore = useAgentsStore()
|
||||||
|
const selectionStore = useSelectionStore()
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'click'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
step?: any
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 获取分支数量
|
||||||
|
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>
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
// 模拟后端原始返回格式的 Mock 数据 - fill_stepTask_TaskProcess 接口
|
||||||
|
// 后端返回格式: IRawStepTask { StepName, TaskContent, InputObject_List, OutputObject, AgentSelection, TaskProcess, Collaboration_Brief_frontEnd }
|
||||||
|
|
||||||
|
import type { IRawStepTask } from '@/stores'
|
||||||
|
|
||||||
|
// TaskProcess 项格式
|
||||||
|
interface RawTaskProcessItem {
|
||||||
|
ID: string
|
||||||
|
ActionType: string
|
||||||
|
AgentName: string
|
||||||
|
Description: string
|
||||||
|
ImportantInput: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collaboration_Brief_frontEnd 数据项格式
|
||||||
|
interface RawBriefDataItem {
|
||||||
|
text: string
|
||||||
|
color: number[] // [h, s, l]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 后端返回的完整数据格式
|
||||||
|
export interface RawAgentTaskProcessResponse {
|
||||||
|
StepName: string
|
||||||
|
TaskContent: string
|
||||||
|
InputObject_List: string[]
|
||||||
|
OutputObject: string
|
||||||
|
AgentSelection: string[]
|
||||||
|
TaskProcess: RawTaskProcessItem[]
|
||||||
|
Collaboration_Brief_frontEnd?: {
|
||||||
|
template: string
|
||||||
|
data: Record<string, RawBriefDataItem>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟后端返回的原始数据结构(与后端缓存数据格式一致)
|
||||||
|
// 使用与 AgentAssignmentBackendMock 相同的 agent 列表
|
||||||
|
export const mockBackendAgentTaskProcessData: RawAgentTaskProcessResponse = {
|
||||||
|
StepName: '腐蚀类型识别',
|
||||||
|
TaskContent: '分析船舶制造中常见的材料腐蚀类型及其成因。',
|
||||||
|
InputObject_List: [],
|
||||||
|
OutputObject: '腐蚀类型及成因列表',
|
||||||
|
AgentSelection: ['腐蚀机理研究员', '实验材料学家', '防护工程专家'],
|
||||||
|
TaskProcess: [
|
||||||
|
{
|
||||||
|
ID: 'action_101',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '分析海洋环境下的腐蚀机理,确定关键防护要素',
|
||||||
|
ImportantInput: ['海洋环境参数', '防护性能指标'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action_102',
|
||||||
|
ActionType: 'Critique',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '基于腐蚀机理分析结果,设计涂层材料的基础配方',
|
||||||
|
ImportantInput: ['腐蚀机理分析结果', '涂层材料配方'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action_103',
|
||||||
|
ActionType: 'Improve',
|
||||||
|
AgentName: '防护工程专家',
|
||||||
|
Description: '筛选适用于防护涂层的二维材料,评估其性能潜力',
|
||||||
|
ImportantInput: ['材料配方设计', '涂层材料配方'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action_104',
|
||||||
|
ActionType: 'Finalize',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '制定涂层材料性能测试实验方案,包括测试指标和方法',
|
||||||
|
ImportantInput: ['二维材料筛选结果', '防护性能指标'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action_105',
|
||||||
|
ActionType: 'Critique',
|
||||||
|
AgentName: '防护工程专家',
|
||||||
|
Description: '模拟海洋流体环境对涂层材料的影响,优化涂层结构',
|
||||||
|
ImportantInput: ['实验方案', '海洋环境参数'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action_106',
|
||||||
|
ActionType: 'Improve',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '综合评估涂层材料的防护性能,提出改进建议',
|
||||||
|
ImportantInput: ['流体力学模拟结果', '实验材料学测试结果', '二维材料性能数据'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action_107',
|
||||||
|
ActionType: 'Improve',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '整理研发数据和测试结果,撰写完整的研发报告',
|
||||||
|
ImportantInput: ['综合性能评估', '所有研发数据'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '基于!<0>!、!<1>!和!<2>!,!<3>!、!<4>!、!<5>!和!<6>!执行!<7>!任务,以获得!<8>!。',
|
||||||
|
data: {
|
||||||
|
'0': {
|
||||||
|
text: '涂层材料配方',
|
||||||
|
color: [120, 60, 70], // hsl(120, 60%, 70%)
|
||||||
|
},
|
||||||
|
'1': {
|
||||||
|
text: '海洋环境参数',
|
||||||
|
color: [120, 60, 70], // hsl(120, 60%, 70%)
|
||||||
|
},
|
||||||
|
'2': {
|
||||||
|
text: '防护性能指标',
|
||||||
|
color: [120, 60, 70], // hsl(120, 60%, 70%)
|
||||||
|
},
|
||||||
|
'3': {
|
||||||
|
text: '腐蚀机理研究员',
|
||||||
|
color: [0, 0, 90], // hsl(0, 0%, 90%)
|
||||||
|
},
|
||||||
|
'4': {
|
||||||
|
text: '先进材料研发员',
|
||||||
|
color: [0, 0, 90], // hsl(0, 0%, 90%)
|
||||||
|
},
|
||||||
|
'5': {
|
||||||
|
text: '二维材料科学家',
|
||||||
|
color: [0, 0, 90], // hsl(0, 0%, 90%)
|
||||||
|
},
|
||||||
|
'6': {
|
||||||
|
text: '实验材料学家',
|
||||||
|
color: [0, 0, 90], // hsl(0, 0%, 90%)
|
||||||
|
},
|
||||||
|
'7': {
|
||||||
|
text: '研发适用于海洋环境的耐腐蚀防护涂层材料,并进行性能测试与评估',
|
||||||
|
color: [0, 0, 87], // hsl(0, 0%, 87%)
|
||||||
|
},
|
||||||
|
'8': {
|
||||||
|
text: '防护涂层材料研发报告',
|
||||||
|
color: [30, 100, 80], // hsl(30, 100%, 80%)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟后端API调用 - fill_stepTask_TaskProcess
|
||||||
|
export const mockBackendFillAgentTaskProcess = async (
|
||||||
|
goal: string,
|
||||||
|
stepTask: any,
|
||||||
|
agents: string[],
|
||||||
|
): Promise<RawAgentTaskProcessResponse> => {
|
||||||
|
// 模拟网络延迟 500ms
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||||
|
|
||||||
|
// 在真实场景中,后端会根据传入的 goal、stepTask 和 agents 生成不同的 TaskProcess
|
||||||
|
// 这里我们直接返回预设的 Mock 数据
|
||||||
|
// 可以根据传入的 agents 动态修改 AgentSelection 和 TaskProcess
|
||||||
|
|
||||||
|
// 确保 agents 数组不为空
|
||||||
|
const safeAgents = agents.length > 0 ? agents : ['腐蚀机理研究员']
|
||||||
|
|
||||||
|
const responseData: RawAgentTaskProcessResponse = {
|
||||||
|
...mockBackendAgentTaskProcessData,
|
||||||
|
AgentSelection: agents,
|
||||||
|
TaskProcess: mockBackendAgentTaskProcessData.TaskProcess.map((action, index) => ({
|
||||||
|
...action,
|
||||||
|
AgentName: safeAgents[index % safeAgents.length],
|
||||||
|
})),
|
||||||
|
Collaboration_Brief_frontEnd: mockBackendAgentTaskProcessData.Collaboration_Brief_frontEnd
|
||||||
|
? {
|
||||||
|
template: mockBackendAgentTaskProcessData.Collaboration_Brief_frontEnd.template,
|
||||||
|
data: { ...mockBackendAgentTaskProcessData.Collaboration_Brief_frontEnd.data },
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 Collaboration_Brief_frontEnd.data 中的 agent 引用
|
||||||
|
if (responseData.Collaboration_Brief_frontEnd?.data) {
|
||||||
|
const agentCount = Math.min(safeAgents.length, 4) // 最多4个agent
|
||||||
|
for (let i = 0; i < agentCount; i++) {
|
||||||
|
const key = String(i + 3) // agent从索引3开始
|
||||||
|
if (responseData.Collaboration_Brief_frontEnd.data[key]) {
|
||||||
|
responseData.Collaboration_Brief_frontEnd.data[key] = {
|
||||||
|
...responseData.Collaboration_Brief_frontEnd.data[key],
|
||||||
|
text: safeAgents[i]!,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseData
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
<!-- AdditionalOutputCard.vue -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, nextTick } from 'vue'
|
||||||
|
import { useAgentsStore } from '@/stores'
|
||||||
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||||
|
|
||||||
|
const agentsStore = useAgentsStore()
|
||||||
|
const props = defineProps<{
|
||||||
|
index: number
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 获取产物名称
|
||||||
|
const currentOutput = computed(() => {
|
||||||
|
return agentsStore.additionalOutputs[props.index] || ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 编辑状态
|
||||||
|
const isEditing = ref(false)
|
||||||
|
const inputValue = ref('')
|
||||||
|
const originalValue = ref('')
|
||||||
|
const inputRef = ref<HTMLElement>()
|
||||||
|
|
||||||
|
// 点击编辑图标
|
||||||
|
const handleEditClick = () => {
|
||||||
|
isEditing.value = true
|
||||||
|
originalValue.value = inputValue.value
|
||||||
|
|
||||||
|
// 等待 DOM 更新后聚焦输入框
|
||||||
|
nextTick(() => {
|
||||||
|
if (inputRef.value) {
|
||||||
|
const inputEl = inputRef.value.querySelector('input')
|
||||||
|
if (inputEl) {
|
||||||
|
inputEl.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存编辑
|
||||||
|
const handleSave = () => {
|
||||||
|
if (isEditing.value) {
|
||||||
|
isEditing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消编辑
|
||||||
|
const handleCancel = () => {
|
||||||
|
if (isEditing.value) {
|
||||||
|
inputValue.value = originalValue.value // 恢复原始值
|
||||||
|
isEditing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理键盘事件
|
||||||
|
const handleKeydown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault()
|
||||||
|
handleSave()
|
||||||
|
} else if (event.key === 'Escape') {
|
||||||
|
event.preventDefault()
|
||||||
|
handleCancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输入框失去焦点处理
|
||||||
|
const handleBlur = () => {
|
||||||
|
// 延迟处理,避免点击按钮时立即触发
|
||||||
|
setTimeout(() => {
|
||||||
|
if (isEditing.value) {
|
||||||
|
handleSave()
|
||||||
|
}
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- 当产物存在时才显示 -->
|
||||||
|
<div v-if="currentOutput" class="card-item">
|
||||||
|
<el-card
|
||||||
|
class="card-item w-full relative output-object-card"
|
||||||
|
:shadow="true"
|
||||||
|
:id="`additional-output-${index}`"
|
||||||
|
>
|
||||||
|
<!-- 显示产物名称 -->
|
||||||
|
<div class="text-start w-[100%]">
|
||||||
|
<div class="text-[18px] font-bold text-[var(--color-text)] mb-2">
|
||||||
|
{{ currentOutput }}
|
||||||
|
</div>
|
||||||
|
<div ref="inputRef">
|
||||||
|
<el-input
|
||||||
|
v-model="inputValue"
|
||||||
|
:readonly="!isEditing"
|
||||||
|
:placeholder="isEditing ? '请输入内容...' : '点击编辑图标开始编辑...'"
|
||||||
|
@keydown="handleKeydown"
|
||||||
|
@blur="handleBlur"
|
||||||
|
:class="{ editing: isEditing }"
|
||||||
|
>
|
||||||
|
<template #suffix>
|
||||||
|
<!-- 只读状态:显示编辑图标 -->
|
||||||
|
<div v-if="!isEditing" class="flex items-center">
|
||||||
|
<svg-icon
|
||||||
|
icon-class="Edit"
|
||||||
|
size="20px"
|
||||||
|
class="cursor-pointer hover:text-[#409eff] transition-colors"
|
||||||
|
@click="handleEditClick"
|
||||||
|
title="点击编辑"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 编辑状态下的提示 -->
|
||||||
|
<div v-if="isEditing" class="mt-2 text-end text-xs text-gray-500">
|
||||||
|
<svg-icon
|
||||||
|
icon-class="Check"
|
||||||
|
size="20px"
|
||||||
|
color="#328621"
|
||||||
|
class="cursor-pointer mr-4"
|
||||||
|
@click="handleSave"
|
||||||
|
title="保存"
|
||||||
|
/>
|
||||||
|
<svg-icon
|
||||||
|
icon-class="Cancel"
|
||||||
|
size="20px"
|
||||||
|
color="#8e0707"
|
||||||
|
class="cursor-pointer mr-4"
|
||||||
|
@click="handleCancel"
|
||||||
|
title="取消"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.card-item {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-object-card {
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
min-height: 80px;
|
||||||
|
display: flex;
|
||||||
|
align-items: start;
|
||||||
|
justify-content: start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 输入框样式 */
|
||||||
|
:deep(.el-input .el-input__wrapper) {
|
||||||
|
box-shadow: none;
|
||||||
|
background-color: var(--color-bg-three);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 编辑状态下的输入框样式 */
|
||||||
|
:deep(.el-input.editing .el-input__wrapper) {
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input.editing .el-input__wrapper.is-focus) {
|
||||||
|
border-color: #c0c4cc;
|
||||||
|
box-shadow: 0 0 0 1px #c0c4cc;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -15,16 +15,14 @@ const md = new MarkdownIt({
|
|||||||
html: true,
|
html: true,
|
||||||
linkify: true,
|
linkify: true,
|
||||||
typographer: true,
|
typographer: true,
|
||||||
breaks: true,
|
breaks: true
|
||||||
})
|
})
|
||||||
|
|
||||||
function sanitize(str?: string) {
|
function sanitize(str?: string) {
|
||||||
if (!str) {
|
if (!str) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
const cleanStr = str
|
const cleanStr = str.replace(/\\n/g, '\n').replace(/\n\s*\d+\./g, '\n$&')
|
||||||
.replace(/\\n/g, '\n')
|
|
||||||
.replace(/\n\s*\d+\./g, '\n$&')
|
|
||||||
const html = md.render(cleanStr)
|
const html = md.render(cleanStr)
|
||||||
return html
|
return html
|
||||||
// return DOMPurify.sanitize(html)
|
// return DOMPurify.sanitize(html)
|
||||||
@@ -40,11 +38,12 @@ const data = computed<Data | null>(() => {
|
|||||||
if (result.NodeId === props.nodeId) {
|
if (result.NodeId === props.nodeId) {
|
||||||
// LogNodeType 为 object直接渲染Content
|
// LogNodeType 为 object直接渲染Content
|
||||||
if (result.LogNodeType === 'object') {
|
if (result.LogNodeType === 'object') {
|
||||||
return {
|
const data = {
|
||||||
Description: props.nodeId,
|
Description: props.nodeId,
|
||||||
Content: sanitize(result.content),
|
Content: sanitize(result.content),
|
||||||
LogNodeType: result.LogNodeType,
|
LogNodeType: result.LogNodeType
|
||||||
}
|
}
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.ActionHistory) {
|
if (!result.ActionHistory) {
|
||||||
@@ -56,7 +55,7 @@ const data = computed<Data | null>(() => {
|
|||||||
return {
|
return {
|
||||||
Description: action.Description,
|
Description: action.Description,
|
||||||
Content: sanitize(action.Action_Result),
|
Content: sanitize(action.Action_Result),
|
||||||
LogNodeType: result.LogNodeType,
|
LogNodeType: result.LogNodeType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +76,9 @@ const data = computed<Data | null>(() => {
|
|||||||
{{ data.Description }}
|
{{ data.Description }}
|
||||||
<Iod v-if="data.LogNodeType !== 'object'" />
|
<Iod v-if="data.LogNodeType !== 'object'" />
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-[8px] p-[15px] text-[14px] bg-[var(--color-bg-quaternary)]">
|
<div
|
||||||
|
class="rounded-[8px] p-[15px] text-[14px] bg-[var(--color-bg-result-detail)] text-[var(--color-text-detail)]"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="markdown-content max-h-[240px] overflow-y-auto max-w-full"
|
class="markdown-content max-h-[240px] overflow-y-auto max-w-full"
|
||||||
v-html="data.Content"
|
v-html="data.Content"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { readConfig } from '@/utils/readJson.ts'
|
import { readConfig } from '@/utils/readJson.ts'
|
||||||
import { onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
|
||||||
interface Iod {
|
interface Iod {
|
||||||
name: string
|
name: string
|
||||||
@@ -28,10 +28,22 @@ function handleNext() {
|
|||||||
displayIndex.value++
|
displayIndex.value++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handlePrev() {
|
||||||
|
if (displayIndex.value === 0) {
|
||||||
|
displayIndex.value = data.value.length - 1
|
||||||
|
} else {
|
||||||
|
displayIndex.value--
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-popover trigger="hover" width="440">
|
<el-popover
|
||||||
|
trigger="hover"
|
||||||
|
width="440"
|
||||||
|
popper-style="background-color: var(--color-bg-result); border: none;"
|
||||||
|
>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<div
|
<div
|
||||||
class="rounded-full w-[20px] h-[20px] bg-[var(--color-bg-quaternary)] flex justify-center items-center cursor-pointer"
|
class="rounded-full w-[20px] h-[20px] bg-[var(--color-bg-quaternary)] flex justify-center items-center cursor-pointer"
|
||||||
@@ -40,12 +52,14 @@ function handleNext() {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #default v-if="data.length">
|
<template #default v-if="data.length">
|
||||||
<div>
|
<div class="bg-[var(--color-bg-result)]">
|
||||||
<div class="flex justify-between items-center p-2 pb-0 rounded-[8px] text-[16px] font-bold">
|
<div class="flex justify-between items-center p-2 pb-0 rounded-[8px] text-[16px] font-bold">
|
||||||
<span>数联网搜索结果</span>
|
<span>数联网搜索结果</span>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div>{{ `${displayIndex + 1}/${data.length}` }}</div>
|
<!-- <div>{{ `${displayIndex + 1}/${data.length}` }}</div>
|
||||||
<el-button type="primary" size="small" @click="handleNext">下一个</el-button>
|
<el-button type="primary" size="small" @click="handleNext">下一个</el-button> -->
|
||||||
|
<!-- 关闭 -->
|
||||||
|
<!-- <SvgIcon icon-class="close" size="15px" /> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 分割线 -->
|
<!-- 分割线 -->
|
||||||
@@ -53,19 +67,50 @@ function handleNext() {
|
|||||||
<div class="p-2 pt-0">
|
<div class="p-2 pt-0">
|
||||||
<div class="flex items-center w-full gap-3">
|
<div class="flex items-center w-full gap-3">
|
||||||
<div class="font-bold w-[75px] text-right flex-shrink-0">名称:</div>
|
<div class="font-bold w-[75px] text-right flex-shrink-0">名称:</div>
|
||||||
<div class="text-[var(--color-text-secondary)] flex-1 break-words">{{ displayIod.name }}</div>
|
<div class="text-[var(--color-text-detail)] flex-1 break-words">
|
||||||
|
{{ displayIod.name }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center w-full gap-3">
|
<div class="flex items-center w-full gap-3">
|
||||||
<div class="font-bold w-[75px] text-right flex-shrink-0">数据空间:</div>
|
<div class="font-bold w-[75px] text-right flex-shrink-0">数据空间:</div>
|
||||||
<div class="text-[var(--color-text-secondary)] lex-1 break-words">{{ displayIod.data_space }}</div>
|
<div class="text-[var(--color-text-detail)] lex-1 break-words">
|
||||||
|
{{ displayIod.data_space }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center w-full gap-3">
|
<div class="flex items-center w-full gap-3">
|
||||||
<div class="font-bold w-[75px] text-right flex-shrink-0">DOID:</div>
|
<div class="font-bold w-[75px] text-right flex-shrink-0">DOID:</div>
|
||||||
<div class="text-[var(--color-text-secondary)] lex-1 break-words">{{ displayIod.doId }}</div>
|
<div class="text-[var(--color-text-detail)] lex-1 break-words">
|
||||||
|
{{ displayIod.doId }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center w-full gap-3">
|
<div class="flex items-center w-full gap-3">
|
||||||
<div class="font-bold w-[75px] text-right flex-shrink-0">来源仓库:</div>
|
<div class="font-bold w-[75px] text-right flex-shrink-0">来源仓库:</div>
|
||||||
<div class="text-[var(--color-text-secondary)] flex-1 break-words break-al">{{ displayIod.fromRepo }}</div>
|
<div class="text-[var(--color-text-detail)] flex-1 break-words break-al">
|
||||||
|
{{ displayIod.fromRepo }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="card-item w-[80px] h-[25px] flex justify-between items-center rounded-[25px] bg-[#b1b1b1] ml-auto px-2"
|
||||||
|
>
|
||||||
|
<div class="text-[14px] text-[#ffffff] font-medium">
|
||||||
|
{{ `${displayIndex + 1}/${data.length}` }}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<svg-icon
|
||||||
|
icon-class="left"
|
||||||
|
size="15px"
|
||||||
|
@click="handlePrev"
|
||||||
|
class="cursor-pointer hover:opacity-70"
|
||||||
|
></svg-icon>
|
||||||
|
<svg-icon
|
||||||
|
icon-class="right"
|
||||||
|
size="15px"
|
||||||
|
@click="handleNext"
|
||||||
|
class="cursor-pointer hover:opacity-70"
|
||||||
|
></svg-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,31 +1,56 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
isAdding?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
(e: 'start-add-output'): void
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="absolute inset-0 flex items-start gap-[14%]">
|
<div class="absolute inset-0 flex items-start gap-[14%]">
|
||||||
<!-- 左侧元素 -->
|
<!-- 左侧元素 -->
|
||||||
<div class="flex-1 relative h-full flex justify-center">
|
<div class="flex-1 relative h-full flex justify-center">
|
||||||
<!-- 背景那一根线 -->
|
<!-- 背景那一根线 -->
|
||||||
<div
|
<div class="h-full bg-[var(--color-bg-flow)] w-[5px]">
|
||||||
class="h-full bg-[var(--color-bg-tertiary)] w-[5px]"
|
|
||||||
>
|
|
||||||
<!-- 线底部的小圆球 -->
|
<!-- 线底部的小圆球 -->
|
||||||
<div
|
<div
|
||||||
class="absolute bottom-0 left-1/2 transform -translate-x-1/2 bg-[var(--color-bg-tertiary)] w-[15px] h-[15px] rounded-full"
|
class="absolute bottom-0 left-1/2 transform -translate-x-1/2 bg-[var(--color-bg-flow)] w-[15px] h-[15px] rounded-full"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧元素 -->
|
<!-- 右侧元素 -->
|
||||||
<div class="flex-1 relative h-full flex justify-center">
|
<div class="flex-1 relative h-full flex justify-center">
|
||||||
<!-- 背景那一根线 -->
|
<!-- 背景那一根线 -->
|
||||||
<div
|
<div class="h-full bg-[var(--color-bg-flow)] w-[5px]">
|
||||||
class="h-full bg-[var(--color-bg-tertiary)] 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
|
<div
|
||||||
class="absolute bottom-0 left-1/2 transform -translate-x-1/2 bg-[var(--color-bg-tertiary)] w-[15px] h-[15px] rounded-full"
|
class="absolute bottom-0 left-1/2 transform -translate-x-1/2 bg-[var(--color-bg-flow)] w-[15px] h-[15px] rounded-full"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<style scoped>
|
||||||
</script>
|
.plus-area:hover .plus-icon {
|
||||||
|
opacity: 1;
|
||||||
|
border: 1px dashed var(--color-text);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,365 @@
|
|||||||
|
<template>
|
||||||
|
<div class="root-node-wrapper">
|
||||||
|
<!-- 左侧连接点(输入) -->
|
||||||
|
<Handle type="target" :position="Position.Left" id="left" />
|
||||||
|
<!-- 右侧连接点(输出) -->
|
||||||
|
<Handle type="source" :position="Position.Right" id="right" />
|
||||||
|
<!-- 底部连接点(用于分支) -->
|
||||||
|
<Handle type="source" :position="Position.Bottom" id="bottom" />
|
||||||
|
|
||||||
|
<el-card class="root-node-card" :shadow="true">
|
||||||
|
<!-- 目标内容 -->
|
||||||
|
<div class="goal-content">
|
||||||
|
<div class="goal-label">初始目标</div>
|
||||||
|
<div class="goal-text">{{ goal }}</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 底部添加按钮 -->
|
||||||
|
<div v-if="!isAddingBranch" class="external-add-btn" @click="startAddBranch">
|
||||||
|
<el-icon :size="20" color="#409eff">
|
||||||
|
<CirclePlus />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 取消按钮(输入框显示时) -->
|
||||||
|
<div v-else class="external-add-btn cancel-btn" @click="cancelAddBranch">
|
||||||
|
<el-icon :size="20" color="#f56c6c">
|
||||||
|
<Remove />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 外部输入框 -->
|
||||||
|
<div v-if="isAddingBranch" class="external-input-container">
|
||||||
|
<el-input
|
||||||
|
v-model="branchInput"
|
||||||
|
placeholder="输入分支需求..."
|
||||||
|
size="small"
|
||||||
|
@keydown="handleBranchKeydown"
|
||||||
|
ref="branchInputRef"
|
||||||
|
class="branch-input"
|
||||||
|
>
|
||||||
|
<template #suffix>
|
||||||
|
<svg-icon
|
||||||
|
icon-class="paper-plane"
|
||||||
|
size="16px"
|
||||||
|
color="#409eff"
|
||||||
|
class="submit-icon"
|
||||||
|
:class="{ 'is-disabled': !branchInput.trim() }"
|
||||||
|
@click="submitBranch"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, nextTick } from 'vue'
|
||||||
|
import { CirclePlus, Remove } from '@element-plus/icons-vue'
|
||||||
|
import { Handle, Position } from '@vue-flow/core'
|
||||||
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||||
|
interface RootNodeData {
|
||||||
|
goal: string
|
||||||
|
initialInput?: string[] | string
|
||||||
|
isRoot?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
id: string
|
||||||
|
data: RootNodeData
|
||||||
|
isAddingBranch?: boolean
|
||||||
|
[key: string]: any
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'add-branch', nodeId: string, branchContent: string): void
|
||||||
|
(e: 'start-add-branch', nodeId: string): void
|
||||||
|
(e: 'cancel-add-branch'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const goal = computed(() => props.data.goal || '')
|
||||||
|
|
||||||
|
const initialInput = computed(() => props.data.initialInput)
|
||||||
|
|
||||||
|
const displayInput = computed(() => {
|
||||||
|
if (!initialInput.value) return false
|
||||||
|
if (Array.isArray(initialInput.value)) {
|
||||||
|
return initialInput.value.length > 0
|
||||||
|
}
|
||||||
|
return initialInput.value.trim().length > 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 分支添加相关状态(使用父组件传入的 prop)
|
||||||
|
const branchInput = ref('')
|
||||||
|
const branchInputRef = ref<InstanceType<typeof HTMLInputElement>>()
|
||||||
|
|
||||||
|
// 计算属性,使用父组件传入的状态
|
||||||
|
const isAddingBranch = computed(() => props.isAddingBranch || false)
|
||||||
|
|
||||||
|
// 分支添加相关方法
|
||||||
|
const startAddBranch = () => {
|
||||||
|
emit('start-add-branch', props.id)
|
||||||
|
branchInput.value = ''
|
||||||
|
nextTick(() => {
|
||||||
|
branchInputRef.value?.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelAddBranch = () => {
|
||||||
|
emit('cancel-add-branch')
|
||||||
|
branchInput.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitBranch = () => {
|
||||||
|
if (branchInput.value.trim()) {
|
||||||
|
emit('add-branch', props.id, branchInput.value.trim())
|
||||||
|
branchInput.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBranchKeydown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault()
|
||||||
|
submitBranch()
|
||||||
|
} else if (event.key === 'Escape') {
|
||||||
|
cancelAddBranch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.root-node-card {
|
||||||
|
width: 200px;
|
||||||
|
min-height: 100px;
|
||||||
|
background: var(--color-bg-three);
|
||||||
|
border: 2px solid #409eff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 6px 16px rgba(64, 158, 255, 0.3);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.root-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: -10px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goal-icon {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goal-content {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goal-label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #409eff;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goal-text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-title-header);
|
||||||
|
line-height: 1.6;
|
||||||
|
word-break: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.initial-input {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-divider {
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(to right, transparent, #409eff, transparent);
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
word-break: break-word;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节点包装器
|
||||||
|
.root-node-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 外部添加按钮
|
||||||
|
.external-add-btn {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #409eff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
z-index: 10;
|
||||||
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #409eff;
|
||||||
|
transform: translateX(-50%) scale(1.1);
|
||||||
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateX(-50%) scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消按钮样式
|
||||||
|
&.cancel-btn {
|
||||||
|
border-color: #f56c6c;
|
||||||
|
box-shadow: 0 2px 8px rgba(245, 108, 108, 0.2);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f56c6c;
|
||||||
|
box-shadow: 0 4px 12px rgba(245, 108, 108, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 外部输入容器
|
||||||
|
.external-input-container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -80px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 260px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 100;
|
||||||
|
animation: slideDown 0.2s ease-out;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -6px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background: #fff;
|
||||||
|
border-left: 1px solid #dcdfe6;
|
||||||
|
border-top: 1px solid #dcdfe6;
|
||||||
|
transform: translateX(-50%) rotate(45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50%) translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.branch-input {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
:deep(.el-input__wrapper) {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #c0c4cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-focus {
|
||||||
|
border-color: #409eff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__inner {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #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>
|
||||||
@@ -0,0 +1,518 @@
|
|||||||
|
<template>
|
||||||
|
<div class="task-node-wrapper">
|
||||||
|
<!-- 左侧连接点(输入) -->
|
||||||
|
<Handle type="target" :position="Position.Left" id="left" />
|
||||||
|
<!-- 右侧连接点(输出) -->
|
||||||
|
<Handle type="source" :position="Position.Right" id="right" />
|
||||||
|
<!-- 底部连接点(用于分支) -->
|
||||||
|
<Handle type="source" :position="Position.Bottom" id="bottom" />
|
||||||
|
|
||||||
|
<el-card
|
||||||
|
class="task-node-card"
|
||||||
|
:class="{
|
||||||
|
'is-editing': isEditing,
|
||||||
|
'is-active': isActive,
|
||||||
|
'is-branch-selected': props.isBranchSelected
|
||||||
|
}"
|
||||||
|
:shadow="true"
|
||||||
|
>
|
||||||
|
<!-- 任务名称 -->
|
||||||
|
<div class="task-name">{{ task.StepName }}</div>
|
||||||
|
|
||||||
|
<!-- 智能体列表 -->
|
||||||
|
<div class="agents-container">
|
||||||
|
<el-tooltip
|
||||||
|
v-for="agentSelection in task.AgentSelection"
|
||||||
|
:key="agentSelection"
|
||||||
|
effect="light"
|
||||||
|
placement="top"
|
||||||
|
:show-after="500"
|
||||||
|
popper-class="task-syllabus-tooltip-popper"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div class="agent-tooltip">
|
||||||
|
<div class="text-[16px] font-bold">{{ agentSelection }}</div>
|
||||||
|
<div class="h-[1px] w-full bg-[#494B51] my-[4px]"></div>
|
||||||
|
<div class="text-[12px]">
|
||||||
|
{{ task.TaskProcess.find(i => i.AgentName === agentSelection)?.Description }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="agent-icon" :style="{ background: getAgentIcon(agentSelection).color }">
|
||||||
|
<svg-icon :icon-class="getAgentIcon(agentSelection).icon" color="#fff" size="20px" />
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 底部添加按钮(卡片外部) -->
|
||||||
|
<div v-if="!isAddingBranch" class="external-add-btn" @click="startAddBranch">
|
||||||
|
<el-icon :size="20" color="#409eff">
|
||||||
|
<CirclePlus />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 取消按钮(输入框显示时) -->
|
||||||
|
<div v-else class="external-add-btn cancel-btn" @click="cancelAddBranch">
|
||||||
|
<el-icon :size="20" color="#f56c6c">
|
||||||
|
<Remove />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 外部输入框 -->
|
||||||
|
<div v-if="isAddingBranch" class="external-input-container">
|
||||||
|
<el-input
|
||||||
|
v-model="branchInput"
|
||||||
|
placeholder="输入分支需求..."
|
||||||
|
size="small"
|
||||||
|
@keydown="handleBranchKeydown"
|
||||||
|
ref="branchInputRef"
|
||||||
|
class="branch-input"
|
||||||
|
>
|
||||||
|
<template #suffix>
|
||||||
|
<svg-icon
|
||||||
|
icon-class="paper-plane"
|
||||||
|
size="16px"
|
||||||
|
color="#409eff"
|
||||||
|
class="submit-icon"
|
||||||
|
:class="{ 'is-disabled': !branchInput.trim() }"
|
||||||
|
@click="submitBranch"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, nextTick } from 'vue'
|
||||||
|
import { CirclePlus, Remove } from '@element-plus/icons-vue'
|
||||||
|
import { Handle, Position } from '@vue-flow/core'
|
||||||
|
import { type IRawStepTask } from '@/stores'
|
||||||
|
import { getAgentMapIcon } from '@/layout/components/config'
|
||||||
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||||
|
|
||||||
|
interface TaskNodeData {
|
||||||
|
task: IRawStepTask
|
||||||
|
isEditing: boolean
|
||||||
|
editingContent: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用更宽松的类型定义来避免类型错误
|
||||||
|
const props = defineProps<{
|
||||||
|
id: string
|
||||||
|
data: TaskNodeData
|
||||||
|
isAddingBranch?: boolean
|
||||||
|
isBranchSelected?: boolean // 是否属于选中的分支路径
|
||||||
|
[key: string]: any
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'edit-task', nodeId: string): void
|
||||||
|
(e: 'save-task', nodeId: string, content: string): void
|
||||||
|
(e: 'cancel-edit', nodeId: string): void
|
||||||
|
(e: 'add-branch', nodeId: string, branchContent: string): void
|
||||||
|
(e: 'start-add-branch', nodeId: string): void
|
||||||
|
(e: 'cancel-add-branch'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const editingContent = ref(props.data.task.TaskContent || '')
|
||||||
|
|
||||||
|
// 分支添加相关状态(使用父组件传入的 prop)
|
||||||
|
const branchInput = ref('')
|
||||||
|
const branchInputRef = ref<InstanceType<typeof HTMLInputElement>>()
|
||||||
|
|
||||||
|
// 计算属性,使用父组件传入的状态
|
||||||
|
const isAddingBranch = computed(() => props.isAddingBranch || false)
|
||||||
|
|
||||||
|
const isEditing = computed({
|
||||||
|
get: () => props.data.isEditing,
|
||||||
|
set: value => {
|
||||||
|
if (!value) {
|
||||||
|
emit('cancel-edit', props.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const isActive = computed(() => {
|
||||||
|
return props.data.task.StepName === props.data.task.StepName
|
||||||
|
})
|
||||||
|
|
||||||
|
const task = computed(() => props.data.task)
|
||||||
|
|
||||||
|
const getAgentIcon = (agentName: string) => {
|
||||||
|
return getAgentMapIcon(agentName)
|
||||||
|
}
|
||||||
|
|
||||||
|
const startEdit = () => {
|
||||||
|
emit('edit-task', props.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveEdit = () => {
|
||||||
|
emit('save-task', props.id, editingContent.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelEdit = () => {
|
||||||
|
emit('cancel-edit', props.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeydown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault()
|
||||||
|
saveEdit()
|
||||||
|
} else if (event.key === 'Escape') {
|
||||||
|
cancelEdit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分支添加相关方法
|
||||||
|
const startAddBranch = () => {
|
||||||
|
emit('start-add-branch', props.id)
|
||||||
|
branchInput.value = ''
|
||||||
|
nextTick(() => {
|
||||||
|
branchInputRef.value?.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelAddBranch = () => {
|
||||||
|
emit('cancel-add-branch')
|
||||||
|
branchInput.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitBranch = () => {
|
||||||
|
if (branchInput.value.trim()) {
|
||||||
|
emit('add-branch', props.id, branchInput.value.trim())
|
||||||
|
branchInput.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBranchKeydown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault()
|
||||||
|
submitBranch()
|
||||||
|
} else if (event.key === 'Escape') {
|
||||||
|
cancelAddBranch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.task-node-card {
|
||||||
|
width: 150px;
|
||||||
|
min-height: 100px;
|
||||||
|
background-color: var(--color-card-bg-task);
|
||||||
|
border: 1px solid var(--color-card-border-task);
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-card-bg-task-hover);
|
||||||
|
border-color: var(--color-card-border-hover);
|
||||||
|
box-shadow: var(--color-card-shadow-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分支选中高亮样式(绿色)
|
||||||
|
&.is-branch-selected {
|
||||||
|
border-color: #67c23a;
|
||||||
|
box-shadow: 0 0 0 3px rgba(103, 194, 58, 0.3);
|
||||||
|
background-color: rgba(103, 194, 58, 0.05);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #67c23a;
|
||||||
|
box-shadow: 0 0 0 3px rgba(103, 194, 58, 0.4);
|
||||||
|
background-color: rgba(103, 194, 58, 0.08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-editing {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
padding: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--color-text-title-header);
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--color-border-separate);
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-content {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
min-height: 40px;
|
||||||
|
word-break: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-content-editing {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agents-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-icon {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-outputs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-tooltip {
|
||||||
|
padding: 8px;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑器样式
|
||||||
|
.task-content-editor {
|
||||||
|
:deep(.el-textarea__inner) {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 4px;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节点包装器(用于容纳外部按钮)
|
||||||
|
.task-node-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 外部添加按钮
|
||||||
|
.external-add-btn {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #409eff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
z-index: 10;
|
||||||
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #409eff;
|
||||||
|
transform: translateX(-50%) scale(1.1);
|
||||||
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateX(-50%) scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消按钮样式
|
||||||
|
&.cancel-btn {
|
||||||
|
border-color: #f56c6c;
|
||||||
|
box-shadow: 0 2px 8px rgba(245, 108, 108, 0.2);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f56c6c;
|
||||||
|
box-shadow: 0 4px 12px rgba(245, 108, 108, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 外部输入容器
|
||||||
|
.external-input-container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -80px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 260px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 100;
|
||||||
|
animation: slideDown 0.2s ease-out;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -6px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background: #fff;
|
||||||
|
border-left: 1px solid #dcdfe6;
|
||||||
|
border-top: 1px solid #dcdfe6;
|
||||||
|
transform: translateX(-50%) rotate(45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50%) translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-branch-section {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px dashed var(--color-border-separate);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-branch-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.branch-input-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.branch-input {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
:deep(.el-input__wrapper) {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #c0c4cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-focus {
|
||||||
|
border-color: #409eff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__inner {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #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>
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# Mock 数据说明
|
||||||
|
|
||||||
|
本目录包含用于分支功能的 mock 数据,支持在开发环境中测试分支逻辑,无需调用真实后端 API。
|
||||||
|
|
||||||
|
## 文件说明
|
||||||
|
|
||||||
|
### 1. `branchPlanOutlineMock.ts`
|
||||||
|
**用途**: 根节点级别的分支(任务大纲分支)
|
||||||
|
|
||||||
|
**类型**: `IApiStepTask[][]`
|
||||||
|
|
||||||
|
**说明**: 返回多个分支方案,每个方案是一个完整的任务流程(IApiStepTask[])
|
||||||
|
|
||||||
|
**示例结构**:
|
||||||
|
```typescript
|
||||||
|
[
|
||||||
|
// 第一个分支方案(瀑布流开发)
|
||||||
|
[task1, task2, ...],
|
||||||
|
// 第二个分支方案(敏捷开发)
|
||||||
|
[task1, task2, ...],
|
||||||
|
// 第三个分支方案(快速原型)
|
||||||
|
[task1, task2, ...]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `branchTaskProcessMock.ts`
|
||||||
|
**用途**: 任务节点级别的分支(任务流程分支)
|
||||||
|
|
||||||
|
**类型**: `IApiAgentAction[][]`
|
||||||
|
|
||||||
|
**说明**: 返回多个分支方案,每个方案是一系列动作(IApiAgentAction[]),这些动作会追加到现有任务的 TaskProcess 中
|
||||||
|
|
||||||
|
**示例结构**:
|
||||||
|
```typescript
|
||||||
|
[
|
||||||
|
// 第一个分支方案(标准开发流程)
|
||||||
|
[action1, action2, action3, ...],
|
||||||
|
// 第二个分支方案(快速原型流程)
|
||||||
|
[action1, action2, ...],
|
||||||
|
// ... 更多方案
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 如何使用
|
||||||
|
|
||||||
|
### 切换 Mock 数据开关
|
||||||
|
|
||||||
|
在 `PlanModification.vue` 文件中,找到以下配置:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 开关:控制是否使用 mock 数据(开发时设置为 true,生产时设置为 false)
|
||||||
|
const USE_MOCK_DATA = true
|
||||||
|
```
|
||||||
|
|
||||||
|
- **开发阶段**: 设置为 `true`,使用 mock 数据
|
||||||
|
- **生产环境**: 设置为 `false`,调用真实 API
|
||||||
|
|
||||||
|
### 数据转换流程
|
||||||
|
|
||||||
|
```
|
||||||
|
IApiStepTask (API 格式)
|
||||||
|
↓ convertToIRawStepTask()
|
||||||
|
IRawStepTask (内部格式)
|
||||||
|
↓
|
||||||
|
更新到 agentsStore.agentRawPlan.data
|
||||||
|
↓
|
||||||
|
Vue Flow 流程图自动更新
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mock 数据特点
|
||||||
|
|
||||||
|
1. **模拟网络延迟**: Mock 数据调用会模拟 800ms 的网络延迟
|
||||||
|
2. **多方案选择**: 每个分支接口提供多个备选方案(目前默认使用第一个)
|
||||||
|
3. **完整数据结构**: Mock 数据包含完整的字段,与真实 API 返回格式一致
|
||||||
|
4. **类型安全**: 使用 TypeScript 类型定义,确保类型正确
|
||||||
|
|
||||||
|
## 扩展 Mock 数据
|
||||||
|
|
||||||
|
### 添加新的分支方案
|
||||||
|
|
||||||
|
在对应的 mock 文件中添加新的数组元素:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// branchPlanOutlineMock.ts
|
||||||
|
const mockPlanBranchData: IApiStepTask[][] = [
|
||||||
|
// 现有方案...
|
||||||
|
[
|
||||||
|
// 新增方案
|
||||||
|
{
|
||||||
|
name: '新方案步骤1',
|
||||||
|
content: '...',
|
||||||
|
// ... 其他字段
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '新方案步骤2',
|
||||||
|
content: '...',
|
||||||
|
// ... 其他字段
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 随机选择方案
|
||||||
|
|
||||||
|
修改 `PlanModification.vue` 中的方案选择逻辑:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 当前:固定选择第一个
|
||||||
|
const mockBranchTasks = branchPlanOutlineMock[0]
|
||||||
|
|
||||||
|
// 改为:随机选择一个方案
|
||||||
|
const randomIndex = Math.floor(Math.random() * branchPlanOutlineMock.length)
|
||||||
|
const mockBranchTasks = branchPlanOutlineMock[randomIndex]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. ⚠️ **生产环境**: 发布前务必将 `USE_MOCK_DATA` 设置为 `false`
|
||||||
|
2. 🔍 **调试**: 查看控制台日志,以 `[Mock]` 开头的是 mock 数据相关日志
|
||||||
|
3. 📝 **数据一致性**: 确保 mock 数据结构与真实 API 返回格式一致
|
||||||
|
4. 🔄 **数据持久化**: Mock 数据仅存在于前端,刷新页面后会丢失
|
||||||
|
|
||||||
|
## 相关文件
|
||||||
|
|
||||||
|
- `PlanModification.vue`: 主要逻辑文件,包含分支添加和 mock 数据集成
|
||||||
|
- `api/index.ts`: 真实 API 接口定义
|
||||||
|
- `stores/modules/agents.ts`: 类型定义和数据存储
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
// branch_PlanOutline 接口的 Mock 数据和 Mock API
|
||||||
|
// 模拟后端返回的原始数据格式(IRawPlanResponse)
|
||||||
|
|
||||||
|
import type { IRawPlanResponse, IRawStepTask } from '@/stores'
|
||||||
|
|
||||||
|
// 后端返回的数据格式
|
||||||
|
export type BranchPlanOutlineResponse = IRawPlanResponse
|
||||||
|
|
||||||
|
// Mock 数据:模拟后端返回的原始分支数据(不含 Collaboration_Brief_FrontEnd)
|
||||||
|
// 注意:这里模拟的是 branch_PlanOutline 函数返回的数据,不是前端转换后的数据
|
||||||
|
const mockBranchDataRaw: IRawStepTask[][] = [
|
||||||
|
// 第一个分支方案
|
||||||
|
[
|
||||||
|
{
|
||||||
|
StepName: '分析用户需求',
|
||||||
|
TaskContent: '分析用户需求,制定项目开发计划',
|
||||||
|
InputObject_List: ['腐蚀类型及成因列表'],
|
||||||
|
OutputObject: '项目开发计划书',
|
||||||
|
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
TaskProcess: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StepName: '系统设计与架构',
|
||||||
|
TaskContent: '设计系统架构和数据库结构',
|
||||||
|
InputObject_List: ['项目开发计划书'],
|
||||||
|
OutputObject: '系统设计文档',
|
||||||
|
AgentSelection: ['腐蚀机理研究员', '防护工程专家'],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
TaskProcess: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 第二个分支方案(快速原型方案)
|
||||||
|
[
|
||||||
|
{
|
||||||
|
StepName: '需求快速原型',
|
||||||
|
TaskContent: '构建快速原型验证核心功能',
|
||||||
|
InputObject_List: ['腐蚀类型及成因列表'],
|
||||||
|
OutputObject: '原型系统',
|
||||||
|
AgentSelection: ['实验材料学家', '防护工程专家'],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
TaskProcess: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StepName: '原型测试与优化',
|
||||||
|
TaskContent: '测试原型并根据反馈快速迭代',
|
||||||
|
InputObject_List: ['原型系统'],
|
||||||
|
OutputObject: '优化后的原型',
|
||||||
|
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
TaskProcess: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 第三个分支方案(质量优先方案)
|
||||||
|
[
|
||||||
|
{
|
||||||
|
StepName: '需求深度分析',
|
||||||
|
TaskContent: '深入分析用户需求和技术可行性',
|
||||||
|
InputObject_List: ['腐蚀类型及成因列表'],
|
||||||
|
OutputObject: '详细需求分析报告',
|
||||||
|
AgentSelection: ['腐蚀机理研究员', '防护工程专家'],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
TaskProcess: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StepName: '质量保障设计',
|
||||||
|
TaskContent: '设计质量保障体系和测试方案',
|
||||||
|
InputObject_List: ['详细需求分析报告'],
|
||||||
|
OutputObject: '质量保障方案',
|
||||||
|
AgentSelection: ['实验材料学家', '防护工程专家'],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
TaskProcess: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StepName: '系统开发与测试',
|
||||||
|
TaskContent: '按质量标准进行系统开发和测试',
|
||||||
|
InputObject_List: ['质量保障方案'],
|
||||||
|
OutputObject: '经过完整测试的系统',
|
||||||
|
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
TaskProcess: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 第四个分支方案(敏捷开发方案)
|
||||||
|
[
|
||||||
|
{
|
||||||
|
StepName: '迭代规划',
|
||||||
|
TaskContent: '制定敏捷开发迭代计划',
|
||||||
|
InputObject_List: ['腐蚀类型及成因列表'],
|
||||||
|
OutputObject: '迭代计划',
|
||||||
|
AgentSelection: ['防护工程专家', '腐蚀机理研究员'],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
TaskProcess: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StepName: '快速开发与验证',
|
||||||
|
TaskContent: '快速开发并持续验证功能',
|
||||||
|
InputObject_List: ['迭代计划'],
|
||||||
|
OutputObject: '可运行的功能模块',
|
||||||
|
AgentSelection: ['实验材料学家', '防护工程专家'],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
TaskProcess: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模拟后端的 Add_Collaboration_Brief_FrontEnd 函数
|
||||||
|
* 为每个任务添加前端协作简报(Collaboration_Brief_frontEnd)
|
||||||
|
*/
|
||||||
|
function Add_Collaboration_Brief_FrontEnd(branchList: IRawStepTask[][]): IRawStepTask[][] {
|
||||||
|
return branchList.map((tasks) =>
|
||||||
|
tasks.map((task) => ({
|
||||||
|
...task,
|
||||||
|
Collaboration_Brief_frontEnd: generateCollaborationBrief(task),
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成协作简报(Collaboration_Brief_frontEnd)
|
||||||
|
* 根据 StepName、TaskContent、AgentSelection 生成模板和数据
|
||||||
|
*/
|
||||||
|
function generateCollaborationBrief(task: IRawStepTask): {
|
||||||
|
template: string
|
||||||
|
data: Record<string, any>
|
||||||
|
} {
|
||||||
|
const agents = task.AgentSelection || []
|
||||||
|
const stepName = task.StepName || ''
|
||||||
|
|
||||||
|
// 为每个 agent 生成颜色
|
||||||
|
const colors = [
|
||||||
|
'hsl(210, 70%, 50%)',
|
||||||
|
'hsl(30, 70%, 50%)',
|
||||||
|
'hsl(120, 70%, 50%)',
|
||||||
|
'hsl(270, 70%, 50%)',
|
||||||
|
]
|
||||||
|
|
||||||
|
// 生成 data 对象
|
||||||
|
const data: Record<string, any> = {}
|
||||||
|
agents.forEach((agent, index) => {
|
||||||
|
data[agent] = {
|
||||||
|
text: agent,
|
||||||
|
style: { background: colors[index % colors.length] },
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 生成 template(简化版本,实际应根据任务内容生成)
|
||||||
|
const template =
|
||||||
|
agents.length > 0
|
||||||
|
? agents.map((agent, i) => `!<${agent}>!负责!<${stepName}-${i}>!`).join(',')
|
||||||
|
: ''
|
||||||
|
|
||||||
|
return {
|
||||||
|
template,
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock API:模拟后端 branch_PlanOutline 接口调用
|
||||||
|
*
|
||||||
|
* @param branch_Number - 分支数量
|
||||||
|
* @param Modification_Requirement - 修改需求
|
||||||
|
* @param Existing_Steps - 现有步骤列表(包含完整信息的对象数组)
|
||||||
|
* @param Baseline_Completion - 基线完成度
|
||||||
|
* @param InitialObject_List - 初始对象列表
|
||||||
|
* @param General_Goal - 总体目标
|
||||||
|
* @returns Promise<IRawPlanResponse> - 返回包含 'Collaboration Process' 的响应
|
||||||
|
*/
|
||||||
|
export const mockBranchPlanOutlineAPI = async (params: {
|
||||||
|
branch_Number: number
|
||||||
|
Modification_Requirement: string
|
||||||
|
Existing_Steps: 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
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
// branch_TaskProcess 接口的 Mock 数据和 Mock API
|
||||||
|
export interface BranchAction {
|
||||||
|
ID: string
|
||||||
|
ActionType: string
|
||||||
|
AgentName: string
|
||||||
|
Description: string
|
||||||
|
ImportantInput: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BranchTaskProcessResponse = BranchAction[][]
|
||||||
|
|
||||||
|
// Mock 数据:模拟后端返回的原始任务流程数据(2D 数组)
|
||||||
|
// 格式:[[action1, action2], [action3, action4]]
|
||||||
|
const mockBranchTaskProcessDataRaw: BranchAction[][] = [
|
||||||
|
// 第一个任务分支方案
|
||||||
|
[
|
||||||
|
{
|
||||||
|
ID: 'agent3',
|
||||||
|
ActionType: 'Critique',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '详细分析用户需求文档',
|
||||||
|
ImportantInput: ['agent2'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'agent4',
|
||||||
|
ActionType: 'Critique',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '设计系统整体架构',
|
||||||
|
ImportantInput: ['agent3'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'agent5',
|
||||||
|
ActionType: 'Improve',
|
||||||
|
AgentName: '防护工程专家',
|
||||||
|
Description: '实现系统核心功能',
|
||||||
|
ImportantInput: ['agent4'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'agent6',
|
||||||
|
ActionType: 'Finalize',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '进行系统集成测试',
|
||||||
|
ImportantInput: ['agent5'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 第二个任务分支方案
|
||||||
|
[
|
||||||
|
{
|
||||||
|
ID: 'agent7',
|
||||||
|
ActionType: 'Critique',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '深入分析用户需求和技术约束',
|
||||||
|
ImportantInput: ['agent2'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'agent8',
|
||||||
|
ActionType: 'Critique',
|
||||||
|
AgentName: '防护工程专家',
|
||||||
|
Description: '设计系统技术架构和数据流',
|
||||||
|
ImportantInput: ['agent8'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'agent9',
|
||||||
|
ActionType: 'Improve',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '评估系统安全性',
|
||||||
|
ImportantInput: ['agent4'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'agent10',
|
||||||
|
ActionType: 'Finalize',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '完成系统安全测试',
|
||||||
|
ImportantInput: ['agent9'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
//第三个任务分支方案
|
||||||
|
[
|
||||||
|
{
|
||||||
|
ID: 'agent12',
|
||||||
|
ActionType: 'Critique',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '设计系统整体架构',
|
||||||
|
ImportantInput: ['agent11'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'agent13',
|
||||||
|
ActionType: 'Improve',
|
||||||
|
AgentName: '防护工程专家',
|
||||||
|
Description: '实现系统核心功能',
|
||||||
|
ImportantInput: ['agent12'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'agent14',
|
||||||
|
ActionType: 'Finalize',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '进行系统集成测试',
|
||||||
|
ImportantInput: ['agent13'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock API:模拟后端 branch_TaskProcess 接口调用
|
||||||
|
*
|
||||||
|
* @param branch_Number - 分支数量
|
||||||
|
* @param Modification_Requirement - 修改需求
|
||||||
|
* @param Existing_Steps - 现有步骤列表(包含完整信息的对象数组)
|
||||||
|
* @param Baseline_Completion - 基线完成度
|
||||||
|
* @param stepTaskExisting - 现有任务
|
||||||
|
* @param General_Goal - 总体目标
|
||||||
|
* @returns Promise<BranchAction[][]> - 返回 2D 数组,与后端格式完全一致
|
||||||
|
*/
|
||||||
|
export const mockBranchTaskProcessAPI = async (params: {
|
||||||
|
branch_Number: number
|
||||||
|
Modification_Requirement: string
|
||||||
|
Existing_Steps: 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
|
||||||
@@ -0,0 +1,617 @@
|
|||||||
|
// fill_stepTask 接口的 Mock API
|
||||||
|
// 简化版本:硬编码多个流程卡片的填充内容(支持多个分支方案)
|
||||||
|
|
||||||
|
import type { IRawStepTask } from '@/stores'
|
||||||
|
|
||||||
|
// 后端返回的数据格式
|
||||||
|
export type FillStepTaskResponse = IRawStepTask
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock API:模拟后端 fill_stepTask 接口调用
|
||||||
|
*
|
||||||
|
* 根据 stepTask 的 StepName 返回对应的填充内容
|
||||||
|
* 支持多个分支方案的轮询选择
|
||||||
|
*
|
||||||
|
* @param General_Goal - 总体目标
|
||||||
|
* @param stepTask - 待填充的任务(包含基本字段)
|
||||||
|
* @returns Promise<IRawStepTask> - 返回填充了 AgentSelection 和 TaskProcess 的完整任务
|
||||||
|
*/
|
||||||
|
export const mockFillStepTaskAPI = async (params: {
|
||||||
|
General_Goal: string
|
||||||
|
stepTask: any
|
||||||
|
}): Promise<IRawStepTask> => {
|
||||||
|
// 模拟网络延迟 300ms
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 300))
|
||||||
|
|
||||||
|
console.log('[Mock API] fill_stepTask 调用参数:', params)
|
||||||
|
|
||||||
|
const stepName = params.stepTask.StepName
|
||||||
|
|
||||||
|
// 🆕 使用轮询方式选择填充方案(依次循环使用所有分支方案)
|
||||||
|
const branchIndexKey = `fill-step-task-index-${params.stepTask?.Id || stepName || 'default'}`
|
||||||
|
let lastIndex = parseInt(sessionStorage.getItem(branchIndexKey) || '0')
|
||||||
|
const totalBranches = 4 // 总共4个分支方案
|
||||||
|
const selectedBranchIndex = (lastIndex + 1) % totalBranches
|
||||||
|
sessionStorage.setItem(branchIndexKey, selectedBranchIndex.toString())
|
||||||
|
|
||||||
|
console.log('[Mock API] fill_stepTask 选择分支方案:', selectedBranchIndex + 1, '/', totalBranches)
|
||||||
|
|
||||||
|
// ==================== 第一个任务组 ====================
|
||||||
|
if (stepName === '分析用户需求' || stepName === '需求快速原型' || stepName === '需求深度分析' || stepName === '迭代规划') {
|
||||||
|
let filledTask: IRawStepTask
|
||||||
|
|
||||||
|
if (selectedBranchIndex === 0) {
|
||||||
|
// 第一个分支方案:分析用户需求
|
||||||
|
filledTask = {
|
||||||
|
Id: params.stepTask.Id || 'task-1',
|
||||||
|
StepName: '分析用户需求',
|
||||||
|
TaskContent: '分析用户需求,制定项目开发计划',
|
||||||
|
InputObject_List: ['用户需求文档', '技术规范'],
|
||||||
|
OutputObject: '项目开发计划书',
|
||||||
|
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '!腐蚀机理研究员!负责!腐蚀类型识别-0!,!实验材料学家!负责!腐蚀类型识别-1!',
|
||||||
|
data: {
|
||||||
|
腐蚀机理研究员: {
|
||||||
|
text: '腐蚀机理研究员',
|
||||||
|
style: { background: 'hsl(210, 70%, 50%)' },
|
||||||
|
},
|
||||||
|
实验材料学家: {
|
||||||
|
text: '实验材料学家',
|
||||||
|
style: { background: 'hsl(120, 70%, 50%)' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TaskProcess: [
|
||||||
|
{
|
||||||
|
ID: 'action-1-1',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '分析腐蚀环境和介质特征,识别潜在腐蚀类型',
|
||||||
|
ImportantInput: ['InputObject:腐蚀类型及成因列表'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-1-2',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '设计腐蚀实验方案,确定测试参数',
|
||||||
|
ImportantInput: ['ActionResult:action-1-1'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-1-3',
|
||||||
|
ActionType: 'Critique',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '评估实验方案的可行性',
|
||||||
|
ImportantInput: [
|
||||||
|
'ActionResult:action-1-2',
|
||||||
|
'ActionResult:action-1-1',
|
||||||
|
'InputObject:腐蚀类型及成因列表',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-1-4',
|
||||||
|
ActionType: 'Improve',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '优化实验设计,完善测试流程',
|
||||||
|
ImportantInput: ['ActionResult:action-1-3'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-1-5',
|
||||||
|
ActionType: 'Finalize',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '确认最终的腐蚀类型识别方案',
|
||||||
|
ImportantInput: ['ActionResult:action-1-4', 'ActionResult:action-1-3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
} else if (selectedBranchIndex === 1) {
|
||||||
|
// 第二个分支方案:需求快速原型
|
||||||
|
filledTask = {
|
||||||
|
Id: params.stepTask.Id || 'task-1',
|
||||||
|
StepName: '需求快速原型',
|
||||||
|
TaskContent: '构建快速原型验证核心功能',
|
||||||
|
InputObject_List: ['腐蚀类型及成因列表'],
|
||||||
|
OutputObject: '原型系统',
|
||||||
|
AgentSelection: ['实验材料学家', '防护工程专家'],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '!实验材料学家!负责!需求快速原型-0!,!防护工程专家!负责!需求快速原型-1!',
|
||||||
|
data: {
|
||||||
|
实验材料学家: {
|
||||||
|
text: '实验材料学家',
|
||||||
|
style: { background: 'hsl(120, 70%, 50%)' },
|
||||||
|
},
|
||||||
|
防护工程专家: {
|
||||||
|
text: '防护工程专家',
|
||||||
|
style: { background: 'hsl(30, 70%, 50%)' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TaskProcess: [
|
||||||
|
{
|
||||||
|
ID: 'action-1-1-branch2',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '基于腐蚀类型列表,设计快速原型验证方案',
|
||||||
|
ImportantInput: ['InputObject:腐蚀类型及成因列表'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-1-2-branch2',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '防护工程专家',
|
||||||
|
Description: '实现原型核心功能和用户界面',
|
||||||
|
ImportantInput: ['ActionResult:action-1-1-branch2'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-1-3-branch2',
|
||||||
|
ActionType: 'Critique',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '评估原型功能完整性和用户体验',
|
||||||
|
ImportantInput: ['ActionResult:action-1-2-branch2'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-1-4-branch2',
|
||||||
|
ActionType: 'Improve',
|
||||||
|
AgentName: '防护工程专家',
|
||||||
|
Description: '根据反馈优化原型功能和界面',
|
||||||
|
ImportantInput: ['ActionResult:action-1-3-branch2'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-1-5-branch2',
|
||||||
|
ActionType: 'Finalize',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '确认最终版本的原型系统',
|
||||||
|
ImportantInput: ['ActionResult:action-1-4-branch2'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
} else if (selectedBranchIndex === 2) {
|
||||||
|
// 第三个分支方案:需求深度分析
|
||||||
|
filledTask = {
|
||||||
|
Id: params.stepTask.Id || 'task-1',
|
||||||
|
StepName: '需求深度分析',
|
||||||
|
TaskContent: '深入分析用户需求和技术可行性',
|
||||||
|
InputObject_List: ['腐蚀类型及成因列表'],
|
||||||
|
OutputObject: '详细需求分析报告',
|
||||||
|
AgentSelection: ['腐蚀机理研究员', '防护工程专家'],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '!腐蚀机理研究员!负责!需求深度分析-0!,!防护工程专家!负责!需求深度分析-1!',
|
||||||
|
data: {
|
||||||
|
腐蚀机理研究员: {
|
||||||
|
text: '腐蚀机理研究员',
|
||||||
|
style: { background: 'hsl(210, 70%, 50%)' },
|
||||||
|
},
|
||||||
|
防护工程专家: {
|
||||||
|
text: '防护工程专家',
|
||||||
|
style: { background: 'hsl(30, 70%, 50%)' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TaskProcess: [
|
||||||
|
{
|
||||||
|
ID: 'action-1-1-branch3',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '全面分析腐蚀环境和技术约束条件',
|
||||||
|
ImportantInput: ['InputObject:腐蚀类型及成因列表'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-1-2-branch3',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '防护工程专家',
|
||||||
|
Description: '评估技术可行性和防护方案',
|
||||||
|
ImportantInput: ['ActionResult:action-1-1-branch3'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-1-3-branch3',
|
||||||
|
ActionType: 'Critique',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '评审需求分析的完整性和准确性',
|
||||||
|
ImportantInput: ['ActionResult:action-1-2-branch3', 'ActionResult:action-1-1-branch3'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-1-4-branch3',
|
||||||
|
ActionType: 'Improve',
|
||||||
|
AgentName: '防护工程专家',
|
||||||
|
Description: '补充技术风险评估和应对策略',
|
||||||
|
ImportantInput: ['ActionResult:action-1-3-branch3'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-1-5-branch3',
|
||||||
|
ActionType: 'Finalize',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '确认详细需求分析报告',
|
||||||
|
ImportantInput: ['ActionResult:action-1-4-branch3', 'ActionResult:action-1-3-branch3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 第四个分支方案:迭代规划
|
||||||
|
filledTask = {
|
||||||
|
Id: params.stepTask.Id || 'task-1',
|
||||||
|
StepName: '迭代规划',
|
||||||
|
TaskContent: '制定敏捷开发迭代计划',
|
||||||
|
InputObject_List: ['腐蚀类型及成因列表'],
|
||||||
|
OutputObject: '迭代计划',
|
||||||
|
AgentSelection: ['防护工程专家', '腐蚀机理研究员'],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '!防护工程专家!负责!迭代规划-0!,!腐蚀机理研究员!负责!迭代规划-1!',
|
||||||
|
data: {
|
||||||
|
防护工程专家: {
|
||||||
|
text: '防护工程专家',
|
||||||
|
style: { background: 'hsl(30, 70%, 50%)' },
|
||||||
|
},
|
||||||
|
腐蚀机理研究员: {
|
||||||
|
text: '腐蚀机理研究员',
|
||||||
|
style: { background: 'hsl(210, 70%, 50%)' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TaskProcess: [
|
||||||
|
{
|
||||||
|
ID: 'action-1-1-branch4',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '防护工程专家',
|
||||||
|
Description: '制定敏捷开发总体框架和迭代周期',
|
||||||
|
ImportantInput: ['InputObject:腐蚀类型及成因列表'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-1-2-branch4',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '规划各迭代阶段的技术验证重点',
|
||||||
|
ImportantInput: ['ActionResult:action-1-1-branch4'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-1-3-branch4',
|
||||||
|
ActionType: 'Critique',
|
||||||
|
AgentName: '防护工程专家',
|
||||||
|
Description: '评审迭代计划的合理性和可执行性',
|
||||||
|
ImportantInput: ['ActionResult:action-1-2-branch4'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-1-4-branch4',
|
||||||
|
ActionType: 'Improve',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '优化迭代节奏和里程碑设置',
|
||||||
|
ImportantInput: ['ActionResult:action-1-3-branch4'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-1-5-branch4',
|
||||||
|
ActionType: 'Finalize',
|
||||||
|
AgentName: '防护工程专家',
|
||||||
|
Description: '确认最终迭代计划',
|
||||||
|
ImportantInput: ['ActionResult:action-1-4-branch4', 'ActionResult:action-1-3-branch4'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[Mock API] fill_stepTask 返回数据:', filledTask)
|
||||||
|
return filledTask
|
||||||
|
}
|
||||||
|
// ==================== 第二个任务组 ====================
|
||||||
|
else if (stepName === '系统设计与架构' || stepName === '原型测试与优化' || stepName === '质量保障设计' || stepName === '快速开发与验证') {
|
||||||
|
let filledTask: IRawStepTask
|
||||||
|
|
||||||
|
if (selectedBranchIndex === 0) {
|
||||||
|
// 第一个分支方案:系统设计与架构
|
||||||
|
filledTask = {
|
||||||
|
Id: params.stepTask.Id || 'task-2',
|
||||||
|
StepName: '系统设计与架构',
|
||||||
|
TaskContent: '设计系统架构和数据库结构',
|
||||||
|
InputObject_List: ['项目开发计划书'],
|
||||||
|
OutputObject: '系统设计文档',
|
||||||
|
AgentSelection: ['腐蚀机理研究员', '防护工程专家'],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '!腐蚀机理研究员!负责!系统设计与架构-0!,!防护工程专家!负责!系统设计与架构-1!',
|
||||||
|
data: {
|
||||||
|
腐蚀机理研究员: {
|
||||||
|
text: '腐蚀机理研究员',
|
||||||
|
style: { background: 'hsl(210, 70%, 50%)' },
|
||||||
|
},
|
||||||
|
防护工程专家: {
|
||||||
|
text: '防护工程专家',
|
||||||
|
style: { background: 'hsl(30, 70%, 50%)' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TaskProcess: [
|
||||||
|
{
|
||||||
|
ID: 'action-2-1',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '分析系统功能需求,提出整体架构方案',
|
||||||
|
ImportantInput: ['InputObject:项目开发计划书'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-2-2',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '防护工程专家',
|
||||||
|
Description: '设计防护系统架构和数据库模型',
|
||||||
|
ImportantInput: ['ActionResult:action-2-1'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-2-3',
|
||||||
|
ActionType: 'Critique',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '评审架构设计的合理性和可扩展性',
|
||||||
|
ImportantInput: ['ActionResult:action-2-2', 'InputObject:项目开发计划书'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-2-4',
|
||||||
|
ActionType: 'Improve',
|
||||||
|
AgentName: '防护工程专家',
|
||||||
|
Description: '优化架构设计,补充性能和安全方案',
|
||||||
|
ImportantInput: ['ActionResult:action-2-3', 'ActionResult:action-2-2'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-2-5',
|
||||||
|
ActionType: 'Finalize',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '确认最终系统架构设计文档',
|
||||||
|
ImportantInput: ['ActionResult:action-2-4', 'ActionResult:action-2-3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
} else if (selectedBranchIndex === 1) {
|
||||||
|
// 第二个分支方案:原型测试与优化
|
||||||
|
filledTask = {
|
||||||
|
Id: params.stepTask.Id || 'task-2',
|
||||||
|
StepName: '原型测试与优化',
|
||||||
|
TaskContent: '测试原型并根据反馈快速迭代',
|
||||||
|
InputObject_List: ['原型系统'],
|
||||||
|
OutputObject: '优化后的原型',
|
||||||
|
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '!腐蚀机理研究员!负责!原型测试与优化-0!,!实验材料学家!负责!原型测试与优化-1!',
|
||||||
|
data: {
|
||||||
|
腐蚀机理研究员: {
|
||||||
|
text: '腐蚀机理研究员',
|
||||||
|
style: { background: 'hsl(210, 70%, 50%)' },
|
||||||
|
},
|
||||||
|
实验材料学家: {
|
||||||
|
text: '实验材料学家',
|
||||||
|
style: { background: 'hsl(120, 70%, 50%)' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TaskProcess: [
|
||||||
|
{
|
||||||
|
ID: 'action-2-1-branch2',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '制定原型测试计划和验证标准',
|
||||||
|
ImportantInput: ['InputObject:原型系统'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-2-2-branch2',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '执行原型测试并收集性能数据',
|
||||||
|
ImportantInput: ['ActionResult:action-2-1-branch2'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-2-3-branch2',
|
||||||
|
ActionType: 'Critique',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '分析测试结果和用户反馈',
|
||||||
|
ImportantInput: ['ActionResult:action-2-2-branch2'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-2-4-branch2',
|
||||||
|
ActionType: 'Improve',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '根据测试结果优化原型功能和性能',
|
||||||
|
ImportantInput: ['ActionResult:action-2-3-branch2'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-2-5-branch2',
|
||||||
|
ActionType: 'Finalize',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '确认优化后的原型版本',
|
||||||
|
ImportantInput: ['ActionResult:action-2-4-branch2', 'ActionResult:action-2-3-branch2'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
} else if (selectedBranchIndex === 2) {
|
||||||
|
// 第三个分支方案:质量保障设计
|
||||||
|
filledTask = {
|
||||||
|
Id: params.stepTask.Id || 'task-2',
|
||||||
|
StepName: '质量保障设计',
|
||||||
|
TaskContent: '设计质量保障体系和测试方案',
|
||||||
|
InputObject_List: ['详细需求分析报告'],
|
||||||
|
OutputObject: '质量保障方案',
|
||||||
|
AgentSelection: ['实验材料学家', '防护工程专家'],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '!实验材料学家!负责!质量保障设计-0!,!防护工程专家!负责!质量保障设计-1!',
|
||||||
|
data: {
|
||||||
|
实验材料学家: {
|
||||||
|
text: '实验材料学家',
|
||||||
|
style: { background: 'hsl(120, 70%, 50%)' },
|
||||||
|
},
|
||||||
|
防护工程专家: {
|
||||||
|
text: '防护工程专家',
|
||||||
|
style: { background: 'hsl(30, 70%, 50%)' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TaskProcess: [
|
||||||
|
{
|
||||||
|
ID: 'action-2-1-branch3',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '制定质量标准和测试指标体系',
|
||||||
|
ImportantInput: ['InputObject:详细需求分析报告'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-2-2-branch3',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '防护工程专家',
|
||||||
|
Description: '设计质量保障流程和验证方案',
|
||||||
|
ImportantInput: ['ActionResult:action-2-1-branch3'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-2-3-branch3',
|
||||||
|
ActionType: 'Critique',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '评审质量保障方案的完整性',
|
||||||
|
ImportantInput: ['ActionResult:action-2-2-branch3'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-2-4-branch3',
|
||||||
|
ActionType: 'Improve',
|
||||||
|
AgentName: '防护工程专家',
|
||||||
|
Description: '完善质量保障方案,补充风险控制',
|
||||||
|
ImportantInput: ['ActionResult:action-2-3-branch3'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-2-5-branch3',
|
||||||
|
ActionType: 'Finalize',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '确认最终质量保障方案',
|
||||||
|
ImportantInput: ['ActionResult:action-2-4-branch3', 'ActionResult:action-2-3-branch3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 第四个分支方案:快速开发与验证
|
||||||
|
filledTask = {
|
||||||
|
Id: params.stepTask.Id || 'task-2',
|
||||||
|
StepName: '快速开发与验证',
|
||||||
|
TaskContent: '快速开发并持续验证功能',
|
||||||
|
InputObject_List: ['迭代计划'],
|
||||||
|
OutputObject: '可运行的功能模块',
|
||||||
|
AgentSelection: ['实验材料学家', '防护工程专家'],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '!实验材料学家!负责!快速开发与验证-0!,!防护工程专家!负责!快速开发与验证-1!',
|
||||||
|
data: {
|
||||||
|
实验材料学家: {
|
||||||
|
text: '实验材料学家',
|
||||||
|
style: { background: 'hsl(120, 70%, 50%)' },
|
||||||
|
},
|
||||||
|
防护工程专家: {
|
||||||
|
text: '防护工程专家',
|
||||||
|
style: { background: 'hsl(30, 70%, 50%)' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TaskProcess: [
|
||||||
|
{
|
||||||
|
ID: 'action-2-1-branch4',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '根据迭代计划快速开发功能模块',
|
||||||
|
ImportantInput: ['InputObject:迭代计划'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-2-2-branch4',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '防护工程专家',
|
||||||
|
Description: '实现功能防护机制和验证逻辑',
|
||||||
|
ImportantInput: ['ActionResult:action-2-1-branch4'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-2-3-branch4',
|
||||||
|
ActionType: 'Critique',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '验证功能模块的正确性和性能',
|
||||||
|
ImportantInput: ['ActionResult:action-2-2-branch4'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-2-4-branch4',
|
||||||
|
ActionType: 'Improve',
|
||||||
|
AgentName: '防护工程专家',
|
||||||
|
Description: '根据验证结果优化功能实现',
|
||||||
|
ImportantInput: ['ActionResult:action-2-3-branch4'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-2-5-branch4',
|
||||||
|
ActionType: 'Finalize',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '确认可运行的功能模块',
|
||||||
|
ImportantInput: ['ActionResult:action-2-4-branch4', 'ActionResult:action-2-3-branch4'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[Mock API] fill_stepTask 返回数据:', filledTask)
|
||||||
|
return filledTask
|
||||||
|
}
|
||||||
|
// ==================== 第三个任务组(仅第三个分支方案) ====================
|
||||||
|
else if (stepName === '系统开发与测试') {
|
||||||
|
const filledTask: IRawStepTask = {
|
||||||
|
Id: params.stepTask.Id || 'task-3',
|
||||||
|
StepName: '系统开发与测试',
|
||||||
|
TaskContent: '按质量标准进行系统开发和测试',
|
||||||
|
InputObject_List: ['质量保障方案'],
|
||||||
|
OutputObject: '经过完整测试的系统',
|
||||||
|
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
|
||||||
|
Collaboration_Brief_frontEnd: {
|
||||||
|
template: '!腐蚀机理研究员!负责!系统开发与测试-0!,!实验材料学家!负责!系统开发与测试-1!',
|
||||||
|
data: {
|
||||||
|
腐蚀机理研究员: {
|
||||||
|
text: '腐蚀机理研究员',
|
||||||
|
style: { background: 'hsl(210, 70%, 50%)' },
|
||||||
|
},
|
||||||
|
实验材料学家: {
|
||||||
|
text: '实验材料学家',
|
||||||
|
style: { background: 'hsl(120, 70%, 50%)' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TaskProcess: [
|
||||||
|
{
|
||||||
|
ID: 'action-3-1',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '根据质量标准制定系统开发计划',
|
||||||
|
ImportantInput: ['InputObject:质量保障方案'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-3-2',
|
||||||
|
ActionType: 'Propose',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '按标准实施系统开发和单元测试',
|
||||||
|
ImportantInput: ['ActionResult:action-3-1'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-3-3',
|
||||||
|
ActionType: 'Critique',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '评审系统开发质量和测试覆盖率',
|
||||||
|
ImportantInput: ['ActionResult:action-3-2'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-3-4',
|
||||||
|
ActionType: 'Improve',
|
||||||
|
AgentName: '实验材料学家',
|
||||||
|
Description: '根据评审结果完善系统功能和测试',
|
||||||
|
ImportantInput: ['ActionResult:action-3-3'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 'action-3-5',
|
||||||
|
ActionType: 'Finalize',
|
||||||
|
AgentName: '腐蚀机理研究员',
|
||||||
|
Description: '确认经过完整测试的系统版本',
|
||||||
|
ImportantInput: ['ActionResult:action-3-4', 'ActionResult:action-3-3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[Mock API] fill_stepTask 返回数据:', filledTask)
|
||||||
|
return filledTask
|
||||||
|
}
|
||||||
|
// ==================== 其他任务 ====================
|
||||||
|
else {
|
||||||
|
// 其他任务,返回空 TaskProcess
|
||||||
|
console.warn('[Mock API] 未知的 StepName:', stepName)
|
||||||
|
return {
|
||||||
|
...params.stepTask,
|
||||||
|
AgentSelection: [],
|
||||||
|
TaskProcess: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default mockFillStepTaskAPI
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||||
|
import { useAgentsStore } from '@/stores'
|
||||||
|
|
||||||
|
const agentsStore = useAgentsStore()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'click'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 判断是否禁用 - 必须先点击任务大纲中的卡片
|
||||||
|
const isDisabled = computed(() => {
|
||||||
|
return !agentsStore.currentTask
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取agent组合卡片数量
|
||||||
|
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>
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,446 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
// 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
// 模拟后端原始返回格式的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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
// 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,15 +3,29 @@ import SvgIcon from '@/components/SvgIcon/index.vue'
|
|||||||
import { getAgentMapIcon } from '@/layout/components/config.ts'
|
import { getAgentMapIcon } from '@/layout/components/config.ts'
|
||||||
import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/utils.ts'
|
import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/utils.ts'
|
||||||
import { type IRawStepTask, useAgentsStore } from '@/stores'
|
import { type IRawStepTask, useAgentsStore } from '@/stores'
|
||||||
import { computed } from 'vue'
|
import { computed, ref, nextTick, watch, onMounted } from 'vue'
|
||||||
import { AnchorLocations } from '@jsplumb/browser-ui'
|
import { AnchorLocations } from '@jsplumb/browser-ui'
|
||||||
|
import { Loading } from '@element-plus/icons-vue'
|
||||||
import MultiLineTooltip from '@/components/MultiLineTooltip/index.vue'
|
import MultiLineTooltip from '@/components/MultiLineTooltip/index.vue'
|
||||||
|
|
||||||
import Bg from './Bg.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<{
|
const emit = defineEmits<{
|
||||||
(el: 'resetAgentRepoLine'): void
|
(el: 'resetAgentRepoLine'): void
|
||||||
(el: 'setCurrentTask', task: IRawStepTask): void
|
(el: 'setCurrentTask', task: IRawStepTask): void
|
||||||
|
(el: 'add-output', outputName: string): void
|
||||||
|
(el: 'click-branch'): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const jsplumb = new Jsplumb('task-syllabus')
|
const jsplumb = new Jsplumb('task-syllabus')
|
||||||
@@ -26,6 +40,206 @@ const collaborationProcess = computed(() => {
|
|||||||
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
|
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 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) {
|
||||||
|
console.warn('Task ID is missing, cannot start editing')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
editingTaskId.value = task.Id
|
||||||
|
editingContent.value = task.TaskContent || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存编辑
|
||||||
|
const saveEditing = () => {
|
||||||
|
if (editingTaskId.value && editingContent.value.trim()) {
|
||||||
|
const taskToUpdate = collaborationProcess.value.find(item => item.Id === editingTaskId.value)
|
||||||
|
if (taskToUpdate) {
|
||||||
|
// 保存旧值用于比较
|
||||||
|
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 未发生变化,不记录修改`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
editingTaskId.value = null
|
||||||
|
editingContent.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消编辑
|
||||||
|
const cancelEditing = () => {
|
||||||
|
editingTaskId.value = null
|
||||||
|
editingContent.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理键盘事件
|
||||||
|
const handleKeydown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault()
|
||||||
|
saveEditing()
|
||||||
|
} else if (event.key === 'Escape') {
|
||||||
|
cancelEditing()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleCurrentTask(task: IRawStepTask, transparent: boolean): ConnectArg[] {
|
function handleCurrentTask(task: IRawStepTask, transparent: boolean): ConnectArg[] {
|
||||||
// 创建当前流程与产出的连线
|
// 创建当前流程与产出的连线
|
||||||
const arr: ConnectArg[] = [
|
const arr: ConnectArg[] = [
|
||||||
@@ -34,14 +248,14 @@ function handleCurrentTask(task: IRawStepTask, transparent: boolean): ConnectArg
|
|||||||
targetId: `task-syllabus-output-object-${task.Id}`,
|
targetId: `task-syllabus-output-object-${task.Id}`,
|
||||||
anchor: [AnchorLocations.Right, AnchorLocations.Left],
|
anchor: [AnchorLocations.Right, AnchorLocations.Left],
|
||||||
config: {
|
config: {
|
||||||
transparent,
|
transparent
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
// 创建当前产出与流程的连线
|
// 创建当前产出与流程的连线
|
||||||
task.InputObject_List?.forEach((item) => {
|
task.InputObject_List?.forEach(item => {
|
||||||
const id = collaborationProcess.value.find((i) => i.OutputObject === item)?.Id
|
const id = collaborationProcess.value.find(i => i.OutputObject === item)?.Id
|
||||||
if (id) {
|
if (id) {
|
||||||
arr.push({
|
arr.push({
|
||||||
sourceId: `task-syllabus-output-object-${id}`,
|
sourceId: `task-syllabus-output-object-${id}`,
|
||||||
@@ -49,8 +263,8 @@ function handleCurrentTask(task: IRawStepTask, transparent: boolean): ConnectArg
|
|||||||
anchor: [AnchorLocations.Left, AnchorLocations.Right],
|
anchor: [AnchorLocations.Left, AnchorLocations.Right],
|
||||||
config: {
|
config: {
|
||||||
type: 'output',
|
type: 'output',
|
||||||
transparent,
|
transparent
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -61,7 +275,7 @@ function handleCurrentTask(task: IRawStepTask, transparent: boolean): ConnectArg
|
|||||||
function changeTask(task?: IRawStepTask, isEmit?: boolean) {
|
function changeTask(task?: IRawStepTask, isEmit?: boolean) {
|
||||||
jsplumb.reset()
|
jsplumb.reset()
|
||||||
const arr: ConnectArg[] = []
|
const arr: ConnectArg[] = []
|
||||||
agentsStore.agentRawPlan.data?.['Collaboration Process']?.forEach((item) => {
|
agentsStore.agentRawPlan.data?.['Collaboration Process']?.forEach(item => {
|
||||||
arr.push(...handleCurrentTask(item, item.Id !== task?.Id))
|
arr.push(...handleCurrentTask(item, item.Id !== task?.Id))
|
||||||
})
|
})
|
||||||
jsplumb.connects(arr)
|
jsplumb.connects(arr)
|
||||||
@@ -74,28 +288,85 @@ function clear() {
|
|||||||
jsplumb.reset()
|
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({
|
defineExpose({
|
||||||
changeTask,
|
changeTask,
|
||||||
clear,
|
clear
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-full flex flex-col">
|
<div class="h-full flex flex-col">
|
||||||
<div class="text-[18px] font-bold mb-[18px]">任务大纲</div>
|
<!-- Notification 通知系统 -->
|
||||||
|
<Notification
|
||||||
|
:notifications="notifications"
|
||||||
|
@close="(id) => removeNotification(id)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="text-[18px] font-bold mb-[18px] text-[var(--color-text-title-header)]">
|
||||||
|
任务大纲
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-loading="agentsStore.agentRawPlan.loading"
|
v-loading="agentsStore.agentRawPlan.loading"
|
||||||
class="flex-1 w-full overflow-y-auto relative"
|
class="flex-1 w-full overflow-y-auto relative"
|
||||||
@scroll="handleScroll"
|
@scroll="handleScroll"
|
||||||
>
|
>
|
||||||
<div v-show="collaborationProcess.length > 0" class="w-full relative min-h-full" id="task-syllabus">
|
<div
|
||||||
<Bg />
|
v-show="collaborationProcess.length > 0"
|
||||||
|
class="w-full relative min-h-full"
|
||||||
|
id="task-syllabus"
|
||||||
|
>
|
||||||
|
<Bg :is-adding="isAddingOutput" @start-add-output="handleAddOutputClick" />
|
||||||
|
|
||||||
<div class="w-full flex items-center gap-[14%] mb-[35px]">
|
<div class="w-full flex items-center gap-[14%] mb-[35px]">
|
||||||
<div class="flex-1 flex justify-center">
|
<div class="flex-1 flex justify-center">
|
||||||
<div
|
<div
|
||||||
class="card-item w-[45%] h-[41px] flex justify-center relative z-99 items-center rounded-[20px] bg-[var(--color-bg-tertiary)]"
|
class="card-item w-[168px] h-[41px] flex justify-center relative z-99 items-center rounded-[20px] bg-[var(--color-bg-flow)]"
|
||||||
>
|
>
|
||||||
流程
|
流程
|
||||||
</div>
|
</div>
|
||||||
@@ -103,49 +374,135 @@ defineExpose({
|
|||||||
|
|
||||||
<div class="flex-1 flex justify-center">
|
<div class="flex-1 flex justify-center">
|
||||||
<div
|
<div
|
||||||
class="card-item w-[45%] h-[41px] flex justify-center relative z-99 items-center rounded-[20px] bg-[var(--color-bg-tertiary)]"
|
class="card-item w-[168px] h-[41px] flex justify-center relative z-99 items-center rounded-[20px] bg-[var(--color-bg-flow)]"
|
||||||
>
|
>
|
||||||
产物
|
产物
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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
|
<div
|
||||||
v-for="item in collaborationProcess"
|
v-for="item in collaborationProcess"
|
||||||
:key="item.Id"
|
:key="item.Id"
|
||||||
class="card-item w-full flex items-center gap-[14%]"
|
class="card-it w-full flex items-center gap-[14%] bg-[var(--color-card-bg)] mb-[100px]"
|
||||||
>
|
>
|
||||||
<!-- 流程卡片 -->
|
<!-- 流程卡片 -->
|
||||||
<el-card
|
<el-card
|
||||||
class="w-[43%] overflow-y-auto relative z-99 task-syllabus-flow-card"
|
class="w-[43%] overflow-y-auto relative z-99 task-syllabus-flow-card"
|
||||||
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
|
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
|
||||||
shadow="hover"
|
:shadow="true"
|
||||||
:id="`task-syllabus-flow-${item.Id}`"
|
:id="`task-syllabus-flow-${item.Id}`"
|
||||||
@click="changeTask(item, true)"
|
@click="changeTask(item, true)"
|
||||||
>
|
>
|
||||||
<MultiLineTooltip placement="right" :text="item.StepName" :lines="2">
|
<MultiLineTooltip placement="right" :text="item.StepName" :lines="2">
|
||||||
<div class="text-[18px] font-bold text-center">
|
<div class="text-[18px] font-bold text-center">{{ item.StepName }}</div>
|
||||||
{{ item.StepName }}
|
|
||||||
</div>
|
|
||||||
</MultiLineTooltip>
|
</MultiLineTooltip>
|
||||||
<div class="h-[1px] w-full bg-[#494B51] my-[8px]"></div>
|
<div class="h-[1px] w-full bg-[var(--color-border-separate)] my-[8px]"></div>
|
||||||
<MultiLineTooltip placement="right" :text="item.StepName" :lines="3">
|
|
||||||
<div
|
<!-- 任务内容区域 - 支持双击编辑 -->
|
||||||
class="text-[14px] text-[var(--color-text-secondary)]"
|
<div v-if="editingTaskId === item.Id" class="w-full">
|
||||||
:title="item.TaskContent"
|
<div class="flex flex-col gap-3">
|
||||||
>
|
<el-input
|
||||||
|
v-model="editingContent"
|
||||||
|
type="textarea"
|
||||||
|
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||||
|
placeholder="请输入任务内容"
|
||||||
|
@keydown="handleKeydown"
|
||||||
|
class="task-content-editor"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else @dblclick="startEditing(item)" class="w-full cursor-pointer">
|
||||||
|
<MultiLineTooltip placement="right" :text="item.TaskContent" :lines="3">
|
||||||
|
<div class="text-[14px] text-[var(--color-text-secondary)] task-content-display">
|
||||||
{{ item.TaskContent }}
|
{{ item.TaskContent }}
|
||||||
</div>
|
</div>
|
||||||
</MultiLineTooltip>
|
</MultiLineTooltip>
|
||||||
<div class="h-[1px] w-full bg-[#494B51] my-[8px]"></div>
|
</div>
|
||||||
|
|
||||||
|
<div class="h-[1px] w-full bg-[var(--color-border-separate)] my-[8px]"></div>
|
||||||
<div
|
<div
|
||||||
class="flex items-center gap-2 overflow-y-auto flex-wrap relative w-full max-h-[72px]"
|
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]'"
|
||||||
>
|
>
|
||||||
<!-- 连接到智能体库的连接点 -->
|
<!-- 连接到智能体库的连接点 -->
|
||||||
<div
|
<div
|
||||||
class="absolute left-[-10px] top-1/2 transform -translate-y-1/2"
|
class="absolute left-[-10px] top-1/2 transform -translate-y-1/2"
|
||||||
:id="`task-syllabus-flow-agents-${item.Id}`"
|
:id="`task-syllabus-flow-agents-${item.Id}`"
|
||||||
></div>
|
></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
|
<el-tooltip
|
||||||
v-for="agentSelection in item.AgentSelection"
|
v-for="agentSelection in item.AgentSelection"
|
||||||
:key="agentSelection"
|
:key="agentSelection"
|
||||||
@@ -158,7 +515,7 @@ defineExpose({
|
|||||||
<div class="h-[1px] w-full bg-[#494B51] my-[8px]"></div>
|
<div class="h-[1px] w-full bg-[#494B51] my-[8px]"></div>
|
||||||
<div>
|
<div>
|
||||||
{{
|
{{
|
||||||
item.TaskProcess.find((i) => i.AgentName === agentSelection)?.Description
|
item.TaskProcess.find(i => i.AgentName === agentSelection)?.Description
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -169,17 +526,18 @@ defineExpose({
|
|||||||
>
|
>
|
||||||
<svg-icon
|
<svg-icon
|
||||||
:icon-class="getAgentMapIcon(agentSelection).icon"
|
:icon-class="getAgentMapIcon(agentSelection).icon"
|
||||||
color="var(--color-text)"
|
color="#fff"
|
||||||
size="24px"
|
size="24px"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
<!-- 产物卡片 -->
|
<!-- 产物卡片 -->
|
||||||
<el-card
|
<el-card
|
||||||
class="w-[43%] relative"
|
class="w-[43%] relative task-syllabus-output-object-card"
|
||||||
shadow="hover"
|
:shadow="true"
|
||||||
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
|
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
|
||||||
:id="`task-syllabus-output-object-${item.Id}`"
|
:id="`task-syllabus-output-object-${item.Id}`"
|
||||||
>
|
>
|
||||||
@@ -188,10 +546,20 @@ defineExpose({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<BranchButton v-if="planReady" @click="openPlanModification" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.task-syllabus-flow-card {
|
.task-syllabus-flow-card {
|
||||||
|
background-color: var(--color-card-bg-task);
|
||||||
|
border: 1px solid var(--color-card-border-task);
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-card-bg-task-hover);
|
||||||
|
border-color: var(--color-card-border-hover);
|
||||||
|
box-shadow: var(--color-card-shadow-hover);
|
||||||
|
}
|
||||||
:deep(.el-card__body) {
|
:deep(.el-card__body) {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -201,4 +569,165 @@ defineExpose({
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.task-syllabus-output-object-card {
|
||||||
|
background-color: var(--color-card-bg-task);
|
||||||
|
border: 1px solid var(--color-card-border-task);
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-card-bg-task-hover);
|
||||||
|
border-color: var(--color-card-border-hover);
|
||||||
|
box-shadow: var(--color-card-shadow-hover);
|
||||||
|
}
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-content-editor {
|
||||||
|
:deep(.el-textarea__inner) {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 4px;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-content-display {
|
||||||
|
min-height: 40px;
|
||||||
|
word-break: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-output-btn {
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(59, 130, 246, 0.05);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-output-form {
|
||||||
|
animation: slideDown 0.3s ease-out;
|
||||||
|
: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 {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输入框样式
|
||||||
|
:deep(.el-input__wrapper) {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #c0c4cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-focus {
|
||||||
|
border-color: #409eff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__inner) {
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-size: 14px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务内容编辑按钮样式 - 匹配执行结果编辑按钮样式
|
||||||
|
.task-content-editor {
|
||||||
|
.el-button {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
&.el-button--small {
|
||||||
|
padding: 4px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在深色模式下,按钮背景会自动适配为深色
|
||||||
|
html.dark & {
|
||||||
|
.el-button {
|
||||||
|
&.el-button--primary {
|
||||||
|
background-color: var(--color-bg-detail);
|
||||||
|
border-color: var(--color-border);
|
||||||
|
color: var(--color-text);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-bg-hover);
|
||||||
|
border-color: var(--color-text-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.el-button--primary) {
|
||||||
|
background-color: var(--color-bg-detail);
|
||||||
|
border-color: var(--color-border);
|
||||||
|
color: var(--color-text);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-bg-hover);
|
||||||
|
border-color: var(--color-text-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import TaskResult from './TaskResult/index.vue'
|
|||||||
import { Jsplumb } from './utils.ts'
|
import { Jsplumb } from './utils.ts'
|
||||||
import { type IRawStepTask, useAgentsStore } from '@/stores'
|
import { type IRawStepTask, useAgentsStore } from '@/stores'
|
||||||
import { BezierConnector } from '@jsplumb/browser-ui'
|
import { BezierConnector } from '@jsplumb/browser-ui'
|
||||||
|
import { ref } from 'vue'
|
||||||
const agentsStore = useAgentsStore()
|
const agentsStore = useAgentsStore()
|
||||||
|
|
||||||
// 智能体库
|
// 智能体库
|
||||||
@@ -15,9 +15,9 @@ const agentRepoJsplumb = new Jsplumb('task-template', {
|
|||||||
options: {
|
options: {
|
||||||
curviness: 30, // 曲线弯曲程度
|
curviness: 30, // 曲线弯曲程度
|
||||||
stub: 20, // 添加连接点与端点的距离
|
stub: 20, // 添加连接点与端点的距离
|
||||||
alwaysRespectStubs: true,
|
alwaysRespectStubs: true
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 任务流程
|
// 任务流程
|
||||||
@@ -32,18 +32,16 @@ const taskResultRef = ref<{
|
|||||||
}>()
|
}>()
|
||||||
const taskResultJsplumb = new Jsplumb('task-template')
|
const taskResultJsplumb = new Jsplumb('task-template')
|
||||||
|
|
||||||
|
|
||||||
function scrollToElementTop(elementId: string) {
|
function scrollToElementTop(elementId: string) {
|
||||||
const element = document.getElementById(elementId);
|
const element = document.getElementById(elementId)
|
||||||
if (element) {
|
if (element) {
|
||||||
element.scrollIntoView({
|
element.scrollIntoView({
|
||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
block: 'start'
|
block: 'start'
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function handleTaskSyllabusCurrentTask(task: IRawStepTask) {
|
function handleTaskSyllabusCurrentTask(task: IRawStepTask) {
|
||||||
scrollToElementTop(`task-results-${task.Id}-0`)
|
scrollToElementTop(`task-results-${task.Id}-0`)
|
||||||
agentsStore.setCurrentTask(task)
|
agentsStore.setCurrentTask(task)
|
||||||
@@ -73,16 +71,22 @@ function clear() {
|
|||||||
taskResultJsplumb.repaintEverything()
|
taskResultJsplumb.repaintEverything()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const additionalOutputs = ref<string[]>([])
|
||||||
|
const handleAddOutput = (outputName: string) => {
|
||||||
|
additionalOutputs.value.unshift(outputName)
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
changeTask,
|
changeTask,
|
||||||
resetAgentRepoLine,
|
resetAgentRepoLine,
|
||||||
clear,
|
clear
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<!-- 删除overflow-hidden -->
|
||||||
<div
|
<div
|
||||||
class="task-template flex gap-6 items-center h-[calc(100%-84px)] relative overflow-hidden"
|
class="task-template flex gap-6 items-center h-[calc(100%-84px)] relative"
|
||||||
id="task-template"
|
id="task-template"
|
||||||
>
|
>
|
||||||
<!-- 智能体库 -->
|
<!-- 智能体库 -->
|
||||||
@@ -95,6 +99,7 @@ defineExpose({
|
|||||||
ref="taskSyllabusRef"
|
ref="taskSyllabusRef"
|
||||||
@resetAgentRepoLine="resetAgentRepoLine"
|
@resetAgentRepoLine="resetAgentRepoLine"
|
||||||
@set-current-task="handleTaskSyllabusCurrentTask"
|
@set-current-task="handleTaskSyllabusCurrentTask"
|
||||||
|
@add-output="handleAddOutput"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<!-- 执行结果 -->
|
<!-- 执行结果 -->
|
||||||
@@ -103,6 +108,7 @@ defineExpose({
|
|||||||
ref="taskResultRef"
|
ref="taskResultRef"
|
||||||
@refresh-line="taskResultJsplumb.repaintEverything"
|
@refresh-line="taskResultJsplumb.repaintEverything"
|
||||||
@set-current-task="handleTaskResultCurrentTask"
|
@set-current-task="handleTaskResultCurrentTask"
|
||||||
|
:additional-outputs="additionalOutputs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,10 +117,10 @@ defineExpose({
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.task-template {
|
.task-template {
|
||||||
& > div {
|
& > div {
|
||||||
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.8);
|
box-shadow: var(--color-card-shadow-three);
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
border: 1px solid #414752;
|
border: 1px solid var(--color-card-border-three);
|
||||||
background: var(--color-bg-quinary);
|
background: var(--color-bg-three);
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ export class Jsplumb {
|
|||||||
const stops = _config.stops ?? this.getStops(config.type)
|
const stops = _config.stops ?? this.getStops(config.type)
|
||||||
// 如果config.transparent为true,则将stops都加一些透明度
|
// 如果config.transparent为true,则将stops都加一些透明度
|
||||||
if (config.transparent) {
|
if (config.transparent) {
|
||||||
stops[0][1] = stops[0][1] + '30'
|
stops[0][1] = stops[0][1] + '80'
|
||||||
stops[1][1] = stops[1][1] + '30'
|
stops[1][1] = stops[1][1] + '80'
|
||||||
}
|
}
|
||||||
if (targetElement && sourceElement) {
|
if (targetElement && sourceElement) {
|
||||||
this.instance.connect({
|
this.instance.connect({
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Task from './Task.vue'
|
import Task from './Task.vue'
|
||||||
import TaskTemplate from './TaskTemplate/index.vue'
|
import TaskTemplate from './TaskTemplate/index.vue'
|
||||||
import { nextTick } from 'vue'
|
import { nextTick, ref } from 'vue'
|
||||||
|
|
||||||
const taskTemplateRef = ref<{ changeTask: () => void, clear: () => void }>()
|
const taskTemplateRef = ref<{ changeTask: () => void; clear: () => void }>()
|
||||||
|
|
||||||
function handleSearch() {
|
function handleSearch() {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ export interface AgentMapDuty {
|
|||||||
name: string
|
name: string
|
||||||
key: string
|
key: string
|
||||||
color: string
|
color: string
|
||||||
|
border: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 职责映射
|
// 职责映射
|
||||||
@@ -122,21 +123,25 @@ export const agentMapDuty: Record<string, AgentMapDuty> = {
|
|||||||
name: '提议',
|
name: '提议',
|
||||||
key: 'propose',
|
key: 'propose',
|
||||||
color: '#06A3FF',
|
color: '#06A3FF',
|
||||||
|
border:'#0f95e4'
|
||||||
},
|
},
|
||||||
Critique: {
|
Critique: {
|
||||||
name: '评审',
|
name: '评审',
|
||||||
key: 'review',
|
key: 'review',
|
||||||
color: '#FFFC08',
|
color: '#2cd235',
|
||||||
|
border:'#19ab21'
|
||||||
},
|
},
|
||||||
Improve: {
|
Improve: {
|
||||||
name: '改进',
|
name: '改进',
|
||||||
key: 'improve',
|
key: 'improve',
|
||||||
color: '#BF65FF',
|
color: '#ff8a01',
|
||||||
|
border:'#dc7700'
|
||||||
},
|
},
|
||||||
Finalize: {
|
Finalize: {
|
||||||
name: '总结',
|
name: '总结',
|
||||||
key: 'summary',
|
key: 'summary',
|
||||||
color: '#FFA236',
|
color: '#bf65ff',
|
||||||
|
border:'#a13de8'
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,112 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Header from './components/Header.vue'
|
import Header from './components/Header.vue'
|
||||||
import Main from './components/Main/index.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')
|
||||||
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
|
||||||
|
if (savedTheme === 'dark' || (!savedTheme && prefersDark)) {
|
||||||
|
enableDarkMode()
|
||||||
|
} else {
|
||||||
|
enableLightMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启用深色模式
|
||||||
|
const enableDarkMode = () => {
|
||||||
|
document.documentElement.classList.add('dark')
|
||||||
|
isDarkMode.value = true
|
||||||
|
localStorage.setItem('theme', 'dark')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启用浅色模式
|
||||||
|
const enableLightMode = () => {
|
||||||
|
document.documentElement.classList.remove('dark')
|
||||||
|
isDarkMode.value = false
|
||||||
|
localStorage.setItem('theme', 'light')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换主题
|
||||||
|
const toggleTheme = () => {
|
||||||
|
if (isDarkMode.value) {
|
||||||
|
enableLightMode()
|
||||||
|
} else {
|
||||||
|
enableDarkMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时初始化主题
|
||||||
|
onMounted(() => {
|
||||||
|
initTheme()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-full">
|
<div class="h-full relative">
|
||||||
|
<!-- 主题切换按钮 - 放置在页面右上角 -->
|
||||||
|
<div class="absolute top-4 right-4 z-50">
|
||||||
|
<button
|
||||||
|
@click="toggleTheme"
|
||||||
|
class="w-10 h-10 rounded-full transition-all duration-300 flex items-center justify-center"
|
||||||
|
:class="{
|
||||||
|
'bg-[#ebebeb] hover:bg-[var(--color-bg-hover)]': !isDarkMode,
|
||||||
|
'bg-[#0e131c] hover:bg-[var(--color-bg-hover)]': isDarkMode
|
||||||
|
}"
|
||||||
|
:title="isDarkMode ? '切换到浅色模式' : '切换到深色模式'"
|
||||||
|
>
|
||||||
|
<!-- 浅色模式图标 -->
|
||||||
|
<svg-icon
|
||||||
|
v-if="!isDarkMode"
|
||||||
|
icon-class="sunny"
|
||||||
|
size="20px"
|
||||||
|
:color="isDarkMode ? 'var(--color-text-title)' : 'var(--color-text-title)'"
|
||||||
|
class="transition-opacity duration-300"
|
||||||
|
/>
|
||||||
|
<!-- 深色模式图标 -->
|
||||||
|
<svg-icon
|
||||||
|
v-else
|
||||||
|
icon-class="moon"
|
||||||
|
size="20px"
|
||||||
|
:color="isDarkMode ? 'var(--color-text-title)' : 'var(--color-text-title)'"
|
||||||
|
class="transition-opacity duration-300"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Header />
|
<Header />
|
||||||
<Main />
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -7,14 +7,38 @@ import './styles/tailwindcss.css'
|
|||||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||||
import 'virtual:svg-icons-register'
|
import 'virtual:svg-icons-register'
|
||||||
import { initService } from '@/utils/request.ts'
|
import { initService } from '@/utils/request.ts'
|
||||||
|
import websocket from '@/utils/websocket'
|
||||||
import { setupStore, useConfigStore } from '@/stores'
|
import { setupStore, useConfigStore } from '@/stores'
|
||||||
|
import { setupDirective } from '@/ directive'
|
||||||
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||||
async function init() {
|
async function init() {
|
||||||
const app = createApp(App)
|
|
||||||
setupStore(app)
|
|
||||||
initService()
|
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
await configStore.initConfig()
|
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
|
document.title = configStore.config.centerTitle
|
||||||
app.use(router)
|
app.use(router)
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|||||||
@@ -10,3 +10,4 @@ export function setupStore(app: App<Element>) {
|
|||||||
|
|
||||||
export * from './modules/agents.ts'
|
export * from './modules/agents.ts'
|
||||||
export * from './modules/config.ts'
|
export * from './modules/config.ts'
|
||||||
|
export * from './modules/selection.ts'
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
import { ref } from 'vue'
|
import { ref, watch, computed } from 'vue'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { getAgentMapIcon } from '@/layout/components/config'
|
||||||
import { store } from '../index'
|
import { store } from '../index'
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import type { IExecuteRawResponse } from '@/api'
|
import type { IExecuteRawResponse } from '@/api'
|
||||||
import { useConfigStore } from '@/stores/modules/config.ts'
|
import { useConfigStore } from '@/stores/modules/config.ts'
|
||||||
|
|
||||||
export interface Agent {
|
export interface Agent {
|
||||||
Name: string
|
Name: string
|
||||||
Profile: string
|
Profile: string
|
||||||
Icon: string
|
Icon: string
|
||||||
Classification: string
|
Classification: string
|
||||||
|
apiUrl?: string
|
||||||
|
apiKey?: string
|
||||||
|
apiModel?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type HslColorVector = [number, number, number]
|
type HslColorVector = [number, number, number]
|
||||||
@@ -43,10 +45,51 @@ export interface IRawStepTask {
|
|||||||
InputObject_List?: string[]
|
InputObject_List?: string[]
|
||||||
OutputObject?: string
|
OutputObject?: string
|
||||||
AgentSelection?: string[]
|
AgentSelection?: string[]
|
||||||
Collaboration_Brief_FrontEnd: IRichText
|
Collaboration_Brief_frontEnd: IRichText
|
||||||
TaskProcess: TaskProcess[]
|
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 {
|
export interface IRawPlanResponse {
|
||||||
'Initial Input Object'?: string[] | string
|
'Initial Input Object'?: string[] | string
|
||||||
'General Goal'?: string
|
'General Goal'?: string
|
||||||
@@ -64,19 +107,199 @@ function clearStorageByVersion() {
|
|||||||
|
|
||||||
export const useAgentsStore = defineStore('agents', () => {
|
export const useAgentsStore = defineStore('agents', () => {
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
|
|
||||||
const agents = useStorage<Agent[]>(`${storageKey}-repository`, [])
|
const agents = useStorage<Agent[]>(`${storageKey}-repository`, [])
|
||||||
function setAgents(agent: Agent[]) {
|
function setAgents(agent: Agent[]) {
|
||||||
agents.value = 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`, '')
|
const searchValue = useStorage<string>(`${storageKey}-search-value`, '')
|
||||||
function setSearchValue(value: string) {
|
function setSearchValue(value: string) {
|
||||||
searchValue.value = value
|
searchValue.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
const storageVersionIdentifier = useStorage<string>(`${storageKey}-storage-version-identifier`, '')
|
const storageVersionIdentifier = useStorage<string>(
|
||||||
|
`${storageKey}-storage-version-identifier`,
|
||||||
|
'',
|
||||||
|
)
|
||||||
// 监听 configStore.config.agentRepository.storageVersionIdentifier 改变
|
// 监听 configStore.config.agentRepository.storageVersionIdentifier 改变
|
||||||
watch(
|
watch(
|
||||||
() => configStore.config.agentRepository.storageVersionIdentifier,
|
() => configStore.config.agentRepository.storageVersionIdentifier,
|
||||||
@@ -89,13 +312,132 @@ export const useAgentsStore = defineStore('agents', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true,
|
immediate: true,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// 当前的展示的任务流程
|
// 当前的展示的任务流程
|
||||||
const currentTask = ref<IRawStepTask>()
|
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) {
|
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
|
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 })
|
const agentRawPlan = ref<{ data?: IRawPlanResponse; loading?: boolean }>({ loading: false })
|
||||||
@@ -104,7 +446,8 @@ export const useAgentsStore = defineStore('agents', () => {
|
|||||||
if (plan.data) {
|
if (plan.data) {
|
||||||
plan.data['Collaboration Process'] = plan.data['Collaboration Process']?.map((item) => ({
|
plan.data['Collaboration Process'] = plan.data['Collaboration Process']?.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
Id: uuidv4(),
|
// ✅ 只在任务没有 Id 时才生成新的 ID,保持已存在任务的 Id 不变
|
||||||
|
Id: item.Id || uuidv4(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
agentRawPlan.value = {
|
agentRawPlan.value = {
|
||||||
@@ -125,6 +468,69 @@ export const useAgentsStore = defineStore('agents', () => {
|
|||||||
}
|
}
|
||||||
currentTask.value = undefined
|
currentTask.value = undefined
|
||||||
executePlan.value = []
|
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)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除额外产物
|
||||||
|
function removeAdditionalOutput(outputObject: string) {
|
||||||
|
const index = additionalOutputs.value.indexOf(outputObject)
|
||||||
|
if (index > -1) {
|
||||||
|
additionalOutputs.value.splice(index, 1)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除额外产物
|
||||||
|
function clearAdditionalOutputs() {
|
||||||
|
additionalOutputs.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记是否用户已停止智能体分配过程
|
||||||
|
const hasStoppedFilling = ref(false)
|
||||||
|
|
||||||
|
// 设置停止状态
|
||||||
|
function setHasStoppedFilling(value: boolean) {
|
||||||
|
hasStoppedFilling.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -134,11 +540,64 @@ export const useAgentsStore = defineStore('agents', () => {
|
|||||||
setSearchValue,
|
setSearchValue,
|
||||||
currentTask,
|
currentTask,
|
||||||
setCurrentTask,
|
setCurrentTask,
|
||||||
|
setCurrentTaskProcess, // 🆕 设置当前任务的 TaskProcess
|
||||||
|
updateCurrentAgentSelection, // 🆕 强制更新 AgentSelection(用于 AgentAllocation 切换组合)
|
||||||
|
syncCurrentTaskToMainProcess, // 🆕 同步 currentTask 到主流程
|
||||||
agentRawPlan,
|
agentRawPlan,
|
||||||
setAgentRawPlan,
|
setAgentRawPlan,
|
||||||
executePlan,
|
executePlan,
|
||||||
setExecutePlan,
|
setExecutePlan,
|
||||||
resetAgent,
|
resetAgent,
|
||||||
|
additionalOutputs,
|
||||||
|
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,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
import { readConfig } from '@/utils/readJson.ts'
|
import { readConfig } from '@/utils/readJson.ts'
|
||||||
import { store } from '@/stores'
|
import { store } from '@/stores'
|
||||||
|
|
||||||
@@ -10,15 +11,25 @@ export interface Config {
|
|||||||
agentRepository: {
|
agentRepository: {
|
||||||
storageVersionIdentifier: string
|
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', () => {
|
export const useConfigStore = defineStore('config', () => {
|
||||||
const config = ref<Config>({} as Config)
|
const config = ref<Config>({} as Config)
|
||||||
|
|
||||||
// 异步调用readConfig
|
// 异步调用readConfig
|
||||||
async function initConfig() {
|
async function initConfig() {
|
||||||
config.value = await readConfig<Config>('config.json')
|
const data = await readConfig<Config>('config.json')
|
||||||
|
config.value = {
|
||||||
|
...defaultConfig,
|
||||||
|
...data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
463
frontend/src/stores/modules/selection.ts
Normal file
@@ -0,0 +1,463 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
@@ -1,16 +1,222 @@
|
|||||||
|
// 浅色模式
|
||||||
:root {
|
:root {
|
||||||
--color-bg: #fff;
|
--color-bg: #ffffff;
|
||||||
--color-bg-secondary: #fafafa;
|
--color-bg-secondary: #ffffff;
|
||||||
--color-bg-tertiary: #f5f5f5;
|
--color-bg-tertiary: #f0f0f0; //正确
|
||||||
--color-text: #000;
|
--color-bg-quaternary: rgba(0, 0, 0, 11%);
|
||||||
|
--color-bg-quinary: #f7f7f7;
|
||||||
|
--color-text: #36404F;
|
||||||
|
// 标题文字颜色
|
||||||
|
--color-text-title-header: #262626;
|
||||||
|
--color-text-secondary: #4f4f4f;
|
||||||
|
--color-border-default: #e0e0e0;
|
||||||
|
--color-accent: #222222;
|
||||||
|
--color-accent-secondary: #315ab4;
|
||||||
|
// 鼠标悬浮背景色
|
||||||
|
--color-bg-hover: #f0f0f0;
|
||||||
|
// 鼠标悬浮字体颜色
|
||||||
|
--color-text-hover: #222222;
|
||||||
|
// 分割线颜色
|
||||||
|
--color-border: #D8D8d8;
|
||||||
|
// 内容区鼠标悬浮颜色
|
||||||
|
--color-bg-content-hover: #f0f0f0;
|
||||||
|
//列表背景颜色
|
||||||
|
--color-bg-list: #E1E1E1;
|
||||||
|
// 详情卡文字内容
|
||||||
|
--color-text-detail: #4f4f4f;
|
||||||
|
// 详情卡背景颜色
|
||||||
|
--color-bg-detail: #f7f7f7;
|
||||||
|
// 任务栏背景颜色
|
||||||
|
--color-bg-taskbar: #ffffff;
|
||||||
|
// 详情列表背景颜色
|
||||||
|
--color-bg-detail-list: rgba(230, 230, 230, 0.6) ;//#E6E6E6 60%
|
||||||
|
// 详情列表运行后背景颜色
|
||||||
|
--color-bg-detail-list-run: #e6e6e6; //运行后背景色#e6e6e6 100%
|
||||||
|
// 旋转图标背景颜色
|
||||||
|
--color-bg-icon-rotate: #a7a7a7;
|
||||||
|
// 智能体列表背景颜色
|
||||||
|
--color-agent-list-bg: #ededed; //背景色#fbfcff
|
||||||
|
// 智能体边框颜色
|
||||||
|
--color-agent-list-border: #E0E0E070; //边框#FFFFFF60 60%
|
||||||
|
//智能体鼠标悬浮颜色
|
||||||
|
--color-agent-list-hover-bg: #f7f7f7; //hover背景色#FFFFFF 100%
|
||||||
|
// 智能体鼠标悬浮边框颜色
|
||||||
|
--color-agent-list-hover-border: #e0e0e0; //hover边框#E0E0E0 100%
|
||||||
|
//智能体鼠标悬浮阴影
|
||||||
|
--color-agent-list-hover-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.2); //hover阴影 #FFFFFF 100%
|
||||||
|
//任务框下拉阴影
|
||||||
|
--color-task-shadow: 0px 10px 10px 5px rgba(0, 0, 0, 0.2);
|
||||||
|
// 卡片边框颜色
|
||||||
|
--color-card-border: #ececec;
|
||||||
|
// 卡片鼠标悬浮边框颜色
|
||||||
|
--color-card-border-hover: #ececec;
|
||||||
|
// 任务大纲卡片颜色
|
||||||
|
--color-card-bg-task: #ededed;
|
||||||
|
// 任务大纲卡片悬浮颜色
|
||||||
|
--color-card-bg-task-hover: #f7f7f7;
|
||||||
|
// 任务大纲卡片悬浮阴影效果
|
||||||
|
--color-card-shadow-hover: 0px 2px 4px 0px rgba(0, 0, 0, 0.2);
|
||||||
|
// 卡片阴影效果
|
||||||
|
--color-card-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.17);
|
||||||
|
// 任务大纲边框颜色
|
||||||
|
--color-card-border-task: #E0E0E0; //边框颜色#FFFFFF60
|
||||||
|
// 执行结果卡片颜色
|
||||||
|
--color-card-bg-result: #ececec;
|
||||||
|
// 执行结果边框颜色
|
||||||
|
--color-card-border-result: #ececec;
|
||||||
|
// 执行结果卡片悬浮颜色
|
||||||
|
--color-card-bg-result-hover: #ededed;
|
||||||
|
// 头部背景颜色
|
||||||
|
--color-header-bg: #ffffff;
|
||||||
|
// 头部字体颜色
|
||||||
|
--color-text-title: #2a3342;
|
||||||
|
//结果卡背景颜色
|
||||||
|
--color-bg-result: #eaedee;
|
||||||
|
// 任务字体背景颜色
|
||||||
|
--color-text-task: #36404F;
|
||||||
|
// 指示灯背景颜色
|
||||||
|
--color-bg-indicator: #f7f7f7;
|
||||||
|
// 三个卡片背景颜色
|
||||||
|
--color-bg-three: #fbfcff;
|
||||||
|
// 三个卡片边框颜色
|
||||||
|
--color-card-border-three: #FFFFFF60;
|
||||||
|
// 流程卡片背景颜色
|
||||||
|
--color-bg-flow: #f0f0f0;
|
||||||
|
// 三个卡片阴影
|
||||||
|
--color-card-shadow-three: 0px 0px 5px 0px rgba(0, 0, 0, 0.17);
|
||||||
|
// 头部阴影
|
||||||
|
--color-header-shadow: 0px 0px 5px 0px rgba(161, 161, 161, 0.5);
|
||||||
|
// 执行结果详情背景颜色
|
||||||
|
--color-bg-result-detail: #f7f7f7;
|
||||||
|
// 智能体库字体颜色
|
||||||
|
--color-text-agent-list: #636364;
|
||||||
|
// 智能体库字体悬浮颜色
|
||||||
|
--color-text-agent-list-hover: #222222;
|
||||||
|
// 任务栏字体颜色
|
||||||
|
--color-text-taskbar: #222222;
|
||||||
|
// 智能体卡片被选中后背景颜色
|
||||||
|
--color-agent-list-selected-bg: #f7f7f7; //选中后背景色#171b22 100%
|
||||||
|
// 结果卡片运行前颜色
|
||||||
|
--color-text-result-detail: rgba(0,0,0,0.6);
|
||||||
|
// 结果卡片运行后颜色
|
||||||
|
--color-text-result-detail-run: #000;
|
||||||
|
// 分割线
|
||||||
|
--color-border-separate: #D6D6D6;
|
||||||
|
// url颜色
|
||||||
|
--color-text-url: #FF0000;
|
||||||
|
// model颜色
|
||||||
|
--color-text-model: #0580ff;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 深色模式
|
||||||
html.dark {
|
html.dark {
|
||||||
--color-bg: #131A27;
|
--color-bg: #131A27;
|
||||||
--color-bg-secondary: #050505;
|
--color-bg-secondary: rgba(5, 5, 5, 0.4);
|
||||||
--color-bg-tertiary: #20222A;
|
--color-bg-tertiary: #20222A;
|
||||||
--color-bg-quaternary: #24252A;
|
--color-bg-quaternary: #24252A;
|
||||||
--color-bg-quinary: #29303c;
|
--color-bg-quinary: #29303c;
|
||||||
--color-text: #fff;
|
--color-text: #fff;
|
||||||
|
// 标题文字颜色
|
||||||
|
--color-text-title-header: #ffffff;
|
||||||
--color-text-secondary: #C9C9C9;
|
--color-text-secondary: #C9C9C9;
|
||||||
|
--color-border-default: #494B51;
|
||||||
|
--color-accent: #00F3FF;
|
||||||
|
--color-accent-secondary: #315ab4;
|
||||||
|
// 鼠标悬浮背景色
|
||||||
|
--color-bg-hover: #1c1e25;
|
||||||
|
// 鼠标悬浮字体颜色
|
||||||
|
--color-text-hover: #00f3ff;
|
||||||
|
// 分割线颜色
|
||||||
|
--color-border: #494B51;
|
||||||
|
// 内容区鼠标悬浮颜色
|
||||||
|
--color-bg-content-hover: #171b22;
|
||||||
|
// 列表背景颜色
|
||||||
|
--color-bg-list: #171B22;
|
||||||
|
// 详情卡文字内容
|
||||||
|
--color-text-detail: #ffffff;
|
||||||
|
// 详情卡背景颜色
|
||||||
|
--color-bg-detail: #20222a;
|
||||||
|
// 任务栏背景颜色
|
||||||
|
--color-bg-taskbar: #20222a;
|
||||||
|
// 详情列表背景颜色
|
||||||
|
--color-bg-detail-list: #050505; //运行后背景色#18191f 60%
|
||||||
|
//详情列表运行后背景颜色
|
||||||
|
--color-bg-detail-list-run: #050505;
|
||||||
|
// 旋转图标背景颜色
|
||||||
|
--color-bg-icon-rotate: #151619;
|
||||||
|
// 智能体列表背景颜色
|
||||||
|
--color-agent-list-bg: #1d222b; //背景色#181b20 100%
|
||||||
|
// 智能体边框颜色
|
||||||
|
--color-agent-list-border: rgba(8,8,8,0.6);
|
||||||
|
//智能体鼠标悬浮颜色
|
||||||
|
--color-agent-list-hover-bg: rgba(23,27,34,1); //hover背景色#171b22 100%
|
||||||
|
// 智能体鼠标悬浮边框颜色
|
||||||
|
--color-agent-list-hover-border: rgba(8,8,8,1); //hover边框#080808 100%
|
||||||
|
//智能体鼠标悬浮阴影
|
||||||
|
--color-agent-list-hover-shadow: 0 2 4 #00000050; //hover阴影 0 2 4 #00000050
|
||||||
|
//任务框下拉阴影
|
||||||
|
--color-task-shadow: 0px 10px 10px 5px rgba(0, 0, 0, 0.2);
|
||||||
|
// 卡片边框颜色
|
||||||
|
--color-card-border: #1a1a1a;
|
||||||
|
// 卡片鼠标悬浮边框颜色
|
||||||
|
--color-card-border-hover: #151515;
|
||||||
|
// 任务大纲卡片颜色
|
||||||
|
--color-card-bg-task: #20222a;
|
||||||
|
// 任务大纲卡片悬浮颜色
|
||||||
|
--color-card-bg-task-hover: #171b22;
|
||||||
|
// 任务大纲边框颜色
|
||||||
|
--color-card-border-task: #151515;
|
||||||
|
// 执行结果卡片颜色
|
||||||
|
--color-card-bg-result: #1a1a1a;
|
||||||
|
// 执行结果边框颜色
|
||||||
|
--color-card-border-result: #151515;
|
||||||
|
// 执行结果卡片悬浮颜色
|
||||||
|
--color-card-bg-result-hover: #171b22;
|
||||||
|
// 头部背景颜色
|
||||||
|
--color-header-bg: #050505;
|
||||||
|
// 标题头部字体颜色
|
||||||
|
--color-text-title: #2c72e7;
|
||||||
|
// 卡片阴影效果
|
||||||
|
--color-card-shadow: 0px 0px 5px 5px rgba(0, 0, 0, 0.17);
|
||||||
|
// 任务大纲卡片悬浮阴影效果
|
||||||
|
--color-card-shadow-hover: 0px 2px 4px 0px rgba(0, 0, 0, 0.2);
|
||||||
|
// 结果卡背景颜色
|
||||||
|
--color-bg-result: #0d1119;
|
||||||
|
// 任务字体背景颜色
|
||||||
|
--color-text-task: #d7d7d7;
|
||||||
|
// 指示灯背景颜色
|
||||||
|
--color-bg-indicator: #17181a;
|
||||||
|
// 三个卡片背景颜色
|
||||||
|
--color-bg-three: #29303c;
|
||||||
|
// 三个卡片边框颜色
|
||||||
|
--color-card-border-three: #393d42;
|
||||||
|
// 流程卡片背景颜色
|
||||||
|
--color-bg-flow: #1C222a;
|
||||||
|
// 三个卡片阴影
|
||||||
|
--color-card-shadow-three: 0px 0px 6px 0px rgba(0, 0, 0, 0.8);
|
||||||
|
// 头部阴影
|
||||||
|
--color-header-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.5);
|
||||||
|
// 执行结果详情背景颜色
|
||||||
|
--color-bg-result-detail: #24252a;
|
||||||
|
// 智能体库字体颜色
|
||||||
|
--color-text-agent-list: #969696;
|
||||||
|
// 智能体库字体悬浮颜色
|
||||||
|
--color-text-agent-list-hover: #b8b8b8;
|
||||||
|
// 任务栏字体颜色
|
||||||
|
--color-text-taskbar: #ffffff;
|
||||||
|
// 智能体卡片被选中后背景颜色
|
||||||
|
--color-agent-list-selected-bg: #20222a; //选中后背景色#171b22 100%
|
||||||
|
// 结果卡片运行前颜色
|
||||||
|
--color-text-result-detail: #6c6e72;
|
||||||
|
// 结果卡片运行后颜色
|
||||||
|
--color-text-result-detail-run: #fff;
|
||||||
|
// 分割线
|
||||||
|
--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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,100 @@ $bg-tertiary: var(--color-bg-tertiary);
|
|||||||
$bg-quaternary: var(--color-bg-quaternary);
|
$bg-quaternary: var(--color-bg-quaternary);
|
||||||
$text: var(--color-text);
|
$text: var(--color-text);
|
||||||
$text-secondary: var(--color-text-secondary);
|
$text-secondary: var(--color-text-secondary);
|
||||||
|
// 鼠标悬浮
|
||||||
|
$bg-hover: var(--color-bg-hover);
|
||||||
|
// 字体悬浮
|
||||||
|
$text-hover: var(--color-text-hover);
|
||||||
|
// 分割线
|
||||||
|
$border: var(--color-border);
|
||||||
|
// 内容区鼠标悬浮
|
||||||
|
$bg-content-hover: var(--color-bg-content-hover);
|
||||||
|
// 列表背景颜色
|
||||||
|
$bg-list: var(--color-bg-list);
|
||||||
|
// 详情卡文字内容
|
||||||
|
$text-detail: var(--color-text-detail);
|
||||||
|
// 详情卡背景颜色
|
||||||
|
$bg-detail: var(--color-bg-detail);
|
||||||
|
// 任务栏背景颜色
|
||||||
|
$bg-taskbar: var(--color-bg-taskbar);
|
||||||
|
// 详情列表背景颜色
|
||||||
|
$bg-detail-list: var(--color-bg-detail-list);
|
||||||
|
// 旋转图标背景颜色
|
||||||
|
$bg-icon-rotate: var(--color-bg-icon-rotate);
|
||||||
|
// 智能体列表背景颜色
|
||||||
|
$agent-list-bg: var(--color-agent-list-bg);
|
||||||
|
// 智能体边框颜色
|
||||||
|
$agent-list-border: var(--color-agent-list-border);
|
||||||
|
//智能体鼠标悬浮颜色
|
||||||
|
$agent-list-hover-bg: var(--color-agent-list-hover-bg);
|
||||||
|
// 智能体鼠标悬浮边框颜色
|
||||||
|
$agent-list-hover-border: var(--color-agent-list-hover-border);
|
||||||
|
//智能体鼠标悬浮阴影
|
||||||
|
$agent-list-hover-shadow: var(--color-agent-list-hover-shadow);
|
||||||
|
//任务框下拉阴影
|
||||||
|
$task-shadow: var(--color-task-shadow);
|
||||||
|
// 卡片边框颜色
|
||||||
|
$card-border: var(--color-card-border);
|
||||||
|
// 卡片鼠标悬浮边框颜色
|
||||||
|
$card-border-hover: var(--color-card-border-hover);
|
||||||
|
// 标题文字颜色
|
||||||
|
$text-title: var(--color-text-title);
|
||||||
|
// 任务大纲卡片颜色
|
||||||
|
$card-bg-task: var(--color-card-bg-task);
|
||||||
|
// 任务大纲边框颜色
|
||||||
|
$card-border-task: var(--color-card-border-task);
|
||||||
|
// 执行结果卡片颜色
|
||||||
|
$card-bg-result: var(--color-card-bg-result);
|
||||||
|
// 执行结果边框颜色
|
||||||
|
$card-border-result: var(--color-card-border-result);
|
||||||
|
// 头部背景颜色
|
||||||
|
$header-bg: var(--color-header-bg);
|
||||||
|
// 头部字体颜色
|
||||||
|
$header-text-title: var(--color-text-title);
|
||||||
|
// 卡片阴影效果
|
||||||
|
$card-shadow: var(--color-card-shadow);
|
||||||
|
// 任务大纲卡片悬浮颜色
|
||||||
|
$card-bg-task-hover: var(--color-card-bg-task-hover);
|
||||||
|
// 任务大纲卡片悬浮阴影效果
|
||||||
|
$card-shadow-hover: var(--color-card-shadow-hover);
|
||||||
|
// 结果卡背景颜色
|
||||||
|
$bg-result: var(--color-bg-result);
|
||||||
|
// 执行结果卡片悬浮颜色
|
||||||
|
$card-bg-result-hover: var(--color-card-bg-result-hover);
|
||||||
|
// 详情列表运行后背景颜色
|
||||||
|
$bg-detail-list-run: var(--color-bg-detail-list-run);
|
||||||
|
// 任务字体背景颜色
|
||||||
|
$text-task: var(--color-text-task);
|
||||||
|
// 指示灯背景颜色
|
||||||
|
$bg-indicator: var(--color-bg-indicator);
|
||||||
|
// 三个卡片背景颜色
|
||||||
|
$bg-three: var(--color-bg-three);
|
||||||
|
// 流程卡片背景颜色
|
||||||
|
$bg-flow: var(--color-bg-flow);
|
||||||
|
// 三个卡片边框颜色
|
||||||
|
$card-border-three: var(--color-card-border-three);
|
||||||
|
// 三个卡片阴影
|
||||||
|
$card-shadow-three: var(--color-card-shadow-three);
|
||||||
|
// 头部阴影
|
||||||
|
$header-shadow: var(--color-header-shadow);
|
||||||
|
// 执行结果详情背景颜色
|
||||||
|
$bg-result-detail: var(--color-bg-result-detail);
|
||||||
|
// 智能体库字体颜色
|
||||||
|
$text-agent-list: var(--color-text-agent-list);
|
||||||
|
// 智能体库字体悬浮颜色
|
||||||
|
$text-agent-list-hover: var(--color-text-agent-list-hover);
|
||||||
|
// 任务栏字体颜色
|
||||||
|
$text-taskbar: var(--color-text-taskbar);
|
||||||
|
// 智能体卡片被选中后背景颜色
|
||||||
|
$agent-list-selected-bg: var(--color-agent-list-selected-bg); //选中后背景色#171b22 100%
|
||||||
|
// 结果卡片运行前颜色
|
||||||
|
$text-result-detail: var(--color-text-result-detail);
|
||||||
|
// url和model颜色
|
||||||
|
$text-url: var(--color-text-url);
|
||||||
|
$text-model: var(--color-text-model);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
:export {
|
:export {
|
||||||
bg: $bg;
|
bg: $bg;
|
||||||
@@ -12,4 +106,51 @@ $text-secondary: var(--color-text-secondary);
|
|||||||
bg-quaternary: $bg-quaternary;
|
bg-quaternary: $bg-quaternary;
|
||||||
text: $text;
|
text: $text;
|
||||||
text-secondary: $text-secondary;
|
text-secondary: $text-secondary;
|
||||||
|
bg-hover:$bg-hover;
|
||||||
|
text-hover:$text-hover;
|
||||||
|
border: $border;
|
||||||
|
bg-content-hover:$bg-content-hover;
|
||||||
|
bg-list: $bg-list;
|
||||||
|
text-detail: $text-detail;
|
||||||
|
bg-detail: $bg-detail;
|
||||||
|
bg-taskbar: $bg-taskbar;
|
||||||
|
bg-detail-list: $bg-detail-list;
|
||||||
|
bg-icon-rotate:$bg-icon-rotate;
|
||||||
|
agent-list-bg: $agent-list-bg;
|
||||||
|
agent-list-border: $agent-list-border;
|
||||||
|
agent-list-hover-bg: $agent-list-hover-bg;
|
||||||
|
agent-list-hover-border: $agent-list-hover-border;
|
||||||
|
agent-list-hover-shadow: $agent-list-hover-shadow;
|
||||||
|
task-shadow: $task-shadow;
|
||||||
|
card-border: $card-border;
|
||||||
|
card-border-hover: $card-border-hover;
|
||||||
|
text-title: $text-title;
|
||||||
|
card-border: $card-border;
|
||||||
|
card-bg-task: $card-bg-task;
|
||||||
|
card-border-task: $card-border-task;
|
||||||
|
card-bg-result: $card-bg-result;
|
||||||
|
card-border-result: $card-border-result;
|
||||||
|
header-bg: $header-bg;
|
||||||
|
header-text-title: $header-text-title;
|
||||||
|
card-shadow: $card-shadow;
|
||||||
|
card-bg-task-hover: $card-bg-task-hover;
|
||||||
|
card-shadow-hover: $card-shadow-hover;
|
||||||
|
bg-result: $bg-result;
|
||||||
|
card-bg-result-hover: $card-bg-result-hover;
|
||||||
|
bg-detail-list-run: $bg-detail-list-run;
|
||||||
|
text-task: $text-task;
|
||||||
|
bg-indicator: $bg-indicator;
|
||||||
|
bg-three: $bg-three;
|
||||||
|
bg-flow: $bg-flow;
|
||||||
|
card-border-three: $card-border-three;
|
||||||
|
card-shadow-three: $card-shadow-three;
|
||||||
|
header-shadow: $header-shadow;
|
||||||
|
bg-result-detail: $bg-result-detail;
|
||||||
|
text-agent-list: $text-agent-list;
|
||||||
|
text-agent-list-hover: $text-agent-list-hover;
|
||||||
|
text-taskbar: $text-taskbar;
|
||||||
|
agent-list-selected-bg: $agent-list-selected-bg;
|
||||||
|
text-result-detail: $text-result-detail;
|
||||||
|
text-url: $text-url;
|
||||||
|
text-model: $text-model;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export function changeBriefs(task?: IRawStepTask[]): IRawStepTask[] {
|
|||||||
return task.map((item) => {
|
return task.map((item) => {
|
||||||
const record = {
|
const record = {
|
||||||
...item,
|
...item,
|
||||||
Collaboration_Brief_FrontEnd: changeBrief(item),
|
Collaboration_Brief_frontEnd: changeBrief(item),
|
||||||
}
|
}
|
||||||
return record
|
return record
|
||||||
})
|
})
|
||||||
@@ -30,10 +30,10 @@ function changeBrief(task: IRawStepTask): IRichText {
|
|||||||
// 如果不存在AgentSelection直接返回
|
// 如果不存在AgentSelection直接返回
|
||||||
const agents = task.AgentSelection ?? []
|
const agents = task.AgentSelection ?? []
|
||||||
if (agents.length === 0) {
|
if (agents.length === 0) {
|
||||||
return task.Collaboration_Brief_FrontEnd
|
return task.Collaboration_Brief_frontEnd
|
||||||
}
|
}
|
||||||
const data: IRichText['data'] = {};
|
const data: IRichText['data'] = {}
|
||||||
let indexOffset = 0;
|
let indexOffset = 0
|
||||||
|
|
||||||
// 根据InputObject_List修改
|
// 根据InputObject_List修改
|
||||||
const inputs = task.InputObject_List ?? []
|
const inputs = task.InputObject_List ?? []
|
||||||
@@ -41,63 +41,62 @@ function changeBrief(task: IRawStepTask): IRichText {
|
|||||||
data[(index + indexOffset).toString()] = {
|
data[(index + indexOffset).toString()] = {
|
||||||
text,
|
text,
|
||||||
style: { background: '#ACDBA0' },
|
style: { background: '#ACDBA0' },
|
||||||
};
|
}
|
||||||
return `!<${index + indexOffset}>!`;
|
return `!<${index + indexOffset}>!`
|
||||||
});
|
})
|
||||||
const inputSentence = nameJoin(inputPlaceHolders);
|
const inputSentence = nameJoin(inputPlaceHolders)
|
||||||
indexOffset += inputs.length;
|
indexOffset += inputs.length
|
||||||
|
|
||||||
// 根据AgentSelection修改
|
// 根据AgentSelection修改
|
||||||
const namePlaceholders = agents.map((text, index) => {
|
const namePlaceholders = agents.map((text, index) => {
|
||||||
data[(index + indexOffset).toString()] = {
|
data[(index + indexOffset).toString()] = {
|
||||||
text,
|
text,
|
||||||
style: { background: '#E5E5E5', boxShadow: '1px 1px 4px 1px #0003' },
|
style: { background: '#E5E5E5', boxShadow: '1px 1px 4px 1px #0003' },
|
||||||
};
|
}
|
||||||
return `!<${index + indexOffset}>!`;
|
return `!<${index + indexOffset}>!`
|
||||||
});
|
})
|
||||||
const nameSentence = nameJoin(namePlaceholders);
|
const nameSentence = nameJoin(namePlaceholders)
|
||||||
indexOffset += agents.length;
|
indexOffset += agents.length
|
||||||
|
|
||||||
|
let actionSentence = task.TaskContent ?? ''
|
||||||
let actionSentence = task.TaskContent ?? '';
|
|
||||||
|
|
||||||
// delete the last '.' of actionSentence
|
// delete the last '.' of actionSentence
|
||||||
if (actionSentence[actionSentence.length - 1] === '.') {
|
if (actionSentence[actionSentence.length - 1] === '.') {
|
||||||
actionSentence = actionSentence.slice(0, -1);
|
actionSentence = actionSentence.slice(0, -1)
|
||||||
}
|
}
|
||||||
const actionIndex = indexOffset++;
|
const actionIndex = indexOffset++
|
||||||
|
|
||||||
data[actionIndex.toString()] = {
|
data[actionIndex.toString()] = {
|
||||||
text: actionSentence,
|
text: actionSentence,
|
||||||
style: { background: '#DDD', border: '1.5px solid #ddd' },
|
style: { background: '#DDD', border: '1.5px solid #ddd' },
|
||||||
};
|
}
|
||||||
|
|
||||||
let outputSentence = '';
|
let outputSentence = ''
|
||||||
const output = task.OutputObject ?? '';
|
const output = task.OutputObject ?? ''
|
||||||
if (output) {
|
if (output) {
|
||||||
data[indexOffset.toString()] = {
|
data[indexOffset.toString()] = {
|
||||||
text: output,
|
text: output,
|
||||||
style: { background: '#FFCA8C' },
|
style: { background: '#FFCA8C' },
|
||||||
};
|
}
|
||||||
outputSentence = `得到 !<${indexOffset}>!`;
|
outputSentence = `得到 !<${indexOffset}>!`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Join them togeter
|
// Join them togeter
|
||||||
let content = inputSentence;
|
let content = inputSentence
|
||||||
if (content) {
|
if (content) {
|
||||||
content = `基于${content}, ${nameSentence} 执行任务 !<${actionIndex}>!`;
|
content = `基于${content}, ${nameSentence} 执行任务 !<${actionIndex}>!`
|
||||||
} else {
|
} else {
|
||||||
content = `${nameSentence} 执行任务 !<${actionIndex}>!`;
|
content = `${nameSentence} 执行任务 !<${actionIndex}>!`
|
||||||
}
|
}
|
||||||
if (outputSentence) {
|
if (outputSentence) {
|
||||||
content = `${content}, ${outputSentence}.`;
|
content = `${content}, ${outputSentence}.`
|
||||||
} else {
|
} else {
|
||||||
content = `${content}.`;
|
content = `${content}.`
|
||||||
}
|
}
|
||||||
content = content.trim();
|
content = content.trim()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
template: content,
|
template: content,
|
||||||
data,
|
data,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
|
import type {
|
||||||
|
AxiosError,
|
||||||
|
AxiosInstance,
|
||||||
|
AxiosRequestConfig,
|
||||||
|
AxiosResponse,
|
||||||
|
InternalAxiosRequestConfig,
|
||||||
|
} from 'axios'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import qs from 'qs'
|
import qs from 'qs'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import { ElNotification } from 'element-plus'
|
import { ElNotification } from 'element-plus'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import { useConfigStoreHook } from '@/stores'
|
||||||
|
|
||||||
// 创建 axios 实例
|
// 创建 axios 实例
|
||||||
let service: AxiosInstance
|
let service: AxiosInstance
|
||||||
@@ -14,9 +21,9 @@ export interface AxiosResponseData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function initService() {
|
export function initService() {
|
||||||
|
const configStore = useConfigStoreHook()
|
||||||
service = axios.create({
|
service = axios.create({
|
||||||
baseURL: '/api',
|
baseURL: configStore.config.apiBaseUrl,
|
||||||
timeout: 50000,
|
|
||||||
headers: { 'Content-Type': 'application/json;charset=utf-8' },
|
headers: { 'Content-Type': 'application/json;charset=utf-8' },
|
||||||
paramsSerializer: (params) => {
|
paramsSerializer: (params) => {
|
||||||
return qs.stringify(params)
|
return qs.stringify(params)
|
||||||
|
|||||||
270
frontend/src/utils/websocket.ts
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
/**
|
||||||
|
* 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,6 +14,7 @@ const pathSrc = resolve(__dirname, 'src')
|
|||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
base: '',
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
@@ -38,7 +39,7 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
@@ -46,13 +47,14 @@ export default defineConfig({
|
|||||||
'/api': {
|
'/api': {
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
// 接口地址
|
// 接口地址
|
||||||
target: 'http://localhost:8000',
|
// target: 'http://82.157.183.212:21092',
|
||||||
rewrite: (path: string) =>
|
target: 'http://82.157.183.212:21097',
|
||||||
path.replace(/^\/api/, ''),
|
// target: 'http://localhost:8000',
|
||||||
configure: (proxy, options) => {
|
// rewrite: (path: string) => path.replace(/^\/api/, ''),
|
||||||
console.log('Proxy configured:', options)
|
// configure: (proxy, options) => {
|
||||||
}
|
// console.log('Proxy configured:', options)
|
||||||
|
// },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|||||||