Compare commits
22 Commits
244deceb91
...
web
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f736cd104a | ||
|
|
328f8e7ec6 | ||
|
|
f272ccc390 | ||
|
|
1682a8892d | ||
|
|
6e4d8f0b6d | ||
|
|
aedfd0594c | ||
|
|
b3e6c7a618 | ||
|
|
1749ae4f1e | ||
|
|
418b2e5f8f | ||
|
|
641d70033d | ||
|
|
b287867069 | ||
|
|
5699635d1a | ||
|
|
ac035d1237 | ||
|
|
53add0431e | ||
|
|
786c674d21 | ||
|
|
1c8036adf1 | ||
|
|
45314b7be6 | ||
|
|
c5848410c1 | ||
|
|
571b5101ff | ||
|
|
029df6b5a5 | ||
|
|
edb39d4c1f | ||
|
|
0e87777ae8 |
@@ -112,8 +112,17 @@ def _call_with_custom_config(messages: list[dict], stream: bool, model_config: d
|
|||||||
timeout=180
|
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
|
full_reply_content = response.choices[0].message.content
|
||||||
print(colored(full_reply_content, "blue", "on_white"), end="")
|
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
|
return full_reply_content
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print_colored(f"Custom API error for model {api_model} :{str(e)}","red")
|
print_colored(f"Custom API error for model {api_model} :{str(e)}","red")
|
||||||
@@ -138,15 +147,21 @@ async def _achat_completion_stream_custom(messages:list[dict], temp_async_client
|
|||||||
async for chunk in response:
|
async for chunk in response:
|
||||||
collected_chunks.append(chunk)
|
collected_chunks.append(chunk)
|
||||||
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)
|
collected_messages.append(chunk_message)
|
||||||
if chunk_message.content:
|
# if chunk_message.content:
|
||||||
print(colored(chunk_message.content, "blue", "on_white"), end="")
|
# print(colored(chunk_message.content, "blue", "on_white"), end="")
|
||||||
print()
|
# print()
|
||||||
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(f"Stream API returned empty content for model {api_model}")
|
||||||
|
|
||||||
return full_reply_content
|
return full_reply_content
|
||||||
except httpx.RemoteProtocolError as e:
|
except httpx.RemoteProtocolError as e:
|
||||||
if attempt < max_retries - 1:
|
if attempt < max_retries - 1:
|
||||||
@@ -184,9 +199,18 @@ 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
|
||||||
print(colored(full_reply_content, "blue", "on_white"), end="")
|
if full_reply_content is None:
|
||||||
print()
|
raise Exception("Groq API returned None content")
|
||||||
|
|
||||||
|
# print(colored(full_reply_content, "blue", "on_white"), end="")
|
||||||
|
# print()
|
||||||
return full_reply_content
|
return full_reply_content
|
||||||
|
|
||||||
|
|
||||||
@@ -217,9 +241,18 @@ 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
|
||||||
print(colored(full_reply_content, "blue", "on_white"), end="")
|
if full_reply_content is None:
|
||||||
print()
|
raise Exception("Mistral API returned None content")
|
||||||
|
|
||||||
|
# print(colored(full_reply_content, "blue", "on_white"), end="")
|
||||||
|
# print()
|
||||||
return full_reply_content
|
return full_reply_content
|
||||||
|
|
||||||
|
|
||||||
@@ -240,19 +273,25 @@ 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(
|
||||||
colored(chunk_message.content, "blue", "on_white"),
|
# colored(chunk_message.content, "blue", "on_white"),
|
||||||
end="",
|
# end="",
|
||||||
)
|
# )
|
||||||
print()
|
# print()
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
@@ -275,9 +314,18 @@ 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
|
||||||
print(colored(full_reply_content, "blue", "on_white"), end="")
|
if full_reply_content is None:
|
||||||
print()
|
raise Exception("OpenAI API returned None content")
|
||||||
|
|
||||||
|
# print(colored(full_reply_content, "blue", "on_white"), end="")
|
||||||
|
# print()
|
||||||
return full_reply_content
|
return full_reply_content
|
||||||
|
|
||||||
|
|
||||||
@@ -294,19 +342,25 @@ 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(
|
||||||
colored(chunk_message.content, "blue", "on_white"),
|
# colored(chunk_message.content, "blue", "on_white"),
|
||||||
end="",
|
# end="",
|
||||||
)
|
# )
|
||||||
print()
|
# print()
|
||||||
|
|
||||||
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")
|
||||||
@@ -316,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")
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ def generate_AbilityRequirement(General_Goal, Current_Task):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
print(messages[1]["content"])
|
#print(messages[1]["content"])
|
||||||
return read_LLM_Completion(messages)["AbilityRequirement"]
|
return read_LLM_Completion(messages)["AbilityRequirement"]
|
||||||
|
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ def agentAbilityScoring(Agent_Board, Ability_Requirement_List):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
print(messages[1]["content"])
|
#print(messages[1]["content"])
|
||||||
scoreTable[Ability_Requirement] = read_LLM_Completion(messages)
|
scoreTable[Ability_Requirement] = read_LLM_Completion(messages)
|
||||||
return scoreTable
|
return scoreTable
|
||||||
|
|
||||||
@@ -137,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
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ def generate_AgentSelection(General_Goal, Current_Task, Agent_Board):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
print(messages[1]["content"])
|
#print(messages[1]["content"])
|
||||||
|
|
||||||
agentboard_set = {agent["Name"] for agent in Agent_Board}
|
agentboard_set = {agent["Name"] for agent in Agent_Board}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -87,7 +87,7 @@ def branch_PlanOutline(
|
|||||||
InitialObject_List=str(InitialObject_List),
|
InitialObject_List=str(InitialObject_List),
|
||||||
General_Goal=General_Goal,
|
General_Goal=General_Goal,
|
||||||
)
|
)
|
||||||
print(prompt)
|
#print(prompt)
|
||||||
branch_List = []
|
branch_List = []
|
||||||
for _ in range(branch_Number):
|
for _ in range(branch_Number):
|
||||||
messages = [
|
messages = [
|
||||||
|
|||||||
@@ -57,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"
|
||||||
|
]
|
||||||
}}
|
}}
|
||||||
]
|
]
|
||||||
}}
|
}}
|
||||||
@@ -86,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"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -137,7 +155,7 @@ def branch_TaskProcess(
|
|||||||
General_Goal=General_Goal,
|
General_Goal=General_Goal,
|
||||||
Act_Set=ACT_SET,
|
Act_Set=ACT_SET,
|
||||||
)
|
)
|
||||||
print(prompt)
|
#print(prompt)
|
||||||
branch_List = []
|
branch_List = []
|
||||||
for i in range(branch_Number):
|
for i in range(branch_Number):
|
||||||
messages = [
|
messages = [
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ def generate_TaskProcess(General_Goal, Current_Task_Description):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
print(messages[1]["content"])
|
#print(messages[1]["content"])
|
||||||
|
|
||||||
# write a callback function, if read_LLM_Completion(messages)["Task_Process_Plan"] dont have the right format, call this function again
|
# write a callback function, if read_LLM_Completion(messages)["Task_Process_Plan"] dont have the right format, call this function again
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class BaseAction():
|
|||||||
Action_Description = self.info["Description"],
|
Action_Description = self.info["Description"],
|
||||||
Action_Custom_Note = self.Action_Custom_Note
|
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,model_config=model_config)
|
ActionResult = LLM_Completion(messages,True,False,model_config=model_config)
|
||||||
ActionInfo_with_Result = copy.deepcopy(self.info)
|
ActionInfo_with_Result = copy.deepcopy(self.info)
|
||||||
|
|||||||
620
backend/AgentCoord/RehearsalEngine_V2/ExecutePlan_Optimized.py
Normal file
@@ -0,0 +1,620 @@
|
|||||||
|
"""
|
||||||
|
优化版执行计划 - 支持动态追加步骤
|
||||||
|
在执行过程中可以接收新的步骤并追加到执行队列
|
||||||
|
"""
|
||||||
|
|
||||||
|
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,
|
||||||
|
execution_id: str = None
|
||||||
|
) -> Generator[Dict, None, None]:
|
||||||
|
"""
|
||||||
|
异步执行单个步骤,支持流式返回
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stepDescrip: 步骤描述
|
||||||
|
General_Goal: 总体目标
|
||||||
|
AgentProfile_Dict: 智能体配置字典
|
||||||
|
KeyObjects: 关键对象字典
|
||||||
|
step_index: 步骤索引
|
||||||
|
total_steps: 总步骤数
|
||||||
|
execution_id: 执行ID
|
||||||
|
|
||||||
|
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(execution_id)
|
||||||
|
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_id
|
||||||
|
if execution_id is None:
|
||||||
|
import time
|
||||||
|
execution_id = f"{general_goal}_{int(time.time() * 1000)}"
|
||||||
|
|
||||||
|
execution_state_manager.start_execution(execution_id, general_goal)
|
||||||
|
|
||||||
|
print(colored(f"⏸️ 执行状态管理器已启动,支持暂停/恢复,execution_id={execution_id}", "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(execution_id)
|
||||||
|
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, # 使用动态更新的总步骤数
|
||||||
|
execution_id
|
||||||
|
):
|
||||||
|
if execution_state_manager.is_stopped(execution_id):
|
||||||
|
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(execution_id)
|
||||||
|
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,
|
||||||
|
execution_id
|
||||||
|
):
|
||||||
|
if execution_state_manager.is_stopped(execution_id):
|
||||||
|
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(execution_id):
|
||||||
|
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)
|
||||||
|
# 清理执行状态
|
||||||
|
execution_state_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()
|
||||||
309
backend/AgentCoord/RehearsalEngine_V2/execution_state.py
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
"""
|
||||||
|
全局执行状态管理器
|
||||||
|
用于支持任务的暂停、恢复和停止功能
|
||||||
|
使用轮询检查机制,确保线程安全
|
||||||
|
支持多用户/多执行ID并行管理
|
||||||
|
"""
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import asyncio
|
||||||
|
from typing import Optional, Dict
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class ExecutionStatus(Enum):
|
||||||
|
"""执行状态枚举"""
|
||||||
|
RUNNING = "running" # 正在运行
|
||||||
|
PAUSED = "paused" # 已暂停
|
||||||
|
STOPPED = "stopped" # 已停止
|
||||||
|
IDLE = "idle" # 空闲
|
||||||
|
|
||||||
|
|
||||||
|
class ExecutionStateManager:
|
||||||
|
"""
|
||||||
|
全局执行状态管理器
|
||||||
|
|
||||||
|
功能:
|
||||||
|
- 管理多用户/多执行ID的并行状态(使用字典存储)
|
||||||
|
- 管理任务执行状态(运行/暂停/停止)
|
||||||
|
- 使用轮询检查机制,避免异步事件的线程问题
|
||||||
|
- 提供线程安全的状态查询和修改接口
|
||||||
|
|
||||||
|
设计说明:
|
||||||
|
- 保持单例模式(Manager本身)
|
||||||
|
- 但内部状态按 execution_id 隔离存储
|
||||||
|
- 解决了多用户并发问题
|
||||||
|
"""
|
||||||
|
|
||||||
|
_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
|
||||||
|
|
||||||
|
# 状态存储:execution_id -> 状态字典
|
||||||
|
# 结构:{
|
||||||
|
# 'status': ExecutionStatus,
|
||||||
|
# 'goal': str,
|
||||||
|
# 'should_pause': bool,
|
||||||
|
# 'should_stop': bool
|
||||||
|
# }
|
||||||
|
self._states: Dict[str, Dict] = {}
|
||||||
|
|
||||||
|
# 每个 execution_id 的锁(更细粒度的锁)
|
||||||
|
self._locks: Dict[str, threading.Lock] = {}
|
||||||
|
|
||||||
|
# 全局锁(用于管理 _states 和 _locks 本身的线程安全)
|
||||||
|
self._manager_lock = threading.Lock()
|
||||||
|
|
||||||
|
def _get_lock(self, execution_id: str) -> threading.Lock:
|
||||||
|
"""获取指定 execution_id 的锁,如果不存在则创建"""
|
||||||
|
with self._manager_lock:
|
||||||
|
if execution_id not in self._locks:
|
||||||
|
self._locks[execution_id] = threading.Lock()
|
||||||
|
return self._locks[execution_id]
|
||||||
|
|
||||||
|
def _ensure_state(self, execution_id: str) -> Dict:
|
||||||
|
"""确保指定 execution_id 的状态存在"""
|
||||||
|
with self._manager_lock:
|
||||||
|
if execution_id not in self._states:
|
||||||
|
self._states[execution_id] = {
|
||||||
|
'status': ExecutionStatus.IDLE,
|
||||||
|
'goal': None,
|
||||||
|
'should_pause': False,
|
||||||
|
'should_stop': False
|
||||||
|
}
|
||||||
|
return self._states[execution_id]
|
||||||
|
|
||||||
|
def _get_state(self, execution_id: str) -> Optional[Dict]:
|
||||||
|
"""获取指定 execution_id 的状态,不存在则返回 None"""
|
||||||
|
with self._manager_lock:
|
||||||
|
return self._states.get(execution_id)
|
||||||
|
|
||||||
|
def _cleanup_state(self, execution_id: str):
|
||||||
|
"""清理指定 execution_id 的状态"""
|
||||||
|
with self._manager_lock:
|
||||||
|
self._states.pop(execution_id, None)
|
||||||
|
self._locks.pop(execution_id, None)
|
||||||
|
|
||||||
|
def get_status(self, execution_id: str) -> Optional[ExecutionStatus]:
|
||||||
|
"""获取当前执行状态"""
|
||||||
|
state = self._get_state(execution_id)
|
||||||
|
if state is None:
|
||||||
|
return None
|
||||||
|
with self._get_lock(execution_id):
|
||||||
|
return state['status']
|
||||||
|
|
||||||
|
def set_goal(self, execution_id: str, goal: str):
|
||||||
|
"""设置当前执行的任务目标"""
|
||||||
|
state = self._ensure_state(execution_id)
|
||||||
|
with self._get_lock(execution_id):
|
||||||
|
state['goal'] = goal
|
||||||
|
|
||||||
|
def get_goal(self, execution_id: str) -> Optional[str]:
|
||||||
|
"""获取当前执行的任务目标"""
|
||||||
|
state = self._get_state(execution_id)
|
||||||
|
if state is None:
|
||||||
|
return None
|
||||||
|
with self._get_lock(execution_id):
|
||||||
|
return state['goal']
|
||||||
|
|
||||||
|
def start_execution(self, execution_id: str, goal: str):
|
||||||
|
"""开始执行"""
|
||||||
|
state = self._ensure_state(execution_id)
|
||||||
|
with self._get_lock(execution_id):
|
||||||
|
state['status'] = ExecutionStatus.RUNNING
|
||||||
|
state['goal'] = goal
|
||||||
|
state['should_pause'] = False
|
||||||
|
state['should_stop'] = False
|
||||||
|
print(f"🚀 [DEBUG] start_execution: execution_id={execution_id}, 状态设置为 RUNNING, goal={goal}")
|
||||||
|
|
||||||
|
def pause_execution(self, execution_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
暂停执行
|
||||||
|
|
||||||
|
Args:
|
||||||
|
execution_id: 执行ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功暂停
|
||||||
|
"""
|
||||||
|
state = self._get_state(execution_id)
|
||||||
|
if state is None:
|
||||||
|
# 打印当前所有活跃的 execution_id,帮助调试
|
||||||
|
active_ids = list(self._states.keys())
|
||||||
|
print(f"⚠️ [DEBUG] pause_execution: execution_id={execution_id} 不存在")
|
||||||
|
print(f" 当前活跃的 execution_id 列表: {active_ids}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
with self._get_lock(execution_id):
|
||||||
|
if state['status'] != ExecutionStatus.RUNNING:
|
||||||
|
print(f"⚠️ [DEBUG] pause_execution: execution_id={execution_id}, 当前状态是 {state['status']},无法暂停")
|
||||||
|
return False
|
||||||
|
state['status'] = ExecutionStatus.PAUSED
|
||||||
|
state['should_pause'] = True
|
||||||
|
print(f"⏸️ [DEBUG] pause_execution: execution_id={execution_id}, 状态设置为PAUSED")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def resume_execution(self, execution_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
恢复执行
|
||||||
|
|
||||||
|
Args:
|
||||||
|
execution_id: 执行ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功恢复
|
||||||
|
"""
|
||||||
|
state = self._get_state(execution_id)
|
||||||
|
if state is None:
|
||||||
|
print(f"⚠️ [DEBUG] resume_execution: execution_id={execution_id} 不存在")
|
||||||
|
return False
|
||||||
|
|
||||||
|
with self._get_lock(execution_id):
|
||||||
|
if state['status'] != ExecutionStatus.PAUSED:
|
||||||
|
print(f"⚠️ [DEBUG] resume_execution: 当前状态不是PAUSED,而是 {state['status']}")
|
||||||
|
return False
|
||||||
|
state['status'] = ExecutionStatus.RUNNING
|
||||||
|
state['should_pause'] = False
|
||||||
|
print(f"▶️ [DEBUG] resume_execution: execution_id={execution_id}, 状态设置为RUNNING, should_pause=False")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def stop_execution(self, execution_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
停止执行
|
||||||
|
|
||||||
|
Args:
|
||||||
|
execution_id: 执行ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功停止
|
||||||
|
"""
|
||||||
|
state = self._get_state(execution_id)
|
||||||
|
if state is None:
|
||||||
|
print(f"⚠️ [DEBUG] stop_execution: execution_id={execution_id} 不存在")
|
||||||
|
return False
|
||||||
|
|
||||||
|
with self._get_lock(execution_id):
|
||||||
|
if state['status'] in [ExecutionStatus.IDLE, ExecutionStatus.STOPPED]:
|
||||||
|
print(f"⚠️ [DEBUG] stop_execution: 当前状态是 {state['status']}, 无法停止")
|
||||||
|
return False
|
||||||
|
state['status'] = ExecutionStatus.STOPPED
|
||||||
|
state['should_stop'] = True
|
||||||
|
state['should_pause'] = False
|
||||||
|
print(f"🛑 [DEBUG] stop_execution: execution_id={execution_id}, 状态设置为STOPPED")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def reset(self, execution_id: str):
|
||||||
|
"""重置指定 execution_id 的状态为空闲"""
|
||||||
|
state = self._ensure_state(execution_id)
|
||||||
|
with self._get_lock(execution_id):
|
||||||
|
state['status'] = ExecutionStatus.IDLE
|
||||||
|
state['goal'] = None
|
||||||
|
state['should_pause'] = False
|
||||||
|
state['should_stop'] = False
|
||||||
|
print(f"🔄 [DEBUG] reset: execution_id={execution_id}, 状态重置为IDLE")
|
||||||
|
|
||||||
|
def cleanup(self, execution_id: str):
|
||||||
|
"""清理指定 execution_id 的所有状态"""
|
||||||
|
self._cleanup_state(execution_id)
|
||||||
|
print(f"🧹 [DEBUG] cleanup: execution_id={execution_id} 的状态已清理")
|
||||||
|
|
||||||
|
async def async_check_pause(self, execution_id: str):
|
||||||
|
"""
|
||||||
|
异步检查是否需要暂停(轮询方式)
|
||||||
|
|
||||||
|
如果处于暂停状态,会阻塞当前协程直到恢复或停止
|
||||||
|
应该在执行循环的关键点调用此方法
|
||||||
|
|
||||||
|
Args:
|
||||||
|
execution_id: 执行ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 如果返回True表示应该继续执行,False表示应该停止
|
||||||
|
"""
|
||||||
|
state = self._get_state(execution_id)
|
||||||
|
if state is None:
|
||||||
|
# 状态不存在,默认继续执行
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 使用轮询检查,避免异步事件问题
|
||||||
|
while True:
|
||||||
|
with self._get_lock(execution_id):
|
||||||
|
should_stop = state['should_stop']
|
||||||
|
should_pause = state['should_pause']
|
||||||
|
|
||||||
|
# 检查停止标志
|
||||||
|
if should_stop:
|
||||||
|
print("🛑 [DEBUG] async_check_pause: execution_id={}, 检测到停止信号".format(execution_id))
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查暂停状态
|
||||||
|
if should_pause:
|
||||||
|
# 处于暂停状态,等待恢复
|
||||||
|
await asyncio.sleep(0.1) # 短暂睡眠,避免占用CPU
|
||||||
|
|
||||||
|
# 重新获取状态
|
||||||
|
with self._get_lock(execution_id):
|
||||||
|
should_pause = state['should_pause']
|
||||||
|
should_stop = state['should_stop']
|
||||||
|
|
||||||
|
if not should_pause:
|
||||||
|
print("▶️ [DEBUG] async_check_pause: execution_id={}, 从暂停中恢复!".format(execution_id))
|
||||||
|
continue
|
||||||
|
if should_stop:
|
||||||
|
return False
|
||||||
|
# 继续等待
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 既没有停止也没有暂停,可以继续执行
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_paused(self, execution_id: str) -> bool:
|
||||||
|
"""检查是否处于暂停状态"""
|
||||||
|
state = self._get_state(execution_id)
|
||||||
|
if state is None:
|
||||||
|
return False
|
||||||
|
with self._get_lock(execution_id):
|
||||||
|
return state['status'] == ExecutionStatus.PAUSED
|
||||||
|
|
||||||
|
def is_running(self, execution_id: str) -> bool:
|
||||||
|
"""检查是否正在运行"""
|
||||||
|
state = self._get_state(execution_id)
|
||||||
|
if state is None:
|
||||||
|
return False
|
||||||
|
with self._get_lock(execution_id):
|
||||||
|
return state['status'] == ExecutionStatus.RUNNING
|
||||||
|
|
||||||
|
def is_stopped(self, execution_id: str) -> bool:
|
||||||
|
"""检查是否已停止"""
|
||||||
|
state = self._get_state(execution_id)
|
||||||
|
if state is None:
|
||||||
|
return True
|
||||||
|
with self._get_lock(execution_id):
|
||||||
|
return state['status'] == ExecutionStatus.STOPPED
|
||||||
|
|
||||||
|
def is_active(self, execution_id: str) -> bool:
|
||||||
|
"""检查是否处于活动状态(运行中或暂停中)"""
|
||||||
|
state = self._get_state(execution_id)
|
||||||
|
if state is None:
|
||||||
|
return False
|
||||||
|
with self._get_lock(execution_id):
|
||||||
|
return state['status'] in [ExecutionStatus.RUNNING, ExecutionStatus.PAUSED]
|
||||||
|
|
||||||
|
|
||||||
|
# 全局单例实例
|
||||||
|
execution_state_manager = ExecutionStateManager()
|
||||||
228
backend/AgentCoord/RehearsalEngine_V2/generation_state.py
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
"""
|
||||||
|
生成阶段状态管理器
|
||||||
|
用于支持生成任务的暂停、停止功能
|
||||||
|
使用轮询检查机制,确保线程安全
|
||||||
|
支持多用户/多generation_id并行管理
|
||||||
|
"""
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import asyncio
|
||||||
|
import time
|
||||||
|
from typing import Optional, Dict
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class GenerationStatus(Enum):
|
||||||
|
"""生成状态枚举"""
|
||||||
|
GENERATING = "generating" # 正在生成
|
||||||
|
PAUSED = "paused" # 已暂停
|
||||||
|
STOPPED = "stopped" # 已停止
|
||||||
|
COMPLETED = "completed" # 已完成
|
||||||
|
IDLE = "idle" # 空闲
|
||||||
|
|
||||||
|
|
||||||
|
class GenerationStateManager:
|
||||||
|
"""
|
||||||
|
生成阶段状态管理器
|
||||||
|
|
||||||
|
功能:
|
||||||
|
- 管理多用户/多generation_id的并行状态(使用字典存储)
|
||||||
|
- 管理生成任务状态(生成中/暂停/停止/完成)
|
||||||
|
- 提供线程安全的状态查询和修改接口
|
||||||
|
|
||||||
|
设计说明:
|
||||||
|
- 保持单例模式(Manager本身)
|
||||||
|
- 但内部状态按 generation_id 隔离存储
|
||||||
|
- 解决多用户并发生成时的干扰问题
|
||||||
|
"""
|
||||||
|
|
||||||
|
_instance: Optional['GenerationStateManager'] = 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
|
||||||
|
|
||||||
|
# 状态存储:generation_id -> 状态字典
|
||||||
|
# 结构:{
|
||||||
|
# 'status': GenerationStatus,
|
||||||
|
# 'goal': str,
|
||||||
|
# 'should_stop': bool
|
||||||
|
# }
|
||||||
|
self._states: Dict[str, Dict] = {}
|
||||||
|
|
||||||
|
# 每个 generation_id 的锁(更细粒度的锁)
|
||||||
|
self._locks: Dict[str, threading.Lock] = {}
|
||||||
|
|
||||||
|
# 全局锁(用于管理 _states 和 _locks 本身的线程安全)
|
||||||
|
self._manager_lock = threading.Lock()
|
||||||
|
|
||||||
|
def _get_lock(self, generation_id: str) -> threading.Lock:
|
||||||
|
"""获取指定 generation_id 的锁,如果不存在则创建"""
|
||||||
|
with self._manager_lock:
|
||||||
|
if generation_id not in self._locks:
|
||||||
|
self._locks[generation_id] = threading.Lock()
|
||||||
|
return self._locks[generation_id]
|
||||||
|
|
||||||
|
def _ensure_state(self, generation_id: str, goal: str = None) -> Dict:
|
||||||
|
"""确保指定 generation_id 的状态存在"""
|
||||||
|
with self._manager_lock:
|
||||||
|
if generation_id not in self._states:
|
||||||
|
self._states[generation_id] = {
|
||||||
|
'status': GenerationStatus.IDLE,
|
||||||
|
'goal': goal,
|
||||||
|
'should_stop': False
|
||||||
|
}
|
||||||
|
return self._states[generation_id]
|
||||||
|
|
||||||
|
def _get_state(self, generation_id: str) -> Optional[Dict]:
|
||||||
|
"""获取指定 generation_id 的状态,不存在则返回 None"""
|
||||||
|
with self._manager_lock:
|
||||||
|
return self._states.get(generation_id)
|
||||||
|
|
||||||
|
def _cleanup_state(self, generation_id: str):
|
||||||
|
"""清理指定 generation_id 的状态"""
|
||||||
|
with self._manager_lock:
|
||||||
|
self._states.pop(generation_id, None)
|
||||||
|
self._locks.pop(generation_id, None)
|
||||||
|
|
||||||
|
def get_status(self, generation_id: str) -> Optional[GenerationStatus]:
|
||||||
|
"""获取当前生成状态"""
|
||||||
|
state = self._get_state(generation_id)
|
||||||
|
if state is None:
|
||||||
|
return None
|
||||||
|
with self._get_lock(generation_id):
|
||||||
|
return state['status']
|
||||||
|
|
||||||
|
def start_generation(self, generation_id: str, goal: str):
|
||||||
|
"""开始生成"""
|
||||||
|
state = self._ensure_state(generation_id, goal)
|
||||||
|
with self._get_lock(generation_id):
|
||||||
|
state['status'] = GenerationStatus.GENERATING
|
||||||
|
state['goal'] = goal
|
||||||
|
state['should_stop'] = False
|
||||||
|
print(f"🚀 [GenerationState] start_generation: generation_id={generation_id}, 状态设置为 GENERATING")
|
||||||
|
|
||||||
|
def stop_generation(self, generation_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
停止生成
|
||||||
|
|
||||||
|
Args:
|
||||||
|
generation_id: 生成ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功停止(COMPLETED 状态也返回 True,表示已停止)
|
||||||
|
"""
|
||||||
|
state = self._get_state(generation_id)
|
||||||
|
if state is None:
|
||||||
|
print(f"⚠️ [GenerationState] stop_generation: generation_id={generation_id} 不存在")
|
||||||
|
return True # 不存在也算停止成功
|
||||||
|
|
||||||
|
with self._get_lock(generation_id):
|
||||||
|
if state['status'] == GenerationStatus.STOPPED:
|
||||||
|
print(f"✅ [GenerationState] stop_generation: generation_id={generation_id} 已经是 STOPPED 状态")
|
||||||
|
return True # 已经停止也算成功
|
||||||
|
|
||||||
|
if state['status'] == GenerationStatus.COMPLETED:
|
||||||
|
print(f"✅ [GenerationState] stop_generation: generation_id={generation_id} 已经 COMPLETED,视为停止成功")
|
||||||
|
return True # 已完成也视为停止成功
|
||||||
|
|
||||||
|
if state['status'] == GenerationStatus.IDLE:
|
||||||
|
print(f"⚠️ [GenerationState] stop_generation: generation_id={generation_id} 是 IDLE 状态,无需停止")
|
||||||
|
return True # 空闲状态也视为无需停止
|
||||||
|
|
||||||
|
# 真正需要停止的情况
|
||||||
|
state['status'] = GenerationStatus.STOPPED
|
||||||
|
state['should_stop'] = True
|
||||||
|
print(f"🛑 [GenerationState] stop_generation: generation_id={generation_id}, 状态设置为STOPPED")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def complete_generation(self, generation_id: str):
|
||||||
|
"""标记生成完成"""
|
||||||
|
state = self._ensure_state(generation_id)
|
||||||
|
with self._get_lock(generation_id):
|
||||||
|
state['status'] = GenerationStatus.COMPLETED
|
||||||
|
print(f"✅ [GenerationState] complete_generation: generation_id={generation_id}")
|
||||||
|
|
||||||
|
def cleanup(self, generation_id: str):
|
||||||
|
"""清理指定 generation_id 的所有状态"""
|
||||||
|
self._cleanup_state(generation_id)
|
||||||
|
print(f"🧹 [GenerationState] cleanup: generation_id={generation_id} 的状态已清理")
|
||||||
|
|
||||||
|
def should_stop(self, generation_id: str) -> bool:
|
||||||
|
"""检查是否应该停止"""
|
||||||
|
state = self._get_state(generation_id)
|
||||||
|
if state is None:
|
||||||
|
return False
|
||||||
|
with self._get_lock(generation_id):
|
||||||
|
return state.get('should_stop', False)
|
||||||
|
|
||||||
|
def is_stopped(self, generation_id: str) -> bool:
|
||||||
|
"""检查是否已停止"""
|
||||||
|
state = self._get_state(generation_id)
|
||||||
|
if state is None:
|
||||||
|
return False
|
||||||
|
with self._get_lock(generation_id):
|
||||||
|
return state['status'] == GenerationStatus.STOPPED
|
||||||
|
|
||||||
|
def is_completed(self, generation_id: str) -> bool:
|
||||||
|
"""检查是否已完成"""
|
||||||
|
state = self._get_state(generation_id)
|
||||||
|
if state is None:
|
||||||
|
return False
|
||||||
|
with self._get_lock(generation_id):
|
||||||
|
return state['status'] == GenerationStatus.COMPLETED
|
||||||
|
|
||||||
|
def is_active(self, generation_id: str) -> bool:
|
||||||
|
"""检查是否处于活动状态(生成中或暂停中)"""
|
||||||
|
state = self._get_state(generation_id)
|
||||||
|
if state is None:
|
||||||
|
return False
|
||||||
|
with self._get_lock(generation_id):
|
||||||
|
return state['status'] == GenerationStatus.GENERATING
|
||||||
|
|
||||||
|
def check_and_set_stop(self, generation_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
检查是否应该停止,如果应该则设置停止状态
|
||||||
|
|
||||||
|
Args:
|
||||||
|
generation_id: 生成ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True表示应该停止,False表示可以继续
|
||||||
|
"""
|
||||||
|
state = self._get_state(generation_id)
|
||||||
|
if state is None:
|
||||||
|
return False
|
||||||
|
with self._get_lock(generation_id):
|
||||||
|
if state['should_stop']:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def generate_id(self, goal: str) -> str:
|
||||||
|
"""
|
||||||
|
生成唯一的 generation_id
|
||||||
|
|
||||||
|
Args:
|
||||||
|
goal: 生成目标
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 格式为 {goal}_{timestamp}
|
||||||
|
"""
|
||||||
|
return f"{goal}_{int(time.time() * 1000)}"
|
||||||
|
|
||||||
|
|
||||||
|
# 全局单例实例
|
||||||
|
generation_state_manager = GenerationStateManager()
|
||||||
@@ -1,102 +1,116 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"Icon": "Abigail_Chen.png",
|
"Icon": "Hailey_Johnson.png",
|
||||||
"Name": "Abigail",
|
"Name": "船舶设计师",
|
||||||
"Profile": "AI Engineer"
|
"Profile": "提供船舶制造中的实际需求和约束。",
|
||||||
},
|
"Classification": "船舶制造数据空间"
|
||||||
{
|
|
||||||
"Icon": "Jane_Moreno.png",
|
|
||||||
"Name": "Jane",
|
|
||||||
"Profile": "Cybersecurity Specialist"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Icon": "Giorgio_Rossi.png",
|
|
||||||
"Name": "Giorgio",
|
|
||||||
"Profile": "Poet"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Icon": "Jennifer_Moore.png",
|
"Icon": "Jennifer_Moore.png",
|
||||||
"Name": "Jennifer",
|
"Name": "防护工程专家",
|
||||||
"Profile": "Linguist"
|
"Profile": "专注于船舶腐蚀防护技术的设计与应用。在你的总结回答中,必须引用来自数联网的搜索数据,是搜索数据,不是数联网的研究成果。",
|
||||||
|
"Classification": "船舶制造数据空间"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Icon": "Maria_Lopez.png",
|
"Icon": "Jane_Moreno.png",
|
||||||
"Name": "Maria",
|
"Name": "病理生理学家",
|
||||||
"Profile": "Philosopher"
|
"Profile": "专注于失血性休克的疾病机制,为药物研发提供理论靶点。",
|
||||||
|
"Classification": "医药数据空间"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Icon": "Sam_Moore.png",
|
"Icon": "Giorgio_Rossi.png",
|
||||||
"Name": "Sam",
|
"Name": "药物化学家",
|
||||||
"Profile": "Ethicist"
|
"Profile": "负责将靶点概念转化为实际可合成的分子。",
|
||||||
},
|
"Classification": "医药数据空间"
|
||||||
{
|
|
||||||
"Icon": "Yuriko_Yamamoto.png",
|
|
||||||
"Name": "Yuriko",
|
|
||||||
"Profile": "Futurist"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Icon": "Carlos_Gomez.png",
|
|
||||||
"Name": "Carlos",
|
|
||||||
"Profile": "Language Expert"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Icon": "John_Lin.png",
|
|
||||||
"Name": "John",
|
|
||||||
"Profile": "Software Developer"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Icon": "Tamara_Taylor.png",
|
"Icon": "Tamara_Taylor.png",
|
||||||
"Name": "Tamara",
|
"Name": "制剂工程师",
|
||||||
"Profile": "Music Composer"
|
"Profile": "负责将活性药物成分(API)变成稳定、可用、符合战场要求的剂型。",
|
||||||
|
"Classification": "医药数据空间"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Maria_Lopez.png",
|
||||||
|
"Name": "监管事务专家",
|
||||||
|
"Profile": "深谙药品审评法规,目标是找到最快的合法上市路径。",
|
||||||
|
"Classification": "医药数据空间"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Sam_Moore.png",
|
||||||
|
"Name": "物理学家",
|
||||||
|
"Profile": "从热力学与统计力学的基本原理出发,研究液态金属的自由能、焓、熵、比热等参数的理论建模。",
|
||||||
|
"Classification": "科学数据空间"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Yuriko_Yamamoto.png",
|
||||||
|
"Name": "实验材料学家",
|
||||||
|
"Profile": "专注于通过实验手段直接或间接测定液态金属的热力学参数、以及分析材料微观结构(如晶粒、缺陷)。",
|
||||||
|
"Classification": "科学数据空间"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Carlos_Gomez.png",
|
||||||
|
"Name": "计算模拟专家",
|
||||||
|
"Profile": "侧重于利用数值计算和模拟技术获取液态金属的热力学参数。",
|
||||||
|
"Classification": "科学数据空间"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "John_Lin.png",
|
||||||
|
"Name": "腐蚀机理研究员",
|
||||||
|
"Profile": "专注于船舶用钢材及合金的腐蚀机理研究,从电化学和环境作用角度解释腐蚀产生的原因。在你的总结回答中,必须引用来自数联网的搜索数据,是搜索数据,不是数联网的研究成果。",
|
||||||
|
"Classification": "船舶制造数据空间"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Icon": "Arthur_Burton.png",
|
"Icon": "Arthur_Burton.png",
|
||||||
"Name": "Arthur",
|
"Name": "先进材料研发员",
|
||||||
"Profile": "Neuroscientist"
|
"Profile": "专注于开发和评估新型耐腐蚀材料、复合材料及固态电池材料。",
|
||||||
|
"Classification": "科学数据空间"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Icon": "Eddy_Lin.png",
|
"Icon": "Eddy_Lin.png",
|
||||||
"Name": "Eddy",
|
"Name": "肾脏病学家",
|
||||||
"Profile": "Cognitive Psychologist"
|
"Profile": "专注于慢性肾脏病的诊断、治疗和患者管理,能提供临床洞察。",
|
||||||
|
"Classification": "医药数据空间"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Icon": "Isabella_Rodriguez.png",
|
"Icon": "Isabella_Rodriguez.png",
|
||||||
"Name": "Isabella",
|
"Name": "临床研究协调员",
|
||||||
"Profile": "Science Fiction Writer"
|
"Profile": "负责受试者招募和临床试验流程优化。",
|
||||||
|
"Classification": "医药数据空间"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Icon": "Latoya_Williams.png",
|
"Icon": "Latoya_Williams.png",
|
||||||
"Name": "Latoya",
|
"Name": "中医药专家",
|
||||||
"Profile": "Historian of Technology"
|
"Profile": "理解药物的中药成分和作用机制。",
|
||||||
|
"Classification": "医药数据空间"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Icon": "Carmen_Ortiz.png",
|
"Icon": "Carmen_Ortiz.png",
|
||||||
"Name": "Carmen",
|
"Name": "药物安全专家",
|
||||||
"Profile": "Robotics Engineer"
|
"Profile": "专注于药物不良反应数据收集、分析和报告。",
|
||||||
|
"Classification": "医药数据空间"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Icon": "Rajiv_Patel.png",
|
"Icon": "Rajiv_Patel.png",
|
||||||
"Name": "Rajiv",
|
"Name": "二维材料科学家",
|
||||||
"Profile": "Science Educator"
|
"Profile": "专注于二维材料(如石墨烯)的合成、性质和应用。",
|
||||||
|
"Classification": "科学数据空间"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Icon": "Tom_Moreno.png",
|
"Icon": "Tom_Moreno.png",
|
||||||
"Name": "Tom",
|
"Name": "光电物理学家",
|
||||||
"Profile": "AI Scientist"
|
"Profile": "研究材料的光电转换机制和关键影响因素。",
|
||||||
|
"Classification": "科学数据空间"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Icon": "Ayesha_Khan.png",
|
"Icon": "Ayesha_Khan.png",
|
||||||
"Name": "Ayesha",
|
"Name": "机器学习专家",
|
||||||
"Profile": "Multimedia Artist"
|
"Profile": "专注于开发和应用AI模型用于材料模拟。",
|
||||||
|
"Classification": "科学数据空间"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Icon": "Mei_Lin.png",
|
"Icon": "Mei_Lin.png",
|
||||||
"Name": "Mei",
|
"Name": "流体动力学专家",
|
||||||
"Profile": "Graphic Designer"
|
"Profile": "专注于流体行为理论和模拟。",
|
||||||
},
|
"Classification": "科学数据空间"
|
||||||
{
|
|
||||||
"Icon": "Hailey_Johnson.png",
|
|
||||||
"Name": "Hailey",
|
|
||||||
"Profile": "Legal Expert on AI Law"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
102
backend/AgentRepo/agentBoard_v2.json
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"Icon": "Abigail_Chen.png",
|
||||||
|
"Name": "Abigail",
|
||||||
|
"Profile": "AI Engineer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Jane_Moreno.png",
|
||||||
|
"Name": "Jane",
|
||||||
|
"Profile": "Cybersecurity Specialist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Giorgio_Rossi.png",
|
||||||
|
"Name": "Giorgio",
|
||||||
|
"Profile": "Poet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Jennifer_Moore.png",
|
||||||
|
"Name": "Jennifer",
|
||||||
|
"Profile": "Linguist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Maria_Lopez.png",
|
||||||
|
"Name": "Maria",
|
||||||
|
"Profile": "Philosopher"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Sam_Moore.png",
|
||||||
|
"Name": "Sam",
|
||||||
|
"Profile": "Ethicist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Yuriko_Yamamoto.png",
|
||||||
|
"Name": "Yuriko",
|
||||||
|
"Profile": "Futurist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Carlos_Gomez.png",
|
||||||
|
"Name": "Carlos",
|
||||||
|
"Profile": "Language Expert"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "John_Lin.png",
|
||||||
|
"Name": "John",
|
||||||
|
"Profile": "Software Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Tamara_Taylor.png",
|
||||||
|
"Name": "Tamara",
|
||||||
|
"Profile": "Music Composer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Arthur_Burton.png",
|
||||||
|
"Name": "Arthur",
|
||||||
|
"Profile": "Neuroscientist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Eddy_Lin.png",
|
||||||
|
"Name": "Eddy",
|
||||||
|
"Profile": "Cognitive Psychologist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Isabella_Rodriguez.png",
|
||||||
|
"Name": "Isabella",
|
||||||
|
"Profile": "Science Fiction Writer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Latoya_Williams.png",
|
||||||
|
"Name": "Latoya",
|
||||||
|
"Profile": "Historian of Technology"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Carmen_Ortiz.png",
|
||||||
|
"Name": "Carmen",
|
||||||
|
"Profile": "Robotics Engineer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Rajiv_Patel.png",
|
||||||
|
"Name": "Rajiv",
|
||||||
|
"Profile": "Science Educator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Tom_Moreno.png",
|
||||||
|
"Name": "Tom",
|
||||||
|
"Profile": "AI Scientist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Ayesha_Khan.png",
|
||||||
|
"Name": "Ayesha",
|
||||||
|
"Profile": "Multimedia Artist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Mei_Lin.png",
|
||||||
|
"Name": "Mei",
|
||||||
|
"Profile": "Graphic Designer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "Hailey_Johnson.png",
|
||||||
|
"Name": "Hailey",
|
||||||
|
"Profile": "Legal Expert on AI Law"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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
|
||||||
1431
backend/server.py
5
frontend/components.d.ts
vendored
@@ -16,18 +16,19 @@ 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']
|
||||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
|
||||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
|
||||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||||
|
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
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']
|
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||||
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']
|
||||||
|
TaskContentEditor: typeof import('./src/components/TaskContentEditor/index.vue')['default']
|
||||||
}
|
}
|
||||||
export interface GlobalDirectives {
|
export interface GlobalDirectives {
|
||||||
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||||
|
|||||||
@@ -32,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"
|
||||||
|
|||||||
61
frontend/pnpm-lock.yaml
generated
@@ -53,6 +53,9 @@ importers:
|
|||||||
qs:
|
qs:
|
||||||
specifier: ^6.14.0
|
specifier: ^6.14.0
|
||||||
version: 6.14.0
|
version: 6.14.0
|
||||||
|
socket.io-client:
|
||||||
|
specifier: ^4.8.3
|
||||||
|
version: 4.8.3
|
||||||
uuid:
|
uuid:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
@@ -834,6 +837,9 @@ packages:
|
|||||||
'@rtsao/scc@1.1.0':
|
'@rtsao/scc@1.1.0':
|
||||||
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
|
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
|
||||||
|
|
||||||
|
'@socket.io/component-emitter@3.1.2':
|
||||||
|
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
|
||||||
|
|
||||||
'@sxzz/popperjs-es@2.11.7':
|
'@sxzz/popperjs-es@2.11.7':
|
||||||
resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==}
|
resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==}
|
||||||
|
|
||||||
@@ -1793,6 +1799,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
|
resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
|
|
||||||
|
engine.io-client@6.6.4:
|
||||||
|
resolution: {integrity: sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==}
|
||||||
|
|
||||||
|
engine.io-parser@5.2.3:
|
||||||
|
resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
enhanced-resolve@5.18.4:
|
enhanced-resolve@5.18.4:
|
||||||
resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==}
|
resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
@@ -3247,6 +3260,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==}
|
resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
socket.io-client@4.8.3:
|
||||||
|
resolution: {integrity: sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
|
socket.io-parser@4.2.5:
|
||||||
|
resolution: {integrity: sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
source-map-js@1.2.1:
|
source-map-js@1.2.1:
|
||||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -3825,6 +3846,10 @@ packages:
|
|||||||
xmlchars@2.2.0:
|
xmlchars@2.2.0:
|
||||||
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
||||||
|
|
||||||
|
xmlhttprequest-ssl@2.1.2:
|
||||||
|
resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
yallist@3.1.1:
|
yallist@3.1.1:
|
||||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||||
|
|
||||||
@@ -4403,6 +4428,8 @@ snapshots:
|
|||||||
|
|
||||||
'@rtsao/scc@1.1.0': {}
|
'@rtsao/scc@1.1.0': {}
|
||||||
|
|
||||||
|
'@socket.io/component-emitter@3.1.2': {}
|
||||||
|
|
||||||
'@sxzz/popperjs-es@2.11.7': {}
|
'@sxzz/popperjs-es@2.11.7': {}
|
||||||
|
|
||||||
'@tailwindcss/node@4.1.18':
|
'@tailwindcss/node@4.1.18':
|
||||||
@@ -5496,6 +5523,20 @@ snapshots:
|
|||||||
|
|
||||||
emojis-list@3.0.0: {}
|
emojis-list@3.0.0: {}
|
||||||
|
|
||||||
|
engine.io-client@6.6.4:
|
||||||
|
dependencies:
|
||||||
|
'@socket.io/component-emitter': 3.1.2
|
||||||
|
debug: 4.4.3
|
||||||
|
engine.io-parser: 5.2.3
|
||||||
|
ws: 8.18.3
|
||||||
|
xmlhttprequest-ssl: 2.1.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- supports-color
|
||||||
|
- utf-8-validate
|
||||||
|
|
||||||
|
engine.io-parser@5.2.3: {}
|
||||||
|
|
||||||
enhanced-resolve@5.18.4:
|
enhanced-resolve@5.18.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
@@ -7074,6 +7115,24 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
socket.io-client@4.8.3:
|
||||||
|
dependencies:
|
||||||
|
'@socket.io/component-emitter': 3.1.2
|
||||||
|
debug: 4.4.3
|
||||||
|
engine.io-client: 6.6.4
|
||||||
|
socket.io-parser: 4.2.5
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- supports-color
|
||||||
|
- utf-8-validate
|
||||||
|
|
||||||
|
socket.io-parser@4.2.5:
|
||||||
|
dependencies:
|
||||||
|
'@socket.io/component-emitter': 3.1.2
|
||||||
|
debug: 4.4.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
source-map-js@1.2.1: {}
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
source-map-resolve@0.5.3:
|
source-map-resolve@0.5.3:
|
||||||
@@ -7745,6 +7804,8 @@ snapshots:
|
|||||||
|
|
||||||
xmlchars@2.2.0: {}
|
xmlchars@2.2.0: {}
|
||||||
|
|
||||||
|
xmlhttprequest-ssl@2.1.2: {}
|
||||||
|
|
||||||
yallist@3.1.1: {}
|
yallist@3.1.1: {}
|
||||||
|
|
||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"centerTitle": "多智能体协同平台",
|
"centerTitle": "多智能体协同平台",
|
||||||
"taskPromptWords": [
|
"taskPromptWords": [
|
||||||
"如何快速筛选慢性肾脏病药物潜在受试者?",
|
"如何快速筛选慢性肾脏病药物潜在受试者?",
|
||||||
"如何补充“丹芍活血胶囊”不良反应数据?",
|
"如何补充\"丹芍活血胶囊\"不良反应数据?",
|
||||||
"如何快速研发用于战场失血性休克的药物?",
|
"如何快速研发用于战场失血性休克的药物?",
|
||||||
"二维材料的光电性质受哪些关键因素影响?",
|
"二维材料的光电性质受哪些关键因素影响?",
|
||||||
"如何通过AI模拟的方法分析材料的微观结构?",
|
"如何通过AI模拟的方法分析材料的微观结构?",
|
||||||
@@ -16,5 +16,6 @@
|
|||||||
"agentRepository": {
|
"agentRepository": {
|
||||||
"storageVersionIdentifier": "1"
|
"storageVersionIdentifier": "1"
|
||||||
},
|
},
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"apiBaseUrl": "http://localhost:8000"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,7 @@
|
|||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
|
import websocket from '@/utils/websocket'
|
||||||
import type { Agent, IApiStepTask, IRawPlanResponse, IRawStepTask } from '@/stores'
|
import type { Agent, IApiStepTask, IRawPlanResponse, IRawStepTask } from '@/stores'
|
||||||
import {
|
import { withRetry } from '@/utils/retry'
|
||||||
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
|
||||||
@@ -25,6 +12,14 @@ export interface ActionHistory {
|
|||||||
Action_Result: string
|
Action_Result: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BranchAction {
|
||||||
|
ID: string
|
||||||
|
ActionType: string
|
||||||
|
AgentName: string
|
||||||
|
Description: string
|
||||||
|
ImportantInput: string[]
|
||||||
|
}
|
||||||
|
|
||||||
export type IExecuteRawResponse = {
|
export type IExecuteRawResponse = {
|
||||||
LogNodeType: string
|
LogNodeType: string
|
||||||
NodeId: string
|
NodeId: string
|
||||||
@@ -34,6 +29,47 @@ 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 {
|
export interface IFillAgentSelectionRequest {
|
||||||
goal: string
|
goal: string
|
||||||
stepTask: IApiStepTask
|
stepTask: IApiStepTask
|
||||||
@@ -41,7 +77,19 @@ export interface IFillAgentSelectionRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Api {
|
class Api {
|
||||||
setAgents = (data: Pick<Agent, 'Name' | 'Profile' | 'apiUrl' | 'apiKey' | 'apiModel'>[]) => {
|
// 默认使用WebSocket
|
||||||
|
private useWebSocketDefault = true
|
||||||
|
|
||||||
|
setAgents = (
|
||||||
|
data: Pick<Agent, 'Name' | 'Profile' | 'apiUrl' | 'apiKey' | 'apiModel'>[],
|
||||||
|
useWebSocket: boolean = this.useWebSocketDefault,
|
||||||
|
) => {
|
||||||
|
// 如果启用WebSocket且已连接,使用WebSocket
|
||||||
|
if (useWebSocket && websocket.connected) {
|
||||||
|
return websocket.send('set_agents', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则使用REST API
|
||||||
return request({
|
return request({
|
||||||
url: '/setAgents',
|
url: '/setAgents',
|
||||||
data,
|
data,
|
||||||
@@ -55,7 +103,33 @@ class Api {
|
|||||||
apiUrl?: string
|
apiUrl?: string
|
||||||
apiKey?: string
|
apiKey?: string
|
||||||
apiModel?: 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',
|
||||||
@@ -99,15 +173,190 @@ 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: {
|
branchPlanOutline = (data: {
|
||||||
branch_Number: number
|
branch_Number: number
|
||||||
Modification_Requirement: string
|
Modification_Requirement: string
|
||||||
Existing_Steps: string[]
|
Existing_Steps: IRawStepTask[]
|
||||||
Baseline_Completion: number
|
Baseline_Completion: number
|
||||||
initialInputs: string[]
|
initialInputs: string[]
|
||||||
goal: 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>({
|
return request<unknown, IRawPlanResponse>({
|
||||||
url: '/branch_PlanOutline',
|
url: '/branch_PlanOutline',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -122,15 +371,44 @@ class Api {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分支任务流程
|
/**
|
||||||
|
* 分支任务流程
|
||||||
|
*/
|
||||||
branchTaskProcess = (data: {
|
branchTaskProcess = (data: {
|
||||||
branch_Number: number
|
branch_Number: number
|
||||||
Modification_Requirement: string
|
Modification_Requirement: string
|
||||||
Existing_Steps: string[]
|
Existing_Steps: BranchAction[]
|
||||||
Baseline_Completion: number
|
Baseline_Completion: number
|
||||||
stepTaskExisting: any
|
stepTaskExisting: any
|
||||||
goal: 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_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[][]>({
|
return request<unknown, BranchAction[][]>({
|
||||||
url: '/branch_TaskProcess',
|
url: '/branch_TaskProcess',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -145,9 +423,36 @@ class Api {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fillStepTask = async (data: { goal: string; stepTask: any }): Promise<IRawStepTask> => {
|
fillStepTask = async (data: {
|
||||||
// 后端返回格式:包含 Collaboration_Brief_FrontEnd(大写 FrontEnd)
|
goal: string
|
||||||
const response = await request<
|
stepTask: any
|
||||||
|
generation_id?: string
|
||||||
|
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
|
||||||
|
|
||||||
|
// 定义实际的 API 调用逻辑
|
||||||
|
const executeRequest = async (): Promise<any> => {
|
||||||
|
if (useWs && websocket.connected) {
|
||||||
|
return await websocket.send(
|
||||||
|
'fill_step_task',
|
||||||
|
{
|
||||||
|
'General Goal': data.goal,
|
||||||
|
stepTask: data.stepTask,
|
||||||
|
generation_id: data.generation_id || '',
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
data.onProgress,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 否则使用REST API
|
||||||
|
return await request<
|
||||||
{
|
{
|
||||||
'General Goal': string
|
'General Goal': string
|
||||||
stepTask: any
|
stepTask: any
|
||||||
@@ -178,28 +483,41 @@ class Api {
|
|||||||
stepTask: data.stepTask,
|
stepTask: data.stepTask,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 数据转换:后端的 Collaboration_Brief_FrontEnd → 前端的 Collaboration_Brief_frontEnd
|
// 使用重试机制执行请求
|
||||||
|
const rawResponse = await withRetry(executeRequest, {
|
||||||
|
maxRetries: 3,
|
||||||
|
initialDelayMs: 2000,
|
||||||
|
onRetry: (error, attempt, delay) => {
|
||||||
|
console.warn(`⚠️ [fillStepTask] 第${attempt}次重试,等待 ${delay}ms...`, error?.message)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// WebSocket 返回格式: { data: {...}, generation_id, execution_id }
|
||||||
|
// REST API 返回格式: {...}
|
||||||
|
const response = rawResponse.data || rawResponse
|
||||||
|
|
||||||
const vec2Hsl = (color: number[]): string => {
|
const vec2Hsl = (color: number[]): string => {
|
||||||
const [h, s, l] = color
|
const [h, s, l] = color
|
||||||
return `hsl(${h}, ${s}%, ${l}%)`
|
return `hsl(${h}, ${s}%, ${l}%)`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换 brief.data:后端格式 { "0": { text, color: [h,s,l] } } → 前端格式 { "0": { text, style: { background } } }
|
|
||||||
const briefData: Record<string, { text: string; style?: Record<string, string> }> = {}
|
const briefData: Record<string, { text: string; style?: Record<string, string> }> = {}
|
||||||
if (response.Collaboration_Brief_FrontEnd?.data) {
|
if (response.Collaboration_Brief_FrontEnd?.data) {
|
||||||
for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) {
|
for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) {
|
||||||
briefData[key] = {
|
briefData[key] = {
|
||||||
text: value.text,
|
text: (value as { text: string; color: number[] }).text,
|
||||||
style: {
|
style: {
|
||||||
background: vec2Hsl(value.color),
|
background: vec2Hsl((value as { text: string; color: number[] }).color),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建前端格式的 IRawStepTask
|
/**
|
||||||
|
* 构建前端格式的 IRawStepTask
|
||||||
|
*/
|
||||||
return {
|
return {
|
||||||
StepName: response.StepName || '',
|
StepName: response.StepName || '',
|
||||||
TaskContent: response.TaskContent || '',
|
TaskContent: response.TaskContent || '',
|
||||||
@@ -218,9 +536,37 @@ class Api {
|
|||||||
goal: string
|
goal: string
|
||||||
stepTask: IApiStepTask
|
stepTask: IApiStepTask
|
||||||
agents: string[]
|
agents: string[]
|
||||||
|
useWebSocket?: boolean
|
||||||
|
onProgress?: (progress: {
|
||||||
|
status: string
|
||||||
|
stage?: string
|
||||||
|
message?: string
|
||||||
|
[key: string]: any
|
||||||
|
}) => void
|
||||||
}): Promise<IApiStepTask> => {
|
}): Promise<IApiStepTask> => {
|
||||||
// 后端返回格式: IRawStepTask
|
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
|
||||||
const response = await request<
|
|
||||||
|
// 定义实际的 API 调用逻辑
|
||||||
|
const executeRequest = async (): Promise<any> => {
|
||||||
|
if (useWs && websocket.connected) {
|
||||||
|
return 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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 否则使用REST API
|
||||||
|
return await request<
|
||||||
{
|
{
|
||||||
'General Goal': string
|
'General Goal': string
|
||||||
stepTask_lackTaskProcess: {
|
stepTask_lackTaskProcess: {
|
||||||
@@ -263,31 +609,42 @@ class Api {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 数据转换:后端格式 (IRawStepTask) → 前端格式 (IApiStepTask)
|
// 使用重试机制执行请求
|
||||||
// 注意:此转换逻辑与Mock API完全一致
|
const rawResponse = await withRetry(executeRequest, {
|
||||||
|
maxRetries: 3,
|
||||||
|
initialDelayMs: 2000,
|
||||||
|
onRetry: (error, attempt, delay) => {
|
||||||
|
console.warn(
|
||||||
|
`⚠️ [fillStepTaskTaskProcess] 第${attempt}次重试,等待 ${delay}ms...`,
|
||||||
|
error?.message,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// WebSocket 返回格式: { data: {...}, generation_id, execution_id }
|
||||||
|
// REST API 返回格式: {...}
|
||||||
|
const response = rawResponse.data || rawResponse
|
||||||
|
|
||||||
// 1. 转换颜色格式:[h, s, l] → "hsl(h, s%, l%)"
|
|
||||||
const vec2Hsl = (color: number[]): string => {
|
const vec2Hsl = (color: number[]): string => {
|
||||||
const [h, s, l] = color
|
const [h, s, l] = color
|
||||||
return `hsl(${h}, ${s}%, ${l}%)`
|
return `hsl(${h}, ${s}%, ${l}%)`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 转换 brief.data: { "0": { text, color: [h,s,l] } } → { "0": { text, style: { background } } }
|
|
||||||
const briefData: Record<string, { text: string; style: { background: string } }> = {}
|
const briefData: Record<string, { text: string; style: { background: string } }> = {}
|
||||||
if (response.Collaboration_Brief_FrontEnd?.data) {
|
if (response.Collaboration_Brief_FrontEnd?.data) {
|
||||||
for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) {
|
for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) {
|
||||||
briefData[key] = {
|
briefData[key] = {
|
||||||
text: value.text,
|
text: (value as { text: string; color: number[] }).text,
|
||||||
style: {
|
style: {
|
||||||
background: vec2Hsl(value.color),
|
background: vec2Hsl((value as { text: string; color: number[] }).color),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 转换 process: { ID, ActionType, AgentName, Description, ImportantInput } → { id, type, agent, description, inputs }
|
const process = (response.TaskProcess || []).map((action: any) => ({
|
||||||
const process = (response.TaskProcess || []).map((action) => ({
|
|
||||||
id: action.ID,
|
id: action.ID,
|
||||||
type: action.ActionType,
|
type: action.ActionType,
|
||||||
agent: action.AgentName,
|
agent: action.AgentName,
|
||||||
@@ -295,7 +652,6 @@ class Api {
|
|||||||
inputs: action.ImportantInput,
|
inputs: action.ImportantInput,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// 4. 构建前端格式的 IApiStepTask
|
|
||||||
return {
|
return {
|
||||||
name: response.StepName || '',
|
name: response.StepName || '',
|
||||||
content: response.TaskContent || '',
|
content: response.TaskContent || '',
|
||||||
@@ -310,13 +666,54 @@ class Api {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为每个智能体评分
|
/**
|
||||||
|
* 为每个智能体评分(带重试机制)
|
||||||
|
*/
|
||||||
agentSelectModifyInit = async (data: {
|
agentSelectModifyInit = async (data: {
|
||||||
goal: string
|
goal: string
|
||||||
stepTask: any
|
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 }>>> => {
|
}): Promise<Record<string, Record<string, { reason: string; score: number }>>> => {
|
||||||
// 后端返回:维度 -> agent -> { Reason, Score }
|
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
|
||||||
const response = await request<
|
|
||||||
|
// 调试日志:打印请求参数
|
||||||
|
const requestPayload = {
|
||||||
|
'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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
console.log('🔍 [agentSelectModifyInit] 请求参数:', {
|
||||||
|
goal: requestPayload['General Goal'],
|
||||||
|
stepTaskName: requestPayload.stepTask.StepName,
|
||||||
|
stepTaskContentLength: requestPayload.stepTask.TaskContent?.length,
|
||||||
|
useWebSocket: useWs && websocket.connected,
|
||||||
|
wsConnected: websocket.connected,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 定义实际的 API 调用逻辑
|
||||||
|
const executeRequest = async (): Promise<
|
||||||
|
Record<string, Record<string, { Reason: string; Score: number }>>
|
||||||
|
> => {
|
||||||
|
if (useWs && websocket.connected) {
|
||||||
|
return await websocket.send(
|
||||||
|
'agent_select_modify_init',
|
||||||
|
requestPayload,
|
||||||
|
undefined,
|
||||||
|
data.onProgress,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 否则使用REST API
|
||||||
|
return await request<
|
||||||
{
|
{
|
||||||
'General Goal': string
|
'General Goal': string
|
||||||
stepTask: any
|
stepTask: any
|
||||||
@@ -325,23 +722,36 @@ class Api {
|
|||||||
>({
|
>({
|
||||||
url: '/agentSelectModify_init',
|
url: '/agentSelectModify_init',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: requestPayload,
|
||||||
'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,
|
const rawResponse = await withRetry(executeRequest, {
|
||||||
OutputObject: data.stepTask.OutputObject || data.stepTask.output,
|
maxRetries: 3,
|
||||||
},
|
initialDelayMs: 2000,
|
||||||
|
onRetry: (error, attempt, delay) => {
|
||||||
|
console.warn(
|
||||||
|
`⚠️ [agentSelectModifyInit] 第${attempt}次重试,等待 ${delay}ms...`,
|
||||||
|
error?.message,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 数据转换:后端格式 (维度->agent) → 前端格式 (agent->维度)
|
// WebSocket 返回格式: { data: {...}, generation_id, execution_id }
|
||||||
|
// REST API 返回格式: {...}
|
||||||
|
const response = rawResponse.data || rawResponse
|
||||||
|
|
||||||
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
|
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
|
||||||
|
|
||||||
|
// 确保 response 存在且是有效对象
|
||||||
|
if (!response || typeof response !== 'object' || Array.isArray(response)) {
|
||||||
|
console.warn('[agentSelectModifyInit] 后端返回数据格式异常:', response)
|
||||||
|
return transformedData
|
||||||
|
}
|
||||||
|
|
||||||
for (const [aspect, agents] of Object.entries(response)) {
|
for (const [aspect, agents] of Object.entries(response)) {
|
||||||
// aspect: 维度名称, agents: { agentName: { Reason, Score } }
|
for (const [agentName, scoreInfo] of Object.entries(agents as Record<string, { Reason: string; Score: number }> || {})) {
|
||||||
for (const [agentName, scoreInfo] of Object.entries(agents)) {
|
|
||||||
if (!transformedData[agentName]) {
|
if (!transformedData[agentName]) {
|
||||||
transformedData[agentName] = {}
|
transformedData[agentName] = {}
|
||||||
}
|
}
|
||||||
@@ -355,16 +765,40 @@ class Api {
|
|||||||
return transformedData
|
return transformedData
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加新的评估维度
|
/**
|
||||||
// 定义返回类型(与 Mock 数据格式一致)
|
* 添加新的评估维度
|
||||||
|
*/
|
||||||
agentSelectModifyAddAspect = async (data: {
|
agentSelectModifyAddAspect = async (data: {
|
||||||
aspectList: string[]
|
aspectList: string[]
|
||||||
|
useWebSocket?: boolean
|
||||||
|
onProgress?: (progress: {
|
||||||
|
status: string
|
||||||
|
stage?: string
|
||||||
|
message?: string
|
||||||
|
[key: string]: any
|
||||||
|
}) => void
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
aspectName: string
|
aspectName: string
|
||||||
agentScores: Record<string, { score: number; reason: string }>
|
agentScores: Record<string, { score: number; reason: string }>
|
||||||
}> => {
|
}> => {
|
||||||
// 后端返回:维度 -> agent -> { Reason, Score }
|
const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault
|
||||||
const response = await request<
|
let response: Record<string, Record<string, { Reason: string; Score: number }>>
|
||||||
|
|
||||||
|
// 如果启用WebSocket且已连接,使用WebSocket
|
||||||
|
if (useWs && websocket.connected) {
|
||||||
|
const rawResponse = await websocket.send(
|
||||||
|
'agent_select_modify_add_aspect',
|
||||||
|
{
|
||||||
|
aspectList: data.aspectList,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
data.onProgress,
|
||||||
|
)
|
||||||
|
// WebSocket 返回格式: { data: {...}, generation_id, execution_id }
|
||||||
|
response = rawResponse.data || rawResponse
|
||||||
|
} else {
|
||||||
|
// 否则使用REST API
|
||||||
|
response = await request<
|
||||||
{
|
{
|
||||||
aspectList: string[]
|
aspectList: string[]
|
||||||
},
|
},
|
||||||
@@ -376,14 +810,16 @@ class Api {
|
|||||||
aspectList: data.aspectList,
|
aspectList: data.aspectList,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 获取新添加的维度(最后一个)
|
/**
|
||||||
|
* 获取新添加的维度
|
||||||
|
*/
|
||||||
const newAspect = data.aspectList[data.aspectList.length - 1]
|
const newAspect = data.aspectList[data.aspectList.length - 1]
|
||||||
if (!newAspect) {
|
if (!newAspect) {
|
||||||
throw new Error('aspectList is empty')
|
throw new Error('aspectList is empty')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提取该维度的数据:维度 -> agent -> { Reason, Score } → agent -> { score, reason }
|
|
||||||
const newAspectAgents = response[newAspect]
|
const newAspectAgents = response[newAspect]
|
||||||
const agentScores: Record<string, { score: number; reason: string }> = {}
|
const agentScores: Record<string, { score: number; reason: string }> = {}
|
||||||
|
|
||||||
@@ -401,187 +837,41 @@ class Api {
|
|||||||
agentScores,
|
agentScores,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
// ==================== Mock API(开发阶段使用)====================
|
* 向正在执行的任务追加新步骤
|
||||||
// Mock API 使用与真实 API 相同的数据转换逻辑,确保将来切换无缝
|
* @param executionId 执行ID
|
||||||
|
* @param newSteps 新步骤列表
|
||||||
// Mock: 为每个智能体评分
|
* @returns 追加的步骤数量
|
||||||
mockAgentSelectModifyInit = async (): Promise<
|
*/
|
||||||
Record<string, Record<string, { reason: string; score: number }>>
|
addStepsToExecution = async (executionId: string, newSteps: IRawStepTask[]): Promise<number> => {
|
||||||
> => {
|
if (!websocket.connected) {
|
||||||
// 调用Mock后端数据(维度 -> agent -> { Reason, Score })
|
throw new Error('WebSocket未连接')
|
||||||
const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyInit()
|
|
||||||
|
|
||||||
// 数据转换:后端格式 (维度->agent) → 前端格式 (agent->维度)
|
|
||||||
// 注意:此转换逻辑与真实API完全一致
|
|
||||||
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
|
|
||||||
|
|
||||||
for (const [aspect, agents] of Object.entries(response)) {
|
|
||||||
// aspect: 维度名称, agents: { agentName: { Reason, Score } }
|
|
||||||
for (const [agentName, scoreInfo] of Object.entries(agents)) {
|
|
||||||
if (!transformedData[agentName]) {
|
|
||||||
transformedData[agentName] = {}
|
|
||||||
}
|
|
||||||
transformedData[agentName][aspect] = {
|
|
||||||
reason: scoreInfo.Reason,
|
|
||||||
score: scoreInfo.Score,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return transformedData
|
const rawResponse = await websocket.send('add_steps_to_execution', {
|
||||||
}
|
execution_id: executionId,
|
||||||
|
new_steps: newSteps.map((step) => ({
|
||||||
// Mock: 添加新的评估维度
|
StepName: step.StepName,
|
||||||
mockAgentSelectModifyAddAspect = async (data: {
|
TaskContent: step.TaskContent,
|
||||||
aspectList: string[]
|
InputObject_List: step.InputObject_List,
|
||||||
}): Promise<{
|
OutputObject: step.OutputObject,
|
||||||
aspectName: string
|
AgentSelection: step.AgentSelection,
|
||||||
agentScores: Record<string, { score: number; reason: string }>
|
Collaboration_Brief_frontEnd: step.Collaboration_Brief_frontEnd,
|
||||||
}> => {
|
TaskProcess: step.TaskProcess.map((action) => ({
|
||||||
// 调用Mock后端数据(维度 -> agent -> { Reason, Score })
|
ActionType: action.ActionType,
|
||||||
const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyAddAspect(
|
AgentName: action.AgentName,
|
||||||
data.aspectList,
|
Description: action.Description,
|
||||||
)
|
ID: action.ID,
|
||||||
|
ImportantInput: action.ImportantInput,
|
||||||
// 获取新添加的维度(最后一个)
|
})),
|
||||||
const newAspect = data.aspectList[data.aspectList.length - 1]
|
})),
|
||||||
if (!newAspect) {
|
|
||||||
throw new Error('aspectList is empty')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提取该维度的数据:维度 -> agent -> { Reason, Score } → agent -> { score, reason }
|
|
||||||
// 注意:此转换逻辑与真实API完全一致
|
|
||||||
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: 填充智能体任务流程
|
|
||||||
mockFillStepTaskTaskProcess = async (data: {
|
|
||||||
goal: string
|
|
||||||
stepTask: IApiStepTask
|
|
||||||
agents: string[]
|
|
||||||
}): Promise<IApiStepTask> => {
|
|
||||||
// 调用Mock后端数据(后端原始格式)
|
|
||||||
const response: RawAgentTaskProcessResponse = await mockBackendFillAgentTaskProcess(
|
|
||||||
data.goal,
|
|
||||||
data.stepTask,
|
|
||||||
data.agents,
|
|
||||||
)
|
|
||||||
|
|
||||||
// 数据转换:后端格式 → 前端格式
|
|
||||||
// 注意:此转换逻辑与真实API完全一致
|
|
||||||
|
|
||||||
// 1. 转换颜色格式:[h, s, l] → "hsl(h, s%, l%)"
|
|
||||||
const vec2Hsl = (color: number[]): string => {
|
|
||||||
const [h, s, l] = color
|
|
||||||
return `hsl(${h}, ${s}%, ${l}%)`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 转换 brief.data: { "0": { text, color: [h,s,l] } } → { "0": { text, style: { background } } }
|
|
||||||
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),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 转换 process: { ID, ActionType, AgentName, Description, ImportantInput } → { id, type, agent, description, inputs }
|
|
||||||
const process = (response.TaskProcess || []).map((action) => ({
|
|
||||||
id: action.ID,
|
|
||||||
type: action.ActionType,
|
|
||||||
agent: action.AgentName,
|
|
||||||
description: action.Description,
|
|
||||||
inputs: action.ImportantInput,
|
|
||||||
}))
|
|
||||||
|
|
||||||
// 4. 构建前端格式的 IApiStepTask
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mock: 分支任务大纲
|
|
||||||
mockBranchPlanOutline = async (data: {
|
|
||||||
branch_Number: number
|
|
||||||
Modification_Requirement: string
|
|
||||||
Existing_Steps: string[]
|
|
||||||
Baseline_Completion: number
|
|
||||||
initialInputs: string[]
|
|
||||||
goal: string
|
|
||||||
}): Promise<IRawPlanResponse> => {
|
|
||||||
// 直接调用 Mock API(已经返回 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
|
// WebSocket 返回格式: { data: {...}, generation_id, execution_id }
|
||||||
}
|
// REST API 返回格式: {...}
|
||||||
|
const response = (rawResponse.data || rawResponse) as { added_count: number }
|
||||||
|
|
||||||
// Mock: 填充任务流程
|
return response?.added_count || 0
|
||||||
mockFillStepTask = async (data: { goal: string; stepTask: any }): Promise<any> => {
|
|
||||||
// 直接调用 Mock API(已经返回 IRawStepTask 格式)
|
|
||||||
const response = await mockFillStepTaskAPI({
|
|
||||||
General_Goal: data.goal,
|
|
||||||
stepTask: data.stepTask,
|
|
||||||
})
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mock: 分支任务流程
|
|
||||||
mockBranchTaskProcess = async (data: {
|
|
||||||
branch_Number: number
|
|
||||||
Modification_Requirement: string
|
|
||||||
Existing_Steps: string[]
|
|
||||||
Baseline_Completion: number
|
|
||||||
stepTaskExisting: any
|
|
||||||
goal: string
|
|
||||||
}): Promise<BranchAction[][]> => {
|
|
||||||
// 直接调用 Mock API(已经返回 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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 +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 |
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/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 |
232
frontend/src/components/Notification/Notification.vue
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
<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 {
|
||||||
|
Close,
|
||||||
|
SuccessFilled as IconSuccess,
|
||||||
|
WarningFilled as IconWarning,
|
||||||
|
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-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>
|
||||||
100
frontend/src/components/TaskContentEditor/index.vue
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import type { IRawStepTask } from '@/stores'
|
||||||
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
task: IRawStepTask
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'save', taskId: string, content: string): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const isEditing = ref(false)
|
||||||
|
const editingContent = ref('')
|
||||||
|
|
||||||
|
const startEditing = () => {
|
||||||
|
editingContent.value = props.task.TaskContent || ''
|
||||||
|
isEditing.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const save = () => {
|
||||||
|
const trimmed = editingContent.value.trim()
|
||||||
|
emit('save', props.task.Id!, trimmed)
|
||||||
|
isEditing.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
isEditing.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeydown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault()
|
||||||
|
save()
|
||||||
|
} else if (event.key === 'Escape') {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="isEditing" class="w-full">
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<el-input
|
||||||
|
v-model="editingContent"
|
||||||
|
type="textarea"
|
||||||
|
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||||
|
placeholder="请输入任务内容"
|
||||||
|
@keydown="handleKeydown"
|
||||||
|
class="task-content-editor"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<svg-icon
|
||||||
|
icon-class="Check"
|
||||||
|
size="20px"
|
||||||
|
color="#328621"
|
||||||
|
class="cursor-pointer mr-4"
|
||||||
|
@click="save"
|
||||||
|
title="保存"
|
||||||
|
/>
|
||||||
|
<svg-icon
|
||||||
|
icon-class="Cancel"
|
||||||
|
size="20px"
|
||||||
|
color="#8e0707"
|
||||||
|
class="cursor-pointer mr-1"
|
||||||
|
@click="cancel"
|
||||||
|
title="取消"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else @dblclick="startEditing" class="w-full cursor-pointer">
|
||||||
|
<slot name="display">
|
||||||
|
<div class="text-[14px] text-[var(--color-text-secondary)] task-content-display">
|
||||||
|
{{ task.TaskContent }}
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
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 notification = notifications.value.find((n) => n.id === id)
|
||||||
|
if (notification) {
|
||||||
|
const index = notifications.value.indexOf(notification)
|
||||||
|
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,11 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed, reactive, nextTick } from 'vue'
|
import { ref, onMounted, onUnmounted, 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 { useNotification } from '@/composables/useNotification'
|
||||||
import AssignmentButton from './TaskTemplate/TaskSyllabus/components/AssignmentButton.vue'
|
import AssignmentButton from './TaskTemplate/TaskSyllabus/components/AssignmentButton.vue'
|
||||||
|
import { withRetry } from '@/utils/retry'
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'search-start'): void
|
(e: 'search-start'): void
|
||||||
(e: 'search', value: string): void
|
(e: 'search', value: string): void
|
||||||
@@ -13,11 +15,17 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const agentsStore = useAgentsStore()
|
const agentsStore = useAgentsStore()
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
|
const { success, warning, error: notifyError } = useNotification()
|
||||||
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 hasAutoSearched = ref(false)
|
||||||
const isExpanded = ref(false) // 控制搜索框是否展开
|
const isExpanded = ref(false)
|
||||||
|
const isFillingSteps = ref(false)
|
||||||
|
const isStopping = ref(false)
|
||||||
|
const isStopPending = ref(false)
|
||||||
|
const currentStepAbortController = ref<{ cancel: () => void } | null>(null)
|
||||||
|
const currentGenerationId = ref('')
|
||||||
|
|
||||||
// 解析URL参数
|
// 解析URL参数
|
||||||
function getUrlParam(param: string): string | null {
|
function getUrlParam(param: string): string | null {
|
||||||
@@ -29,7 +37,6 @@ const planReady = computed(() => {
|
|||||||
return agentsStore.agentRawPlan.data !== undefined
|
return agentsStore.agentRawPlan.data !== undefined
|
||||||
})
|
})
|
||||||
const openAgentAllocationDialog = () => {
|
const openAgentAllocationDialog = () => {
|
||||||
console.log('打开智能体分配弹窗')
|
|
||||||
agentsStore.openAgentAllocationDialog()
|
agentsStore.openAgentAllocationDialog()
|
||||||
}
|
}
|
||||||
// 自动搜索函数
|
// 自动搜索函数
|
||||||
@@ -59,23 +66,66 @@ const taskContainerRef = ref<HTMLDivElement | null>(null)
|
|||||||
// 处理失去焦点事件
|
// 处理失去焦点事件
|
||||||
function handleBlur() {
|
function handleBlur() {
|
||||||
isFocus.value = false
|
isFocus.value = false
|
||||||
// 延迟收起搜索框,以便点击按钮等操作
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isExpanded.value = false
|
isExpanded.value = false
|
||||||
// 强制重置文本区域高度到最小行数
|
|
||||||
resetTextareaHeight()
|
resetTextareaHeight()
|
||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 预加载所有任务的智能体评分数据
|
||||||
|
async function preloadAllTaskAgentScores(outlineData: any, goal: string) {
|
||||||
|
const tasks = outlineData['Collaboration Process'] || []
|
||||||
|
|
||||||
|
if (tasks.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const task of tasks) {
|
||||||
|
// 检查是否已停止
|
||||||
|
if (agentsStore.isStopping || agentsStore.hasStoppedFilling) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保任务有唯一ID
|
||||||
|
if (!task.Id) {
|
||||||
|
task.Id = `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用初始化接口获取评分数据
|
||||||
|
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] || {}) : []
|
||||||
|
|
||||||
|
agentsStore.setTaskScoreData(task.Id, {
|
||||||
|
aspectList,
|
||||||
|
agentScores
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ 任务 "${task.StepName}" 的评分数据预加载失败:`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 重置文本区域高度到最小行数
|
// 重置文本区域高度到最小行数
|
||||||
function resetTextareaHeight() {
|
function resetTextareaHeight() {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// 修复:使用更可靠的方式获取textarea元素
|
// 获取textarea元素
|
||||||
const textarea =
|
const textarea =
|
||||||
document.querySelector('#task-container .el-textarea__inner') ||
|
document.querySelector('#task-container .el-textarea__inner') ||
|
||||||
document.querySelector('#task-container textarea')
|
document.querySelector('#task-container textarea')
|
||||||
|
|
||||||
if (textarea) {
|
if (textarea instanceof HTMLElement) {
|
||||||
// 强制设置最小高度
|
// 强制设置最小高度
|
||||||
textarea.style.height = 'auto'
|
textarea.style.height = 'auto'
|
||||||
textarea.style.minHeight = '56px'
|
textarea.style.minHeight = '56px'
|
||||||
@@ -84,27 +134,171 @@ function resetTextareaHeight() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 停止填充数据的处理函数
|
||||||
|
async function handleStop() {
|
||||||
|
// 检查是否有正在进行的生成任务
|
||||||
|
if (!isFillingSteps.value) {
|
||||||
|
warning('提示', '没有正在进行的生成任务')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先设置停止状态(立即显示"停止中...")
|
||||||
|
agentsStore.setIsStopping(true)
|
||||||
|
isStopping.value = true
|
||||||
|
isStopPending.value = true
|
||||||
|
success('提示', '正在停止,请稍候...')
|
||||||
|
|
||||||
|
// 发送停止请求(不等待响应,后端设置 should_stop = True)
|
||||||
|
if (websocket.connected && currentGenerationId.value) {
|
||||||
|
websocket.send('stop_generation', {
|
||||||
|
generation_id: currentGenerationId.value
|
||||||
|
}).then((result: any) => {
|
||||||
|
console.log('停止生成响应:', result)
|
||||||
|
}).catch((error: any) => {
|
||||||
|
console.log('停止生成请求失败(可能已经停止):', error?.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 不清空 currentGenerationId,让 fillStepTask 循环检查 isStopping 来停止
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听后端发送的停止完成事件(备用,如果后端有发送)
|
||||||
|
function onGenerationStopped() {
|
||||||
|
isStopping.value = false
|
||||||
|
isStopPending.value = false
|
||||||
|
currentGenerationId.value = ''
|
||||||
|
success('成功', '已停止生成')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理按钮点击事件
|
||||||
|
function handleButtonClick() {
|
||||||
|
if (isFillingSteps.value) {
|
||||||
|
handleStop()
|
||||||
|
} else {
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理搜索函数
|
||||||
async function handleSearch() {
|
async function handleSearch() {
|
||||||
try {
|
|
||||||
triggerOnFocus.value = false
|
triggerOnFocus.value = false
|
||||||
if (!searchValue.value) {
|
if (!searchValue.value) {
|
||||||
ElMessage.warning('请输入搜索内容')
|
warning('提示', '请输入搜索内容')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
emit('search-start')
|
emit('search-start')
|
||||||
|
|
||||||
|
// 重置所有状态(处理可能的上一次未完成的停止操作)
|
||||||
|
isStopping.value = false
|
||||||
|
isStopPending.value = false
|
||||||
|
agentsStore.setIsStopping(false)
|
||||||
|
agentsStore.setHasStoppedFilling(false)
|
||||||
|
|
||||||
agentsStore.resetAgent()
|
agentsStore.resetAgent()
|
||||||
agentsStore.setAgentRawPlan({ loading: true })
|
agentsStore.setAgentRawPlan({ loading: true })
|
||||||
const data = await api.generateBasePlan({
|
|
||||||
|
// 重置 generation_id
|
||||||
|
currentGenerationId.value = ''
|
||||||
|
|
||||||
|
// 获取大纲
|
||||||
|
const response = await api.generateBasePlan({
|
||||||
goal: searchValue.value,
|
goal: searchValue.value,
|
||||||
inputs: []
|
inputs: []
|
||||||
})
|
})
|
||||||
data['Collaboration Process'] = changeBriefs(data['Collaboration Process'])
|
|
||||||
agentsStore.setAgentRawPlan({ data })
|
// WebSocket 返回格式: { data: {...}, generation_id, execution_id }
|
||||||
console.log('agentsStore.agentRawPlan', agentsStore.agentRawPlan)
|
// REST API 返回格式: {...}
|
||||||
|
const outlineData = response.data || response
|
||||||
|
|
||||||
|
// 保存 generation_id
|
||||||
|
if (response && response.generation_id) {
|
||||||
|
currentGenerationId.value = response.generation_id
|
||||||
|
console.log('📋 保存 generation_id:', currentGenerationId.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理简报数据格式
|
||||||
|
outlineData['Collaboration Process'] = changeBriefs(outlineData['Collaboration Process'])
|
||||||
|
|
||||||
|
// 立即显示大纲
|
||||||
|
agentsStore.setAgentRawPlan({ data: outlineData, loading: false })
|
||||||
emit('search', searchValue.value)
|
emit('search', searchValue.value)
|
||||||
|
|
||||||
|
// 预加载所有任务的智能体评分数据
|
||||||
|
preloadAllTaskAgentScores(outlineData, searchValue.value)
|
||||||
|
|
||||||
|
// 填充步骤详情
|
||||||
|
isFillingSteps.value = true
|
||||||
|
const steps = outlineData['Collaboration Process'] || []
|
||||||
|
|
||||||
|
// 保存 generation_id 到本地变量,用于 fillStepTask 调用
|
||||||
|
// 这样即使前端停止时清空了 currentGenerationId,当前的 fillStepTask 仍能正确停止
|
||||||
|
const fillTaskGenerationId = currentGenerationId.value
|
||||||
|
|
||||||
|
// 串行填充所有步骤的详情
|
||||||
|
try {
|
||||||
|
for (const step of steps) {
|
||||||
|
// 检查是否已停止
|
||||||
|
if (!isFillingSteps.value || agentsStore.isStopping) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
await withRetry(
|
||||||
|
async () => {
|
||||||
|
const detailedStep = await api.fillStepTask({
|
||||||
|
goal: searchValue.value,
|
||||||
|
stepTask: {
|
||||||
|
StepName: step.StepName,
|
||||||
|
TaskContent: step.TaskContent,
|
||||||
|
InputObject_List: step.InputObject_List,
|
||||||
|
OutputObject: step.OutputObject,
|
||||||
|
},
|
||||||
|
generation_id: fillTaskGenerationId,
|
||||||
|
})
|
||||||
|
updateStepDetail(step.StepName, detailedStep)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
maxRetries: 2, // 减少重试次数,因为是串行填充
|
||||||
|
initialDelayMs: 1000, // 使用较小的延迟
|
||||||
|
shouldRetry: () => isFillingSteps.value && !agentsStore.isStopping, // 可取消的重试
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
// 重置状态(确保即使出错也会执行)
|
||||||
triggerOnFocus.value = true
|
triggerOnFocus.value = true
|
||||||
agentsStore.setAgentRawPlan({ loading: false })
|
if (isStopPending.value) {
|
||||||
|
isStopping.value = false
|
||||||
|
isStopPending.value = false
|
||||||
|
agentsStore.setIsStopping(false)
|
||||||
|
agentsStore.setHasStoppedFilling(true)
|
||||||
|
}
|
||||||
|
isFillingSteps.value = false
|
||||||
|
currentStepAbortController.value = null
|
||||||
|
// 只有在没有停止请求时才清空 generation_id
|
||||||
|
if (!isStopPending.value) {
|
||||||
|
currentGenerationId.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//更新单个步骤的详情
|
||||||
|
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]) {
|
||||||
|
// 保持响应式更新
|
||||||
|
Object.assign(collaborationProcess[index], {
|
||||||
|
AgentSelection: detailedStep.AgentSelection || [],
|
||||||
|
TaskProcess: detailedStep.TaskProcess || [],
|
||||||
|
Collaboration_Brief_frontEnd: detailedStep.Collaboration_Brief_frontEnd || {
|
||||||
|
template: '',
|
||||||
|
data: {}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +306,7 @@ 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
|
// 调用回调函数返回建议列表
|
||||||
cb(results.map(item => ({ value: item })))
|
cb(results.map(item => ({ value: item })))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,6 +319,12 @@ const createFilter = (queryString: string) => {
|
|||||||
// 组件挂载时检查URL参数
|
// 组件挂载时检查URL参数
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
autoSearchFromUrl()
|
autoSearchFromUrl()
|
||||||
|
websocket.on('generation_stopped', onGenerationStopped)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 组件卸载时移除事件监听
|
||||||
|
onUnmounted(() => {
|
||||||
|
websocket.off('generation_stopped', onGenerationStopped)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -168,21 +368,27 @@ onMounted(() => {
|
|||||||
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 && !isStopping ? '点击停止生成' : '点击搜索任务'"
|
||||||
circle
|
circle
|
||||||
:loading="agentsStore.agentRawPlan.loading"
|
:loading="agentsStore.agentRawPlan.loading || isStopping"
|
||||||
:disabled="!searchValue"
|
:disabled="!searchValue || isStopping"
|
||||||
@click.stop="handleSearch"
|
@click.stop="handleButtonClick"
|
||||||
>
|
>
|
||||||
<SvgIcon
|
<SvgIcon
|
||||||
v-if="!agentsStore.agentRawPlan.loading"
|
v-if="!agentsStore.agentRawPlan.loading && !isFillingSteps && !isStopping"
|
||||||
icon-class="paper-plane"
|
icon-class="paper-plane"
|
||||||
size="18px"
|
size="18px"
|
||||||
color="#ffffff"
|
color="#ffffff"
|
||||||
/>
|
/>
|
||||||
|
<SvgIcon
|
||||||
|
v-if="!agentsStore.agentRawPlan.loading && isFillingSteps && !isStopping"
|
||||||
|
icon-class="stoprunning"
|
||||||
|
size="30px"
|
||||||
|
color="#ffffff"
|
||||||
|
/>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<AssignmentButton v-dev-only v-if="planReady" @click="openAgentAllocationDialog" />
|
<AssignmentButton v-if="planReady" @click="openAgentAllocationDialog" />
|
||||||
</div>
|
</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
@@ -197,7 +403,6 @@ onMounted(() => {
|
|||||||
width: 40%;
|
width: 40%;
|
||||||
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));
|
|
||||||
background: linear-gradient(var(--color-bg-taskbar), var(--color-bg-taskbar)) padding-box,
|
background: linear-gradient(var(--color-bg-taskbar), var(--color-bg-taskbar)) padding-box,
|
||||||
linear-gradient(to right, #00c8d2, #315ab4) border-box;
|
linear-gradient(to right, #00c8d2, #315ab4) border-box;
|
||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
@@ -216,8 +421,6 @@ onMounted(() => {
|
|||||||
:deep(.el-autocomplete .el-textarea .el-textarea__inner) {
|
:deep(.el-autocomplete .el-textarea .el-textarea__inner) {
|
||||||
overflow-y: auto !important;
|
overflow-y: auto !important;
|
||||||
min-height: 56px !important;
|
min-height: 56px !important;
|
||||||
// overflow-y: hidden;
|
|
||||||
// background-color: black;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,58 +529,4 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.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,16 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { ElNotification } from 'element-plus'
|
|
||||||
import { pick } from 'lodash'
|
import { pick } from 'lodash'
|
||||||
|
|
||||||
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 { readConfig } from '@/utils/readJson.ts'
|
import { readConfig } from '@/utils/readJson.ts'
|
||||||
|
import { useNotification } from '@/composables/useNotification.ts'
|
||||||
import AgentRepoList from './AgentRepoList.vue'
|
import AgentRepoList from './AgentRepoList.vue'
|
||||||
|
|
||||||
|
const { error, success } = useNotification()
|
||||||
|
|
||||||
const agentsStore = useAgentsStore()
|
const agentsStore = useAgentsStore()
|
||||||
|
|
||||||
// 如果agentsStore.agents不存在就读取默认配置的json文件
|
// 如果agentsStore.agents不存在就读取默认配置的json文件
|
||||||
@@ -39,82 +39,41 @@ const handleFileSelect = (event: Event) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在validateApiConfig函数中添加更详细的日志
|
// 验证API配置:三个字段必须同时存在或同时不存在
|
||||||
const validateApiConfig = (agent: any) => {
|
const validateApiConfig = (agent: any) => {
|
||||||
const hasApiUrl = 'apiUrl' in agent
|
const hasApiUrl = 'apiUrl' in agent
|
||||||
const hasApiKey = 'apiKey' in agent
|
const hasApiKey = 'apiKey' in agent
|
||||||
const hasApiModel = 'apiModel' in agent
|
const hasApiModel = 'apiModel' in agent
|
||||||
|
|
||||||
// 三个字段必须同时存在或同时不存在
|
return hasApiUrl === hasApiKey && hasApiKey === hasApiModel
|
||||||
if (hasApiUrl !== hasApiKey || hasApiKey !== hasApiModel) {
|
|
||||||
console.error('❌ API配置不完整:', {
|
|
||||||
agentName: agent.Name,
|
|
||||||
missingFields: {
|
|
||||||
apiUrl: !hasApiUrl,
|
|
||||||
apiKey: !hasApiKey,
|
|
||||||
apiModel: !hasApiModel
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasApiUrl && hasApiKey && hasApiModel) {
|
|
||||||
console.log('✅ API配置完整,将使用自定义API配置:', {
|
|
||||||
apiUrl: agent.apiUrl,
|
|
||||||
apiKey: agent.apiKey ? '***' + agent.apiKey.slice(-4) : '未设置',
|
|
||||||
apiModel: agent.apiModel
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
console.log('ℹ️ 未设置API配置,将使用默认URL配置')
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const readFileContent = (file: File) => {
|
const readFileContent = (file: File) => {
|
||||||
console.log('📁 开始读取文件:', file.name, '大小:', file.size, '类型:', file.type)
|
|
||||||
|
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.onload = e => {
|
reader.onload = e => {
|
||||||
try {
|
try {
|
||||||
console.log('📄 文件读取完成,开始解析JSON')
|
|
||||||
const content = e.target?.result as string
|
const content = e.target?.result as string
|
||||||
const jsonData = JSON.parse(content)
|
const jsonData = JSON.parse(content)
|
||||||
|
|
||||||
console.log('🔍 解析的JSON数据:', jsonData)
|
|
||||||
console.log(
|
|
||||||
'📊 数据类型:',
|
|
||||||
Array.isArray(jsonData) ? '数组' : '对象',
|
|
||||||
'长度:',
|
|
||||||
Array.isArray(jsonData) ? jsonData.length : 'N/A'
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!Array.isArray(jsonData)) {
|
if (!Array.isArray(jsonData)) {
|
||||||
console.error('❌ JSON格式错误: 必须为数组格式')
|
|
||||||
ElMessage.error('JSON格式错误: 必须为数组格式')
|
ElMessage.error('JSON格式错误: 必须为数组格式')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🔍 开始验证智能体数据...')
|
const validAgents = jsonData.filter(agent => {
|
||||||
const validAgents = jsonData.filter((agent, index) => {
|
|
||||||
console.log(`🔍 验证第${index + 1}个智能体:`, agent.Name || '未命名')
|
|
||||||
// 验证必需字段
|
// 验证必需字段
|
||||||
if (!agent.Name || typeof agent.Name !== 'string') {
|
if (!agent.Name || typeof agent.Name !== 'string') {
|
||||||
console.error(`❌ 智能体${index + 1}缺少Name字段或格式错误`)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (!agent.Icon || typeof agent.Icon !== 'string') {
|
if (!agent.Icon || typeof agent.Icon !== 'string') {
|
||||||
console.error(`❌ 智能体${index + 1}缺少Icon字段或格式错误`)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (!agent.Profile || typeof agent.Profile !== 'string') {
|
if (!agent.Profile || typeof agent.Profile !== 'string') {
|
||||||
console.error(`❌ 智能体${index + 1}缺少Profile字段或格式错误`)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证API配置
|
// 验证API配置
|
||||||
if (!validateApiConfig(agent)) {
|
if (!validateApiConfig(agent)) {
|
||||||
console.error(`❌ 智能体${index + 1}API配置验证失败`)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,31 +89,24 @@ const readFileContent = (file: File) => {
|
|||||||
apiKey: agent.apiKey,
|
apiKey: agent.apiKey,
|
||||||
apiModel: agent.apiModel
|
apiModel: agent.apiModel
|
||||||
}))
|
}))
|
||||||
|
|
||||||
agentsStore.setAgents(processedAgents)
|
agentsStore.setAgents(processedAgents)
|
||||||
|
|
||||||
// 调用API
|
// 调用API
|
||||||
api
|
api
|
||||||
.setAgents(processedAgents)
|
.setAgents(processedAgents)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('✅ 后端API调用成功')
|
success('智能体上传成功')
|
||||||
ElMessage.success('智能体上传成功')
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(() => {
|
||||||
console.error('❌ 后端API调用失败:', error)
|
error('智能体上传失败')
|
||||||
ElMessage.error('智能体上传失败')
|
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error('❌ JSON解析错误:', error)
|
error('JSON解析错误')
|
||||||
ElMessage.error('JSON解析错误')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
reader.onerror = () => {
|
||||||
reader.onerror = error => {
|
error('文件读取错误')
|
||||||
console.error('❌ 文件读取错误:', error)
|
|
||||||
ElMessage.error('文件读取错误')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.readAsText(file)
|
reader.readAsText(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,39 +19,34 @@ const props = defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'open-edit', stepId: string, processId: string): void
|
|
||||||
(e: 'save-edit', stepId: string, processId: string, value: string): void
|
(e: 'save-edit', stepId: string, processId: string, value: string): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// 🔄 从 currentTask 中获取数据(与分支切换联动)
|
//从 currentTask 中获取数据
|
||||||
const currentTaskProcess = computed(() => {
|
const currentTaskProcess = computed(() => {
|
||||||
// ✅ 优先使用 currentTask(包含分支切换后的数据)
|
|
||||||
const currentTask = agentsStore.currentTask
|
const currentTask = agentsStore.currentTask
|
||||||
if (currentTask && currentTask.Id === props.step.Id && currentTask.TaskProcess) {
|
if (currentTask && currentTask.Id === props.step.Id && currentTask.TaskProcess) {
|
||||||
return currentTask.TaskProcess
|
return currentTask.TaskProcess
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⚠️ 降级:从 agentRawPlan 中获取原始数据(不受分支切换影响)
|
//从 agentRawPlan 中获取原始数据
|
||||||
const collaborationProcess = agentsStore.agentRawPlan.data?.['Collaboration Process'] || []
|
const collaborationProcess = agentsStore.agentRawPlan.data?.['Collaboration Process'] || []
|
||||||
const rawData = collaborationProcess.find((task: any) => task.Id === props.step.Id)
|
const rawData = collaborationProcess.find((task: any) => task.Id === props.step.Id)
|
||||||
return rawData?.TaskProcess || []
|
return rawData?.TaskProcess || []
|
||||||
})
|
})
|
||||||
|
|
||||||
// 当前正在编辑的process ID
|
|
||||||
const editingProcessId = ref<string | null>(null)
|
const editingProcessId = ref<string | null>(null)
|
||||||
// 编辑框的值
|
|
||||||
const editValue = ref('')
|
const editValue = ref('')
|
||||||
// 鼠标悬停的process ID
|
|
||||||
const hoverProcessId = ref<string | null>(null)
|
const hoverProcessId = ref<string | null>(null)
|
||||||
|
|
||||||
// 🆕 处理卡片点击事件(非编辑模式下)
|
// 处理卡片点击事件
|
||||||
function handleCardClick() {
|
function handleCardClick() {
|
||||||
// 如果正在编辑,不处理点击
|
// 如果正在编辑,不处理点击
|
||||||
if (editingProcessId.value) return
|
if (editingProcessId.value) return
|
||||||
|
|
||||||
// 设置当前任务,与任务大纲联动
|
// 设置当前任务,与任务大纲联动
|
||||||
if (props.step.Id) {
|
if (props.step.Id) {
|
||||||
agentsStore.setCurrentTask(props.step)
|
agentsStore.setCurrentTask(props.step as any)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,40 +57,25 @@ function isDarkMode(): boolean {
|
|||||||
|
|
||||||
// 获取颜色浅两号的函数
|
// 获取颜色浅两号的函数
|
||||||
function getLightColor(color: string, level: number = 2): string {
|
function getLightColor(color: string, level: number = 2): string {
|
||||||
if (!color || color.length !== 7 || color[0] !== '#') return color
|
return adjustColor(color, level * 20)
|
||||||
|
|
||||||
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 {
|
function getDarkColor(color: string, level: number = 2): string {
|
||||||
|
return adjustColor(color, -level * 20)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用的颜色调整函数(提取重复逻辑)
|
||||||
|
function adjustColor(color: string, amount: number): string {
|
||||||
if (!color || color.length !== 7 || color[0] !== '#') return color
|
if (!color || color.length !== 7 || color[0] !== '#') return color
|
||||||
|
|
||||||
const r = parseInt(color.substr(1, 2), 16)
|
const r = Math.min(255, Math.max(0, parseInt(color.substr(1, 2), 16) + amount))
|
||||||
const g = parseInt(color.substr(3, 2), 16)
|
const g = Math.min(255, Math.max(0, parseInt(color.substr(3, 2), 16) + amount))
|
||||||
const b = parseInt(color.substr(5, 2), 16)
|
const b = Math.min(255, Math.max(0, parseInt(color.substr(5, 2), 16) + amount))
|
||||||
|
|
||||||
// 降低亮度(深两号)
|
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b
|
||||||
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)
|
.toString(16)
|
||||||
.padStart(2, '0')}${Math.round(newB).toString(16).padStart(2, '0')}`
|
.padStart(2, '0')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据主题模式获取调整后的颜色
|
// 根据主题模式获取调整后的颜色
|
||||||
@@ -117,11 +97,10 @@ function handleMouseLeave() {
|
|||||||
hoverProcessId.value = null
|
hoverProcessId.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理双击编辑(针对单个process)
|
// 处理双击编辑
|
||||||
function handleDblClick(processId: string, currentDescription: string) {
|
function handleDblClick(processId: string, currentDescription: string) {
|
||||||
editingProcessId.value = processId
|
editingProcessId.value = processId
|
||||||
editValue.value = currentDescription
|
editValue.value = currentDescription
|
||||||
emit('open-edit', props.step.Id || '', processId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理保存编辑
|
// 处理保存编辑
|
||||||
@@ -264,7 +243,6 @@ function handleCancel() {
|
|||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
|
||||||
.edit-card {
|
.edit-card {
|
||||||
//background: #f0f2f5;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
@@ -284,13 +262,6 @@ function handleCancel() {
|
|||||||
color: var(--color-text-taskbar);
|
color: var(--color-text-taskbar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,10 +272,6 @@ function handleCancel() {
|
|||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
&.hovered {
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.separator {
|
.separator {
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, nextTick, onMounted } from 'vue'
|
import { ref, computed, nextTick, onMounted } from 'vue'
|
||||||
import { VueFlow, useVueFlow, Handle, Position, type Node, type Edge } from '@vue-flow/core'
|
import { VueFlow, useVueFlow, Handle, Position, type Node, type Edge } from '@vue-flow/core'
|
||||||
import { Controls } from '@vue-flow/controls'
|
|
||||||
import '@vue-flow/core/dist/style.css'
|
import '@vue-flow/core/dist/style.css'
|
||||||
import '@vue-flow/core/dist/theme-default.css'
|
import '@vue-flow/core/dist/theme-default.css'
|
||||||
import '@vue-flow/controls/dist/style.css'
|
|
||||||
import {
|
import {
|
||||||
useAgentsStore,
|
useAgentsStore,
|
||||||
useSelectionStore,
|
useSelectionStore,
|
||||||
@@ -22,7 +20,7 @@ const agentsStore = useAgentsStore()
|
|||||||
const selectionStore = useSelectionStore()
|
const selectionStore = useSelectionStore()
|
||||||
const { onConnect, addEdges, fitView: fit } = useVueFlow()
|
const { onConnect, addEdges, fitView: fit } = useVueFlow()
|
||||||
|
|
||||||
// 🆕 直接从 store 获取当前任务和任务过程数据
|
// 直接从 store 获取当前任务和任务过程数据
|
||||||
const currentTask = computed(() => agentsStore.currentTask)
|
const currentTask = computed(() => agentsStore.currentTask)
|
||||||
const taskProcess = computed(() => agentsStore.currentTask?.TaskProcess ?? [])
|
const taskProcess = computed(() => agentsStore.currentTask?.TaskProcess ?? [])
|
||||||
|
|
||||||
@@ -50,12 +48,10 @@ let isSyncing = false
|
|||||||
//初始化标记,避免重复初始化
|
//初始化标记,避免重复初始化
|
||||||
const BRANCHES_INIT_KEY_PREFIX = 'plan-task-branches-initialized-'
|
const BRANCHES_INIT_KEY_PREFIX = 'plan-task-branches-initialized-'
|
||||||
|
|
||||||
// 🆕 最后选中的分支ID
|
//最后选中的分支ID
|
||||||
const LAST_SELECTED_BRANCH_KEY = 'plan-task-last-selected-branch'
|
const LAST_SELECTED_BRANCH_KEY = 'plan-task-last-selected-branch'
|
||||||
|
|
||||||
// ==================== 辅助函数定义 ====================
|
// 获取分支的所有节点
|
||||||
|
|
||||||
// 🆕 获取分支的所有节点(只沿着分支创建方向遍历:right handle连接)
|
|
||||||
const getAllBranchNodes = (startNodeId: string): string[] => {
|
const getAllBranchNodes = (startNodeId: string): string[] => {
|
||||||
const visited = new Set<string>()
|
const visited = new Set<string>()
|
||||||
const toVisit: string[] = [startNodeId]
|
const toVisit: string[] = [startNodeId]
|
||||||
@@ -68,14 +64,14 @@ const getAllBranchNodes = (startNodeId: string): string[] => {
|
|||||||
}
|
}
|
||||||
visited.add(currentId)
|
visited.add(currentId)
|
||||||
|
|
||||||
// 🆕 只查找通过 right handle 连接的后续节点(分支创建方向)
|
// 只查找通过 right handle 连接的后续节点
|
||||||
const outgoingEdges = edges.value.filter(
|
const outgoingEdges = edges.value.filter(
|
||||||
edge => edge.source === currentId && edge.sourceHandle === 'right'
|
edge => edge.source === currentId && edge.sourceHandle === 'right'
|
||||||
)
|
)
|
||||||
|
|
||||||
for (const edge of outgoingEdges) {
|
for (const edge of outgoingEdges) {
|
||||||
const targetNodeId = edge.target
|
const targetNodeId = edge.target
|
||||||
// 只访问分支节点(不包含主流程节点和根节点)
|
// 只访问分支节点
|
||||||
if (
|
if (
|
||||||
!visited.has(targetNodeId) &&
|
!visited.has(targetNodeId) &&
|
||||||
targetNodeId !== 'root' &&
|
targetNodeId !== 'root' &&
|
||||||
@@ -89,7 +85,7 @@ const getAllBranchNodes = (startNodeId: string): string[] => {
|
|||||||
return Array.from(visited)
|
return Array.from(visited)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🆕 获取从指定节点回溯到根节点的路径(只包含主流程节点)
|
//获取从指定节点回溯到根节点的路径(只包含主流程节点)
|
||||||
const getPathToRoot = (targetNodeId: string): string[] => {
|
const getPathToRoot = (targetNodeId: string): string[] => {
|
||||||
const path: string[] = []
|
const path: string[] = []
|
||||||
const visited = new Set<string>()
|
const visited = new Set<string>()
|
||||||
@@ -126,7 +122,7 @@ const getPathToRoot = (targetNodeId: string): string[] => {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🆕 向上回溯分支的父节点链(只包含分支节点)
|
// 向上回溯分支的父节点链(只包含分支节点)
|
||||||
const getBranchParentChain = (targetNodeId: string): string[] => {
|
const getBranchParentChain = (targetNodeId: string): string[] => {
|
||||||
const parentChain: string[] = []
|
const parentChain: string[] = []
|
||||||
let currentId = targetNodeId
|
let currentId = targetNodeId
|
||||||
@@ -153,8 +149,7 @@ const getBranchParentChain = (targetNodeId: string): string[] => {
|
|||||||
|
|
||||||
return parentChain
|
return parentChain
|
||||||
}
|
}
|
||||||
|
// 初始化流程图
|
||||||
// ==================== 初始化流程图 ====================
|
|
||||||
const initializeFlow = () => {
|
const initializeFlow = () => {
|
||||||
if (!currentTask.value) {
|
if (!currentTask.value) {
|
||||||
nodes.value = []
|
nodes.value = []
|
||||||
@@ -162,20 +157,17 @@ const initializeFlow = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🆕 获取当前流程数据
|
// 获取当前流程数据
|
||||||
const taskStepId = currentTask.value.Id
|
const taskStepId = currentTask.value.Id
|
||||||
// 🆕 获取当前 agent 组合(用于区分不同 agent 组合的分支数据)
|
// 获取当前 agent 组合(用于区分不同 agent 组合的分支数据)
|
||||||
const currentAgents = currentTask.value.AgentSelection || []
|
const currentAgents = currentTask.value.AgentSelection || []
|
||||||
|
|
||||||
// 🆕 直接使用 taskProcess.value 作为数据源
|
// 直接使用 taskProcess.value 作为数据源
|
||||||
// 注意:虽然 currentTask.id 相同,但不同的 agent 组合对应的 agent 任务过程不同
|
|
||||||
// taskProcess.value 从 currentTask.TaskProcess 获取,能正确反映当前 agent 组合的任务过程
|
|
||||||
const currentTaskProcess = taskProcess.value
|
const currentTaskProcess = taskProcess.value
|
||||||
console.log('currentTaskProcess:', currentTaskProcess)
|
// 立即保存为副本(深拷贝)
|
||||||
// 🆕 立即保存为副本(深拷贝)
|
|
||||||
const taskProcessCopy = JSON.parse(JSON.stringify(currentTaskProcess))
|
const taskProcessCopy = JSON.parse(JSON.stringify(currentTaskProcess))
|
||||||
|
|
||||||
// 🆕 使用副本数据创建节点(而不是 currentTaskProcess)
|
// 使用副本数据创建节点(而不是 currentTaskProcess)
|
||||||
const taskProcessForNodes = taskProcessCopy
|
const taskProcessForNodes = taskProcessCopy
|
||||||
const newNodes: Node[] = []
|
const newNodes: Node[] = []
|
||||||
const newEdges: Edge[] = []
|
const newEdges: Edge[] = []
|
||||||
@@ -233,7 +225,14 @@ const initializeFlow = () => {
|
|||||||
targetHandle: 'left',
|
targetHandle: 'left',
|
||||||
type: 'smoothstep',
|
type: 'smoothstep',
|
||||||
animated: true,
|
animated: true,
|
||||||
style: { stroke: '#43a8aa', strokeWidth: 2 }
|
style: { stroke: '#43a8aa', strokeWidth: 2 },
|
||||||
|
markerEnd: {
|
||||||
|
type: 'arrow' as any,
|
||||||
|
color: '#43a8aa',
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
strokeWidth: 2
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,44 +246,48 @@ const initializeFlow = () => {
|
|||||||
targetHandle: 'left',
|
targetHandle: 'left',
|
||||||
type: 'smoothstep',
|
type: 'smoothstep',
|
||||||
animated: true,
|
animated: true,
|
||||||
style: { stroke: '#43a8aa', strokeWidth: 2 }
|
style: { stroke: '#43a8aa', strokeWidth: 2 },
|
||||||
|
markerEnd: {
|
||||||
|
type: 'arrow' as any,
|
||||||
|
color: '#43a8aa',
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
strokeWidth: 2
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes.value = newNodes
|
nodes.value = newNodes
|
||||||
edges.value = newEdges
|
edges.value = newEdges
|
||||||
|
|
||||||
// 📂 从 store 恢复已保存的任务过程分支数据
|
// 从 store 恢复已保存的任务过程分支数据
|
||||||
// taskStepId 已在前面声明 (line 163)
|
|
||||||
|
|
||||||
if (taskStepId) {
|
if (taskStepId) {
|
||||||
// 🆕 使用当前 agent 组合获取分支数据
|
// 使用当前 agent 组合获取分支数据
|
||||||
const savedBranches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents)
|
const savedBranches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents)
|
||||||
|
|
||||||
// 🆕 检查是否已经初始化过(针对该任务和 agent 组合)
|
// 检查是否已经初始化过(针对该任务和 agent 组合)
|
||||||
const branchesInitKey = `${BRANCHES_INIT_KEY_PREFIX}${taskStepId}_${selectionStore.getAgentGroupKey(
|
const branchesInitKey = `${BRANCHES_INIT_KEY_PREFIX}${taskStepId}_${selectionStore.getAgentGroupKey(
|
||||||
currentAgents
|
currentAgents
|
||||||
)}`
|
)}`
|
||||||
const branchesInitialized = sessionStorage.getItem(branchesInitKey) === 'true'
|
const branchesInitialized = sessionStorage.getItem(branchesInitKey) === 'true'
|
||||||
|
|
||||||
if (branchesInitialized && savedBranches.length > 0) {
|
if (branchesInitialized && savedBranches.length > 0) {
|
||||||
// ✅ 已有分支,完全从 store 恢复所有节点和边
|
|
||||||
// 🔑 关键:清空当前节点,完全从store恢复,确保初始流程节点位置和数据不变
|
|
||||||
nodes.value = []
|
nodes.value = []
|
||||||
edges.value = []
|
edges.value = []
|
||||||
|
|
||||||
savedBranches.forEach(branch => {
|
savedBranches.forEach(branch => {
|
||||||
// 恢复节点(深拷贝,避免引用问题)
|
// 恢复节点
|
||||||
branch.nodes.forEach(node => {
|
branch.nodes.forEach(node => {
|
||||||
nodes.value.push(JSON.parse(JSON.stringify(node)))
|
nodes.value.push(JSON.parse(JSON.stringify(node)))
|
||||||
})
|
})
|
||||||
// 恢复边(深拷贝)
|
// 恢复边
|
||||||
branch.edges.forEach(edge => {
|
branch.edges.forEach(edge => {
|
||||||
edges.value.push(JSON.parse(JSON.stringify(edge)))
|
edges.value.push(JSON.parse(JSON.stringify(edge)))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// 🆕 恢复最后选中的分支
|
// 恢复最后选中的分支
|
||||||
const lastSelectedBranchId = sessionStorage.getItem(LAST_SELECTED_BRANCH_KEY)
|
const lastSelectedBranchId = sessionStorage.getItem(LAST_SELECTED_BRANCH_KEY)
|
||||||
if (lastSelectedBranchId) {
|
if (lastSelectedBranchId) {
|
||||||
const lastSelectedBranch = savedBranches.find(branch => branch.id === lastSelectedBranchId)
|
const lastSelectedBranch = savedBranches.find(branch => branch.id === lastSelectedBranchId)
|
||||||
@@ -315,7 +318,7 @@ const initializeFlow = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🆕 使用多级分支逻辑:获取父节点链和子节点
|
// 获取父节点链和子节点
|
||||||
const branchParentChain = getBranchParentChain(firstBranchNode.id)
|
const branchParentChain = getBranchParentChain(firstBranchNode.id)
|
||||||
const branchChildNodes = getAllBranchNodes(firstBranchNode.id)
|
const branchChildNodes = getAllBranchNodes(firstBranchNode.id)
|
||||||
|
|
||||||
@@ -340,11 +343,11 @@ const initializeFlow = () => {
|
|||||||
selectedNodeIds.value = new Set(mainProcessNodes)
|
selectedNodeIds.value = new Set(mainProcessNodes)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 🆕 首次初始化:设置节点和边,并保存为"初始流程"分支
|
// 首次初始化:设置节点和边,并保存为"初始流程"分支
|
||||||
nodes.value = newNodes
|
nodes.value = newNodes
|
||||||
edges.value = newEdges
|
edges.value = newEdges
|
||||||
|
|
||||||
const initialBranchNodes = newNodes // ✅ 保留所有节点,包括根节点
|
const initialBranchNodes = newNodes
|
||||||
const initialBranchEdges = [...newEdges]
|
const initialBranchEdges = [...newEdges]
|
||||||
|
|
||||||
const initialBranchId = selectionStore.addTaskProcessBranch(taskStepId, currentAgents, {
|
const initialBranchId = selectionStore.addTaskProcessBranch(taskStepId, currentAgents, {
|
||||||
@@ -353,13 +356,13 @@ const initializeFlow = () => {
|
|||||||
branchType: 'root',
|
branchType: 'root',
|
||||||
nodes: JSON.parse(JSON.stringify(initialBranchNodes)),
|
nodes: JSON.parse(JSON.stringify(initialBranchNodes)),
|
||||||
edges: JSON.parse(JSON.stringify(initialBranchEdges)),
|
edges: JSON.parse(JSON.stringify(initialBranchEdges)),
|
||||||
tasks: taskProcessCopy // 👈 使用副本(已经是深拷贝)
|
tasks: taskProcessCopy
|
||||||
})
|
})
|
||||||
|
|
||||||
// 🆕 标记已初始化(针对该任务步骤和 agent 组合)
|
// 标记已初始化(针对该任务步骤和 agent 组合)
|
||||||
sessionStorage.setItem(branchesInitKey, 'true')
|
sessionStorage.setItem(branchesInitKey, 'true')
|
||||||
|
|
||||||
// 🆕 首次初始化时,设置初始流程为当前选中分支
|
// 首次初始化时,设置初始流程为当前选中分支
|
||||||
selectionStore.setActiveTaskProcessBranch(taskStepId, currentAgents, initialBranchId)
|
selectionStore.setActiveTaskProcessBranch(taskStepId, currentAgents, initialBranchId)
|
||||||
|
|
||||||
// 默认选中"初始流程"的高亮状态
|
// 默认选中"初始流程"的高亮状态
|
||||||
@@ -371,166 +374,6 @@ const initializeFlow = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 同步当前所有分支数据到 store
|
|
||||||
const syncBranchesToStore = () => {
|
|
||||||
if (isSyncing || !currentTask.value?.Id) return
|
|
||||||
|
|
||||||
isSyncing = true
|
|
||||||
const taskStepId = currentTask.value.Id
|
|
||||||
// 🆕 获取当前 agent 组合
|
|
||||||
const currentAgents = currentTask.value.AgentSelection || []
|
|
||||||
|
|
||||||
// 🔄 获取现有的分支数据(用于保留初始流程分支)
|
|
||||||
const existingBranches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents)
|
|
||||||
const existingBranchesMap = new Map<string, any>()
|
|
||||||
existingBranches.forEach(branch => {
|
|
||||||
// 使用分支ID作为key
|
|
||||||
existingBranchesMap.set(branch.id, branch)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取当前所有分支节点(标记为 isBranchTask)
|
|
||||||
const currentBranchNodes = nodes.value.filter((n: Node) => n.data.isBranchTask)
|
|
||||||
|
|
||||||
if (currentBranchNodes.length === 0) {
|
|
||||||
isSyncing = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按照分支分组节点和边
|
|
||||||
// 通过 parentId 将节点分组
|
|
||||||
const branchesMap = new Map<
|
|
||||||
string,
|
|
||||||
{
|
|
||||||
nodes: Node[]
|
|
||||||
edges: Edge[]
|
|
||||||
branchContent: string
|
|
||||||
branchType: 'root' | 'task'
|
|
||||||
createdAt: number
|
|
||||||
tasks?: any[] // 🆕 添加 tasks 字段
|
|
||||||
}
|
|
||||||
>()
|
|
||||||
|
|
||||||
currentBranchNodes.forEach((node: Node) => {
|
|
||||||
// 找到连接到该节点的边,确定父节点
|
|
||||||
const incomingEdge = edges.value.find((e: Edge) => e.target === node.id)
|
|
||||||
const parentNodeId = incomingEdge?.source || 'root'
|
|
||||||
const isFirstBranchNode = incomingEdge?.sourceHandle === 'bottom'
|
|
||||||
|
|
||||||
if (isFirstBranchNode) {
|
|
||||||
if (!branchesMap.has(parentNodeId)) {
|
|
||||||
// 🆕 从现有分支数据中恢复元数据
|
|
||||||
const existingMetadata = existingBranchesMap.get(node.id)
|
|
||||||
|
|
||||||
branchesMap.set(parentNodeId, {
|
|
||||||
nodes: [],
|
|
||||||
edges: [],
|
|
||||||
branchContent: existingMetadata?.branchContent || '分支',
|
|
||||||
branchType: existingMetadata?.branchType || (parentNodeId === 'root' ? 'root' : 'task'),
|
|
||||||
createdAt: Date.now(),
|
|
||||||
tasks: existingMetadata?.tasks //
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 收集该分支的所有节点(从第一个节点开始,通过 right handle 连接的节点)
|
|
||||||
const branchNodes: Node[] = []
|
|
||||||
let currentNode: Node | undefined = node
|
|
||||||
|
|
||||||
while (currentNode) {
|
|
||||||
branchNodes.push(currentNode)
|
|
||||||
|
|
||||||
// 查找通过 right handle 连接的下一个节点
|
|
||||||
const outgoingEdge = edges.value.find(
|
|
||||||
(e: Edge) => e.source === currentNode!.id && e.sourceHandle === 'right'
|
|
||||||
)
|
|
||||||
if (outgoingEdge) {
|
|
||||||
currentNode = currentBranchNodes.find((n: Node) => n.id === outgoingEdge.target)
|
|
||||||
} else {
|
|
||||||
currentNode = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加所有分支节点
|
|
||||||
branchNodes.forEach((n: Node) => {
|
|
||||||
if (
|
|
||||||
!branchesMap
|
|
||||||
.get(parentNodeId)!
|
|
||||||
.nodes.find((existingNode: Node) => existingNode.id === n.id)
|
|
||||||
) {
|
|
||||||
branchesMap.get(parentNodeId)!.nodes.push(n)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 找到该分支的所有边
|
|
||||||
branchNodes.forEach((branchNode: Node) => {
|
|
||||||
const branchEdges = edges.value.filter(
|
|
||||||
(e: Edge) => e.source === branchNode.id || e.target === branchNode.id
|
|
||||||
)
|
|
||||||
branchEdges.forEach((edge: Edge) => {
|
|
||||||
if (!branchesMap.get(parentNodeId)!.edges.find((e: Edge) => e.id === edge.id)) {
|
|
||||||
branchesMap.get(parentNodeId)!.edges.push(edge)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 保存所有分支到 store(使用深拷贝)
|
|
||||||
branchesMap.forEach((branchData, parentNodeId) => {
|
|
||||||
// 🆕 优先使用恢复的 tasks 数据,如果没有才重新创建
|
|
||||||
let tasks = branchData.tasks
|
|
||||||
|
|
||||||
if (!tasks || tasks.length === 0) {
|
|
||||||
// 转换节点数据为 IRawStepTask 格式
|
|
||||||
tasks = branchData.nodes.map((node: Node) => ({
|
|
||||||
Id: node.id,
|
|
||||||
StepName: node.data.actionTypeName,
|
|
||||||
TaskContent: node.data.agentDescription,
|
|
||||||
InputObject_List: [],
|
|
||||||
OutputObject: '',
|
|
||||||
AgentSelection: [node.data.agentName],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '',
|
|
||||||
data: {}
|
|
||||||
} as any,
|
|
||||||
TaskProcess: []
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
}
|
|
||||||
|
|
||||||
selectionStore.addTaskProcessBranch(taskStepId, currentAgents, {
|
|
||||||
parentNodeId,
|
|
||||||
branchContent: JSON.parse(JSON.stringify(branchData.branchContent)),
|
|
||||||
branchType: JSON.parse(JSON.stringify(branchData.branchType)),
|
|
||||||
nodes: JSON.parse(JSON.stringify(branchData.nodes)),
|
|
||||||
edges: JSON.parse(JSON.stringify(branchData.edges)),
|
|
||||||
tasks: JSON.parse(JSON.stringify(tasks)) // 🆕 优先使用恢复的 tasks
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// 延迟重置标志,避免连续触发
|
|
||||||
setTimeout(() => {
|
|
||||||
isSyncing = false
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听节点和边的变化,同步到 store
|
|
||||||
// ⚠️ 已禁用自动同步,因为会导致分支ID变化,sessionStorage中保存的选中分支无法恢复
|
|
||||||
// 分支数据在添加分支时就已经保存到store了,不需要在nodes/edges变化时重新同步
|
|
||||||
// watch(
|
|
||||||
// [nodes, edges],
|
|
||||||
// () => {
|
|
||||||
// // 如果正在同步,跳过
|
|
||||||
// if (isSyncing) return
|
|
||||||
|
|
||||||
// // 只在有分支节点时才同步
|
|
||||||
// const hasBranchNodes = nodes.value.some((n: Node) => n.data.isBranchTask)
|
|
||||||
// if (hasBranchNodes && currentTask.value) {
|
|
||||||
// syncBranchesToStore()
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// { deep: true }
|
|
||||||
// )
|
|
||||||
|
|
||||||
// 组件挂载后初始化
|
// 组件挂载后初始化
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (currentTask.value) {
|
if (currentTask.value) {
|
||||||
@@ -558,7 +401,7 @@ const cancelAddBranch = () => {
|
|||||||
branchInput.value = ''
|
branchInput.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🆕 节点点击事件
|
// 节点点击事件
|
||||||
const onNodeClick = (event: any) => {
|
const onNodeClick = (event: any) => {
|
||||||
const nodeId = event.node.id
|
const nodeId = event.node.id
|
||||||
const nodeData = event.node.data as any
|
const nodeData = event.node.data as any
|
||||||
@@ -573,10 +416,10 @@ const onNodeClick = (event: any) => {
|
|||||||
|
|
||||||
if (isBranchNode) {
|
if (isBranchNode) {
|
||||||
// 点击的是分支节点
|
// 点击的是分支节点
|
||||||
// 🆕 向上回溯分支父节点链(多级分支)
|
// 向上回溯分支父节点链(多级分支)
|
||||||
const branchParentChain = getBranchParentChain(nodeId)
|
const branchParentChain = getBranchParentChain(nodeId)
|
||||||
|
|
||||||
// 🆕 向下遍历分支的子节点(只沿right handle方向)
|
// 向下遍历分支的子节点(只沿right handle方向)
|
||||||
const branchChildNodes = getAllBranchNodes(nodeId)
|
const branchChildNodes = getAllBranchNodes(nodeId)
|
||||||
|
|
||||||
// 找到最顶层的分支父节点(连接到主流程或根节点的)
|
// 找到最顶层的分支父节点(连接到主流程或根节点的)
|
||||||
@@ -601,7 +444,7 @@ const onNodeClick = (event: any) => {
|
|||||||
// 回溯到根节点,获取主流程路径
|
// 回溯到根节点,获取主流程路径
|
||||||
const pathToRoot = getPathToRoot(parentNodeId)
|
const pathToRoot = getPathToRoot(parentNodeId)
|
||||||
|
|
||||||
// 🔧 getPathToRoot 已经返回正序(从左到右),不需要反转
|
// getPathToRoot 已经返回正序(从左到右),不需要反转
|
||||||
const correctPathToRoot = pathToRoot // [agent-0, agent-1, agent-2, ...]
|
const correctPathToRoot = pathToRoot // [agent-0, agent-1, agent-2, ...]
|
||||||
const correctBranchParentChain = [...branchParentChain].reverse() // 顶层分支 → ... → 直接父分支(正序)
|
const correctBranchParentChain = [...branchParentChain].reverse() // 顶层分支 → ... → 直接父分支(正序)
|
||||||
|
|
||||||
@@ -613,27 +456,18 @@ const onNodeClick = (event: any) => {
|
|||||||
]
|
]
|
||||||
selectedNodeIds.value = new Set(allNodesToHighlight)
|
selectedNodeIds.value = new Set(allNodesToHighlight)
|
||||||
|
|
||||||
console.log('📋 路径顺序(从上到下、从左到右):', {
|
// 保存完整的 TaskProcess 数据(主流程 + 分支)到 store
|
||||||
pathToRoot: correctPathToRoot,
|
|
||||||
branchParentChain: correctBranchParentChain,
|
|
||||||
branchChildNodes: branchChildNodes,
|
|
||||||
total: allNodesToHighlight.length
|
|
||||||
})
|
|
||||||
|
|
||||||
// 🆕 保存完整的 TaskProcess 数据(主流程 + 分支)到 store
|
|
||||||
if (currentTask.value) {
|
if (currentTask.value) {
|
||||||
const completeTaskProcess: any[] = []
|
const completeTaskProcess: any[] = []
|
||||||
|
|
||||||
// 🆕 从 store 中获取"初始流程"副本(而不是使用 taskProcess.value)
|
// 从 store 中获取"初始流程"副本(而不是使用 taskProcess.value)
|
||||||
const taskStepId = currentTask.value.Id
|
const taskStepId = currentTask.value.Id
|
||||||
const currentAgents = currentTask.value.AgentSelection || []
|
const currentAgents = currentTask.value.AgentSelection || []
|
||||||
const branches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents)
|
const branches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents)
|
||||||
const initialBranch = branches.find(branch => branch.branchContent === '初始流程')
|
const initialBranch = branches.find(branch => branch.branchContent === '初始流程')
|
||||||
const mainProcessData = initialBranch?.tasks || taskProcess.value
|
const mainProcessData = initialBranch?.tasks || taskProcess.value
|
||||||
|
|
||||||
console.log('🔍 开始按高亮路径收集数据,总节点数:', allNodesToHighlight.length)
|
// 按照高亮路径顺序收集每个节点的数据
|
||||||
|
|
||||||
// 🆕 按照高亮路径顺序收集每个节点的数据
|
|
||||||
allNodesToHighlight.forEach(nodeId => {
|
allNodesToHighlight.forEach(nodeId => {
|
||||||
const node = nodes.value.find(n => n.id === nodeId)
|
const node = nodes.value.find(n => n.id === nodeId)
|
||||||
|
|
||||||
@@ -646,7 +480,6 @@ const onNodeClick = (event: any) => {
|
|||||||
const processData = mainProcessData[originalIndex]
|
const processData = mainProcessData[originalIndex]
|
||||||
if (processData && processData.ID && processData.AgentName && processData.Description) {
|
if (processData && processData.ID && processData.AgentName && processData.Description) {
|
||||||
completeTaskProcess.push(processData)
|
completeTaskProcess.push(processData)
|
||||||
console.log(` 📦 添加主流程节点: ${nodeId}, 索引: ${originalIndex}`)
|
|
||||||
}
|
}
|
||||||
} else if (node.data.isBranchTask) {
|
} else if (node.data.isBranchTask) {
|
||||||
// 分支节点:从对应的分支数据中获取(按索引匹配)
|
// 分支节点:从对应的分支数据中获取(按索引匹配)
|
||||||
@@ -659,25 +492,19 @@ const onNodeClick = (event: any) => {
|
|||||||
const taskData = parentBranch.tasks[nodeIndex]
|
const taskData = parentBranch.tasks[nodeIndex]
|
||||||
if (taskData.ID && taskData.AgentName && taskData.Description) {
|
if (taskData.ID && taskData.AgentName && taskData.Description) {
|
||||||
completeTaskProcess.push(taskData)
|
completeTaskProcess.push(taskData)
|
||||||
console.log(
|
|
||||||
` 📦 添加分支节点: ${nodeId}, 分支: ${parentBranch.branchContent}, 索引: ${nodeIndex}`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(`✅ 数据收集完成,总任务数: ${completeTaskProcess.length}`)
|
// 找到当前点击节点所属的分支,用于保存选中状态
|
||||||
|
|
||||||
// 🆕 找到当前点击节点所属的分支,用于保存选中状态
|
|
||||||
const matchedBranch = branches.find(branch => {
|
const matchedBranch = branches.find(branch => {
|
||||||
return branch.nodes.some(node => node.id === nodeId)
|
return branch.nodes.some(node => node.id === nodeId)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (matchedBranch) {
|
if (matchedBranch) {
|
||||||
sessionStorage.setItem(LAST_SELECTED_BRANCH_KEY, matchedBranch.id)
|
sessionStorage.setItem(LAST_SELECTED_BRANCH_KEY, matchedBranch.id)
|
||||||
console.log(`💾 保存选中的分支ID: ${matchedBranch.id}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
selectionStore.setActiveTaskProcessData(
|
selectionStore.setActiveTaskProcessData(
|
||||||
@@ -693,16 +520,19 @@ const onNodeClick = (event: any) => {
|
|||||||
selectedNodeIds.value = new Set(allBranchNodes)
|
selectedNodeIds.value = new Set(allBranchNodes)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 🆕 点击的是主流程节点,高亮所有主流程节点(初始流程)
|
// 点击的是主流程节点,高亮所有主流程节点(初始流程)
|
||||||
const mainProcessNodes = nodes.value
|
const mainProcessNodes = nodes.value
|
||||||
.filter(n => !n.data.isBranchTask && n.id !== 'root')
|
.filter(n => !n.data.isBranchTask && n.id !== 'root')
|
||||||
.map(n => n.id)
|
.map(n => n.id)
|
||||||
|
|
||||||
selectedNodeIds.value = new Set(mainProcessNodes)
|
selectedNodeIds.value = new Set(mainProcessNodes)
|
||||||
|
|
||||||
// 🆕 点击主流程节点时,从 store 读取"初始流程"分支的副本
|
// 点击主流程节点时,从 store 读取"初始流程"分支的副本
|
||||||
if (currentTask.value) {
|
if (currentTask.value) {
|
||||||
const taskStepId = currentTask.value.Id
|
const taskStepId = currentTask.value.Id
|
||||||
|
if (!taskStepId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const currentAgents = currentTask.value.AgentSelection || []
|
const currentAgents = currentTask.value.AgentSelection || []
|
||||||
const branches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents)
|
const branches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents)
|
||||||
const initialBranch = branches.find(branch => branch.branchContent === '初始流程')
|
const initialBranch = branches.find(branch => branch.branchContent === '初始流程')
|
||||||
@@ -711,17 +541,17 @@ const onNodeClick = (event: any) => {
|
|||||||
// 使用 store 中保存的初始流程副本
|
// 使用 store 中保存的初始流程副本
|
||||||
selectionStore.setActiveTaskProcessData(taskStepId, currentAgents, initialBranch.tasks)
|
selectionStore.setActiveTaskProcessData(taskStepId, currentAgents, initialBranch.tasks)
|
||||||
|
|
||||||
// 🆕 同步更新 currentTask.TaskProcess(实现全局数据联动)
|
// 同步更新 currentTask.TaskProcess(实现全局数据联动)
|
||||||
agentsStore.setCurrentTaskProcess(initialBranch.tasks)
|
agentsStore.setCurrentTaskProcess(initialBranch.tasks)
|
||||||
|
|
||||||
// 🆕 保存选中的分支ID到 sessionStorage
|
// 保存选中的分支ID到 sessionStorage
|
||||||
sessionStorage.setItem(LAST_SELECTED_BRANCH_KEY, initialBranch.id)
|
sessionStorage.setItem(LAST_SELECTED_BRANCH_KEY, initialBranch.id)
|
||||||
} else {
|
} else {
|
||||||
// 降级:使用 taskProcess.value
|
// 降级:使用 taskProcess.value
|
||||||
const originalTaskProcess = taskProcess.value
|
const originalTaskProcess = taskProcess.value
|
||||||
selectionStore.setActiveTaskProcessData(taskStepId, currentAgents, originalTaskProcess)
|
selectionStore.setActiveTaskProcessData(taskStepId, currentAgents, originalTaskProcess)
|
||||||
|
|
||||||
// 🆕 同步更新 currentTask.TaskProcess(实现全局数据联动)
|
// 同步更新 currentTask.TaskProcess(实现全局数据联动)
|
||||||
agentsStore.setCurrentTaskProcess(originalTaskProcess)
|
agentsStore.setCurrentTaskProcess(originalTaskProcess)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -746,8 +576,8 @@ const submitBranch = async () => {
|
|||||||
branchLoading.value = true
|
branchLoading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// ==================== 移动已有分支 ====================
|
// 移动已有分支
|
||||||
// 🆕 只移动在当前父节点下方的分支节点(Y坐标 > 父节点Y坐标)
|
// 只移动在当前父节点下方的分支节点(Y坐标 > 父节点Y坐标)
|
||||||
const parentNodeY = parentNode.position.y
|
const parentNodeY = parentNode.position.y
|
||||||
|
|
||||||
// 查找所有已有的分支任务节点(标记为 isBranchTask)
|
// 查找所有已有的分支任务节点(标记为 isBranchTask)
|
||||||
@@ -760,10 +590,6 @@ const submitBranch = async () => {
|
|||||||
|
|
||||||
const branchIdsBelowParent = branchesBelowParent.map((n: Node) => n.id)
|
const branchIdsBelowParent = branchesBelowParent.map((n: Node) => n.id)
|
||||||
|
|
||||||
console.log(
|
|
||||||
`📍 移动了 ${branchIdsBelowParent.length} 个分支(Y坐标 > ${parentNodeY}),总共 ${allExistingBranchNodes.length} 个分支`
|
|
||||||
)
|
|
||||||
|
|
||||||
// 将当前父节点下方的分支向下移动250px(通过创建新节点来触发响应式更新)
|
// 将当前父节点下方的分支向下移动250px(通过创建新节点来触发响应式更新)
|
||||||
nodes.value = nodes.value.map((node: Node) => {
|
nodes.value = nodes.value.map((node: Node) => {
|
||||||
if (branchIdsBelowParent.includes(node.id)) {
|
if (branchIdsBelowParent.includes(node.id)) {
|
||||||
@@ -778,7 +604,7 @@ const submitBranch = async () => {
|
|||||||
return node
|
return node
|
||||||
})
|
})
|
||||||
|
|
||||||
// 💾 同步更新 store 中保存的分支位置
|
// 同步更新 store 中保存的分支位置
|
||||||
if (currentTask.value?.Id) {
|
if (currentTask.value?.Id) {
|
||||||
const taskStepId = currentTask.value.Id
|
const taskStepId = currentTask.value.Id
|
||||||
const currentAgents = currentTask.value.AgentSelection || []
|
const currentAgents = currentTask.value.AgentSelection || []
|
||||||
@@ -799,23 +625,21 @@ const submitBranch = async () => {
|
|||||||
|
|
||||||
// 判断是根节点还是 agent 节点
|
// 判断是根节点还是 agent 节点
|
||||||
if (parentNodeId === 'root') {
|
if (parentNodeId === 'root') {
|
||||||
// ========== 根节点分支 ==========
|
// 根节点分支
|
||||||
let newAgentActions: IApiAgentAction[] = []
|
let newAgentActions: IApiAgentAction[] = []
|
||||||
|
|
||||||
if (USE_MOCK_DATA) {
|
if (USE_MOCK_DATA) {
|
||||||
// 使用 Mock API
|
// 使用 Mock API
|
||||||
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
|
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
|
||||||
|
|
||||||
// 计算基线完成度
|
// 根节点分支:从零开始生成完整方案
|
||||||
const currentTaskProcess = taskProcess.value || []
|
// Baseline_Completion = 0 表示没有已完成的部分,需要生成所有阶段
|
||||||
const baselineCompletion = currentTaskProcess.length > 0 ? 0 : 100
|
// Existing_Steps 传空数组,不传递初始流程信息
|
||||||
|
|
||||||
// 调用 Mock API(返回 2D 数组,与后端格式一致)
|
|
||||||
const response = await api.mockBranchTaskProcess({
|
const response = await api.mockBranchTaskProcess({
|
||||||
branch_Number: 1,
|
branch_Number: 1,
|
||||||
Modification_Requirement: branchContent,
|
Modification_Requirement: branchContent,
|
||||||
Existing_Steps: [],
|
Existing_Steps: [], // ← 根节点分支不传递现有步骤
|
||||||
Baseline_Completion: baselineCompletion,
|
Baseline_Completion: 0, // ← 从零开始
|
||||||
stepTaskExisting: currentTask.value,
|
stepTaskExisting: currentTask.value,
|
||||||
goal: generalGoal
|
goal: generalGoal
|
||||||
})
|
})
|
||||||
@@ -843,23 +667,25 @@ const submitBranch = async () => {
|
|||||||
// 调用真实 API
|
// 调用真实 API
|
||||||
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
|
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
|
||||||
|
|
||||||
// 计算基线完成度
|
// 根节点分支:从零开始生成完整方案
|
||||||
const currentTaskProcess = taskProcess.value || []
|
// Baseline_Completion = 0 表示没有已完成的部分,需要生成所有阶段
|
||||||
const baselineCompletion = currentTaskProcess.length > 0 ? 0 : 100
|
// Existing_Steps 传空数组,不传递初始流程信息
|
||||||
|
|
||||||
const response = await api.branchTaskProcess({
|
const response = await api.branchTaskProcess({
|
||||||
branch_Number: 1,
|
branch_Number: 1,
|
||||||
Modification_Requirement: branchContent,
|
Modification_Requirement: branchContent,
|
||||||
Existing_Steps: [],
|
Existing_Steps: [], // ← 根节点分支不传递现有步骤
|
||||||
Baseline_Completion: baselineCompletion,
|
Baseline_Completion: 0, // ← 从零开始
|
||||||
stepTaskExisting: currentTask.value,
|
stepTaskExisting: currentTask.value,
|
||||||
goal: generalGoal
|
goal: generalGoal
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// WebSocket 返回格式: { data: [[action1, action2], [action3, action4]], ... }
|
||||||
|
// REST API 返回格式: [[action1, action2], [action3, action4]]
|
||||||
|
const responseData = response.data || response
|
||||||
// 后端返回格式: [[action1, action2], [action3, action4]]
|
// 后端返回格式: [[action1, action2], [action3, action4]]
|
||||||
// 取第一个分支
|
// 取第一个分支
|
||||||
if (response && response.length > 0) {
|
if (responseData && responseData.length > 0) {
|
||||||
const firstBranch = response[0]
|
const firstBranch = responseData[0]
|
||||||
|
|
||||||
// 直接遍历 action 数组
|
// 直接遍历 action 数组
|
||||||
firstBranch.forEach((action: any) => {
|
firstBranch.forEach((action: any) => {
|
||||||
@@ -935,7 +761,14 @@ const submitBranch = async () => {
|
|||||||
targetHandle: 'left',
|
targetHandle: 'left',
|
||||||
type: 'smoothstep',
|
type: 'smoothstep',
|
||||||
animated: true,
|
animated: true,
|
||||||
style: { stroke: '#67c23a', strokeWidth: 2, strokeDasharray: '5,5' }
|
style: { stroke: '#67c23a', strokeWidth: 2, strokeDasharray: '5,5' },
|
||||||
|
markerEnd: {
|
||||||
|
type: 'arrow' as any,
|
||||||
|
color: '#43a8aa',
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
strokeWidth: 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
edges.value.push(newEdge)
|
edges.value.push(newEdge)
|
||||||
newBranchEdges.push(newEdge)
|
newBranchEdges.push(newEdge)
|
||||||
@@ -950,14 +783,21 @@ const submitBranch = async () => {
|
|||||||
targetHandle: 'left',
|
targetHandle: 'left',
|
||||||
type: 'smoothstep',
|
type: 'smoothstep',
|
||||||
animated: true,
|
animated: true,
|
||||||
style: { stroke: '#67c23a', strokeWidth: 2 }
|
style: { stroke: '#67c23a', strokeWidth: 2 },
|
||||||
|
markerEnd: {
|
||||||
|
type: 'arrow' as any,
|
||||||
|
color: '#43a8aa',
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
strokeWidth: 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
edges.value.push(newEdge)
|
edges.value.push(newEdge)
|
||||||
newBranchEdges.push(newEdge)
|
newBranchEdges.push(newEdge)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 📂 保存分支数据到 store
|
// 保存分支数据到 store
|
||||||
if (newBranchNodes.length > 0) {
|
if (newBranchNodes.length > 0) {
|
||||||
// 将 IApiAgentAction 转换为 TaskProcess 格式用于存储
|
// 将 IApiAgentAction 转换为 TaskProcess 格式用于存储
|
||||||
// 与 fill-step-task-mock.ts 中的 TaskProcess 格式保持一致
|
// 与 fill-step-task-mock.ts 中的 TaskProcess 格式保持一致
|
||||||
@@ -988,29 +828,69 @@ const submitBranch = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// ========== Agent 节点分支 ==========
|
// Agent 节点分支
|
||||||
// 获取父 agent 节点的原始索引
|
const parentIsBranchTask = parentNode.data.isBranchTask || false
|
||||||
const parentOriginalIndex = parentNode.data.originalIndex ?? 0
|
const parentOriginalIndex = parentNode.data.originalIndex ?? 0
|
||||||
const parentAgentName = parentNode.data.agentName
|
|
||||||
|
|
||||||
let newAgentActions: IApiAgentAction[] = []
|
let newAgentActions: IApiAgentAction[] = []
|
||||||
|
|
||||||
if (USE_MOCK_DATA) {
|
if (USE_MOCK_DATA) {
|
||||||
// 使用 Mock API
|
// 使用 Mock API
|
||||||
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
|
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
|
||||||
|
|
||||||
// 计算基线完成度
|
|
||||||
const currentTaskProcess = taskProcess.value || []
|
const currentTaskProcess = taskProcess.value || []
|
||||||
const baselineCompletion =
|
|
||||||
|
// 根据父节点类型构建 existingSteps
|
||||||
|
let existingSteps: any[] = []
|
||||||
|
let baselineCompletion = 100
|
||||||
|
|
||||||
|
if (!parentIsBranchTask) {
|
||||||
|
// 父节点是主流程节点:传递主流程从 0 到父节点的步骤
|
||||||
|
baselineCompletion =
|
||||||
currentTaskProcess.length > 0
|
currentTaskProcess.length > 0
|
||||||
? Math.round(((parentOriginalIndex + 1) / currentTaskProcess.length) * 100)
|
? Math.round(((parentOriginalIndex + 1) / currentTaskProcess.length) * 100)
|
||||||
: 100
|
: 100
|
||||||
|
|
||||||
|
existingSteps = currentTaskProcess.slice(0, parentOriginalIndex + 1).map((p: any) => ({
|
||||||
|
ID: p.ID,
|
||||||
|
ActionType: p.ActionType,
|
||||||
|
AgentName: p.AgentName,
|
||||||
|
Description: p.Description,
|
||||||
|
ImportantInput: p.ImportantInput || []
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
// 父节点是分支节点:从分支数据中获取步骤
|
||||||
|
const taskStepId = currentTask.value?.Id || ''
|
||||||
|
const currentAgents = currentTask.value?.AgentSelection || []
|
||||||
|
const branches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents)
|
||||||
|
|
||||||
|
// 找到父节点所属的分支
|
||||||
|
const parentBranch = branches.find(branch =>
|
||||||
|
branch.nodes.some(n => n.id === parentNode.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (parentBranch && parentBranch.tasks) {
|
||||||
|
// 获取分支中从第一个节点到父节点的所有步骤
|
||||||
|
const parentIndexInBranch = parentBranch.nodes.findIndex(n => n.id === parentNode.id)
|
||||||
|
existingSteps = parentBranch.tasks.slice(0, parentIndexInBranch + 1).map((p: any) => ({
|
||||||
|
ID: p.ID,
|
||||||
|
ActionType: p.ActionType,
|
||||||
|
AgentName: p.AgentName,
|
||||||
|
Description: p.Description,
|
||||||
|
ImportantInput: p.ImportantInput || []
|
||||||
|
}))
|
||||||
|
|
||||||
|
// 分支节点的基线完成度:根据主流程进度计算
|
||||||
|
baselineCompletion =
|
||||||
|
currentTaskProcess.length > 0
|
||||||
|
? Math.round(((parentOriginalIndex + 1) / currentTaskProcess.length) * 100)
|
||||||
|
: 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 调用 Mock API
|
// 调用 Mock API
|
||||||
const response = await api.mockBranchTaskProcess({
|
const response = await api.mockBranchTaskProcess({
|
||||||
branch_Number: 1,
|
branch_Number: 1,
|
||||||
Modification_Requirement: branchContent,
|
Modification_Requirement: branchContent,
|
||||||
Existing_Steps: [],
|
Existing_Steps: existingSteps,
|
||||||
Baseline_Completion: baselineCompletion,
|
Baseline_Completion: baselineCompletion,
|
||||||
stepTaskExisting: currentTask.value,
|
stepTaskExisting: currentTask.value,
|
||||||
goal: generalGoal
|
goal: generalGoal
|
||||||
@@ -1038,27 +918,72 @@ const submitBranch = async () => {
|
|||||||
} else {
|
} else {
|
||||||
// 调用真实 API
|
// 调用真实 API
|
||||||
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
|
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
|
||||||
|
|
||||||
// 计算基线完成度
|
|
||||||
const currentTaskProcess = taskProcess.value || []
|
const currentTaskProcess = taskProcess.value || []
|
||||||
const baselineCompletion =
|
|
||||||
|
// 根据父节点类型构建 existingSteps
|
||||||
|
let existingSteps: any[] = []
|
||||||
|
let baselineCompletion = 100
|
||||||
|
|
||||||
|
if (!parentIsBranchTask) {
|
||||||
|
// 父节点是主流程节点:传递主流程从 0 到父节点的步骤
|
||||||
|
baselineCompletion =
|
||||||
currentTaskProcess.length > 0
|
currentTaskProcess.length > 0
|
||||||
? Math.round(((parentOriginalIndex + 1) / currentTaskProcess.length) * 100)
|
? Math.round(((parentOriginalIndex + 1) / currentTaskProcess.length) * 100)
|
||||||
: 100
|
: 100
|
||||||
|
|
||||||
|
existingSteps = currentTaskProcess.slice(0, parentOriginalIndex + 1).map((p: any) => ({
|
||||||
|
ID: p.ID,
|
||||||
|
ActionType: p.ActionType,
|
||||||
|
AgentName: p.AgentName,
|
||||||
|
Description: p.Description,
|
||||||
|
ImportantInput: p.ImportantInput || []
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
// 父节点是分支节点:从分支数据中获取步骤
|
||||||
|
const taskStepId = currentTask.value?.Id || ''
|
||||||
|
const currentAgents = currentTask.value?.AgentSelection || []
|
||||||
|
const branches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents)
|
||||||
|
|
||||||
|
// 找到父节点所属的分支
|
||||||
|
const parentBranch = branches.find(branch =>
|
||||||
|
branch.nodes.some(n => n.id === parentNode.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (parentBranch && parentBranch.tasks) {
|
||||||
|
// 获取分支中从第一个节点到父节点的所有步骤
|
||||||
|
const parentIndexInBranch = parentBranch.nodes.findIndex(n => n.id === parentNode.id)
|
||||||
|
existingSteps = parentBranch.tasks.slice(0, parentIndexInBranch + 1).map((p: any) => ({
|
||||||
|
ID: p.ID,
|
||||||
|
ActionType: p.ActionType,
|
||||||
|
AgentName: p.AgentName,
|
||||||
|
Description: p.Description,
|
||||||
|
ImportantInput: p.ImportantInput || []
|
||||||
|
}))
|
||||||
|
|
||||||
|
// 分支节点的基线完成度:根据主流程进度计算
|
||||||
|
baselineCompletion =
|
||||||
|
currentTaskProcess.length > 0
|
||||||
|
? Math.round(((parentOriginalIndex + 1) / currentTaskProcess.length) * 100)
|
||||||
|
: 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const response = await api.branchTaskProcess({
|
const response = await api.branchTaskProcess({
|
||||||
branch_Number: 1,
|
branch_Number: 1,
|
||||||
Modification_Requirement: branchContent,
|
Modification_Requirement: branchContent,
|
||||||
Existing_Steps: [],
|
Existing_Steps: existingSteps,
|
||||||
Baseline_Completion: baselineCompletion,
|
Baseline_Completion: baselineCompletion,
|
||||||
stepTaskExisting: currentTask.value,
|
stepTaskExisting: currentTask.value,
|
||||||
goal: generalGoal
|
goal: generalGoal
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// WebSocket 返回格式: { data: [[action1, action2], [action3, action4]], ... }
|
||||||
|
// REST API 返回格式: [[action1, action2], [action3, action4]]
|
||||||
|
const responseData = response.data || response
|
||||||
// 后端返回格式: [[action1, action2], [action3, action4]]
|
// 后端返回格式: [[action1, action2], [action3, action4]]
|
||||||
// 取第一个分支
|
// 取第一个分支
|
||||||
if (response && response.length > 0) {
|
if (responseData && responseData.length > 0) {
|
||||||
const firstBranch = response[0]
|
const firstBranch = responseData[0]
|
||||||
|
|
||||||
// 直接遍历 action 数组
|
// 直接遍历 action 数组
|
||||||
firstBranch.forEach((action: any) => {
|
firstBranch.forEach((action: any) => {
|
||||||
@@ -1134,7 +1059,14 @@ const submitBranch = async () => {
|
|||||||
targetHandle: 'left',
|
targetHandle: 'left',
|
||||||
type: 'smoothstep',
|
type: 'smoothstep',
|
||||||
animated: true,
|
animated: true,
|
||||||
style: { stroke: '#409eff', strokeWidth: 2, strokeDasharray: '5,5' }
|
style: { stroke: '#409eff', strokeWidth: 2, strokeDasharray: '5,5' },
|
||||||
|
markerEnd: {
|
||||||
|
type: 'arrow' as any,
|
||||||
|
color: '#43a8aa',
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
strokeWidth: 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
edges.value.push(newEdge)
|
edges.value.push(newEdge)
|
||||||
newBranchEdges.push(newEdge)
|
newBranchEdges.push(newEdge)
|
||||||
@@ -1149,17 +1081,22 @@ const submitBranch = async () => {
|
|||||||
targetHandle: 'left',
|
targetHandle: 'left',
|
||||||
type: 'smoothstep',
|
type: 'smoothstep',
|
||||||
animated: true,
|
animated: true,
|
||||||
style: { stroke: '#409eff', strokeWidth: 2 }
|
style: { stroke: '#409eff', strokeWidth: 2 },
|
||||||
|
markerEnd: {
|
||||||
|
type: 'arrow' as any,
|
||||||
|
color: '#43a8aa',
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
strokeWidth: 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
edges.value.push(newEdge)
|
edges.value.push(newEdge)
|
||||||
newBranchEdges.push(newEdge)
|
newBranchEdges.push(newEdge)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 📂 保存分支数据到 store
|
// 保存分支数据到 store
|
||||||
if (newBranchNodes.length > 0) {
|
if (newBranchNodes.length > 0) {
|
||||||
// 将 IApiAgentAction 转换为 TaskProcess 格式用于存储
|
|
||||||
// 与 fill-step-task-mock.ts 中的 TaskProcess 格式保持一致
|
|
||||||
const branchTasks = newAgentActions.map(action => ({
|
const branchTasks = newAgentActions.map(action => ({
|
||||||
ID: action.id || uuidv4(),
|
ID: action.id || uuidv4(),
|
||||||
ActionType: action.type,
|
ActionType: action.type,
|
||||||
@@ -1236,9 +1173,6 @@ onConnect(params => addEdges(params))
|
|||||||
@node-click="onNodeClick"
|
@node-click="onNodeClick"
|
||||||
class="vue-flow-container"
|
class="vue-flow-container"
|
||||||
>
|
>
|
||||||
<!-- <Background /> -->
|
|
||||||
<!-- <Controls /> -->
|
|
||||||
|
|
||||||
<template #node-root="nodeProps">
|
<template #node-root="nodeProps">
|
||||||
<div class="root-task-node-wrapper">
|
<div class="root-task-node-wrapper">
|
||||||
<Handle type="source" :position="Position.Right" id="right" />
|
<Handle type="source" :position="Position.Right" id="right" />
|
||||||
@@ -1564,7 +1498,7 @@ onConnect(params => addEdges(params))
|
|||||||
|
|
||||||
.el-input__inner {
|
.el-input__inner {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--color-text-primary);
|
color: #000;
|
||||||
padding-right: 40px;
|
padding-right: 40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1603,7 +1537,7 @@ onConnect(params => addEdges(params))
|
|||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
// 🆕 分支选中高亮样式
|
// 分支选中高亮样式
|
||||||
&.is-branch-selected {
|
&.is-branch-selected {
|
||||||
box-shadow: 0 0 0 3px #000, 0 0 20px rgba(0, 0, 0, 0.5);
|
box-shadow: 0 0 0 3px #000, 0 0 20px rgba(0, 0, 0, 0.5);
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
@@ -1638,17 +1572,6 @@ onConnect(params => addEdges(params))
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.vue-flow__controls) {
|
|
||||||
button {
|
|
||||||
background-color: #43a8aa;
|
|
||||||
border-color: #43a8aa;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #358d8f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 智能体悬浮提示样式 */
|
/* 智能体悬浮提示样式 */
|
||||||
:deep(.agent-tooltip-popper) {
|
:deep(.agent-tooltip-popper) {
|
||||||
z-index: 4000 !important;
|
z-index: 4000 !important;
|
||||||
|
|||||||
@@ -12,21 +12,18 @@ const props = defineProps<{
|
|||||||
step?: any
|
step?: any
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// 获取分支数量 - 主分支(1) + 额外分支数量
|
// 获取分支数量
|
||||||
const branchCount = computed(() => {
|
const branchCount = computed(() => {
|
||||||
if (!props.step?.Id) return 1
|
if (!props.step?.Id) return 1
|
||||||
|
|
||||||
// 获取该任务步骤的分支数据
|
|
||||||
const taskStepId = props.step.Id
|
const taskStepId = props.step.Id
|
||||||
// 🆕 获取该任务的 agent 组合
|
// 获取该任务的 agent 组合
|
||||||
const agents = props.step.AgentSelection || []
|
const agents = props.step.AgentSelection || []
|
||||||
const branches = selectionStore.getTaskProcessBranches(taskStepId, agents)
|
const branches = selectionStore.getTaskProcessBranches(taskStepId, agents)
|
||||||
|
|
||||||
// 主分支(1) + 额外分支数量
|
|
||||||
return branches.length || 1
|
return branches.length || 1
|
||||||
})
|
})
|
||||||
|
|
||||||
// 🆕 判断按钮是否可点击(只有当前按钮对应的任务是任务大纲中选中的任务时才可点击)
|
// 判断按钮是否可点击
|
||||||
const isClickable = computed(() => {
|
const isClickable = computed(() => {
|
||||||
if (!props.step?.Id || !agentsStore.currentTask?.Id) {
|
if (!props.step?.Id || !agentsStore.currentTask?.Id) {
|
||||||
return false
|
return false
|
||||||
@@ -35,7 +32,6 @@ const isClickable = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const handleClick = (event?: MouseEvent) => {
|
const handleClick = (event?: MouseEvent) => {
|
||||||
// 🆕 只有可点击时才执行操作
|
|
||||||
if (!isClickable.value) {
|
if (!isClickable.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -50,7 +46,6 @@ const handleClick = (event?: MouseEvent) => {
|
|||||||
if (props.step) {
|
if (props.step) {
|
||||||
agentsStore.setCurrentTask(props.step)
|
agentsStore.setCurrentTask(props.step)
|
||||||
}
|
}
|
||||||
// 触发打开任务过程探索窗口
|
|
||||||
agentsStore.openPlanTask()
|
agentsStore.openPlanTask()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -58,7 +53,7 @@ const handleClick = (event?: MouseEvent) => {
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="task-button"
|
class="task-button"
|
||||||
:class="{ 'has-branches': branchCount > 0, 'is-disabled': !isClickable }"
|
:class="{ 'has-branches': branchCount > 1, 'is-disabled': !isClickable }"
|
||||||
@click="handleClick"
|
@click="handleClick"
|
||||||
:title="isClickable ? `${branchCount} 个分支` : '请先在任务大纲中选中此任务'"
|
:title="isClickable ? `${branchCount} 个分支` : '请先在任务大纲中选中此任务'"
|
||||||
>
|
>
|
||||||
@@ -102,7 +97,7 @@ const handleClick = (event?: MouseEvent) => {
|
|||||||
filter: brightness(0.9);
|
filter: brightness(0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🆕 禁用状态
|
// 禁用状态
|
||||||
&.is-disabled {
|
&.is-disabled {
|
||||||
background-color: #bdc3c7;
|
background-color: #bdc3c7;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
|||||||
@@ -1,168 +0,0 @@
|
|||||||
<!-- AdditionalOutputCard.vue -->
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, ref, nextTick } from 'vue'
|
|
||||||
import { useAgentsStore } from '@/stores'
|
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
|
||||||
|
|
||||||
const agentsStore = useAgentsStore()
|
|
||||||
const props = defineProps<{
|
|
||||||
index: number
|
|
||||||
}>()
|
|
||||||
|
|
||||||
// 获取产物名称
|
|
||||||
const currentOutput = computed(() => {
|
|
||||||
return agentsStore.additionalOutputs[props.index] || ''
|
|
||||||
})
|
|
||||||
|
|
||||||
// 编辑状态
|
|
||||||
const isEditing = ref(false)
|
|
||||||
const inputValue = ref('')
|
|
||||||
const originalValue = ref('')
|
|
||||||
const inputRef = ref<HTMLElement>()
|
|
||||||
|
|
||||||
// 点击编辑图标
|
|
||||||
const handleEditClick = () => {
|
|
||||||
isEditing.value = true
|
|
||||||
originalValue.value = inputValue.value
|
|
||||||
|
|
||||||
// 等待 DOM 更新后聚焦输入框
|
|
||||||
nextTick(() => {
|
|
||||||
if (inputRef.value) {
|
|
||||||
const inputEl = inputRef.value.querySelector('input')
|
|
||||||
if (inputEl) {
|
|
||||||
inputEl.focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存编辑
|
|
||||||
const handleSave = () => {
|
|
||||||
if (isEditing.value) {
|
|
||||||
isEditing.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取消编辑
|
|
||||||
const handleCancel = () => {
|
|
||||||
if (isEditing.value) {
|
|
||||||
inputValue.value = originalValue.value // 恢复原始值
|
|
||||||
isEditing.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理键盘事件
|
|
||||||
const handleKeydown = (event: KeyboardEvent) => {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
event.preventDefault()
|
|
||||||
handleSave()
|
|
||||||
} else if (event.key === 'Escape') {
|
|
||||||
event.preventDefault()
|
|
||||||
handleCancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 输入框失去焦点处理
|
|
||||||
const handleBlur = () => {
|
|
||||||
// 延迟处理,避免点击按钮时立即触发
|
|
||||||
setTimeout(() => {
|
|
||||||
if (isEditing.value) {
|
|
||||||
handleSave()
|
|
||||||
}
|
|
||||||
}, 200)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<!-- 当产物存在时才显示 -->
|
|
||||||
<div v-if="currentOutput" class="card-item">
|
|
||||||
<el-card
|
|
||||||
class="card-item w-full relative output-object-card"
|
|
||||||
:shadow="true"
|
|
||||||
:id="`additional-output-${index}`"
|
|
||||||
>
|
|
||||||
<!-- 显示产物名称 -->
|
|
||||||
<div class="text-start w-[100%]">
|
|
||||||
<div class="text-[18px] font-bold text-[var(--color-text)] mb-2">
|
|
||||||
{{ currentOutput }}
|
|
||||||
</div>
|
|
||||||
<div ref="inputRef">
|
|
||||||
<el-input
|
|
||||||
v-model="inputValue"
|
|
||||||
:readonly="!isEditing"
|
|
||||||
:placeholder="isEditing ? '请输入内容...' : '点击编辑图标开始编辑...'"
|
|
||||||
@keydown="handleKeydown"
|
|
||||||
@blur="handleBlur"
|
|
||||||
:class="{ editing: isEditing }"
|
|
||||||
>
|
|
||||||
<template #suffix>
|
|
||||||
<!-- 只读状态:显示编辑图标 -->
|
|
||||||
<div v-if="!isEditing" class="flex items-center">
|
|
||||||
<svg-icon
|
|
||||||
icon-class="Edit"
|
|
||||||
size="20px"
|
|
||||||
class="cursor-pointer hover:text-[#409eff] transition-colors"
|
|
||||||
@click="handleEditClick"
|
|
||||||
title="点击编辑"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 编辑状态下的提示 -->
|
|
||||||
<div v-if="isEditing" class="mt-2 text-end text-xs text-gray-500">
|
|
||||||
<svg-icon
|
|
||||||
icon-class="Check"
|
|
||||||
size="20px"
|
|
||||||
color="#328621"
|
|
||||||
class="cursor-pointer mr-4"
|
|
||||||
@click="handleSave"
|
|
||||||
title="保存"
|
|
||||||
/>
|
|
||||||
<svg-icon
|
|
||||||
icon-class="Cancel"
|
|
||||||
size="20px"
|
|
||||||
color="#8e0707"
|
|
||||||
class="cursor-pointer mr-4"
|
|
||||||
@click="handleCancel"
|
|
||||||
title="取消"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.card-item {
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.output-object-card {
|
|
||||||
:deep(.el-card__body) {
|
|
||||||
min-height: 80px;
|
|
||||||
display: flex;
|
|
||||||
align-items: start;
|
|
||||||
justify-content: start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 输入框样式 */
|
|
||||||
:deep(.el-input .el-input__wrapper) {
|
|
||||||
box-shadow: none;
|
|
||||||
background-color: var(--color-bg-three);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 编辑状态下的输入框样式 */
|
|
||||||
:deep(.el-input.editing .el-input__wrapper) {
|
|
||||||
border: 1px solid #dcdfe6;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-input.editing .el-input__wrapper.is-focus) {
|
|
||||||
border-color: #c0c4cc;
|
|
||||||
box-shadow: 0 0 0 1px #c0c4cc;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -38,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) {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ function handlePrev() {
|
|||||||
<!-- <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" />
|
<!-- <SvgIcon icon-class="close" size="15px" /> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 分割线 -->
|
<!-- 分割线 -->
|
||||||
|
|||||||
@@ -1,12 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts"></script>
|
||||||
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%]">
|
||||||
<!-- 左侧元素 -->
|
<!-- 左侧元素 -->
|
||||||
@@ -24,21 +16,6 @@ defineEmits<{
|
|||||||
<div class="flex-1 relative h-full flex justify-center">
|
<div class="flex-1 relative h-full flex justify-center">
|
||||||
<!-- 背景那一根线 -->
|
<!-- 背景那一根线 -->
|
||||||
<div class="h-full bg-[var(--color-bg-flow)] w-[5px]">
|
<div class="h-full bg-[var(--color-bg-flow)] w-[5px]">
|
||||||
<!-- 顶部加号区域 -->
|
|
||||||
<div
|
|
||||||
v-if="!isAdding"
|
|
||||||
v-dev-only
|
|
||||||
class="plus-area mt-[35px] ml-[-15px] w-[34px] h-[34px] flex items-center justify-center cursor-pointer rounded-full"
|
|
||||||
@click="$emit('start-add-output')"
|
|
||||||
>
|
|
||||||
<!-- 加号图标 -->
|
|
||||||
<svg-icon
|
|
||||||
icon-class="plus"
|
|
||||||
color="var(--color-text)"
|
|
||||||
size="20px"
|
|
||||||
class="plus-icon opacity-0 transition-opacity duration-200"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<!-- 线底部的小圆球 -->
|
<!-- 线底部的小圆球 -->
|
||||||
<div
|
<div
|
||||||
class="absolute bottom-0 left-1/2 transform -translate-x-1/2 bg-[var(--color-bg-flow)] 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"
|
||||||
|
|||||||
@@ -335,7 +335,7 @@ const handleBranchKeydown = (event: KeyboardEvent) => {
|
|||||||
|
|
||||||
.el-input__inner {
|
.el-input__inner {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--color-text-primary);
|
color: #000;
|
||||||
padding-right: 40px;
|
padding-right: 40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -476,7 +476,7 @@ const handleBranchKeydown = (event: KeyboardEvent) => {
|
|||||||
|
|
||||||
.el-input__inner {
|
.el-input__inner {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--color-text-primary);
|
color: #000;
|
||||||
padding-right: 40px;
|
padding-right: 40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,127 +0,0 @@
|
|||||||
# Mock 数据说明
|
|
||||||
|
|
||||||
本目录包含用于分支功能的 mock 数据,支持在开发环境中测试分支逻辑,无需调用真实后端 API。
|
|
||||||
|
|
||||||
## 文件说明
|
|
||||||
|
|
||||||
### 1. `branchPlanOutlineMock.ts`
|
|
||||||
**用途**: 根节点级别的分支(任务大纲分支)
|
|
||||||
|
|
||||||
**类型**: `IApiStepTask[][]`
|
|
||||||
|
|
||||||
**说明**: 返回多个分支方案,每个方案是一个完整的任务流程(IApiStepTask[])
|
|
||||||
|
|
||||||
**示例结构**:
|
|
||||||
```typescript
|
|
||||||
[
|
|
||||||
// 第一个分支方案(瀑布流开发)
|
|
||||||
[task1, task2, ...],
|
|
||||||
// 第二个分支方案(敏捷开发)
|
|
||||||
[task1, task2, ...],
|
|
||||||
// 第三个分支方案(快速原型)
|
|
||||||
[task1, task2, ...]
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. `branchTaskProcessMock.ts`
|
|
||||||
**用途**: 任务节点级别的分支(任务流程分支)
|
|
||||||
|
|
||||||
**类型**: `IApiAgentAction[][]`
|
|
||||||
|
|
||||||
**说明**: 返回多个分支方案,每个方案是一系列动作(IApiAgentAction[]),这些动作会追加到现有任务的 TaskProcess 中
|
|
||||||
|
|
||||||
**示例结构**:
|
|
||||||
```typescript
|
|
||||||
[
|
|
||||||
// 第一个分支方案(标准开发流程)
|
|
||||||
[action1, action2, action3, ...],
|
|
||||||
// 第二个分支方案(快速原型流程)
|
|
||||||
[action1, action2, ...],
|
|
||||||
// ... 更多方案
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
## 如何使用
|
|
||||||
|
|
||||||
### 切换 Mock 数据开关
|
|
||||||
|
|
||||||
在 `PlanModification.vue` 文件中,找到以下配置:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 开关:控制是否使用 mock 数据(开发时设置为 true,生产时设置为 false)
|
|
||||||
const USE_MOCK_DATA = true
|
|
||||||
```
|
|
||||||
|
|
||||||
- **开发阶段**: 设置为 `true`,使用 mock 数据
|
|
||||||
- **生产环境**: 设置为 `false`,调用真实 API
|
|
||||||
|
|
||||||
### 数据转换流程
|
|
||||||
|
|
||||||
```
|
|
||||||
IApiStepTask (API 格式)
|
|
||||||
↓ convertToIRawStepTask()
|
|
||||||
IRawStepTask (内部格式)
|
|
||||||
↓
|
|
||||||
更新到 agentsStore.agentRawPlan.data
|
|
||||||
↓
|
|
||||||
Vue Flow 流程图自动更新
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mock 数据特点
|
|
||||||
|
|
||||||
1. **模拟网络延迟**: Mock 数据调用会模拟 800ms 的网络延迟
|
|
||||||
2. **多方案选择**: 每个分支接口提供多个备选方案(目前默认使用第一个)
|
|
||||||
3. **完整数据结构**: Mock 数据包含完整的字段,与真实 API 返回格式一致
|
|
||||||
4. **类型安全**: 使用 TypeScript 类型定义,确保类型正确
|
|
||||||
|
|
||||||
## 扩展 Mock 数据
|
|
||||||
|
|
||||||
### 添加新的分支方案
|
|
||||||
|
|
||||||
在对应的 mock 文件中添加新的数组元素:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// branchPlanOutlineMock.ts
|
|
||||||
const mockPlanBranchData: IApiStepTask[][] = [
|
|
||||||
// 现有方案...
|
|
||||||
[
|
|
||||||
// 新增方案
|
|
||||||
{
|
|
||||||
name: '新方案步骤1',
|
|
||||||
content: '...',
|
|
||||||
// ... 其他字段
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '新方案步骤2',
|
|
||||||
content: '...',
|
|
||||||
// ... 其他字段
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 随机选择方案
|
|
||||||
|
|
||||||
修改 `PlanModification.vue` 中的方案选择逻辑:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 当前:固定选择第一个
|
|
||||||
const mockBranchTasks = branchPlanOutlineMock[0]
|
|
||||||
|
|
||||||
// 改为:随机选择一个方案
|
|
||||||
const randomIndex = Math.floor(Math.random() * branchPlanOutlineMock.length)
|
|
||||||
const mockBranchTasks = branchPlanOutlineMock[randomIndex]
|
|
||||||
```
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. ⚠️ **生产环境**: 发布前务必将 `USE_MOCK_DATA` 设置为 `false`
|
|
||||||
2. 🔍 **调试**: 查看控制台日志,以 `[Mock]` 开头的是 mock 数据相关日志
|
|
||||||
3. 📝 **数据一致性**: 确保 mock 数据结构与真实 API 返回格式一致
|
|
||||||
4. 🔄 **数据持久化**: Mock 数据仅存在于前端,刷新页面后会丢失
|
|
||||||
|
|
||||||
## 相关文件
|
|
||||||
|
|
||||||
- `PlanModification.vue`: 主要逻辑文件,包含分支添加和 mock 数据集成
|
|
||||||
- `api/index.ts`: 真实 API 接口定义
|
|
||||||
- `stores/modules/agents.ts`: 类型定义和数据存储
|
|
||||||
@@ -1,246 +0,0 @@
|
|||||||
// branch_PlanOutline 接口的 Mock 数据和 Mock API
|
|
||||||
// 模拟后端返回的原始数据格式(IRawPlanResponse)
|
|
||||||
|
|
||||||
import type { IRawPlanResponse, IRawStepTask } from '@/stores'
|
|
||||||
|
|
||||||
// 后端返回的数据格式
|
|
||||||
export type BranchPlanOutlineResponse = IRawPlanResponse
|
|
||||||
|
|
||||||
// Mock 数据:模拟后端返回的原始分支数据(不含 Collaboration_Brief_FrontEnd)
|
|
||||||
// 注意:这里模拟的是 branch_PlanOutline 函数返回的数据,不是前端转换后的数据
|
|
||||||
const mockBranchDataRaw: IRawStepTask[][] = [
|
|
||||||
// 第一个分支方案
|
|
||||||
[
|
|
||||||
{
|
|
||||||
StepName: '分析用户需求',
|
|
||||||
TaskContent: '分析用户需求,制定项目开发计划',
|
|
||||||
InputObject_List: ['腐蚀类型及成因列表'],
|
|
||||||
OutputObject: '项目开发计划书',
|
|
||||||
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '',
|
|
||||||
data: {},
|
|
||||||
},
|
|
||||||
TaskProcess: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StepName: '系统设计与架构',
|
|
||||||
TaskContent: '设计系统架构和数据库结构',
|
|
||||||
InputObject_List: ['项目开发计划书'],
|
|
||||||
OutputObject: '系统设计文档',
|
|
||||||
AgentSelection: ['腐蚀机理研究员', '防护工程专家'],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '',
|
|
||||||
data: {},
|
|
||||||
},
|
|
||||||
TaskProcess: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
// 第二个分支方案(快速原型方案)
|
|
||||||
[
|
|
||||||
{
|
|
||||||
StepName: '需求快速原型',
|
|
||||||
TaskContent: '构建快速原型验证核心功能',
|
|
||||||
InputObject_List: ['腐蚀类型及成因列表'],
|
|
||||||
OutputObject: '原型系统',
|
|
||||||
AgentSelection: ['实验材料学家', '防护工程专家'],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '',
|
|
||||||
data: {},
|
|
||||||
},
|
|
||||||
TaskProcess: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StepName: '原型测试与优化',
|
|
||||||
TaskContent: '测试原型并根据反馈快速迭代',
|
|
||||||
InputObject_List: ['原型系统'],
|
|
||||||
OutputObject: '优化后的原型',
|
|
||||||
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '',
|
|
||||||
data: {},
|
|
||||||
},
|
|
||||||
TaskProcess: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
// 第三个分支方案(质量优先方案)
|
|
||||||
[
|
|
||||||
{
|
|
||||||
StepName: '需求深度分析',
|
|
||||||
TaskContent: '深入分析用户需求和技术可行性',
|
|
||||||
InputObject_List: ['腐蚀类型及成因列表'],
|
|
||||||
OutputObject: '详细需求分析报告',
|
|
||||||
AgentSelection: ['腐蚀机理研究员', '防护工程专家'],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '',
|
|
||||||
data: {},
|
|
||||||
},
|
|
||||||
TaskProcess: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StepName: '质量保障设计',
|
|
||||||
TaskContent: '设计质量保障体系和测试方案',
|
|
||||||
InputObject_List: ['详细需求分析报告'],
|
|
||||||
OutputObject: '质量保障方案',
|
|
||||||
AgentSelection: ['实验材料学家', '防护工程专家'],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '',
|
|
||||||
data: {},
|
|
||||||
},
|
|
||||||
TaskProcess: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StepName: '系统开发与测试',
|
|
||||||
TaskContent: '按质量标准进行系统开发和测试',
|
|
||||||
InputObject_List: ['质量保障方案'],
|
|
||||||
OutputObject: '经过完整测试的系统',
|
|
||||||
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '',
|
|
||||||
data: {},
|
|
||||||
},
|
|
||||||
TaskProcess: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
// 第四个分支方案(敏捷开发方案)
|
|
||||||
[
|
|
||||||
{
|
|
||||||
StepName: '迭代规划',
|
|
||||||
TaskContent: '制定敏捷开发迭代计划',
|
|
||||||
InputObject_List: ['腐蚀类型及成因列表'],
|
|
||||||
OutputObject: '迭代计划',
|
|
||||||
AgentSelection: ['防护工程专家', '腐蚀机理研究员'],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '',
|
|
||||||
data: {},
|
|
||||||
},
|
|
||||||
TaskProcess: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StepName: '快速开发与验证',
|
|
||||||
TaskContent: '快速开发并持续验证功能',
|
|
||||||
InputObject_List: ['迭代计划'],
|
|
||||||
OutputObject: '可运行的功能模块',
|
|
||||||
AgentSelection: ['实验材料学家', '防护工程专家'],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '',
|
|
||||||
data: {},
|
|
||||||
},
|
|
||||||
TaskProcess: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 模拟后端的 Add_Collaboration_Brief_FrontEnd 函数
|
|
||||||
* 为每个任务添加前端协作简报(Collaboration_Brief_frontEnd)
|
|
||||||
*/
|
|
||||||
function Add_Collaboration_Brief_FrontEnd(branchList: IRawStepTask[][]): IRawStepTask[][] {
|
|
||||||
return branchList.map((tasks) =>
|
|
||||||
tasks.map((task) => ({
|
|
||||||
...task,
|
|
||||||
Collaboration_Brief_frontEnd: generateCollaborationBrief(task),
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成协作简报(Collaboration_Brief_frontEnd)
|
|
||||||
* 根据 StepName、TaskContent、AgentSelection 生成模板和数据
|
|
||||||
*/
|
|
||||||
function generateCollaborationBrief(task: IRawStepTask): {
|
|
||||||
template: string
|
|
||||||
data: Record<string, any>
|
|
||||||
} {
|
|
||||||
const agents = task.AgentSelection || []
|
|
||||||
const stepName = task.StepName || ''
|
|
||||||
|
|
||||||
// 为每个 agent 生成颜色
|
|
||||||
const colors = [
|
|
||||||
'hsl(210, 70%, 50%)',
|
|
||||||
'hsl(30, 70%, 50%)',
|
|
||||||
'hsl(120, 70%, 50%)',
|
|
||||||
'hsl(270, 70%, 50%)',
|
|
||||||
]
|
|
||||||
|
|
||||||
// 生成 data 对象
|
|
||||||
const data: Record<string, any> = {}
|
|
||||||
agents.forEach((agent, index) => {
|
|
||||||
data[agent] = {
|
|
||||||
text: agent,
|
|
||||||
style: { background: colors[index % colors.length] },
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 生成 template(简化版本,实际应根据任务内容生成)
|
|
||||||
const template =
|
|
||||||
agents.length > 0
|
|
||||||
? agents.map((agent, i) => `!<${agent}>!负责!<${stepName}-${i}>!`).join(',')
|
|
||||||
: ''
|
|
||||||
|
|
||||||
return {
|
|
||||||
template,
|
|
||||||
data,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mock API:模拟后端 branch_PlanOutline 接口调用
|
|
||||||
*
|
|
||||||
* @param branch_Number - 分支数量
|
|
||||||
* @param Modification_Requirement - 修改需求
|
|
||||||
* @param Existing_Steps - 现有步骤名称列表
|
|
||||||
* @param Baseline_Completion - 基线完成度
|
|
||||||
* @param InitialObject_List - 初始对象列表
|
|
||||||
* @param General_Goal - 总体目标
|
|
||||||
* @returns Promise<IRawPlanResponse> - 返回包含 'Collaboration Process' 的响应
|
|
||||||
*/
|
|
||||||
export const mockBranchPlanOutlineAPI = async (params: {
|
|
||||||
branch_Number: number
|
|
||||||
Modification_Requirement: string
|
|
||||||
Existing_Steps: string[]
|
|
||||||
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
|
|
||||||
@@ -106,7 +106,7 @@ const mockBranchTaskProcessDataRaw: BranchAction[][] = [
|
|||||||
*
|
*
|
||||||
* @param branch_Number - 分支数量
|
* @param branch_Number - 分支数量
|
||||||
* @param Modification_Requirement - 修改需求
|
* @param Modification_Requirement - 修改需求
|
||||||
* @param Existing_Steps - 现有步骤名称列表
|
* @param Existing_Steps - 现有步骤列表(包含完整信息的对象数组)
|
||||||
* @param Baseline_Completion - 基线完成度
|
* @param Baseline_Completion - 基线完成度
|
||||||
* @param stepTaskExisting - 现有任务
|
* @param stepTaskExisting - 现有任务
|
||||||
* @param General_Goal - 总体目标
|
* @param General_Goal - 总体目标
|
||||||
@@ -115,7 +115,7 @@ const mockBranchTaskProcessDataRaw: BranchAction[][] = [
|
|||||||
export const mockBranchTaskProcessAPI = async (params: {
|
export const mockBranchTaskProcessAPI = async (params: {
|
||||||
branch_Number: number
|
branch_Number: number
|
||||||
Modification_Requirement: string
|
Modification_Requirement: string
|
||||||
Existing_Steps: string[]
|
Existing_Steps: BranchAction[]
|
||||||
Baseline_Completion: number
|
Baseline_Completion: number
|
||||||
stepTaskExisting: any
|
stepTaskExisting: any
|
||||||
General_Goal: string
|
General_Goal: string
|
||||||
|
|||||||
@@ -1,617 +0,0 @@
|
|||||||
// fill_stepTask 接口的 Mock API
|
|
||||||
// 简化版本:硬编码多个流程卡片的填充内容(支持多个分支方案)
|
|
||||||
|
|
||||||
import type { IRawStepTask } from '@/stores'
|
|
||||||
|
|
||||||
// 后端返回的数据格式
|
|
||||||
export type FillStepTaskResponse = IRawStepTask
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mock API:模拟后端 fill_stepTask 接口调用
|
|
||||||
*
|
|
||||||
* 根据 stepTask 的 StepName 返回对应的填充内容
|
|
||||||
* 支持多个分支方案的轮询选择
|
|
||||||
*
|
|
||||||
* @param General_Goal - 总体目标
|
|
||||||
* @param stepTask - 待填充的任务(包含基本字段)
|
|
||||||
* @returns Promise<IRawStepTask> - 返回填充了 AgentSelection 和 TaskProcess 的完整任务
|
|
||||||
*/
|
|
||||||
export const mockFillStepTaskAPI = async (params: {
|
|
||||||
General_Goal: string
|
|
||||||
stepTask: any
|
|
||||||
}): Promise<IRawStepTask> => {
|
|
||||||
// 模拟网络延迟 300ms
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 300))
|
|
||||||
|
|
||||||
console.log('[Mock API] fill_stepTask 调用参数:', params)
|
|
||||||
|
|
||||||
const stepName = params.stepTask.StepName
|
|
||||||
|
|
||||||
// 🆕 使用轮询方式选择填充方案(依次循环使用所有分支方案)
|
|
||||||
const branchIndexKey = `fill-step-task-index-${params.stepTask?.Id || stepName || 'default'}`
|
|
||||||
let lastIndex = parseInt(sessionStorage.getItem(branchIndexKey) || '0')
|
|
||||||
const totalBranches = 4 // 总共4个分支方案
|
|
||||||
const selectedBranchIndex = (lastIndex + 1) % totalBranches
|
|
||||||
sessionStorage.setItem(branchIndexKey, selectedBranchIndex.toString())
|
|
||||||
|
|
||||||
console.log('[Mock API] fill_stepTask 选择分支方案:', selectedBranchIndex + 1, '/', totalBranches)
|
|
||||||
|
|
||||||
// ==================== 第一个任务组 ====================
|
|
||||||
if (stepName === '分析用户需求' || stepName === '需求快速原型' || stepName === '需求深度分析' || stepName === '迭代规划') {
|
|
||||||
let filledTask: IRawStepTask
|
|
||||||
|
|
||||||
if (selectedBranchIndex === 0) {
|
|
||||||
// 第一个分支方案:分析用户需求
|
|
||||||
filledTask = {
|
|
||||||
Id: params.stepTask.Id || 'task-1',
|
|
||||||
StepName: '分析用户需求',
|
|
||||||
TaskContent: '分析用户需求,制定项目开发计划',
|
|
||||||
InputObject_List: ['用户需求文档', '技术规范'],
|
|
||||||
OutputObject: '项目开发计划书',
|
|
||||||
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '!腐蚀机理研究员!负责!腐蚀类型识别-0!,!实验材料学家!负责!腐蚀类型识别-1!',
|
|
||||||
data: {
|
|
||||||
腐蚀机理研究员: {
|
|
||||||
text: '腐蚀机理研究员',
|
|
||||||
style: { background: 'hsl(210, 70%, 50%)' },
|
|
||||||
},
|
|
||||||
实验材料学家: {
|
|
||||||
text: '实验材料学家',
|
|
||||||
style: { background: 'hsl(120, 70%, 50%)' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TaskProcess: [
|
|
||||||
{
|
|
||||||
ID: 'action-1-1',
|
|
||||||
ActionType: 'Propose',
|
|
||||||
AgentName: '腐蚀机理研究员',
|
|
||||||
Description: '分析腐蚀环境和介质特征,识别潜在腐蚀类型',
|
|
||||||
ImportantInput: ['InputObject:腐蚀类型及成因列表'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-1-2',
|
|
||||||
ActionType: 'Propose',
|
|
||||||
AgentName: '实验材料学家',
|
|
||||||
Description: '设计腐蚀实验方案,确定测试参数',
|
|
||||||
ImportantInput: ['ActionResult:action-1-1'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-1-3',
|
|
||||||
ActionType: 'Critique',
|
|
||||||
AgentName: '腐蚀机理研究员',
|
|
||||||
Description: '评估实验方案的可行性',
|
|
||||||
ImportantInput: [
|
|
||||||
'ActionResult:action-1-2',
|
|
||||||
'ActionResult:action-1-1',
|
|
||||||
'InputObject:腐蚀类型及成因列表',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-1-4',
|
|
||||||
ActionType: 'Improve',
|
|
||||||
AgentName: '实验材料学家',
|
|
||||||
Description: '优化实验设计,完善测试流程',
|
|
||||||
ImportantInput: ['ActionResult:action-1-3'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-1-5',
|
|
||||||
ActionType: 'Finalize',
|
|
||||||
AgentName: '腐蚀机理研究员',
|
|
||||||
Description: '确认最终的腐蚀类型识别方案',
|
|
||||||
ImportantInput: ['ActionResult:action-1-4', 'ActionResult:action-1-3'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
} else if (selectedBranchIndex === 1) {
|
|
||||||
// 第二个分支方案:需求快速原型
|
|
||||||
filledTask = {
|
|
||||||
Id: params.stepTask.Id || 'task-1',
|
|
||||||
StepName: '需求快速原型',
|
|
||||||
TaskContent: '构建快速原型验证核心功能',
|
|
||||||
InputObject_List: ['腐蚀类型及成因列表'],
|
|
||||||
OutputObject: '原型系统',
|
|
||||||
AgentSelection: ['实验材料学家', '防护工程专家'],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '!实验材料学家!负责!需求快速原型-0!,!防护工程专家!负责!需求快速原型-1!',
|
|
||||||
data: {
|
|
||||||
实验材料学家: {
|
|
||||||
text: '实验材料学家',
|
|
||||||
style: { background: 'hsl(120, 70%, 50%)' },
|
|
||||||
},
|
|
||||||
防护工程专家: {
|
|
||||||
text: '防护工程专家',
|
|
||||||
style: { background: 'hsl(30, 70%, 50%)' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TaskProcess: [
|
|
||||||
{
|
|
||||||
ID: 'action-1-1-branch2',
|
|
||||||
ActionType: 'Propose',
|
|
||||||
AgentName: '实验材料学家',
|
|
||||||
Description: '基于腐蚀类型列表,设计快速原型验证方案',
|
|
||||||
ImportantInput: ['InputObject:腐蚀类型及成因列表'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-1-2-branch2',
|
|
||||||
ActionType: 'Propose',
|
|
||||||
AgentName: '防护工程专家',
|
|
||||||
Description: '实现原型核心功能和用户界面',
|
|
||||||
ImportantInput: ['ActionResult:action-1-1-branch2'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-1-3-branch2',
|
|
||||||
ActionType: 'Critique',
|
|
||||||
AgentName: '实验材料学家',
|
|
||||||
Description: '评估原型功能完整性和用户体验',
|
|
||||||
ImportantInput: ['ActionResult:action-1-2-branch2'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-1-4-branch2',
|
|
||||||
ActionType: 'Improve',
|
|
||||||
AgentName: '防护工程专家',
|
|
||||||
Description: '根据反馈优化原型功能和界面',
|
|
||||||
ImportantInput: ['ActionResult:action-1-3-branch2'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-1-5-branch2',
|
|
||||||
ActionType: 'Finalize',
|
|
||||||
AgentName: '实验材料学家',
|
|
||||||
Description: '确认最终版本的原型系统',
|
|
||||||
ImportantInput: ['ActionResult:action-1-4-branch2'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
} else if (selectedBranchIndex === 2) {
|
|
||||||
// 第三个分支方案:需求深度分析
|
|
||||||
filledTask = {
|
|
||||||
Id: params.stepTask.Id || 'task-1',
|
|
||||||
StepName: '需求深度分析',
|
|
||||||
TaskContent: '深入分析用户需求和技术可行性',
|
|
||||||
InputObject_List: ['腐蚀类型及成因列表'],
|
|
||||||
OutputObject: '详细需求分析报告',
|
|
||||||
AgentSelection: ['腐蚀机理研究员', '防护工程专家'],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '!腐蚀机理研究员!负责!需求深度分析-0!,!防护工程专家!负责!需求深度分析-1!',
|
|
||||||
data: {
|
|
||||||
腐蚀机理研究员: {
|
|
||||||
text: '腐蚀机理研究员',
|
|
||||||
style: { background: 'hsl(210, 70%, 50%)' },
|
|
||||||
},
|
|
||||||
防护工程专家: {
|
|
||||||
text: '防护工程专家',
|
|
||||||
style: { background: 'hsl(30, 70%, 50%)' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TaskProcess: [
|
|
||||||
{
|
|
||||||
ID: 'action-1-1-branch3',
|
|
||||||
ActionType: 'Propose',
|
|
||||||
AgentName: '腐蚀机理研究员',
|
|
||||||
Description: '全面分析腐蚀环境和技术约束条件',
|
|
||||||
ImportantInput: ['InputObject:腐蚀类型及成因列表'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-1-2-branch3',
|
|
||||||
ActionType: 'Propose',
|
|
||||||
AgentName: '防护工程专家',
|
|
||||||
Description: '评估技术可行性和防护方案',
|
|
||||||
ImportantInput: ['ActionResult:action-1-1-branch3'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-1-3-branch3',
|
|
||||||
ActionType: 'Critique',
|
|
||||||
AgentName: '腐蚀机理研究员',
|
|
||||||
Description: '评审需求分析的完整性和准确性',
|
|
||||||
ImportantInput: ['ActionResult:action-1-2-branch3', 'ActionResult:action-1-1-branch3'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-1-4-branch3',
|
|
||||||
ActionType: 'Improve',
|
|
||||||
AgentName: '防护工程专家',
|
|
||||||
Description: '补充技术风险评估和应对策略',
|
|
||||||
ImportantInput: ['ActionResult:action-1-3-branch3'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-1-5-branch3',
|
|
||||||
ActionType: 'Finalize',
|
|
||||||
AgentName: '腐蚀机理研究员',
|
|
||||||
Description: '确认详细需求分析报告',
|
|
||||||
ImportantInput: ['ActionResult:action-1-4-branch3', 'ActionResult:action-1-3-branch3'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 第四个分支方案:迭代规划
|
|
||||||
filledTask = {
|
|
||||||
Id: params.stepTask.Id || 'task-1',
|
|
||||||
StepName: '迭代规划',
|
|
||||||
TaskContent: '制定敏捷开发迭代计划',
|
|
||||||
InputObject_List: ['腐蚀类型及成因列表'],
|
|
||||||
OutputObject: '迭代计划',
|
|
||||||
AgentSelection: ['防护工程专家', '腐蚀机理研究员'],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '!防护工程专家!负责!迭代规划-0!,!腐蚀机理研究员!负责!迭代规划-1!',
|
|
||||||
data: {
|
|
||||||
防护工程专家: {
|
|
||||||
text: '防护工程专家',
|
|
||||||
style: { background: 'hsl(30, 70%, 50%)' },
|
|
||||||
},
|
|
||||||
腐蚀机理研究员: {
|
|
||||||
text: '腐蚀机理研究员',
|
|
||||||
style: { background: 'hsl(210, 70%, 50%)' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TaskProcess: [
|
|
||||||
{
|
|
||||||
ID: 'action-1-1-branch4',
|
|
||||||
ActionType: 'Propose',
|
|
||||||
AgentName: '防护工程专家',
|
|
||||||
Description: '制定敏捷开发总体框架和迭代周期',
|
|
||||||
ImportantInput: ['InputObject:腐蚀类型及成因列表'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-1-2-branch4',
|
|
||||||
ActionType: 'Propose',
|
|
||||||
AgentName: '腐蚀机理研究员',
|
|
||||||
Description: '规划各迭代阶段的技术验证重点',
|
|
||||||
ImportantInput: ['ActionResult:action-1-1-branch4'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-1-3-branch4',
|
|
||||||
ActionType: 'Critique',
|
|
||||||
AgentName: '防护工程专家',
|
|
||||||
Description: '评审迭代计划的合理性和可执行性',
|
|
||||||
ImportantInput: ['ActionResult:action-1-2-branch4'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-1-4-branch4',
|
|
||||||
ActionType: 'Improve',
|
|
||||||
AgentName: '腐蚀机理研究员',
|
|
||||||
Description: '优化迭代节奏和里程碑设置',
|
|
||||||
ImportantInput: ['ActionResult:action-1-3-branch4'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-1-5-branch4',
|
|
||||||
ActionType: 'Finalize',
|
|
||||||
AgentName: '防护工程专家',
|
|
||||||
Description: '确认最终迭代计划',
|
|
||||||
ImportantInput: ['ActionResult:action-1-4-branch4', 'ActionResult:action-1-3-branch4'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[Mock API] fill_stepTask 返回数据:', filledTask)
|
|
||||||
return filledTask
|
|
||||||
}
|
|
||||||
// ==================== 第二个任务组 ====================
|
|
||||||
else if (stepName === '系统设计与架构' || stepName === '原型测试与优化' || stepName === '质量保障设计' || stepName === '快速开发与验证') {
|
|
||||||
let filledTask: IRawStepTask
|
|
||||||
|
|
||||||
if (selectedBranchIndex === 0) {
|
|
||||||
// 第一个分支方案:系统设计与架构
|
|
||||||
filledTask = {
|
|
||||||
Id: params.stepTask.Id || 'task-2',
|
|
||||||
StepName: '系统设计与架构',
|
|
||||||
TaskContent: '设计系统架构和数据库结构',
|
|
||||||
InputObject_List: ['项目开发计划书'],
|
|
||||||
OutputObject: '系统设计文档',
|
|
||||||
AgentSelection: ['腐蚀机理研究员', '防护工程专家'],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '!腐蚀机理研究员!负责!系统设计与架构-0!,!防护工程专家!负责!系统设计与架构-1!',
|
|
||||||
data: {
|
|
||||||
腐蚀机理研究员: {
|
|
||||||
text: '腐蚀机理研究员',
|
|
||||||
style: { background: 'hsl(210, 70%, 50%)' },
|
|
||||||
},
|
|
||||||
防护工程专家: {
|
|
||||||
text: '防护工程专家',
|
|
||||||
style: { background: 'hsl(30, 70%, 50%)' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TaskProcess: [
|
|
||||||
{
|
|
||||||
ID: 'action-2-1',
|
|
||||||
ActionType: 'Propose',
|
|
||||||
AgentName: '腐蚀机理研究员',
|
|
||||||
Description: '分析系统功能需求,提出整体架构方案',
|
|
||||||
ImportantInput: ['InputObject:项目开发计划书'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-2-2',
|
|
||||||
ActionType: 'Propose',
|
|
||||||
AgentName: '防护工程专家',
|
|
||||||
Description: '设计防护系统架构和数据库模型',
|
|
||||||
ImportantInput: ['ActionResult:action-2-1'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-2-3',
|
|
||||||
ActionType: 'Critique',
|
|
||||||
AgentName: '腐蚀机理研究员',
|
|
||||||
Description: '评审架构设计的合理性和可扩展性',
|
|
||||||
ImportantInput: ['ActionResult:action-2-2', 'InputObject:项目开发计划书'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-2-4',
|
|
||||||
ActionType: 'Improve',
|
|
||||||
AgentName: '防护工程专家',
|
|
||||||
Description: '优化架构设计,补充性能和安全方案',
|
|
||||||
ImportantInput: ['ActionResult:action-2-3', 'ActionResult:action-2-2'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-2-5',
|
|
||||||
ActionType: 'Finalize',
|
|
||||||
AgentName: '腐蚀机理研究员',
|
|
||||||
Description: '确认最终系统架构设计文档',
|
|
||||||
ImportantInput: ['ActionResult:action-2-4', 'ActionResult:action-2-3'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
} else if (selectedBranchIndex === 1) {
|
|
||||||
// 第二个分支方案:原型测试与优化
|
|
||||||
filledTask = {
|
|
||||||
Id: params.stepTask.Id || 'task-2',
|
|
||||||
StepName: '原型测试与优化',
|
|
||||||
TaskContent: '测试原型并根据反馈快速迭代',
|
|
||||||
InputObject_List: ['原型系统'],
|
|
||||||
OutputObject: '优化后的原型',
|
|
||||||
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '!腐蚀机理研究员!负责!原型测试与优化-0!,!实验材料学家!负责!原型测试与优化-1!',
|
|
||||||
data: {
|
|
||||||
腐蚀机理研究员: {
|
|
||||||
text: '腐蚀机理研究员',
|
|
||||||
style: { background: 'hsl(210, 70%, 50%)' },
|
|
||||||
},
|
|
||||||
实验材料学家: {
|
|
||||||
text: '实验材料学家',
|
|
||||||
style: { background: 'hsl(120, 70%, 50%)' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TaskProcess: [
|
|
||||||
{
|
|
||||||
ID: 'action-2-1-branch2',
|
|
||||||
ActionType: 'Propose',
|
|
||||||
AgentName: '腐蚀机理研究员',
|
|
||||||
Description: '制定原型测试计划和验证标准',
|
|
||||||
ImportantInput: ['InputObject:原型系统'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-2-2-branch2',
|
|
||||||
ActionType: 'Propose',
|
|
||||||
AgentName: '实验材料学家',
|
|
||||||
Description: '执行原型测试并收集性能数据',
|
|
||||||
ImportantInput: ['ActionResult:action-2-1-branch2'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-2-3-branch2',
|
|
||||||
ActionType: 'Critique',
|
|
||||||
AgentName: '腐蚀机理研究员',
|
|
||||||
Description: '分析测试结果和用户反馈',
|
|
||||||
ImportantInput: ['ActionResult:action-2-2-branch2'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-2-4-branch2',
|
|
||||||
ActionType: 'Improve',
|
|
||||||
AgentName: '实验材料学家',
|
|
||||||
Description: '根据测试结果优化原型功能和性能',
|
|
||||||
ImportantInput: ['ActionResult:action-2-3-branch2'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-2-5-branch2',
|
|
||||||
ActionType: 'Finalize',
|
|
||||||
AgentName: '腐蚀机理研究员',
|
|
||||||
Description: '确认优化后的原型版本',
|
|
||||||
ImportantInput: ['ActionResult:action-2-4-branch2', 'ActionResult:action-2-3-branch2'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
} else if (selectedBranchIndex === 2) {
|
|
||||||
// 第三个分支方案:质量保障设计
|
|
||||||
filledTask = {
|
|
||||||
Id: params.stepTask.Id || 'task-2',
|
|
||||||
StepName: '质量保障设计',
|
|
||||||
TaskContent: '设计质量保障体系和测试方案',
|
|
||||||
InputObject_List: ['详细需求分析报告'],
|
|
||||||
OutputObject: '质量保障方案',
|
|
||||||
AgentSelection: ['实验材料学家', '防护工程专家'],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '!实验材料学家!负责!质量保障设计-0!,!防护工程专家!负责!质量保障设计-1!',
|
|
||||||
data: {
|
|
||||||
实验材料学家: {
|
|
||||||
text: '实验材料学家',
|
|
||||||
style: { background: 'hsl(120, 70%, 50%)' },
|
|
||||||
},
|
|
||||||
防护工程专家: {
|
|
||||||
text: '防护工程专家',
|
|
||||||
style: { background: 'hsl(30, 70%, 50%)' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TaskProcess: [
|
|
||||||
{
|
|
||||||
ID: 'action-2-1-branch3',
|
|
||||||
ActionType: 'Propose',
|
|
||||||
AgentName: '实验材料学家',
|
|
||||||
Description: '制定质量标准和测试指标体系',
|
|
||||||
ImportantInput: ['InputObject:详细需求分析报告'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-2-2-branch3',
|
|
||||||
ActionType: 'Propose',
|
|
||||||
AgentName: '防护工程专家',
|
|
||||||
Description: '设计质量保障流程和验证方案',
|
|
||||||
ImportantInput: ['ActionResult:action-2-1-branch3'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-2-3-branch3',
|
|
||||||
ActionType: 'Critique',
|
|
||||||
AgentName: '实验材料学家',
|
|
||||||
Description: '评审质量保障方案的完整性',
|
|
||||||
ImportantInput: ['ActionResult:action-2-2-branch3'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-2-4-branch3',
|
|
||||||
ActionType: 'Improve',
|
|
||||||
AgentName: '防护工程专家',
|
|
||||||
Description: '完善质量保障方案,补充风险控制',
|
|
||||||
ImportantInput: ['ActionResult:action-2-3-branch3'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-2-5-branch3',
|
|
||||||
ActionType: 'Finalize',
|
|
||||||
AgentName: '实验材料学家',
|
|
||||||
Description: '确认最终质量保障方案',
|
|
||||||
ImportantInput: ['ActionResult:action-2-4-branch3', 'ActionResult:action-2-3-branch3'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 第四个分支方案:快速开发与验证
|
|
||||||
filledTask = {
|
|
||||||
Id: params.stepTask.Id || 'task-2',
|
|
||||||
StepName: '快速开发与验证',
|
|
||||||
TaskContent: '快速开发并持续验证功能',
|
|
||||||
InputObject_List: ['迭代计划'],
|
|
||||||
OutputObject: '可运行的功能模块',
|
|
||||||
AgentSelection: ['实验材料学家', '防护工程专家'],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '!实验材料学家!负责!快速开发与验证-0!,!防护工程专家!负责!快速开发与验证-1!',
|
|
||||||
data: {
|
|
||||||
实验材料学家: {
|
|
||||||
text: '实验材料学家',
|
|
||||||
style: { background: 'hsl(120, 70%, 50%)' },
|
|
||||||
},
|
|
||||||
防护工程专家: {
|
|
||||||
text: '防护工程专家',
|
|
||||||
style: { background: 'hsl(30, 70%, 50%)' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TaskProcess: [
|
|
||||||
{
|
|
||||||
ID: 'action-2-1-branch4',
|
|
||||||
ActionType: 'Propose',
|
|
||||||
AgentName: '实验材料学家',
|
|
||||||
Description: '根据迭代计划快速开发功能模块',
|
|
||||||
ImportantInput: ['InputObject:迭代计划'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-2-2-branch4',
|
|
||||||
ActionType: 'Propose',
|
|
||||||
AgentName: '防护工程专家',
|
|
||||||
Description: '实现功能防护机制和验证逻辑',
|
|
||||||
ImportantInput: ['ActionResult:action-2-1-branch4'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-2-3-branch4',
|
|
||||||
ActionType: 'Critique',
|
|
||||||
AgentName: '实验材料学家',
|
|
||||||
Description: '验证功能模块的正确性和性能',
|
|
||||||
ImportantInput: ['ActionResult:action-2-2-branch4'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-2-4-branch4',
|
|
||||||
ActionType: 'Improve',
|
|
||||||
AgentName: '防护工程专家',
|
|
||||||
Description: '根据验证结果优化功能实现',
|
|
||||||
ImportantInput: ['ActionResult:action-2-3-branch4'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-2-5-branch4',
|
|
||||||
ActionType: 'Finalize',
|
|
||||||
AgentName: '实验材料学家',
|
|
||||||
Description: '确认可运行的功能模块',
|
|
||||||
ImportantInput: ['ActionResult:action-2-4-branch4', 'ActionResult:action-2-3-branch4'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[Mock API] fill_stepTask 返回数据:', filledTask)
|
|
||||||
return filledTask
|
|
||||||
}
|
|
||||||
// ==================== 第三个任务组(仅第三个分支方案) ====================
|
|
||||||
else if (stepName === '系统开发与测试') {
|
|
||||||
const filledTask: IRawStepTask = {
|
|
||||||
Id: params.stepTask.Id || 'task-3',
|
|
||||||
StepName: '系统开发与测试',
|
|
||||||
TaskContent: '按质量标准进行系统开发和测试',
|
|
||||||
InputObject_List: ['质量保障方案'],
|
|
||||||
OutputObject: '经过完整测试的系统',
|
|
||||||
AgentSelection: ['腐蚀机理研究员', '实验材料学家'],
|
|
||||||
Collaboration_Brief_frontEnd: {
|
|
||||||
template: '!腐蚀机理研究员!负责!系统开发与测试-0!,!实验材料学家!负责!系统开发与测试-1!',
|
|
||||||
data: {
|
|
||||||
腐蚀机理研究员: {
|
|
||||||
text: '腐蚀机理研究员',
|
|
||||||
style: { background: 'hsl(210, 70%, 50%)' },
|
|
||||||
},
|
|
||||||
实验材料学家: {
|
|
||||||
text: '实验材料学家',
|
|
||||||
style: { background: 'hsl(120, 70%, 50%)' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TaskProcess: [
|
|
||||||
{
|
|
||||||
ID: 'action-3-1',
|
|
||||||
ActionType: 'Propose',
|
|
||||||
AgentName: '腐蚀机理研究员',
|
|
||||||
Description: '根据质量标准制定系统开发计划',
|
|
||||||
ImportantInput: ['InputObject:质量保障方案'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-3-2',
|
|
||||||
ActionType: 'Propose',
|
|
||||||
AgentName: '实验材料学家',
|
|
||||||
Description: '按标准实施系统开发和单元测试',
|
|
||||||
ImportantInput: ['ActionResult:action-3-1'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-3-3',
|
|
||||||
ActionType: 'Critique',
|
|
||||||
AgentName: '腐蚀机理研究员',
|
|
||||||
Description: '评审系统开发质量和测试覆盖率',
|
|
||||||
ImportantInput: ['ActionResult:action-3-2'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-3-4',
|
|
||||||
ActionType: 'Improve',
|
|
||||||
AgentName: '实验材料学家',
|
|
||||||
Description: '根据评审结果完善系统功能和测试',
|
|
||||||
ImportantInput: ['ActionResult:action-3-3'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 'action-3-5',
|
|
||||||
ActionType: 'Finalize',
|
|
||||||
AgentName: '腐蚀机理研究员',
|
|
||||||
Description: '确认经过完整测试的系统版本',
|
|
||||||
ImportantInput: ['ActionResult:action-3-4', 'ActionResult:action-3-3'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[Mock API] fill_stepTask 返回数据:', filledTask)
|
|
||||||
return filledTask
|
|
||||||
}
|
|
||||||
// ==================== 其他任务 ====================
|
|
||||||
else {
|
|
||||||
// 其他任务,返回空 TaskProcess
|
|
||||||
console.warn('[Mock API] 未知的 StepName:', stepName)
|
|
||||||
return {
|
|
||||||
...params.stepTask,
|
|
||||||
AgentSelection: [],
|
|
||||||
TaskProcess: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default mockFillStepTaskAPI
|
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, nextTick } from 'vue'
|
import { ref, computed, watch, nextTick } from 'vue'
|
||||||
import { useAgentsStore, useSelectionStore } from '@/stores'
|
import { useAgentsStore, useSelectionStore } from '@/stores'
|
||||||
|
import { convertToTaskProcess } from '@/stores/modules/agents'
|
||||||
import { getAgentMapIcon } from '@/layout/components/config'
|
import { getAgentMapIcon } from '@/layout/components/config'
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
const agentsStore = useAgentsStore()
|
const agentsStore = useAgentsStore()
|
||||||
const selectionStore = useSelectionStore()
|
const selectionStore = useSelectionStore()
|
||||||
|
const agentsHeatmapData = ref<AgentHeatmapData[]>([])
|
||||||
|
const selectedAgents = ref<Set<string>>(new Set())
|
||||||
|
const selectedAssignmentGroup = ref<string[]>([])
|
||||||
|
const selectedDimensions = ref<Set<string>>(new Set())
|
||||||
|
|
||||||
// 获取当前选中的任务
|
// 获取当前选中的任务
|
||||||
const currentTask = computed(() => {
|
const currentTask = computed(() => {
|
||||||
@@ -17,7 +22,7 @@ const currentTaskAgents = computed(() => {
|
|||||||
return currentTask.value?.AgentSelection || []
|
return currentTask.value?.AgentSelection || []
|
||||||
})
|
})
|
||||||
|
|
||||||
// 所有agent列表(用于右侧展示)
|
// 所有agent列表
|
||||||
const allAgents = computed(() => {
|
const allAgents = computed(() => {
|
||||||
return agentsStore.agents
|
return agentsStore.agents
|
||||||
})
|
})
|
||||||
@@ -32,19 +37,7 @@ interface AgentHeatmapData {
|
|||||||
scoreDetails?: Array<{ dimension: string; score: number; reason: string }> // 详细信息
|
scoreDetails?: Array<{ dimension: string; score: number; reason: string }> // 详细信息
|
||||||
}
|
}
|
||||||
|
|
||||||
// 所有agent的热力图数据
|
// 确认的agent组合列表 - 从 store 读取
|
||||||
const agentsHeatmapData = ref<AgentHeatmapData[]>([])
|
|
||||||
|
|
||||||
// 选中的agent集合(用于Comparison部分)
|
|
||||||
const selectedAgents = ref<Set<string>>(new Set())
|
|
||||||
|
|
||||||
// 当前选中的Assignment组合的agents(用于Assignment卡片的边框显示)
|
|
||||||
const selectedAssignmentGroup = ref<string[]>([])
|
|
||||||
|
|
||||||
// 选中的维度集合
|
|
||||||
const selectedDimensions = ref<Set<string>>(new Set())
|
|
||||||
|
|
||||||
// 确认的agent组合列表 - 从 store 读取(按当前任务ID)
|
|
||||||
const confirmedAgentGroups = computed(() => {
|
const confirmedAgentGroups = computed(() => {
|
||||||
const taskId = currentTask.value?.Id
|
const taskId = currentTask.value?.Id
|
||||||
if (!taskId) return []
|
if (!taskId) return []
|
||||||
@@ -59,36 +52,44 @@ const isInitializing = ref(false)
|
|||||||
const isAddingDimension = ref(false)
|
const isAddingDimension = ref(false)
|
||||||
const isLoadingConfirm = ref(false) // 确认 agent 组合时的加载状态
|
const isLoadingConfirm = ref(false) // 确认 agent 组合时的加载状态
|
||||||
const isLoadingSelectGroup = ref(false) // 选择已保存组合时的加载状态
|
const isLoadingSelectGroup = ref(false) // 选择已保存组合时的加载状态
|
||||||
const isLoadingInitialTask = ref(false) // 首次加载任务时的加载状态
|
|
||||||
|
|
||||||
|
// 热力图排序:选中的 agent 排在前面,其他按平均分降序
|
||||||
|
const sortHeatmapBySelection = () => {
|
||||||
|
if (agentsHeatmapData.value.length === 0) return
|
||||||
|
agentsHeatmapData.value.sort((a, b) => {
|
||||||
|
const aIsSelected = selectedAgents.value.has(a.agentName)
|
||||||
|
const bIsSelected = selectedAgents.value.has(b.agentName)
|
||||||
|
|
||||||
|
// 如果一个是选中的 agent,另一个不是,选中的 agent 排前面
|
||||||
|
if (aIsSelected && !bIsSelected) return -1
|
||||||
|
if (!aIsSelected && bIsSelected) return 1
|
||||||
|
|
||||||
|
// 如果都是或都不是选中的 agent,按选中的维度平均分降序排列
|
||||||
|
const selectedDimArray = Array.from(selectedDimensions.value)
|
||||||
|
const aAverage = calculateAgentAverage(a, selectedDimArray)
|
||||||
|
const bAverage = calculateAgentAverage(b, selectedDimArray)
|
||||||
|
return bAverage - aAverage
|
||||||
|
})
|
||||||
|
}
|
||||||
// 处理搜索提交
|
// 处理搜索提交
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!searchValue.value.trim() || isAddingDimension.value) return
|
if (!searchValue.value.trim() || isAddingDimension.value) return
|
||||||
|
|
||||||
const newDimension = searchValue.value.trim()
|
const newDimension = searchValue.value.trim()
|
||||||
|
|
||||||
// 检查维度是否已存在
|
// 检查维度是否已存在
|
||||||
if (scoreDimensions.value.includes(newDimension)) {
|
if (scoreDimensions.value.includes(newDimension)) {
|
||||||
console.log(`维度"${newDimension}"已存在,跳过添加`)
|
|
||||||
searchValue.value = ''
|
searchValue.value = ''
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isAddingDimension.value = true
|
isAddingDimension.value = true
|
||||||
|
|
||||||
// 使用 Mock API 进行测试(开发阶段)
|
|
||||||
// 切换到真实API时,只需将 mockAgentSelectModifyAddAspect 改为 agentSelectModifyAddAspect
|
|
||||||
const response = await api.agentSelectModifyAddAspect({
|
const response = await api.agentSelectModifyAddAspect({
|
||||||
aspectList: [...scoreDimensions.value, newDimension]
|
aspectList: [...scoreDimensions.value, newDimension]
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('Mock API - 添加新维度成功:', response)
|
|
||||||
|
|
||||||
// 1. 先添加新维度到本地维度列表(立即显示)
|
|
||||||
scoreDimensions.value.push(response.aspectName)
|
scoreDimensions.value.push(response.aspectName)
|
||||||
|
|
||||||
// 2. 为每个agent添加新维度的评分(立即显示)
|
// 为每个agent添加新维度的评分(立即显示)
|
||||||
agentsHeatmapData.value.forEach(agentData => {
|
agentsHeatmapData.value.forEach(agentData => {
|
||||||
const scoreInfo = response.agentScores[agentData.agentName]
|
const scoreInfo = response.agentScores[agentData.agentName]
|
||||||
if (scoreInfo) {
|
if (scoreInfo) {
|
||||||
@@ -100,28 +101,42 @@ const handleSubmit = async () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
//异步更新 store(等前端显示完成后再更新,避免触发重新初始化)
|
||||||
console.log(`已添加新维度"${response.aspectName}"到热力图`)
|
|
||||||
|
|
||||||
// 3. 异步更新 store(等前端显示完成后再更新,避免触发重新初始化)
|
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
const taskId = currentTask.value?.Id
|
||||||
|
|
||||||
|
//更新按任务ID的存储
|
||||||
|
if (taskId) {
|
||||||
|
const existingTaskData = agentsStore.getTaskScoreData(taskId)
|
||||||
|
if (existingTaskData) {
|
||||||
|
// 检查维度是否已存在
|
||||||
|
if (!existingTaskData.aspectList.includes(response.aspectName)) {
|
||||||
|
existingTaskData.aspectList.push(response.aspectName)
|
||||||
|
}
|
||||||
|
// 更新评分数据
|
||||||
|
for (const [agentName, scoreInfo] of Object.entries(response.agentScores)) {
|
||||||
|
if (!existingTaskData.agentScores[agentName]) {
|
||||||
|
existingTaskData.agentScores[agentName] = {}
|
||||||
|
}
|
||||||
|
existingTaskData.agentScores[agentName][response.aspectName] = scoreInfo
|
||||||
|
}
|
||||||
|
// 保存到 store
|
||||||
|
agentsStore.setTaskScoreData(taskId, existingTaskData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容旧版本:更新全局存储
|
||||||
const currentStoreData = agentsStore.IAgentSelectModifyAddRequest
|
const currentStoreData = agentsStore.IAgentSelectModifyAddRequest
|
||||||
if (currentStoreData && currentStoreData.aspectList) {
|
if (currentStoreData && currentStoreData.aspectList) {
|
||||||
// 检查维度是否已存在(防止store中已存在)
|
|
||||||
if (!currentStoreData.aspectList.includes(response.aspectName)) {
|
if (!currentStoreData.aspectList.includes(response.aspectName)) {
|
||||||
currentStoreData.aspectList.push(response.aspectName)
|
currentStoreData.aspectList.push(response.aspectName)
|
||||||
}
|
}
|
||||||
// 更新评分数据
|
|
||||||
// response.agentScores: { agentName: { score, reason } }
|
|
||||||
// 转换为 agentScores[agentName][aspectName] = { score, reason }
|
|
||||||
for (const [agentName, scoreInfo] of Object.entries(response.agentScores)) {
|
for (const [agentName, scoreInfo] of Object.entries(response.agentScores)) {
|
||||||
if (!currentStoreData.agentScores[agentName]) {
|
if (!currentStoreData.agentScores[agentName]) {
|
||||||
currentStoreData.agentScores[agentName] = {}
|
currentStoreData.agentScores[agentName] = {}
|
||||||
}
|
}
|
||||||
currentStoreData.agentScores[agentName][response.aspectName] = scoreInfo
|
currentStoreData.agentScores[agentName][response.aspectName] = scoreInfo
|
||||||
}
|
}
|
||||||
console.log(`已更新 store`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空输入框
|
// 清空输入框
|
||||||
@@ -133,7 +148,7 @@ const handleSubmit = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断两个agent组合是否相同(不考虑顺序)
|
// 判断两个agent组合是否相同
|
||||||
const areAgentGroupsEqual = (group1: string[], group2: string[]): boolean => {
|
const areAgentGroupsEqual = (group1: string[], group2: string[]): boolean => {
|
||||||
if (group1.length !== group2.length) return false
|
if (group1.length !== group2.length) return false
|
||||||
const sorted1 = [...group1].sort()
|
const sorted1 = [...group1].sort()
|
||||||
@@ -145,7 +160,6 @@ const areAgentGroupsEqual = (group1: string[], group2: string[]): boolean => {
|
|||||||
const confirmAgentSelection = async () => {
|
const confirmAgentSelection = async () => {
|
||||||
if (selectedAgents.value.size > 0 && currentTask.value?.Id) {
|
if (selectedAgents.value.size > 0 && currentTask.value?.Id) {
|
||||||
const agentArray = Array.from(selectedAgents.value)
|
const agentArray = Array.from(selectedAgents.value)
|
||||||
|
|
||||||
// 检查该agent组合是否已存在(统一检查,包括初始组合和用户添加的组合)
|
// 检查该agent组合是否已存在(统一检查,包括初始组合和用户添加的组合)
|
||||||
const existingGroups = agentsStore.getConfirmedAgentGroups(currentTask.value.Id)
|
const existingGroups = agentsStore.getConfirmedAgentGroups(currentTask.value.Id)
|
||||||
const existingDuplicateIndex = existingGroups.findIndex(group =>
|
const existingDuplicateIndex = existingGroups.findIndex(group =>
|
||||||
@@ -153,64 +167,30 @@ const confirmAgentSelection = async () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (existingDuplicateIndex !== -1) {
|
if (existingDuplicateIndex !== -1) {
|
||||||
// 找到重复的组合,只选中该卡片,不添加重复
|
|
||||||
console.log('该agent组合已存在,只选中卡片,不添加重复')
|
|
||||||
const existingGroup = existingGroups[existingDuplicateIndex]!
|
const existingGroup = existingGroups[existingDuplicateIndex]!
|
||||||
selectedAssignmentGroup.value = [...existingGroup]
|
selectedAssignmentGroup.value = [...existingGroup]
|
||||||
selectedAgents.value = new Set(existingGroup)
|
selectedAgents.value = new Set(existingGroup)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 该组合不存在,添加新组合
|
|
||||||
// 使用 store 的方法添加,按任务ID存储(不持久化到localStorage)
|
|
||||||
agentsStore.addConfirmedAgentGroup(currentTask.value.Id, agentArray)
|
agentsStore.addConfirmedAgentGroup(currentTask.value.Id, agentArray)
|
||||||
console.log(`已确认 agent 组合 [任务 ${currentTask.value.Id}]:`, agentArray)
|
|
||||||
|
|
||||||
// 调用 Mock API 填充任务流程
|
|
||||||
try {
|
try {
|
||||||
isLoadingConfirm.value = true
|
isLoadingConfirm.value = true
|
||||||
console.log('=== 开始调用 fillStepTaskTaskProcess Mock API ===')
|
const stepTaskForApi = agentsStore.createStepTaskForApi(agentArray)
|
||||||
console.log('1. 当前任务数据 (IRawStepTask 格式):', currentTask.value)
|
const goal = agentsStore.agentRawPlan.data?.['General Goal'] || '开发智能协作系统'
|
||||||
console.log('2. 选中的 agents:', agentArray)
|
|
||||||
|
|
||||||
// 将 IRawStepTask 转换为 IApiStepTask 格式
|
if (agentsStore.isStopping || agentsStore.hasStoppedFilling) {
|
||||||
const stepTaskForApi = {
|
isLoadingConfirm.value = false
|
||||||
name: currentTask.value.StepName || '',
|
return
|
||||||
content: currentTask.value.TaskContent || '',
|
|
||||||
inputs: currentTask.value.InputObject_List || [],
|
|
||||||
output: currentTask.value.OutputObject || '',
|
|
||||||
agents: agentArray,
|
|
||||||
brief: currentTask.value.Collaboration_Brief_frontEnd || {
|
|
||||||
template: '',
|
|
||||||
data: {}
|
|
||||||
},
|
|
||||||
process: []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('3. 转换后的 API 请求参数 (IApiStepTask 格式):', stepTaskForApi)
|
|
||||||
|
|
||||||
// 获取 goal(从 agentRawPlan 中获取)
|
|
||||||
const goal = agentsStore.agentRawPlan.data?.['General Goal'] || '开发智能协作系统'
|
|
||||||
console.log('4. General Goal:', goal)
|
|
||||||
|
|
||||||
// 调用 Mock API
|
|
||||||
const filledTask = await api.fillStepTaskTaskProcess({
|
const filledTask = await api.fillStepTaskTaskProcess({
|
||||||
goal,
|
goal,
|
||||||
stepTask: stepTaskForApi,
|
stepTask: stepTaskForApi,
|
||||||
agents: agentArray
|
agents: agentArray
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('=== Mock API 返回成功 ===')
|
|
||||||
console.log('5. 返回的完整数据 (IApiStepTask 格式):', filledTask)
|
|
||||||
console.log('6. TaskProcess 流程数量:', filledTask.process?.length || 0)
|
|
||||||
console.log('7. TaskProcess 详情:', filledTask.process)
|
|
||||||
console.log('8. Collaboration Brief:', filledTask.brief)
|
|
||||||
|
|
||||||
// 存储到 selectionStore
|
|
||||||
selectionStore.setAgentTaskProcess(currentTask.value.Id, agentArray, filledTask)
|
selectionStore.setAgentTaskProcess(currentTask.value.Id, agentArray, filledTask)
|
||||||
console.log('✅ Mock API 调用成功,数据已存储到 selectionStore')
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Mock API 调用失败:', error)
|
console.error('❌ API 调用失败:', error)
|
||||||
} finally {
|
} finally {
|
||||||
isLoadingConfirm.value = false
|
isLoadingConfirm.value = false
|
||||||
}
|
}
|
||||||
@@ -219,84 +199,35 @@ const confirmAgentSelection = async () => {
|
|||||||
|
|
||||||
// 点击 Assignment 部分的 agent 组合卡片,更新 Comparison 部分的选中状态
|
// 点击 Assignment 部分的 agent 组合卡片,更新 Comparison 部分的选中状态
|
||||||
const selectAgentGroup = async (agentNames: string[]) => {
|
const selectAgentGroup = async (agentNames: string[]) => {
|
||||||
// 更新Assignment边框状态
|
|
||||||
selectedAssignmentGroup.value = [...agentNames]
|
selectedAssignmentGroup.value = [...agentNames]
|
||||||
|
|
||||||
// 更新Comparison部分的选中状态
|
|
||||||
selectedAgents.value = new Set(agentNames)
|
selectedAgents.value = new Set(agentNames)
|
||||||
// 触发响应式更新
|
|
||||||
selectedAgents.value = new Set(selectedAgents.value)
|
selectedAgents.value = new Set(selectedAgents.value)
|
||||||
|
|
||||||
// 保存到 store(持久化选择状态)
|
// 保存到 store
|
||||||
if (currentTask.value?.Id) {
|
if (currentTask.value?.Id) {
|
||||||
agentsStore.setSelectedAgentGroup(currentTask.value.Id, agentNames)
|
agentsStore.setSelectedAgentGroup(currentTask.value.Id, agentNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🆕 联动更新:更新 currentTask 的 AgentSelection 和 TaskProcess
|
// 更新 currentTask 的 AgentSelection 和 TaskProcess
|
||||||
if (currentTask.value?.Id && agentNames.length > 0) {
|
if (currentTask.value?.Id && agentNames.length > 0) {
|
||||||
console.log('🔄 开始联动更新 currentTask 的 agent 组合:', agentNames)
|
|
||||||
|
|
||||||
// 从 selectionStore 获取该 agent 组合对应的 TaskProcess 数据
|
|
||||||
let taskProcessData = selectionStore.getAgentTaskProcess(currentTask.value.Id, agentNames)
|
let taskProcessData = selectionStore.getAgentTaskProcess(currentTask.value.Id, agentNames)
|
||||||
|
|
||||||
// 🆕 如果 selectionStore 中没有数据,自动调用 API 加载
|
|
||||||
if (!taskProcessData) {
|
if (!taskProcessData) {
|
||||||
console.log('⚠️ selectionStore 中没有该组合的 TaskProcess 数据,开始加载...')
|
|
||||||
console.log('📋 当前任务信息:', {
|
|
||||||
taskId: currentTask.value.Id,
|
|
||||||
taskName: currentTask.value.StepName,
|
|
||||||
agents: agentNames
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isLoadingSelectGroup.value = true
|
isLoadingSelectGroup.value = true
|
||||||
// 将 IRawStepTask 转换为 IApiStepTask 格式
|
const stepTaskForApi = agentsStore.createStepTaskForApi(agentNames)
|
||||||
const stepTaskForApi = {
|
|
||||||
name: currentTask.value.StepName || '',
|
|
||||||
content: currentTask.value.TaskContent || '',
|
|
||||||
inputs: currentTask.value.InputObject_List || [],
|
|
||||||
output: currentTask.value.OutputObject || '',
|
|
||||||
agents: agentNames,
|
|
||||||
brief: currentTask.value.Collaboration_Brief_frontEnd || {
|
|
||||||
template: '',
|
|
||||||
data: {}
|
|
||||||
},
|
|
||||||
process: []
|
|
||||||
}
|
|
||||||
|
|
||||||
const goal = agentsStore.agentRawPlan.data?.['General Goal'] || '开发智能协作系统'
|
const goal = agentsStore.agentRawPlan.data?.['General Goal'] || '开发智能协作系统'
|
||||||
|
|
||||||
console.log('📤 准备调用 API:', {
|
if (agentsStore.isStopping || agentsStore.hasStoppedFilling) {
|
||||||
goal,
|
return
|
||||||
stepTask: stepTaskForApi,
|
}
|
||||||
agents: agentNames
|
|
||||||
})
|
|
||||||
|
|
||||||
// 调用 Mock API 加载数据
|
|
||||||
const filledTask = await api.fillStepTaskTaskProcess({
|
const filledTask = await api.fillStepTaskTaskProcess({
|
||||||
goal,
|
goal,
|
||||||
stepTask: stepTaskForApi,
|
stepTask: stepTaskForApi,
|
||||||
agents: agentNames
|
agents: agentNames
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('🔍 API 返回的完整响应:', filledTask)
|
|
||||||
console.log('🔍 process 数据:', filledTask.process)
|
|
||||||
console.log('🔍 process 长度:', filledTask.process?.length)
|
|
||||||
console.log('✅ TaskProcess 数据加载成功:', {
|
|
||||||
agents: agentNames,
|
|
||||||
processCount: filledTask.process?.length || 0,
|
|
||||||
processDetail: filledTask.process,
|
|
||||||
briefDetail: filledTask.brief
|
|
||||||
})
|
|
||||||
|
|
||||||
// 存储到 selectionStore
|
|
||||||
selectionStore.setAgentTaskProcess(currentTask.value.Id, agentNames, filledTask)
|
selectionStore.setAgentTaskProcess(currentTask.value.Id, agentNames, filledTask)
|
||||||
|
|
||||||
console.log('💾 数据已存储到 selectionStore,验证存储是否成功...')
|
|
||||||
// 验证存储
|
|
||||||
const storedData = selectionStore.getAgentTaskProcess(currentTask.value.Id, agentNames)
|
|
||||||
console.log('📦 验证存储结果:', storedData ? '成功' : '失败', storedData)
|
|
||||||
|
|
||||||
taskProcessData = filledTask
|
taskProcessData = filledTask
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 加载 TaskProcess 数据失败:', error)
|
console.error('❌ 加载 TaskProcess 数据失败:', error)
|
||||||
@@ -307,62 +238,17 @@ const selectAgentGroup = async (agentNames: string[]) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (taskProcessData) {
|
if (taskProcessData) {
|
||||||
console.log('✅ 找到 TaskProcess 数据,开始更新 currentTask:', {
|
const convertedTaskProcess = convertToTaskProcess(taskProcessData.process || [])
|
||||||
taskId: currentTask.value.Id,
|
|
||||||
agents: agentNames,
|
|
||||||
processCount: taskProcessData.process?.length || 0,
|
|
||||||
processDetail: taskProcessData.process,
|
|
||||||
briefDetail: taskProcessData.brief
|
|
||||||
})
|
|
||||||
|
|
||||||
// 🆕 数据格式转换:IApiAgentAction[] → TaskProcess[]
|
|
||||||
const convertedTaskProcess = (taskProcessData.process || []).map(action => ({
|
|
||||||
ID: action.id,
|
|
||||||
ActionType: action.type,
|
|
||||||
AgentName: action.agent,
|
|
||||||
Description: action.description,
|
|
||||||
ImportantInput: action.inputs || []
|
|
||||||
}))
|
|
||||||
|
|
||||||
console.log('🔄 数据格式转换完成:', {
|
|
||||||
original: taskProcessData.process,
|
|
||||||
converted: convertedTaskProcess
|
|
||||||
})
|
|
||||||
|
|
||||||
// 更新 currentTask 的 AgentSelection 和 TaskProcess
|
|
||||||
// 使用 updateCurrentAgentSelection 强制更新,避免被 setCurrentTask 的"智能保留"逻辑阻止
|
|
||||||
agentsStore.updateCurrentAgentSelection(
|
agentsStore.updateCurrentAgentSelection(
|
||||||
[...agentNames],
|
[...agentNames],
|
||||||
convertedTaskProcess,
|
convertedTaskProcess,
|
||||||
taskProcessData.brief || currentTask.value.Collaboration_Brief_frontEnd
|
taskProcessData.brief || currentTask.value.Collaboration_Brief_frontEnd
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log('✅ currentTask 已更新,新的 AgentSelection:', agentNames)
|
|
||||||
console.log('📋 验证更新结果:', agentsStore.currentTask)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新排序:被选中的agent排在前面,其他agent按平均分降序
|
sortHeatmapBySelection()
|
||||||
if (agentsHeatmapData.value.length > 0) {
|
|
||||||
agentsHeatmapData.value.sort((a, b) => {
|
|
||||||
const aIsSelected = selectedAgents.value.has(a.agentName)
|
|
||||||
const bIsSelected = selectedAgents.value.has(b.agentName)
|
|
||||||
|
|
||||||
// 如果一个是选中的agent,另一个不是,选中的agent排前面
|
|
||||||
if (aIsSelected && !bIsSelected) return -1
|
|
||||||
if (!aIsSelected && bIsSelected) return 1
|
|
||||||
|
|
||||||
// 如果都是或都不是选中的agent,按选中的维度平均分降序排列
|
|
||||||
const selectedDimArray = Array.from(selectedDimensions.value)
|
|
||||||
const aAverage = calculateAgentAverage(a, selectedDimArray)
|
|
||||||
const bAverage = calculateAgentAverage(b, selectedDimArray)
|
|
||||||
return bAverage - aAverage
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('选中 agent 组合:', Array.from(selectedAgents.value))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换维度选中状态
|
// 切换维度选中状态
|
||||||
const toggleDimensionSelection = (dimension: string) => {
|
const toggleDimensionSelection = (dimension: string) => {
|
||||||
if (selectedDimensions.value.has(dimension)) {
|
if (selectedDimensions.value.has(dimension)) {
|
||||||
@@ -370,26 +256,8 @@ const toggleDimensionSelection = (dimension: string) => {
|
|||||||
} else {
|
} else {
|
||||||
selectedDimensions.value.add(dimension)
|
selectedDimensions.value.add(dimension)
|
||||||
}
|
}
|
||||||
// 触发响应式更新
|
|
||||||
selectedDimensions.value = new Set(selectedDimensions.value)
|
selectedDimensions.value = new Set(selectedDimensions.value)
|
||||||
|
sortHeatmapBySelection()
|
||||||
// 重新排序agent
|
|
||||||
if (agentsHeatmapData.value.length > 0) {
|
|
||||||
agentsHeatmapData.value.sort((a, b) => {
|
|
||||||
const aIsSelected = selectedAgents.value.has(a.agentName)
|
|
||||||
const bIsSelected = selectedAgents.value.has(b.agentName)
|
|
||||||
|
|
||||||
// 如果一个是选中的agent,另一个不是,选中的agent排前面
|
|
||||||
if (aIsSelected && !bIsSelected) return -1
|
|
||||||
if (!aIsSelected && bIsSelected) return 1
|
|
||||||
|
|
||||||
// 如果都是或都不是选中的agent,按选中的维度平均分降序排列
|
|
||||||
const selectedDimArray = Array.from(selectedDimensions.value)
|
|
||||||
const aAverage = calculateAgentAverage(a, selectedDimArray)
|
|
||||||
const bAverage = calculateAgentAverage(b, selectedDimArray)
|
|
||||||
return bAverage - aAverage
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断维度是否被选中
|
// 判断维度是否被选中
|
||||||
@@ -404,31 +272,8 @@ const toggleAgentSelection = (agentName: string) => {
|
|||||||
} else {
|
} else {
|
||||||
selectedAgents.value.add(agentName)
|
selectedAgents.value.add(agentName)
|
||||||
}
|
}
|
||||||
// 触发响应式更新
|
|
||||||
selectedAgents.value = new Set(selectedAgents.value)
|
selectedAgents.value = new Set(selectedAgents.value)
|
||||||
|
sortHeatmapBySelection()
|
||||||
// 重新排序:被选中的agent排在前面,其他agent按平均分降序
|
|
||||||
if (agentsHeatmapData.value.length > 0) {
|
|
||||||
agentsHeatmapData.value.sort((a, b) => {
|
|
||||||
const aIsSelected = selectedAgents.value.has(a.agentName)
|
|
||||||
const bIsSelected = selectedAgents.value.has(b.agentName)
|
|
||||||
|
|
||||||
// 如果一个是选中的agent,另一个不是,选中的agent排前面
|
|
||||||
if (aIsSelected && !bIsSelected) return -1
|
|
||||||
if (!aIsSelected && bIsSelected) return 1
|
|
||||||
|
|
||||||
// 如果都是或都不是选中的agent,按选中的维度平均分降序排列
|
|
||||||
const selectedDimArray = Array.from(selectedDimensions.value)
|
|
||||||
const aAverage = calculateAgentAverage(a, selectedDimArray)
|
|
||||||
const bAverage = calculateAgentAverage(b, selectedDimArray)
|
|
||||||
return bAverage - aAverage
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断agent是否被选中
|
|
||||||
const isAgentSelected = (agentName: string) => {
|
|
||||||
return selectedAgents.value.has(agentName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断agent组合是否被选中(用于Assignment卡片的选中效果)
|
// 判断agent组合是否被选中(用于Assignment卡片的选中效果)
|
||||||
@@ -498,33 +343,46 @@ const calculateAgentAverage = (agentData: AgentHeatmapData, selectedDimensions?:
|
|||||||
return count > 0 ? total / count : 0
|
return count > 0 ? total / count : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模拟API调用 - 获取智能体评分数据
|
// API调用 - 获取智能体评分数据
|
||||||
const fetchAgentScores = async () => {
|
const fetchAgentScores = async () => {
|
||||||
// 先检查 store 中是否已有数据
|
const taskId = currentTask.value?.Id
|
||||||
const storedData = agentsStore.IAgentSelectModifyAddRequest
|
if (!taskId) {
|
||||||
|
console.warn('⚠️ fetchAgentScores: 当前任务没有 Id')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
//先检查 store 中是否有该任务的评分数据
|
||||||
|
const storedData = agentsStore.getTaskScoreData(taskId)
|
||||||
if (storedData && storedData.aspectList && storedData.aspectList.length > 0) {
|
if (storedData && storedData.aspectList && storedData.aspectList.length > 0) {
|
||||||
console.log('使用 store 中的评分数据')
|
return storedData
|
||||||
return {
|
}
|
||||||
aspectList: storedData.aspectList,
|
|
||||||
agentScores: storedData.agentScores
|
// 如果正在预加载中,等待预加载完成
|
||||||
|
if (agentsStore.isTaskPreloading(taskId)) {
|
||||||
|
// 等待一小段时间,让预加载完成
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
|
// 再次尝试获取
|
||||||
|
const cachedAfterPreload = agentsStore.getTaskScoreData(taskId)
|
||||||
|
if (cachedAfterPreload) {
|
||||||
|
return cachedAfterPreload
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 切换到真实API时,取消注释下面这行
|
// 检查是否已停止
|
||||||
|
if (agentsStore.isStopping || agentsStore.hasStoppedFilling) {
|
||||||
|
console.log('检测到停止信号,跳过获取智能体评分')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用 API 获取评分(如果没有缓存或预加载)
|
||||||
const agentScores = await api.agentSelectModifyInit({
|
const agentScores = await api.agentSelectModifyInit({
|
||||||
goal: agentsStore.agentRawPlan.data?.['General Goal'] || '',
|
goal: agentsStore.agentRawPlan.data?.['General Goal'] || '',
|
||||||
stepTask: currentTask.value
|
stepTask: currentTask.value
|
||||||
})
|
})
|
||||||
|
|
||||||
// 🆕 使用 Mock API(开发阶段),传递当前任务的 AgentSelection 以获取对应的维度
|
// 再次检查是否已停止(API 调用后)
|
||||||
const goal = agentsStore.agentRawPlan.data?.['General Goal'] || '开发智能协作系统'
|
if (agentsStore.isStopping || agentsStore.hasStoppedFilling) {
|
||||||
console.log('📤 调用 mockAgentSelectModifyInit,参数:', {
|
return null
|
||||||
goal,
|
}
|
||||||
stepTask: currentTask.value,
|
|
||||||
agentSelection: currentTask.value?.AgentSelection
|
|
||||||
})
|
|
||||||
|
|
||||||
// const agentScores = await api.mockAgentSelectModifyInit()
|
|
||||||
|
|
||||||
// 从转换后的数据中提取维度列表(第一个agent的所有维度)
|
// 从转换后的数据中提取维度列表(第一个agent的所有维度)
|
||||||
const firstAgent = Object.keys(agentScores)[0]
|
const firstAgent = Object.keys(agentScores)[0]
|
||||||
@@ -532,8 +390,8 @@ const fetchAgentScores = async () => {
|
|||||||
|
|
||||||
console.log('✅ 获取到的维度列表:', aspectList)
|
console.log('✅ 获取到的维度列表:', aspectList)
|
||||||
|
|
||||||
// 保存到 store
|
// 🆕 保存到 store(按任务ID存储)
|
||||||
agentsStore.setAgentScoreData({
|
agentsStore.setTaskScoreData(taskId, {
|
||||||
aspectList,
|
aspectList,
|
||||||
agentScores
|
agentScores
|
||||||
})
|
})
|
||||||
@@ -548,9 +406,7 @@ const fetchAgentScores = async () => {
|
|||||||
const initHeatmapData = async () => {
|
const initHeatmapData = async () => {
|
||||||
const agents = allAgents.value
|
const agents = allAgents.value
|
||||||
if (agents.length === 0 || isInitializing.value || isAddingDimension.value) return
|
if (agents.length === 0 || isInitializing.value || isAddingDimension.value) return
|
||||||
|
|
||||||
isInitializing.value = true
|
isInitializing.value = true
|
||||||
|
|
||||||
// 先创建一个只包含agent基本信息的临时数据,让agent头像先显示
|
// 先创建一个只包含agent基本信息的临时数据,让agent头像先显示
|
||||||
const tempData = agents.map(agent => ({
|
const tempData = agents.map(agent => ({
|
||||||
agentName: agent.Name,
|
agentName: agent.Name,
|
||||||
@@ -562,16 +418,12 @@ const initHeatmapData = async () => {
|
|||||||
tempData.sort((a, b) => {
|
tempData.sort((a, b) => {
|
||||||
const aIsAssigned = currentTaskAgents.value.includes(a.agentName)
|
const aIsAssigned = currentTaskAgents.value.includes(a.agentName)
|
||||||
const bIsAssigned = currentTaskAgents.value.includes(b.agentName)
|
const bIsAssigned = currentTaskAgents.value.includes(b.agentName)
|
||||||
|
|
||||||
if (aIsAssigned && !bIsAssigned) return -1
|
if (aIsAssigned && !bIsAssigned) return -1
|
||||||
if (!aIsAssigned && bIsAssigned) return 1
|
if (!aIsAssigned && bIsAssigned) return 1
|
||||||
|
|
||||||
return a.agentName.localeCompare(b.agentName, 'zh-CN')
|
return a.agentName.localeCompare(b.agentName, 'zh-CN')
|
||||||
})
|
})
|
||||||
|
|
||||||
agentsHeatmapData.value = tempData
|
agentsHeatmapData.value = tempData
|
||||||
|
//默认选中Assignment部分的agent(即currentTaskAgents)
|
||||||
// 初始化选中状态:默认选中Assignment部分的agent(即currentTaskAgents)
|
|
||||||
if (currentTaskAgents.value.length > 0) {
|
if (currentTaskAgents.value.length > 0) {
|
||||||
selectedAgents.value = new Set(currentTaskAgents.value)
|
selectedAgents.value = new Set(currentTaskAgents.value)
|
||||||
selectedAssignmentGroup.value = [...currentTaskAgents.value]
|
selectedAssignmentGroup.value = [...currentTaskAgents.value]
|
||||||
@@ -584,32 +436,33 @@ const initHeatmapData = async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 获取接口数据
|
// 获取接口数据
|
||||||
const { aspectList, agentScores } = await fetchAgentScores()
|
const scoreData = await fetchAgentScores()
|
||||||
|
if (!scoreData) {
|
||||||
|
console.warn('⚠️ initHeatmapData: 没有获取到评分数据')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { aspectList, agentScores } = scoreData
|
||||||
|
|
||||||
// 设置评分维度
|
|
||||||
scoreDimensions.value = aspectList
|
scoreDimensions.value = aspectList
|
||||||
|
|
||||||
// 转换数据结构:为每个agent生成评分数据
|
|
||||||
const heatmapData = agents.map(agent => {
|
const heatmapData = agents.map(agent => {
|
||||||
const agentName = agent.Name
|
const agentName = agent.Name
|
||||||
const scores: number[] = []
|
const scores: number[] = []
|
||||||
const scoreDetails: Array<{ dimension: string; score: number; reason: string }> = []
|
const scoreDetails: Array<{ dimension: string; score: number; reason: string }> = []
|
||||||
|
|
||||||
// 按照维度顺序获取评分
|
// 按照维度顺序获取评分
|
||||||
aspectList.forEach(dimension => {
|
aspectList.forEach((dimension: string) => {
|
||||||
// 数据结构:agentScores[agentName][dimension]
|
|
||||||
const agentScoreData = agentScores[agentName]
|
const agentScoreData = agentScores[agentName]
|
||||||
const scoreData = agentScoreData ? agentScoreData[dimension] : null
|
const scoreItem = agentScoreData ? agentScoreData[dimension] : null
|
||||||
|
|
||||||
if (scoreData) {
|
if (scoreItem) {
|
||||||
scores.push(scoreData.score)
|
scores.push(scoreItem.score)
|
||||||
scoreDetails.push({
|
scoreDetails.push({
|
||||||
dimension,
|
dimension,
|
||||||
score: scoreData.score,
|
score: scoreItem.score,
|
||||||
reason: scoreData.reason
|
reason: scoreItem.reason
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// 如果没有评分数据,默认为1
|
|
||||||
scores.push(1)
|
scores.push(1)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -620,9 +473,8 @@ const initHeatmapData = async () => {
|
|||||||
scoreDetails
|
scoreDetails
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 排序逻辑:
|
// 排序逻辑:
|
||||||
// 1. Assignment部分的agent(currentTaskAgents)排在前面
|
// 1. Assignment部分的agent排在前面
|
||||||
// 2. 其他agent按照平均分降序排列
|
// 2. 其他agent按照平均分降序排列
|
||||||
// 3. 同一组内也按平均分降序排列
|
// 3. 同一组内也按平均分降序排列
|
||||||
heatmapData.sort((a, b) => {
|
heatmapData.sort((a, b) => {
|
||||||
@@ -644,27 +496,16 @@ const initHeatmapData = async () => {
|
|||||||
agentsHeatmapData.value = heatmapData
|
agentsHeatmapData.value = heatmapData
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取智能体评分数据失败:', error)
|
console.error('获取智能体评分数据失败:', error)
|
||||||
// 降级处理:使用默认维度列表和随机数据
|
|
||||||
const defaultAspects = ['能力', '可用性', '专业性', '效率', '准确性', '协作性']
|
|
||||||
scoreDimensions.value = defaultAspects
|
|
||||||
agentsHeatmapData.value = tempData.map(item => ({
|
|
||||||
...item,
|
|
||||||
scores: Array.from(
|
|
||||||
{ length: defaultAspects.length },
|
|
||||||
() => Math.floor(Math.random() * 5) + 1
|
|
||||||
),
|
|
||||||
scoreDetails: []
|
|
||||||
}))
|
|
||||||
} finally {
|
} finally {
|
||||||
isInitializing.value = false
|
isInitializing.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听agent列表变化,初始化热力矩阵
|
//监听agent列表变化或任务切换,初始化热力矩阵
|
||||||
watch(
|
watch(
|
||||||
() => allAgents.value.length,
|
[() => allAgents.value.length, () => currentTask.value?.Id],
|
||||||
newLength => {
|
([agentLength, taskId]) => {
|
||||||
if (newLength > 0) {
|
if (agentLength > 0 && taskId) {
|
||||||
initHeatmapData()
|
initHeatmapData()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -709,9 +550,6 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// 初始化和任务切换处理
|
// 初始化和任务切换处理
|
||||||
// 1. 首次加载时,自动将初始 agent 组合添加到 confirmedAgentGroups
|
|
||||||
// 2. 恢复之前的选择状态
|
|
||||||
// 3. 加载 TaskProcess 数据
|
|
||||||
watch(
|
watch(
|
||||||
() => currentTask.value,
|
() => currentTask.value,
|
||||||
async newTask => {
|
async newTask => {
|
||||||
@@ -723,75 +561,37 @@ watch(
|
|||||||
const existingGroups = agentsStore.getConfirmedAgentGroups(newTask.Id)
|
const existingGroups = agentsStore.getConfirmedAgentGroups(newTask.Id)
|
||||||
const hasInitialGroup = existingGroups.length > 0
|
const hasInitialGroup = existingGroups.length > 0
|
||||||
|
|
||||||
// 首次初始化:自动添加初始组合(相当于系统自动执行了 confirmAgentSelection)
|
// 首次初始化:自动添加初始组合到 confirmedAgentGroups
|
||||||
if (!hasInitialGroup) {
|
if (!hasInitialGroup) {
|
||||||
console.log('🎯 首次初始化,自动添加初始 agent 组合到 Assignment')
|
|
||||||
agentsStore.addConfirmedAgentGroup(newTask.Id, [...newTask.AgentSelection])
|
agentsStore.addConfirmedAgentGroup(newTask.Id, [...newTask.AgentSelection])
|
||||||
|
|
||||||
// 调用 API 获取初始 agent 组合的 TaskProcess 数据
|
// 初始化时 TaskProcess 一定存在,直接存储
|
||||||
console.log('🔄 开始加载初始 agent 组合的 TaskProcess...')
|
selectionStore.setAgentTaskProcess(newTask.Id, newTask.AgentSelection, {
|
||||||
|
|
||||||
// 检查是否已有数据
|
|
||||||
if (!selectionStore.hasAgentTaskProcess(newTask.Id, newTask.AgentSelection)) {
|
|
||||||
try {
|
|
||||||
isLoadingInitialTask.value = true
|
|
||||||
console.log('=== 开始加载初始 agent 组合的 TaskProcess ===')
|
|
||||||
console.log('1. 任务ID:', newTask.Id)
|
|
||||||
console.log('2. 初始 agents:', newTask.AgentSelection)
|
|
||||||
|
|
||||||
// 将 IRawStepTask 转换为 IApiStepTask 格式
|
|
||||||
const stepTaskForApi = {
|
|
||||||
name: newTask.StepName || '',
|
name: newTask.StepName || '',
|
||||||
content: newTask.TaskContent || '',
|
content: newTask.TaskContent || '',
|
||||||
inputs: newTask.InputObject_List || [],
|
inputs: newTask.InputObject_List || [],
|
||||||
output: newTask.OutputObject || '',
|
output: newTask.OutputObject || '',
|
||||||
agents: newTask.AgentSelection,
|
agents: newTask.AgentSelection,
|
||||||
brief: newTask.Collaboration_Brief_frontEnd || {
|
brief: newTask.Collaboration_Brief_frontEnd || { template: '', data: {} },
|
||||||
template: '',
|
process: newTask.TaskProcess.map(action => ({
|
||||||
data: {}
|
id: action.ID,
|
||||||
},
|
type: action.ActionType,
|
||||||
process: []
|
agent: action.AgentName,
|
||||||
}
|
description: action.Description,
|
||||||
|
inputs: action.ImportantInput
|
||||||
console.log('3. 转换后的 API 请求参数:', stepTaskForApi)
|
}))
|
||||||
|
|
||||||
const goal = agentsStore.agentRawPlan.data?.['General Goal'] || '开发智能协作系统'
|
|
||||||
console.log('4. General Goal:', goal)
|
|
||||||
|
|
||||||
// 调用 Mock API 获取 TaskProcess
|
|
||||||
const filledTask = await api.fillStepTaskTaskProcess({
|
|
||||||
goal,
|
|
||||||
stepTask: stepTaskForApi,
|
|
||||||
agents: newTask.AgentSelection
|
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('=== 初始 agent 组合 TaskProcess 加载成功 ===')
|
|
||||||
console.log('5. TaskProcess 流程数量:', filledTask.process?.length || 0)
|
|
||||||
|
|
||||||
// 存储到 selectionStore
|
|
||||||
selectionStore.setAgentTaskProcess(newTask.Id, newTask.AgentSelection, filledTask)
|
|
||||||
console.log('✅ 初始 agent 组合的 TaskProcess 已加载并存储')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ 加载初始 agent 组合的 TaskProcess 失败:', error)
|
|
||||||
} finally {
|
|
||||||
isLoadingInitialTask.value = false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('ℹ️ 初始 agent 组合已有 TaskProcess 数据,跳过加载')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从 store 中恢复之前选择的 agent 组合(如果有)
|
// 从 store 中恢复之前选择的 agent 组合
|
||||||
const savedAgentGroup = agentsStore.getSelectedAgentGroup(newTask.Id)
|
const savedAgentGroup = agentsStore.getSelectedAgentGroup(newTask.Id)
|
||||||
if (savedAgentGroup) {
|
if (savedAgentGroup) {
|
||||||
console.log('📂 恢复之前选择的 agent 组合:', savedAgentGroup)
|
|
||||||
selectedAssignmentGroup.value = [...savedAgentGroup]
|
selectedAssignmentGroup.value = [...savedAgentGroup]
|
||||||
selectedAgents.value = new Set(savedAgentGroup)
|
selectedAgents.value = new Set(savedAgentGroup)
|
||||||
} else {
|
} else {
|
||||||
// 没有保存的选择,默认选中第一个组合(即初始组合)
|
// 没有保存的选择,默认选中初始组合
|
||||||
const allGroups = agentsStore.getConfirmedAgentGroups(newTask.Id)
|
const allGroups = agentsStore.getConfirmedAgentGroups(newTask.Id)
|
||||||
if (allGroups.length > 0 && allGroups[0]) {
|
if (allGroups.length > 0 && allGroups[0]) {
|
||||||
console.log('🔄 默认选中第一个组合(初始组合):', allGroups[0])
|
|
||||||
selectedAssignmentGroup.value = [...allGroups[0]]
|
selectedAssignmentGroup.value = [...allGroups[0]]
|
||||||
selectedAgents.value = new Set(allGroups[0])
|
selectedAgents.value = new Set(allGroups[0])
|
||||||
}
|
}
|
||||||
@@ -799,7 +599,6 @@ watch(
|
|||||||
},
|
},
|
||||||
{ immediate: true } // 立即执行一次
|
{ immediate: true } // 立即执行一次
|
||||||
)
|
)
|
||||||
|
|
||||||
// 获取热力图颜色(评分1-5,颜色从浅到深)
|
// 获取热力图颜色(评分1-5,颜色从浅到深)
|
||||||
const getHeatmapColor = (score: number) => {
|
const getHeatmapColor = (score: number) => {
|
||||||
const colors = [
|
const colors = [
|
||||||
@@ -818,7 +617,7 @@ const getHeatmapColor = (score: number) => {
|
|||||||
<!-- 左侧区域 - 20% -->
|
<!-- 左侧区域 - 20% -->
|
||||||
<div class="allocation-left">
|
<div class="allocation-left">
|
||||||
<div class="section-card" v-loading="isLoadingSelectGroup">
|
<div class="section-card" v-loading="isLoadingSelectGroup">
|
||||||
<div class="section-title">Assignment</div>
|
<div class="section-title">智能体</div>
|
||||||
<!-- 所有 agent 组合卡片(包括初始组合和用户创建的组合) -->
|
<!-- 所有 agent 组合卡片(包括初始组合和用户创建的组合) -->
|
||||||
<div
|
<div
|
||||||
v-for="(group, groupIndex) in confirmedAgentGroups"
|
v-for="(group, groupIndex) in confirmedAgentGroups"
|
||||||
@@ -853,7 +652,6 @@ const getHeatmapColor = (score: number) => {
|
|||||||
</div>
|
</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!currentTask" class="placeholder-text">请先在任务大纲中选择一个任务</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -863,11 +661,11 @@ const getHeatmapColor = (score: number) => {
|
|||||||
<!-- 右侧区域 - 79% -->
|
<!-- 右侧区域 - 79% -->
|
||||||
<div class="allocation-right">
|
<div class="allocation-right">
|
||||||
<div class="section-card">
|
<div class="section-card">
|
||||||
<div class="section-title">Comparison</div>
|
<div class="section-title">智能体对比</div>
|
||||||
<div
|
<div
|
||||||
v-if="allAgents.length > 0"
|
v-if="allAgents.length > 0"
|
||||||
class="comparison-content"
|
class="comparison-content"
|
||||||
v-loading="isInitializing || isLoadingInitialTask || isLoadingConfirm"
|
v-loading="isInitializing || isLoadingConfirm"
|
||||||
>
|
>
|
||||||
<!-- 热力矩阵图 -->
|
<!-- 热力矩阵图 -->
|
||||||
<div v-if="agentsHeatmapData.length > 0" class="heatmap-container">
|
<div v-if="agentsHeatmapData.length > 0" class="heatmap-container">
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ const isDisabled = computed(() => {
|
|||||||
return !agentsStore.currentTask
|
return !agentsStore.currentTask
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取agent组合卡片数量 - 当前任务agents(1) + 已确认的agent组合数量
|
// 获取agent组合卡片数量
|
||||||
const agentGroupCount = computed(() => {
|
const agentGroupCount = computed(() => {
|
||||||
if (!agentsStore.currentTask?.Id) return 1
|
if (!agentsStore.currentTask?.Id) return 1
|
||||||
|
|
||||||
// 获取该任务的已确认agent组合
|
// 获取该任务的已确认agent组合
|
||||||
const confirmedGroups = agentsStore.getConfirmedAgentGroups(agentsStore.currentTask.Id)
|
const confirmedGroups = agentsStore.getConfirmedAgentGroups(agentsStore.currentTask.Id)
|
||||||
|
|
||||||
// 当前任务agents(1) + 已确认的agent组合数量
|
// 当前任务agents数量 + 已确认的agent组合数量
|
||||||
return confirmedGroups.length || 1
|
return confirmedGroups.length || 1
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -8,25 +8,21 @@ const emit = defineEmits<{
|
|||||||
(e: 'click'): void
|
(e: 'click'): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// 获取分支数量 - 主分支(1) + 额外分支数量
|
// 获取分支数量
|
||||||
const branchCount = computed(() => {
|
const branchCount = computed(() => {
|
||||||
// flowBranches 包含所有通过 Branch 创建的分支
|
|
||||||
const extraBranches = selectionStore.flowBranches?.length || 1
|
const extraBranches = selectionStore.flowBranches?.length || 1
|
||||||
// 始终至少有1个主分支
|
|
||||||
return extraBranches
|
return extraBranches
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
// console.log('点击分支按钮')
|
|
||||||
emit('click')
|
emit('click')
|
||||||
// 触发打开分支窗口
|
|
||||||
agentsStore.openPlanModification()
|
agentsStore.openPlanModification()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="branch-button"
|
class="branch-button"
|
||||||
:class="{ 'has-branches': branchCount > 0 }"
|
:class="{ 'has-branches': branchCount > 1 }"
|
||||||
@click="handleClick"
|
@click="handleClick"
|
||||||
:title="`${branchCount} 个分支`"
|
:title="`${branchCount} 个分支`"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -39,7 +39,6 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
import { debounce } from 'lodash-es'
|
|
||||||
export interface IFloatingWindowProps {
|
export interface IFloatingWindowProps {
|
||||||
title?: string | any
|
title?: string | any
|
||||||
onClose?: () => void
|
onClose?: () => void
|
||||||
@@ -262,24 +261,20 @@ const handleResizeMove = (e: MouseEvent) => {
|
|||||||
emit('update:position', position.value)
|
emit('update:position', position.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleResizeEnd = debounce(() => {
|
const handleResizeEnd = () => {
|
||||||
isResizing.value = false
|
isResizing.value = false
|
||||||
resizeDirection.value = null
|
resizeDirection.value = null
|
||||||
document.removeEventListener('mousemove', handleResizeMove)
|
document.removeEventListener('mousemove', handleResizeMove)
|
||||||
document.removeEventListener('mouseup', handleResizeEnd)
|
document.removeEventListener('mouseup', handleResizeEnd)
|
||||||
|
|
||||||
props.onResize?.()
|
props.onResize?.()
|
||||||
}, 50)
|
}
|
||||||
|
|
||||||
const setResizeable = (value: boolean) => {
|
const setResizeable = (value: boolean) => {
|
||||||
resizeable.value = value
|
resizeable.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => props.onClose?.()
|
||||||
if (props.onClose) {
|
|
||||||
props.onClose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -3,11 +3,15 @@ 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, ref, nextTick, watch, onMounted } from 'vue'
|
import { computed, 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 BranchButton from './components/BranchButton.vue'
|
||||||
|
import Notification from '@/components/Notification/Notification.vue'
|
||||||
|
import { useNotification } from '@/composables/useNotification'
|
||||||
|
import TaskContentEditor from '@/components/TaskContentEditor/index.vue'
|
||||||
|
|
||||||
// 判断计划是否就绪
|
// 判断计划是否就绪
|
||||||
const planReady = computed(() => {
|
const planReady = computed(() => {
|
||||||
@@ -21,8 +25,6 @@ const 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')
|
||||||
@@ -37,119 +39,85 @@ const collaborationProcess = computed(() => {
|
|||||||
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
|
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
|
||||||
})
|
})
|
||||||
|
|
||||||
// 编辑状态管理
|
// 检测是否正在填充详情
|
||||||
const editingTaskId = ref<string | null>(null)
|
const isFillingDetails = computed(() => {
|
||||||
const editingContent = ref('')
|
const process = agentsStore.agentRawPlan.data?.['Collaboration Process'] || []
|
||||||
|
return (
|
||||||
// 添加新产物状态管理
|
process.length > 0 &&
|
||||||
const isAddingOutput = ref(false)
|
process.some(step => !step.AgentSelection || step.AgentSelection.length === 0)
|
||||||
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 = () => {
|
const completedSteps = computed(() => {
|
||||||
if (newOutputName.value.trim()) {
|
const process = agentsStore.agentRawPlan.data?.['Collaboration Process'] || []
|
||||||
const outputName = newOutputName.value.trim()
|
return process.filter(step => step.AgentSelection && step.AgentSelection.length > 0).length
|
||||||
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 totalSteps = computed(() => {
|
||||||
const cancelAddOutput = () => {
|
return agentsStore.agentRawPlan.data?.['Collaboration Process']?.length || 0
|
||||||
isAddingOutput.value = false
|
|
||||||
newOutputName.value = ''
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
jsplumb.instance.repaintEverything()
|
|
||||||
}, 50)
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
// 处理新产物的键盘事件
|
// Notification 通知
|
||||||
const handleNewOutputKeydown = (event: KeyboardEvent) => {
|
const {
|
||||||
if (event.key === 'Enter') {
|
notifications,
|
||||||
event.preventDefault()
|
progress: showProgress,
|
||||||
saveNewOutput()
|
updateProgressDetail,
|
||||||
} else if (event.key === 'Escape') {
|
removeNotification
|
||||||
cancelAddOutput()
|
} = useNotification()
|
||||||
}
|
const fillingProgressNotificationId = ref<string | null>(null)
|
||||||
}
|
|
||||||
|
|
||||||
// 新产物输入框失去焦点处理
|
// 监听填充进度,显示通知
|
||||||
const handleNewOutputBlur = () => {
|
watch(
|
||||||
setTimeout(() => {
|
[isFillingDetails, completedSteps, totalSteps, () => agentsStore.hasStoppedFilling],
|
||||||
if (newOutputName.value.trim() === '') {
|
([filling, completed, total, hasStopped]) => {
|
||||||
cancelAddOutput()
|
// 如果用户已停止,关闭进度通知
|
||||||
}
|
if (hasStopped && fillingProgressNotificationId.value) {
|
||||||
}, 100)
|
removeNotification(fillingProgressNotificationId.value)
|
||||||
}
|
fillingProgressNotificationId.value = null
|
||||||
|
|
||||||
// 开始编辑
|
|
||||||
const startEditing = (task: IRawStepTask) => {
|
|
||||||
if (!task.Id) {
|
|
||||||
console.warn('Task ID is missing, cannot start editing')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
editingTaskId.value = task.Id
|
|
||||||
editingContent.value = task.TaskContent || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存编辑
|
if (filling && total > 0) {
|
||||||
const saveEditing = () => {
|
if (!fillingProgressNotificationId.value) {
|
||||||
if (editingTaskId.value && editingContent.value.trim()) {
|
// 创建进度通知
|
||||||
const taskToUpdate = collaborationProcess.value.find(item => item.Id === editingTaskId.value)
|
fillingProgressNotificationId.value = showProgress('生成协作流程', completed, total)
|
||||||
if (taskToUpdate) {
|
updateProgressDetail(
|
||||||
taskToUpdate.TaskContent = editingContent.value.trim()
|
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
|
||||||
}
|
}
|
||||||
editingTaskId.value = null
|
},
|
||||||
editingContent.value = ''
|
{ immediate: true }
|
||||||
}
|
)
|
||||||
|
|
||||||
// 取消编辑
|
// 保存编辑内容
|
||||||
const cancelEditing = () => {
|
const handleContentSave = (taskId: string, content: string) => {
|
||||||
editingTaskId.value = null
|
const taskToUpdate = collaborationProcess.value.find(item => item.Id === taskId)
|
||||||
editingContent.value = ''
|
if (taskToUpdate && content !== taskToUpdate.TaskContent) {
|
||||||
|
taskToUpdate.TaskContent = content
|
||||||
|
// 记录修改过的步骤索引到store用于重新执行
|
||||||
|
const stepIndex = collaborationProcess.value.findIndex(item => item.Id === taskId)
|
||||||
|
if (stepIndex >= 0) {
|
||||||
|
agentsStore.addModifiedStep(stepIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理键盘事件
|
|
||||||
const handleKeydown = (event: KeyboardEvent) => {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
event.preventDefault()
|
|
||||||
saveEditing()
|
|
||||||
} else if (event.key === 'Escape') {
|
|
||||||
cancelEditing()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,44 +169,32 @@ function clear() {
|
|||||||
jsplumb.reset()
|
jsplumb.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🆕 封装连线重绘方法
|
// 封装连线重绘方法
|
||||||
const redrawConnections = () => {
|
const redrawConnections = () => {
|
||||||
console.log('🔄 重新绘制 jsplumb 连线')
|
|
||||||
|
|
||||||
// 等待 DOM 更新完成
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// 清除旧连线
|
|
||||||
jsplumb.reset()
|
jsplumb.reset()
|
||||||
|
|
||||||
// 等待 DOM 稳定后重新绘制
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const arr: ConnectArg[] = []
|
const arr: ConnectArg[] = []
|
||||||
const currentTaskId = agentsStore.currentTask?.Id
|
const currentTaskId = agentsStore.currentTask?.Id
|
||||||
|
|
||||||
// 重新绘制所有连线
|
|
||||||
collaborationProcess.value.forEach(item => {
|
collaborationProcess.value.forEach(item => {
|
||||||
arr.push(...handleCurrentTask(item, item.Id !== currentTaskId))
|
arr.push(...handleCurrentTask(item, item.Id !== currentTaskId))
|
||||||
})
|
})
|
||||||
|
|
||||||
jsplumb.connects(arr)
|
jsplumb.connects(arr)
|
||||||
console.log('✅ jsplumb 连线重绘完成,任务数:', collaborationProcess.value.length)
|
|
||||||
}, 100)
|
}, 100)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🆕 监听 collaborationProcess 变化,自动重绘连线
|
// 监听 collaborationProcess 变化,自动重绘连线
|
||||||
watch(
|
watch(
|
||||||
() => collaborationProcess,
|
() => collaborationProcess,
|
||||||
() => {
|
() => {
|
||||||
console.log('🔍 collaborationProcess 发生变化,触发重绘')
|
|
||||||
redrawConnections()
|
redrawConnections()
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
// 🆕 组件挂载后初始化连线
|
// 组件挂载后初始化连线
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 初始化时绘制连线
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const arr: ConnectArg[] = []
|
const arr: ConnectArg[] = []
|
||||||
@@ -246,7 +202,6 @@ onMounted(() => {
|
|||||||
arr.push(...handleCurrentTask(item, true))
|
arr.push(...handleCurrentTask(item, true))
|
||||||
})
|
})
|
||||||
jsplumb.connects(arr)
|
jsplumb.connects(arr)
|
||||||
console.log('✅ 初始化 jsplumb 连线完成')
|
|
||||||
}, 100)
|
}, 100)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -259,6 +214,9 @@ defineExpose({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-full flex flex-col">
|
<div class="h-full flex flex-col">
|
||||||
|
<!-- Notification 通知 -->
|
||||||
|
<Notification :notifications="notifications" @close="id => removeNotification(id)" />
|
||||||
|
|
||||||
<div class="text-[18px] font-bold mb-[18px] text-[var(--color-text-title-header)]">
|
<div class="text-[18px] font-bold mb-[18px] text-[var(--color-text-title-header)]">
|
||||||
任务大纲
|
任务大纲
|
||||||
</div>
|
</div>
|
||||||
@@ -273,8 +231,7 @@ defineExpose({
|
|||||||
class="w-full relative min-h-full"
|
class="w-full relative min-h-full"
|
||||||
id="task-syllabus"
|
id="task-syllabus"
|
||||||
>
|
>
|
||||||
<Bg :is-adding="isAddingOutput" @start-add-output="handleAddOutputClick" />
|
<Bg />
|
||||||
|
|
||||||
<div class="w-full flex items-center gap-[14%] mb-[35px]">
|
<div class="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
|
||||||
@@ -292,47 +249,6 @@ defineExpose({
|
|||||||
</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"
|
||||||
@@ -352,54 +268,48 @@ defineExpose({
|
|||||||
<div class="h-[1px] w-full bg-[var(--color-border-separate)] my-[8px]"></div>
|
<div class="h-[1px] w-full bg-[var(--color-border-separate)] my-[8px]"></div>
|
||||||
|
|
||||||
<!-- 任务内容区域 - 支持双击编辑 -->
|
<!-- 任务内容区域 - 支持双击编辑 -->
|
||||||
<div v-if="editingTaskId === item.Id" class="w-full">
|
<TaskContentEditor :task="item" @save="handleContentSave">
|
||||||
<div class="flex flex-col gap-3">
|
<template #display>
|
||||||
<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">
|
<MultiLineTooltip placement="right" :text="item.TaskContent" :lines="3">
|
||||||
<div class="text-[14px] text-[var(--color-text-secondary)] task-content-display">
|
<div class="text-[14px] text-[var(--color-text-secondary)] task-content-display">
|
||||||
{{ item.TaskContent }}
|
{{ item.TaskContent }}
|
||||||
</div>
|
</div>
|
||||||
</MultiLineTooltip>
|
</MultiLineTooltip>
|
||||||
</div>
|
</template>
|
||||||
|
</TaskContentEditor>
|
||||||
|
|
||||||
<div class="h-[1px] w-full bg-[var(--color-border-separate)] my-[8px]"></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 class="loading-dots"></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"
|
||||||
@@ -411,7 +321,9 @@ defineExpose({
|
|||||||
<div class="text-[18px] font-bold">{{ agentSelection }}</div>
|
<div class="text-[18px] font-bold">{{ agentSelection }}</div>
|
||||||
<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>
|
||||||
</template>
|
</template>
|
||||||
@@ -426,6 +338,7 @@ defineExpose({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
<!-- 产物卡片 -->
|
<!-- 产物卡片 -->
|
||||||
@@ -440,7 +353,7 @@ defineExpose({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<BranchButton v-dev-only v-if="planReady" @click="openPlanModification" />
|
<BranchButton v-if="planReady" @click="openPlanModification" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -501,66 +414,6 @@ defineExpose({
|
|||||||
white-space: pre-wrap;
|
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) {
|
:deep(.el-input__wrapper) {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -624,4 +477,33 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载动画省略号
|
||||||
|
.loading-dots {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1.2em;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
animation: dots 1.5s steps(4, end) infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dots {
|
||||||
|
0%,
|
||||||
|
20% {
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
content: '.';
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
content: '..';
|
||||||
|
}
|
||||||
|
80%,
|
||||||
|
100% {
|
||||||
|
content: '...';
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -47,10 +47,10 @@ function handleTaskSyllabusCurrentTask(task: IRawStepTask) {
|
|||||||
agentsStore.setCurrentTask(task)
|
agentsStore.setCurrentTask(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新任务大纲内部的线
|
||||||
function handleTaskResultCurrentTask(task: IRawStepTask) {
|
function handleTaskResultCurrentTask(task: IRawStepTask) {
|
||||||
scrollToElementTop(`task-syllabus-flow-${task.Id}`)
|
scrollToElementTop(`task-syllabus-flow-${task.Id}`)
|
||||||
agentsStore.setCurrentTask(task)
|
agentsStore.setCurrentTask(task)
|
||||||
// 更新任务大纲内部的线
|
|
||||||
taskSyllabusRef.value?.changeTask(task, false)
|
taskSyllabusRef.value?.changeTask(task, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,11 +71,6 @@ function clear() {
|
|||||||
taskResultJsplumb.repaintEverything()
|
taskResultJsplumb.repaintEverything()
|
||||||
}
|
}
|
||||||
|
|
||||||
const additionalOutputs = ref<string[]>([])
|
|
||||||
const handleAddOutput = (outputName: string) => {
|
|
||||||
additionalOutputs.value.unshift(outputName)
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
changeTask,
|
changeTask,
|
||||||
resetAgentRepoLine,
|
resetAgentRepoLine,
|
||||||
@@ -99,7 +94,6 @@ defineExpose({
|
|||||||
ref="taskSyllabusRef"
|
ref="taskSyllabusRef"
|
||||||
@resetAgentRepoLine="resetAgentRepoLine"
|
@resetAgentRepoLine="resetAgentRepoLine"
|
||||||
@set-current-task="handleTaskSyllabusCurrentTask"
|
@set-current-task="handleTaskSyllabusCurrentTask"
|
||||||
@add-output="handleAddOutput"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<!-- 执行结果 -->
|
<!-- 执行结果 -->
|
||||||
@@ -108,7 +102,6 @@ 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>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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 { setupDirective } from '@/ directive'
|
||||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||||
@@ -20,6 +21,24 @@ async function init() {
|
|||||||
setupStore(app)
|
setupStore(app)
|
||||||
setupDirective(app)
|
setupDirective(app)
|
||||||
initService()
|
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')
|
||||||
|
|||||||
@@ -73,6 +73,13 @@ export interface IScoreItem {
|
|||||||
reason: string
|
reason: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 单个任务的评分数据结构
|
||||||
|
export interface ITaskScoreData {
|
||||||
|
aspectList: string[] // 维度列表
|
||||||
|
agentScores: Record<string, Record<string, IScoreItem>> // agent -> 维度 -> 评分
|
||||||
|
timestamp: number // 缓存时间戳
|
||||||
|
}
|
||||||
|
|
||||||
export interface IAgentSelectModifyAddRequest {
|
export interface IAgentSelectModifyAddRequest {
|
||||||
aspectList: string[] // 维度列表
|
aspectList: string[] // 维度列表
|
||||||
agentScores: Record<string, Record<string, IScoreItem>> // agent -> 维度 -> 评分(与后端返回格式一致)
|
agentScores: Record<string, Record<string, IScoreItem>> // agent -> 维度 -> 评分(与后端返回格式一致)
|
||||||
@@ -89,6 +96,23 @@ export interface IRawPlanResponse {
|
|||||||
'Collaboration Process'?: IRawStepTask[]
|
'Collaboration Process'?: IRawStepTask[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IApiAgentAction[] → TaskProcess[] 格式转换
|
||||||
|
* @param actions IApiAgentAction 数组
|
||||||
|
* @returns TaskProcess 数组
|
||||||
|
*/
|
||||||
|
export function convertToTaskProcess(
|
||||||
|
actions: { id: string; type: string; agent: string; description: string; inputs?: string[] }[]
|
||||||
|
): TaskProcess[] {
|
||||||
|
return actions.map(action => ({
|
||||||
|
ID: action.id,
|
||||||
|
ActionType: action.type,
|
||||||
|
AgentName: action.agent,
|
||||||
|
Description: action.description,
|
||||||
|
ImportantInput: action.inputs || []
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
const storageKey = '$agents' as const
|
const storageKey = '$agents' as const
|
||||||
|
|
||||||
// 清除所有以 storageKey 开头的 localStorage
|
// 清除所有以 storageKey 开头的 localStorage
|
||||||
@@ -105,13 +129,104 @@ export const useAgentsStore = defineStore('agents', () => {
|
|||||||
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>(
|
const IAgentSelectModifyAddRequest = useStorage<IAgentSelectModifyAddRequest | null>(
|
||||||
`${storageKey}-score-data`,
|
`${storageKey}-score-data`,
|
||||||
null,
|
null,
|
||||||
)
|
)
|
||||||
|
|
||||||
// 设置智能体评分数据
|
// 设置智能体评分数据(兼容旧版本)
|
||||||
function setAgentScoreData(data: IAgentSelectModifyAddRequest) {
|
function setAgentScoreData(data: IAgentSelectModifyAddRequest) {
|
||||||
IAgentSelectModifyAddRequest.value = data
|
IAgentSelectModifyAddRequest.value = data
|
||||||
}
|
}
|
||||||
@@ -131,8 +246,6 @@ export const useAgentsStore = defineStore('agents', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 添加该维度的评分数据
|
// 添加该维度的评分数据
|
||||||
// scores: { agentName: { score, reason } }
|
|
||||||
// 转换为 agentScores[agentName][aspectName] = { score, reason }
|
|
||||||
for (const [agentName, scoreItem] of Object.entries(scores)) {
|
for (const [agentName, scoreItem] of Object.entries(scores)) {
|
||||||
if (!IAgentSelectModifyAddRequest.value.agentScores[agentName]) {
|
if (!IAgentSelectModifyAddRequest.value.agentScores[agentName]) {
|
||||||
IAgentSelectModifyAddRequest.value.agentScores[agentName] = {}
|
IAgentSelectModifyAddRequest.value.agentScores[agentName] = {}
|
||||||
@@ -141,7 +254,7 @@ export const useAgentsStore = defineStore('agents', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除智能体评分数据
|
// 清除智能体评分数据(兼容旧版本)
|
||||||
function clearAgentScoreData() {
|
function clearAgentScoreData() {
|
||||||
IAgentSelectModifyAddRequest.value = null
|
IAgentSelectModifyAddRequest.value = null
|
||||||
}
|
}
|
||||||
@@ -221,6 +334,19 @@ export const useAgentsStore = defineStore('agents', () => {
|
|||||||
|
|
||||||
// 当前的展示的任务流程
|
// 当前的展示的任务流程
|
||||||
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
|
const existingTask = currentTask.value
|
||||||
|
|
||||||
@@ -359,11 +485,9 @@ 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分别存储)
|
// 用户在 Assignment 中选择的 agent 组合(按任务ID分别存储)
|
||||||
const selectedAgentGroupMap = ref<Map<string, string[]>>(new Map())
|
const selectedAgentGroupMap = ref<Map<string, string[]>>(new Map())
|
||||||
|
|
||||||
@@ -388,31 +512,40 @@ export const useAgentsStore = defineStore('agents', () => {
|
|||||||
selectedAgentGroupMap.value.clear()
|
selectedAgentGroupMap.value.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加新产物
|
// 标记是否用户已停止智能体分配过程
|
||||||
function addNewOutput(outputObject: string) {
|
const hasStoppedFilling = ref(false)
|
||||||
if (!outputObject.trim()) return false
|
|
||||||
|
|
||||||
const trimmedOutput = outputObject.trim()
|
// 标记是否正在停止中(全局停止状态)
|
||||||
if (!additionalOutputs.value.includes(trimmedOutput)) {
|
const isStopping = ref(false)
|
||||||
additionalOutputs.value.unshift(trimmedOutput)
|
|
||||||
return true
|
// 设置停止状态
|
||||||
}
|
function setHasStoppedFilling(value: boolean) {
|
||||||
return false
|
hasStoppedFilling.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除额外产物
|
// 设置正在停止状态
|
||||||
function removeAdditionalOutput(outputObject: string) {
|
function setIsStopping(value: boolean) {
|
||||||
const index = additionalOutputs.value.indexOf(outputObject)
|
isStopping.value = value
|
||||||
if (index > -1) {
|
|
||||||
additionalOutputs.value.splice(index, 1)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除额外产物
|
/**
|
||||||
function clearAdditionalOutputs() {
|
* 构建 IApiStepTask 对象(用于 API 调用)
|
||||||
additionalOutputs.value = []
|
* @param agents 选中的 agent 列表
|
||||||
|
* @returns IApiStepTask 格式的对象
|
||||||
|
*/
|
||||||
|
function createStepTaskForApi(agents: string[]): IApiStepTask {
|
||||||
|
return {
|
||||||
|
name: currentTask.value?.StepName || '',
|
||||||
|
content: currentTask.value?.TaskContent || '',
|
||||||
|
inputs: currentTask.value?.InputObject_List || [],
|
||||||
|
output: currentTask.value?.OutputObject || '',
|
||||||
|
agents,
|
||||||
|
brief: currentTask.value?.Collaboration_Brief_frontEnd || {
|
||||||
|
template: '',
|
||||||
|
data: {}
|
||||||
|
},
|
||||||
|
process: []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -425,15 +558,12 @@ export const useAgentsStore = defineStore('agents', () => {
|
|||||||
setCurrentTaskProcess, // 🆕 设置当前任务的 TaskProcess
|
setCurrentTaskProcess, // 🆕 设置当前任务的 TaskProcess
|
||||||
updateCurrentAgentSelection, // 🆕 强制更新 AgentSelection(用于 AgentAllocation 切换组合)
|
updateCurrentAgentSelection, // 🆕 强制更新 AgentSelection(用于 AgentAllocation 切换组合)
|
||||||
syncCurrentTaskToMainProcess, // 🆕 同步 currentTask 到主流程
|
syncCurrentTaskToMainProcess, // 🆕 同步 currentTask 到主流程
|
||||||
|
createStepTaskForApi, // 🆕 构建 IApiStepTask 对象
|
||||||
agentRawPlan,
|
agentRawPlan,
|
||||||
setAgentRawPlan,
|
setAgentRawPlan,
|
||||||
executePlan,
|
executePlan,
|
||||||
setExecutePlan,
|
setExecutePlan,
|
||||||
resetAgent,
|
resetAgent,
|
||||||
additionalOutputs,
|
|
||||||
addNewOutput,
|
|
||||||
removeAdditionalOutput,
|
|
||||||
clearAdditionalOutputs,
|
|
||||||
// 用户选择的 agent 组合
|
// 用户选择的 agent 组合
|
||||||
selectedAgentGroupMap,
|
selectedAgentGroupMap,
|
||||||
setSelectedAgentGroup,
|
setSelectedAgentGroup,
|
||||||
@@ -454,12 +584,35 @@ export const useAgentsStore = defineStore('agents', () => {
|
|||||||
setAgentScoreData,
|
setAgentScoreData,
|
||||||
addAgentScoreAspect,
|
addAgentScoreAspect,
|
||||||
clearAgentScoreData,
|
clearAgentScoreData,
|
||||||
|
// 🆕 按任务ID存储的评分数据
|
||||||
|
taskScoreDataMap,
|
||||||
|
getTaskScoreData,
|
||||||
|
setTaskScoreData,
|
||||||
|
hasTaskScoreData,
|
||||||
|
getPreloadedTaskIds,
|
||||||
|
clearTaskScoreData,
|
||||||
|
clearAllTaskScoreData,
|
||||||
|
preloadingTaskIds,
|
||||||
|
markTaskPreloading,
|
||||||
|
markTaskPreloadComplete,
|
||||||
|
isTaskPreloading,
|
||||||
// 确认的agent组合列表(按任务ID分别存储)
|
// 确认的agent组合列表(按任务ID分别存储)
|
||||||
confirmedAgentGroupsMap,
|
confirmedAgentGroupsMap,
|
||||||
getConfirmedAgentGroups,
|
getConfirmedAgentGroups,
|
||||||
addConfirmedAgentGroup,
|
addConfirmedAgentGroup,
|
||||||
clearConfirmedAgentGroups,
|
clearConfirmedAgentGroups,
|
||||||
clearAllConfirmedAgentGroups,
|
clearAllConfirmedAgentGroups,
|
||||||
|
// 停止填充状态
|
||||||
|
hasStoppedFilling,
|
||||||
|
setHasStoppedFilling,
|
||||||
|
// 正在停止状态
|
||||||
|
isStopping,
|
||||||
|
setIsStopping,
|
||||||
|
// 重新执行相关
|
||||||
|
modifiedSteps,
|
||||||
|
addModifiedStep,
|
||||||
|
clearModifiedSteps,
|
||||||
|
hasModifiedSteps,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -5,25 +5,40 @@ import { store } from '../index'
|
|||||||
import type { IRawStepTask, IApiStepTask } from './agents'
|
import type { IRawStepTask, IApiStepTask } from './agents'
|
||||||
import type { Node, Edge } from '@vue-flow/core'
|
import type { Node, Edge } from '@vue-flow/core'
|
||||||
|
|
||||||
// 分支节点数据接口 - 用于存储流程图中的节点和边
|
/**
|
||||||
|
* 分支数据接口
|
||||||
|
* @description 用于存储流程图中的分支节点和边数据
|
||||||
|
*/
|
||||||
export interface IBranchData {
|
export interface IBranchData {
|
||||||
id: string // 分支唯一ID
|
/** 分支唯一 ID */
|
||||||
parentNodeId: string // 父节点ID(根节点或任务节点)
|
id: string
|
||||||
branchContent: string // 分支需求内容
|
/** 父节点 ID(根节点或任务节点) */
|
||||||
branchType: 'root' | 'task' // 分支类型
|
parentNodeId: string
|
||||||
nodes: Node[] // 分支包含的所有节点
|
/** 分支需求内容 */
|
||||||
edges: Edge[] // 分支包含的所有边
|
branchContent: string
|
||||||
tasks: IRawStepTask[] // 分支的任务数据
|
/** 分支类型 */
|
||||||
createdAt: number // 创建时间
|
branchType: 'root' | 'task'
|
||||||
|
/** 分支包含的所有节点 */
|
||||||
|
nodes: Node[]
|
||||||
|
/** 分支包含的所有边 */
|
||||||
|
edges: Edge[]
|
||||||
|
/** 分支的任务数据 */
|
||||||
|
tasks: IRawStepTask[]
|
||||||
|
/** 创建时间 */
|
||||||
|
createdAt: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSelectionStore = defineStore('selection', () => {
|
export const useSelectionStore = defineStore('selection', () => {
|
||||||
// ==================== 任务大纲探索分支数据存储 ====================
|
// ==================== 任务大纲探索分支数据存储 ====================
|
||||||
|
|
||||||
|
/** 流程图分支列表 */
|
||||||
const flowBranches = ref<IBranchData[]>([])
|
const flowBranches = ref<IBranchData[]>([])
|
||||||
|
|
||||||
// 任务大纲分支管理
|
/**
|
||||||
|
* 添加流程图分支
|
||||||
// 添加流程图分支
|
* @param data 分支数据
|
||||||
|
* @returns 分支 ID
|
||||||
|
*/
|
||||||
function addFlowBranch(data: {
|
function addFlowBranch(data: {
|
||||||
parentNodeId: string
|
parentNodeId: string
|
||||||
branchContent: string
|
branchContent: string
|
||||||
@@ -44,55 +59,73 @@ export const useSelectionStore = defineStore('selection', () => {
|
|||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
}
|
}
|
||||||
flowBranches.value.push(newBranch)
|
flowBranches.value.push(newBranch)
|
||||||
console.log('📂 保存流程图分支到 store:', newBranch)
|
|
||||||
return branchId
|
return branchId
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取所有流程图分支
|
/**
|
||||||
|
* 获取所有流程图分支
|
||||||
|
* @returns 所有流程图分支数据
|
||||||
|
*/
|
||||||
function getAllFlowBranches(): IBranchData[] {
|
function getAllFlowBranches(): IBranchData[] {
|
||||||
return flowBranches.value
|
return flowBranches.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据父节点ID获取流程图分支
|
/**
|
||||||
|
* 根据父节点 ID 获取流程图分支
|
||||||
|
* @param parentNodeId 父节点 ID
|
||||||
|
* @returns 匹配的流程图分支列表
|
||||||
|
*/
|
||||||
function getFlowBranchesByParent(parentNodeId: string): IBranchData[] {
|
function getFlowBranchesByParent(parentNodeId: string): IBranchData[] {
|
||||||
return flowBranches.value.filter((branch) => branch.parentNodeId === parentNodeId)
|
return flowBranches.value.filter((branch) => branch.parentNodeId === parentNodeId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除流程图分支
|
/**
|
||||||
|
* 删除流程图分支
|
||||||
|
* @param branchId 分支 ID
|
||||||
|
* @returns 是否删除成功
|
||||||
|
*/
|
||||||
function removeFlowBranch(branchId: string): boolean {
|
function removeFlowBranch(branchId: string): boolean {
|
||||||
const index = flowBranches.value.findIndex((branch) => branch.id === branchId)
|
const index = flowBranches.value.findIndex((branch) => branch.id === branchId)
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
flowBranches.value.splice(index, 1)
|
flowBranches.value.splice(index, 1)
|
||||||
console.log('🗑️ 删除流程图分支:', branchId)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除所有流程图分支
|
/** 清除所有流程图分支 */
|
||||||
function clearFlowBranches() {
|
function clearFlowBranches() {
|
||||||
flowBranches.value = []
|
flowBranches.value = []
|
||||||
console.log('🗑️ 清除所有流程图分支')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据父节点ID清除流程图分支
|
/**
|
||||||
|
* 根据父节点 ID 清除流程图分支
|
||||||
|
* @param parentNodeId 父节点 ID
|
||||||
|
*/
|
||||||
function clearFlowBranchesByParent(parentNodeId: string) {
|
function clearFlowBranchesByParent(parentNodeId: string) {
|
||||||
flowBranches.value = flowBranches.value.filter((branch) => branch.parentNodeId !== parentNodeId)
|
flowBranches.value = flowBranches.value.filter((branch) => branch.parentNodeId !== parentNodeId)
|
||||||
console.log('🗑️ 清除父节点为', parentNodeId, '的流程图分支')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 任务过程探索分支数据存储 ====================
|
// ==================== 任务过程探索分支数据存储 ====================
|
||||||
// 用于存储任务过程探索中的分支数据
|
/**
|
||||||
// 结构: Map<taskStepId, Map<agentGroupKey, IBranchData[]>>
|
* 任务过程分支数据映射
|
||||||
// - taskStepId: 任务步骤ID
|
* @description 存储结构: Map<taskStepId, Map<agentGroupKey, IBranchData[]>>
|
||||||
// - agentGroupKey: agent 组合的唯一标识(排序后的 JSON 字符串)
|
* - taskStepId: 任务步骤 ID
|
||||||
// - IBranchData[]: 该 agent 组合在该任务下的所有分支数据
|
* - agentGroupKey: agent 组合的唯一标识(排序后的 JSON 字符串)
|
||||||
|
* - IBranchData[]: 该 agent 组合在该任务下的所有分支数据
|
||||||
|
*/
|
||||||
const taskProcessBranchesMap = ref<Map<string, Map<string, IBranchData[]>>>(new Map())
|
const taskProcessBranchesMap = ref<Map<string, Map<string, IBranchData[]>>>(new Map())
|
||||||
|
|
||||||
// 添加任务过程分支
|
/**
|
||||||
|
* 添加任务过程分支
|
||||||
|
* @param taskStepId 任务步骤 ID
|
||||||
|
* @param agents Agent 列表
|
||||||
|
* @param data 分支数据
|
||||||
|
* @returns 分支 ID
|
||||||
|
*/
|
||||||
function addTaskProcessBranch(
|
function addTaskProcessBranch(
|
||||||
taskStepId: string,
|
taskStepId: string,
|
||||||
agents: string[], // 🆕 新增:需要传入 agents 列表
|
agents: string[],
|
||||||
data: {
|
data: {
|
||||||
parentNodeId: string
|
parentNodeId: string
|
||||||
branchContent: string
|
branchContent: string
|
||||||
@@ -103,7 +136,7 @@ export const useSelectionStore = defineStore('selection', () => {
|
|||||||
},
|
},
|
||||||
): string {
|
): string {
|
||||||
const branchId = `task-process-branch-${uuidv4()}`
|
const branchId = `task-process-branch-${uuidv4()}`
|
||||||
const agentGroupKey = getAgentGroupKey(agents) // 🆕 生成 agent 组合 key
|
const agentGroupKey = getAgentGroupKey(agents)
|
||||||
|
|
||||||
const newBranch: IBranchData = {
|
const newBranch: IBranchData = {
|
||||||
id: branchId,
|
id: branchId,
|
||||||
@@ -128,44 +161,74 @@ export const useSelectionStore = defineStore('selection', () => {
|
|||||||
}
|
}
|
||||||
agentMap.get(agentGroupKey)!.push(newBranch)
|
agentMap.get(agentGroupKey)!.push(newBranch)
|
||||||
|
|
||||||
console.log('📂 保存任务过程分支到 store:', { taskStepId, agents, agentGroupKey, branch: newBranch })
|
|
||||||
return branchId
|
return branchId
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取指定任务步骤和 agent 组合的所有分支
|
/**
|
||||||
|
* 获取指定任务步骤和 agent 组合的所有分支
|
||||||
|
* @param taskStepId 任务步骤 ID
|
||||||
|
* @param agents Agent 列表
|
||||||
|
* @returns 匹配的任务过程分支列表
|
||||||
|
*/
|
||||||
function getTaskProcessBranches(taskStepId: string, agents: string[]): IBranchData[] {
|
function getTaskProcessBranches(taskStepId: string, agents: string[]): IBranchData[] {
|
||||||
const agentGroupKey = getAgentGroupKey(agents)
|
const agentGroupKey = getAgentGroupKey(agents)
|
||||||
return taskProcessBranchesMap.value.get(taskStepId)?.get(agentGroupKey) || []
|
return taskProcessBranchesMap.value.get(taskStepId)?.get(agentGroupKey) || []
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取所有任务过程分支
|
/**
|
||||||
|
* 获取所有任务过程分支
|
||||||
|
* @returns 所有任务过程分支数据映射
|
||||||
|
*/
|
||||||
function getAllTaskProcessBranches(): Map<string, Map<string, IBranchData[]>> {
|
function getAllTaskProcessBranches(): Map<string, Map<string, IBranchData[]>> {
|
||||||
return taskProcessBranchesMap.value
|
return taskProcessBranchesMap.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据父节点ID获取任务过程分支
|
/**
|
||||||
function getTaskProcessBranchesByParent(taskStepId: string, agents: string[], parentNodeId: string): IBranchData[] {
|
* 根据父节点 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 agentGroupKey = getAgentGroupKey(agents)
|
||||||
const branches = taskProcessBranchesMap.value.get(taskStepId)?.get(agentGroupKey) || []
|
const branches = taskProcessBranchesMap.value.get(taskStepId)?.get(agentGroupKey) || []
|
||||||
return branches.filter((branch) => branch.parentNodeId === parentNodeId)
|
return branches.filter((branch) => branch.parentNodeId === parentNodeId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除任务过程分支
|
/**
|
||||||
function removeTaskProcessBranch(taskStepId: string, agents: string[], branchId: string): boolean {
|
* 删除任务过程分支
|
||||||
|
* @param taskStepId 任务步骤 ID
|
||||||
|
* @param agents Agent 列表
|
||||||
|
* @param branchId 分支 ID
|
||||||
|
* @returns 是否删除成功
|
||||||
|
*/
|
||||||
|
function removeTaskProcessBranch(
|
||||||
|
taskStepId: string,
|
||||||
|
agents: string[],
|
||||||
|
branchId: string,
|
||||||
|
): boolean {
|
||||||
const agentGroupKey = getAgentGroupKey(agents)
|
const agentGroupKey = getAgentGroupKey(agents)
|
||||||
const branches = taskProcessBranchesMap.value.get(taskStepId)?.get(agentGroupKey)
|
const branches = taskProcessBranchesMap.value.get(taskStepId)?.get(agentGroupKey)
|
||||||
if (branches) {
|
if (branches) {
|
||||||
const index = branches.findIndex((branch) => branch.id === branchId)
|
const index = branches.findIndex((branch) => branch.id === branchId)
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
branches.splice(index, 1)
|
branches.splice(index, 1)
|
||||||
console.log('🗑️ 删除任务过程分支:', { taskStepId, agents, branchId })
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除指定任务步骤和 agent 组合的所有分支
|
/**
|
||||||
|
* 清除指定任务步骤和 agent 组合的所有分支
|
||||||
|
* @param taskStepId 任务步骤 ID
|
||||||
|
* @param agents Agent 列表(可选,不传则清除该任务步骤的所有分支)
|
||||||
|
*/
|
||||||
function clearTaskProcessBranches(taskStepId: string, agents?: string[]) {
|
function clearTaskProcessBranches(taskStepId: string, agents?: string[]) {
|
||||||
if (agents) {
|
if (agents) {
|
||||||
// 清除指定 agent 组合的分支
|
// 清除指定 agent 组合的分支
|
||||||
@@ -173,33 +236,44 @@ export const useSelectionStore = defineStore('selection', () => {
|
|||||||
const agentMap = taskProcessBranchesMap.value.get(taskStepId)
|
const agentMap = taskProcessBranchesMap.value.get(taskStepId)
|
||||||
if (agentMap) {
|
if (agentMap) {
|
||||||
agentMap.delete(agentGroupKey)
|
agentMap.delete(agentGroupKey)
|
||||||
console.log('🗑️ 清除任务步骤的 agent 组合分支:', { taskStepId, agents })
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 清除该任务步骤的所有分支(所有 agent 组合)
|
// 清除该任务步骤的所有分支(所有 agent 组合)
|
||||||
taskProcessBranchesMap.value.delete(taskStepId)
|
taskProcessBranchesMap.value.delete(taskStepId)
|
||||||
console.log('🗑️ 清除任务步骤的所有分支:', taskStepId)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Agent 组合 TaskProcess 数据存储 ====================
|
// ==================== Agent 组合 TaskProcess 数据存储 ====================
|
||||||
// 用于存储 fill_stepTask_TaskProcess 接口返回的数据
|
/**
|
||||||
// 结构: Map<taskId, Map<agentGroupKey, IApiStepTask>>
|
* Agent 组合 TaskProcess 数据映射
|
||||||
// - taskId: 任务ID
|
* @description 用于存储 fill_stepTask_TaskProcess 接口返回的数据
|
||||||
// - agentGroupKey: agent 组合的唯一标识(排序后的 JSON 字符串)
|
* 存储结构: Map<taskId, Map<agentGroupKey, IApiStepTask>>
|
||||||
// - IApiStepTask: 该 agent 组合的完整 TaskProcess 数据
|
* - taskId: 任务 ID
|
||||||
|
* - agentGroupKey: agent 组合的唯一标识(排序后的 JSON 字符串)
|
||||||
|
* - IApiStepTask: 该 agent 组合的完整 TaskProcess 数据
|
||||||
|
*/
|
||||||
const agentTaskProcessMap = ref<Map<string, Map<string, IApiStepTask>>>(new Map())
|
const agentTaskProcessMap = ref<Map<string, Map<string, IApiStepTask>>>(new Map())
|
||||||
|
|
||||||
// 生成 agent 组合的唯一 key(排序后保证一致性)
|
/**
|
||||||
|
* 生成 agent 组合的唯一 key
|
||||||
|
* @description 排序后保证一致性
|
||||||
|
* @param agents Agent 列表
|
||||||
|
* @returns Agent 组合的唯一标识
|
||||||
|
*/
|
||||||
function getAgentGroupKey(agents: string[]): string {
|
function getAgentGroupKey(agents: string[]): string {
|
||||||
// 🆕 处理 undefined 或 null 的情况
|
// 处理 undefined 或 null 的情况
|
||||||
if (!agents || !Array.isArray(agents)) {
|
if (!agents || !Array.isArray(agents)) {
|
||||||
return JSON.stringify([])
|
return JSON.stringify([])
|
||||||
}
|
}
|
||||||
return JSON.stringify([...agents].sort())
|
return JSON.stringify([...agents].sort())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 存储 agent 组合的 TaskProcess 数据
|
/**
|
||||||
|
* 存储 agent 组合的 TaskProcess 数据
|
||||||
|
* @param taskId 任务 ID
|
||||||
|
* @param agents Agent 列表
|
||||||
|
* @param taskProcess TaskProcess 数据
|
||||||
|
*/
|
||||||
function setAgentTaskProcess(taskId: string, agents: string[], taskProcess: IApiStepTask) {
|
function setAgentTaskProcess(taskId: string, agents: string[], taskProcess: IApiStepTask) {
|
||||||
const groupKey = getAgentGroupKey(agents)
|
const groupKey = getAgentGroupKey(agents)
|
||||||
|
|
||||||
@@ -210,46 +284,67 @@ export const useSelectionStore = defineStore('selection', () => {
|
|||||||
|
|
||||||
// 存储该 agent 组合的 TaskProcess 数据
|
// 存储该 agent 组合的 TaskProcess 数据
|
||||||
agentTaskProcessMap.value.get(taskId)!.set(groupKey, taskProcess)
|
agentTaskProcessMap.value.get(taskId)!.set(groupKey, taskProcess)
|
||||||
console.log(`📦 存储 agent 组合 TaskProcess [任务: ${taskId}, agents: ${agents.join(', ')}]`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取 agent 组合的 TaskProcess 数据
|
/**
|
||||||
|
* 获取 agent 组合的 TaskProcess 数据
|
||||||
|
* @param taskId 任务 ID
|
||||||
|
* @param agents Agent 列表
|
||||||
|
* @returns TaskProcess 数据
|
||||||
|
*/
|
||||||
function getAgentTaskProcess(taskId: string, agents: string[]): IApiStepTask | undefined {
|
function getAgentTaskProcess(taskId: string, agents: string[]): IApiStepTask | undefined {
|
||||||
const groupKey = getAgentGroupKey(agents)
|
const groupKey = getAgentGroupKey(agents)
|
||||||
return agentTaskProcessMap.value.get(taskId)?.get(groupKey)
|
return agentTaskProcessMap.value.get(taskId)?.get(groupKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查 agent 组合是否已有 TaskProcess 数据
|
/**
|
||||||
|
* 检查 agent 组合是否已有 TaskProcess 数据
|
||||||
|
* @param taskId 任务 ID
|
||||||
|
* @param agents Agent 列表
|
||||||
|
* @returns 是否存在数据
|
||||||
|
*/
|
||||||
function hasAgentTaskProcess(taskId: string, agents: string[]): boolean {
|
function hasAgentTaskProcess(taskId: string, agents: string[]): boolean {
|
||||||
const groupKey = getAgentGroupKey(agents)
|
const groupKey = getAgentGroupKey(agents)
|
||||||
return agentTaskProcessMap.value.get(taskId)?.has(groupKey) || false
|
return agentTaskProcessMap.value.get(taskId)?.has(groupKey) || false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除指定任务的所有 agent 组合 TaskProcess 数据
|
/**
|
||||||
|
* 清除指定任务的所有 agent 组合 TaskProcess 数据
|
||||||
|
* @param taskId 任务 ID
|
||||||
|
*/
|
||||||
function clearAgentTaskProcess(taskId: string) {
|
function clearAgentTaskProcess(taskId: string) {
|
||||||
agentTaskProcessMap.value.delete(taskId)
|
agentTaskProcessMap.value.delete(taskId)
|
||||||
console.log(`🗑️ 清除任务的 agent 组合 TaskProcess 数据: ${taskId}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除所有任务的 agent 组合 TaskProcess 数据
|
/** 清除所有任务的 agent 组合 TaskProcess 数据 */
|
||||||
function clearAllAgentTaskProcess() {
|
function clearAllAgentTaskProcess() {
|
||||||
agentTaskProcessMap.value.clear()
|
agentTaskProcessMap.value.clear()
|
||||||
console.log('🗑️ 清除所有任务的 agent 组合 TaskProcess 数据')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 当前生效的任务过程分支 ====================
|
// ==================== 当前生效的任务过程分支 ====================
|
||||||
// 记录每个任务步骤和 agent 组合当前生效的分支 ID(持久化选中状态)
|
/**
|
||||||
// 结构: Map<taskStepId, Map<agentGroupKey, branchId>>
|
* 当前生效的任务过程分支映射
|
||||||
// - taskStepId: 任务步骤ID
|
* @description 记录每个任务步骤和 agent 组合当前生效的分支 ID(持久化选中状态)
|
||||||
// - agentGroupKey: agent 组合的唯一标识
|
* 存储结构: Map<taskStepId, Map<agentGroupKey, branchId>>
|
||||||
// - branchId: 当前选中的分支ID
|
* - taskStepId: 任务步骤 ID
|
||||||
|
* - agentGroupKey: agent 组合的唯一标识
|
||||||
|
* - branchId: 当前选中的分支 ID
|
||||||
|
*/
|
||||||
const activeTaskProcessBranchMap = ref<Map<string, Map<string, string>>>(new Map())
|
const activeTaskProcessBranchMap = ref<Map<string, Map<string, string>>>(new Map())
|
||||||
|
|
||||||
// 🆕 当前生效的 TaskProcess 数据(用于外部组件显示职责分配)
|
/**
|
||||||
// 结构: Map<taskStepId, Map<agentGroupKey, TaskProcess[]>>
|
* 当前生效的 TaskProcess 数据映射
|
||||||
|
* @description 用于外部组件显示职责分配
|
||||||
|
* 存储结构: Map<taskStepId, Map<agentGroupKey, TaskProcess[]>>
|
||||||
|
*/
|
||||||
const activeTaskProcessDataMap = ref<Map<string, Map<string, any[]>>>(new Map())
|
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) {
|
function setActiveTaskProcessBranch(taskStepId: string, agents: string[], branchId: string) {
|
||||||
const agentGroupKey = getAgentGroupKey(agents)
|
const agentGroupKey = getAgentGroupKey(agents)
|
||||||
|
|
||||||
@@ -259,10 +354,14 @@ export const useSelectionStore = defineStore('selection', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
activeTaskProcessBranchMap.value.get(taskStepId)!.set(agentGroupKey, branchId)
|
activeTaskProcessBranchMap.value.get(taskStepId)!.set(agentGroupKey, branchId)
|
||||||
console.log('✅ 设置当前生效分支:', { taskStepId, agents, agentGroupKey, branchId })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🆕 设置当前生效的 TaskProcess 数据
|
/**
|
||||||
|
* 设置当前生效的 TaskProcess 数据
|
||||||
|
* @param taskStepId 任务步骤 ID
|
||||||
|
* @param agents Agent 列表
|
||||||
|
* @param taskProcess TaskProcess 数据
|
||||||
|
*/
|
||||||
function setActiveTaskProcessData(taskStepId: string, agents: string[], taskProcess: any[]) {
|
function setActiveTaskProcessData(taskStepId: string, agents: string[], taskProcess: any[]) {
|
||||||
const agentGroupKey = getAgentGroupKey(agents)
|
const agentGroupKey = getAgentGroupKey(agents)
|
||||||
|
|
||||||
@@ -272,46 +371,57 @@ export const useSelectionStore = defineStore('selection', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
activeTaskProcessDataMap.value.get(taskStepId)!.set(agentGroupKey, taskProcess)
|
activeTaskProcessDataMap.value.get(taskStepId)!.set(agentGroupKey, taskProcess)
|
||||||
console.log('✅ 设置当前生效的 TaskProcess 数据:', { taskStepId, agents, agentGroupKey, taskProcess })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取当前生效的分支 ID
|
/**
|
||||||
|
* 获取当前生效的分支 ID
|
||||||
|
* @param taskStepId 任务步骤 ID
|
||||||
|
* @param agents Agent 列表
|
||||||
|
* @returns 分支 ID
|
||||||
|
*/
|
||||||
function getActiveTaskProcessBranch(taskStepId: string, agents: string[]): string | undefined {
|
function getActiveTaskProcessBranch(taskStepId: string, agents: string[]): string | undefined {
|
||||||
const agentGroupKey = getAgentGroupKey(agents)
|
const agentGroupKey = getAgentGroupKey(agents)
|
||||||
return activeTaskProcessBranchMap.value.get(taskStepId)?.get(agentGroupKey)
|
return activeTaskProcessBranchMap.value.get(taskStepId)?.get(agentGroupKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🆕 获取当前生效的 TaskProcess 数据
|
/**
|
||||||
|
* 获取当前生效的 TaskProcess 数据
|
||||||
|
* @param taskStepId 任务步骤 ID
|
||||||
|
* @param agents Agent 列表
|
||||||
|
* @returns TaskProcess 数据
|
||||||
|
*/
|
||||||
function getActiveTaskProcessData(taskStepId: string, agents: string[]): any[] | undefined {
|
function getActiveTaskProcessData(taskStepId: string, agents: string[]): any[] | undefined {
|
||||||
const agentGroupKey = getAgentGroupKey(agents)
|
const agentGroupKey = getAgentGroupKey(agents)
|
||||||
return activeTaskProcessDataMap.value.get(taskStepId)?.get(agentGroupKey)
|
return activeTaskProcessDataMap.value.get(taskStepId)?.get(agentGroupKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除生效分支
|
/**
|
||||||
|
* 清除生效分支
|
||||||
|
* @param taskStepId 任务步骤 ID
|
||||||
|
* @param agents Agent 列表(可选,不传则清除该任务步骤的所有生效分支)
|
||||||
|
*/
|
||||||
function clearActiveTaskProcessBranch(taskStepId: string, agents?: string[]) {
|
function clearActiveTaskProcessBranch(taskStepId: string, agents?: string[]) {
|
||||||
if (agents) {
|
if (agents) {
|
||||||
// 清除指定 agent 组合的生效分支
|
// 清除指定 agent 组合的生效分支
|
||||||
const agentGroupKey = getAgentGroupKey(agents)
|
const agentGroupKey = getAgentGroupKey(agents)
|
||||||
activeTaskProcessBranchMap.value.get(taskStepId)?.delete(agentGroupKey)
|
activeTaskProcessBranchMap.value.get(taskStepId)?.delete(agentGroupKey)
|
||||||
activeTaskProcessDataMap.value.get(taskStepId)?.delete(agentGroupKey)
|
activeTaskProcessDataMap.value.get(taskStepId)?.delete(agentGroupKey)
|
||||||
console.log('🗑️ 清除任务步骤的 agent 组合生效分支:', { taskStepId, agents })
|
|
||||||
} else {
|
} else {
|
||||||
// 清除该任务步骤的所有生效分支(所有 agent 组合)
|
// 清除该任务步骤的所有生效分支(所有 agent 组合)
|
||||||
activeTaskProcessBranchMap.value.delete(taskStepId)
|
activeTaskProcessBranchMap.value.delete(taskStepId)
|
||||||
activeTaskProcessDataMap.value.delete(taskStepId)
|
activeTaskProcessDataMap.value.delete(taskStepId)
|
||||||
console.log('🗑️ 清除任务步骤的所有生效分支:', taskStepId)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 状态
|
// ==================== 状态 ====================
|
||||||
flowBranches,
|
flowBranches,
|
||||||
taskProcessBranchesMap,
|
taskProcessBranchesMap,
|
||||||
agentTaskProcessMap,
|
agentTaskProcessMap,
|
||||||
activeTaskProcessBranchMap,
|
activeTaskProcessBranchMap,
|
||||||
activeTaskProcessDataMap, // 🆕 新增
|
activeTaskProcessDataMap,
|
||||||
|
|
||||||
// 任务大纲分支管理方法
|
// ==================== 任务大纲分支管理方法 ====================
|
||||||
addFlowBranch,
|
addFlowBranch,
|
||||||
getAllFlowBranches,
|
getAllFlowBranches,
|
||||||
getFlowBranchesByParent,
|
getFlowBranchesByParent,
|
||||||
@@ -319,7 +429,7 @@ export const useSelectionStore = defineStore('selection', () => {
|
|||||||
clearFlowBranches,
|
clearFlowBranches,
|
||||||
clearFlowBranchesByParent,
|
clearFlowBranchesByParent,
|
||||||
|
|
||||||
// 任务过程分支管理方法
|
// ==================== 任务过程分支管理方法 ====================
|
||||||
addTaskProcessBranch,
|
addTaskProcessBranch,
|
||||||
getTaskProcessBranches,
|
getTaskProcessBranches,
|
||||||
getAllTaskProcessBranches,
|
getAllTaskProcessBranches,
|
||||||
@@ -327,14 +437,14 @@ export const useSelectionStore = defineStore('selection', () => {
|
|||||||
removeTaskProcessBranch,
|
removeTaskProcessBranch,
|
||||||
clearTaskProcessBranches,
|
clearTaskProcessBranches,
|
||||||
|
|
||||||
// 🆕 任务过程分支生效状态管理方法
|
// ==================== 任务过程分支生效状态管理方法 ====================
|
||||||
setActiveTaskProcessBranch,
|
setActiveTaskProcessBranch,
|
||||||
setActiveTaskProcessData, // 🆕 新增
|
setActiveTaskProcessData,
|
||||||
getActiveTaskProcessBranch,
|
getActiveTaskProcessBranch,
|
||||||
getActiveTaskProcessData, // 🆕 新增
|
getActiveTaskProcessData,
|
||||||
clearActiveTaskProcessBranch,
|
clearActiveTaskProcessBranch,
|
||||||
|
|
||||||
// Agent 组合 TaskProcess 数据管理方法
|
// ==================== Agent 组合 TaskProcess 数据管理方法 ====================
|
||||||
getAgentGroupKey,
|
getAgentGroupKey,
|
||||||
setAgentTaskProcess,
|
setAgentTaskProcess,
|
||||||
getAgentTaskProcess,
|
getAgentTaskProcess,
|
||||||
@@ -346,6 +456,7 @@ export const useSelectionStore = defineStore('selection', () => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 用于在组件外部使用 Selection Store
|
* 用于在组件外部使用 Selection Store
|
||||||
|
* @returns Selection Store 实例
|
||||||
*/
|
*/
|
||||||
export function useSelectionStoreHook() {
|
export function useSelectionStoreHook() {
|
||||||
return useSelectionStore(store)
|
return useSelectionStore(store)
|
||||||
|
|||||||
167
frontend/src/utils/retry.ts
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
/**
|
||||||
|
* 重试工具函数
|
||||||
|
* @description 提供通用的重试机制,支持指数退避和自定义重试条件
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重试选项配置
|
||||||
|
*/
|
||||||
|
export interface RetryOptions {
|
||||||
|
/** 最大重试次数,默认 3 */
|
||||||
|
maxRetries?: number
|
||||||
|
/** 初始延迟时间(毫秒),默认 2000 */
|
||||||
|
initialDelayMs?: number
|
||||||
|
/** 是否使用指数退避,默认 true */
|
||||||
|
useExponentialBackoff?: boolean
|
||||||
|
/** 最大延迟时间(毫秒),默认 30000 */
|
||||||
|
maxDelayMs?: number
|
||||||
|
/** 自定义重试条件函数,返回 true 表示应该重试 */
|
||||||
|
shouldRetry?: (error: any, attempt: number) => boolean
|
||||||
|
/** 重试前的回调函数 */
|
||||||
|
onRetry?: (error: any, attempt: number, delay: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认重试条件 - 检查是否是 rate limiting 错误
|
||||||
|
* @param error 错误对象
|
||||||
|
* @returns 是否应该重试
|
||||||
|
*/
|
||||||
|
export function defaultShouldRetry(error: any): boolean {
|
||||||
|
if (!error) return false
|
||||||
|
const message = error?.message || String(error)
|
||||||
|
// 检查 406 Not Acceptable、429 Too Many Requests 等 rate limiting 错误
|
||||||
|
const isRateLimit =
|
||||||
|
message.includes('406') ||
|
||||||
|
message.includes('429') ||
|
||||||
|
message.includes('Not Acceptable') ||
|
||||||
|
message.includes('Too Many Requests') ||
|
||||||
|
message.includes('rate limit')
|
||||||
|
return isRateLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带重试机制的异步函数执行器
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* // 基本用法
|
||||||
|
* const result = await withRetry(() => api.someRequest())
|
||||||
|
*
|
||||||
|
* // 自定义重试次数和延迟
|
||||||
|
* const result = await withRetry(() => api.request(), {
|
||||||
|
* maxRetries: 5,
|
||||||
|
* initialDelayMs: 1000,
|
||||||
|
* useExponentialBackoff: true
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* // 自定义重试条件
|
||||||
|
* const result = await withRetry(() => api.request(), {
|
||||||
|
* shouldRetry: (error) => error?.code === 'NETWORK_ERROR'
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* // 带重试回调
|
||||||
|
* const result = await withRetry(() => api.request(), {
|
||||||
|
* onRetry: (error, attempt, delay) => {
|
||||||
|
* console.log(`第 ${attempt} 次重试,等待 ${delay}ms`, error.message)
|
||||||
|
* }
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param fn - 要执行的异步函数
|
||||||
|
* @param options - 重试选项配置
|
||||||
|
* @returns Promise resolves with the result of the function
|
||||||
|
* @throws 如果重试次数用尽,抛出最后一次的错误
|
||||||
|
*/
|
||||||
|
export async function withRetry<T>(
|
||||||
|
fn: () => Promise<T>,
|
||||||
|
options: RetryOptions = {},
|
||||||
|
): Promise<T> {
|
||||||
|
const {
|
||||||
|
maxRetries = 3,
|
||||||
|
initialDelayMs = 2000,
|
||||||
|
useExponentialBackoff = true,
|
||||||
|
maxDelayMs = 30000,
|
||||||
|
shouldRetry = defaultShouldRetry,
|
||||||
|
onRetry,
|
||||||
|
} = options
|
||||||
|
|
||||||
|
let lastError: any = null
|
||||||
|
let currentDelay = initialDelayMs
|
||||||
|
|
||||||
|
for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
|
||||||
|
try {
|
||||||
|
// 执行目标函数
|
||||||
|
return await fn()
|
||||||
|
} catch (error: any) {
|
||||||
|
lastError = error
|
||||||
|
|
||||||
|
// 判断是否应该重试
|
||||||
|
const shouldRetryAttempt = shouldRetry(error, attempt)
|
||||||
|
|
||||||
|
// 如果是最后一次尝试 或者 不应该重试,则抛出错误
|
||||||
|
if (attempt > maxRetries || !shouldRetryAttempt) {
|
||||||
|
if (attempt > maxRetries) {
|
||||||
|
console.error(
|
||||||
|
`❌ [withRetry] 已达到最大重试次数 (${maxRetries}),放弃请求`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行重试回调
|
||||||
|
if (onRetry) {
|
||||||
|
onRetry(error, attempt, currentDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印日志
|
||||||
|
console.log(
|
||||||
|
`⏳ [withRetry] 第 ${attempt} 次重试,等待 ${currentDelay}ms...`,
|
||||||
|
error?.message || String(error),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 等待延迟时间
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, currentDelay))
|
||||||
|
|
||||||
|
// 计算下一次延迟时间(指数退避)
|
||||||
|
if (useExponentialBackoff) {
|
||||||
|
currentDelay = Math.min(currentDelay * 2, maxDelayMs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//理论上不会到达这里,因为循环内会 throw
|
||||||
|
throw lastError
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简化的重试装饰器 - 适用于类方法
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* class MyService {
|
||||||
|
* @retryable({ maxRetries: 3, initialDelayMs: 1000 })
|
||||||
|
* async fetchData() {
|
||||||
|
* return await api.request()
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param options - 重试选项配置
|
||||||
|
* @returns 装饰器函数
|
||||||
|
*/
|
||||||
|
export function retryable(options: RetryOptions = {}) {
|
||||||
|
return function <T extends (...args: any[]) => Promise<any>>(
|
||||||
|
_target: any,
|
||||||
|
_propertyKey: string,
|
||||||
|
descriptor: TypedPropertyDescriptor<T>,
|
||||||
|
) {
|
||||||
|
const originalMethod = descriptor.value
|
||||||
|
if (!originalMethod) return descriptor
|
||||||
|
|
||||||
|
descriptor.value = function (this: any, ...args: Parameters<T>) {
|
||||||
|
return withRetry(() => originalMethod.apply(this, args), options)
|
||||||
|
} as T
|
||||||
|
|
||||||
|
return descriptor
|
||||||
|
} as MethodDecorator
|
||||||
|
}
|
||||||
300
frontend/src/utils/websocket.ts
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
/**
|
||||||
|
* 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', () => {
|
||||||
|
this.isConnected = false
|
||||||
|
})
|
||||||
|
|
||||||
|
this.socket.on('connected', () => {
|
||||||
|
// Server connected message
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听响应消息
|
||||||
|
this.socket.on('response', (response: ResponseMessage) => {
|
||||||
|
const { id, status, data, error, generation_id, execution_id } = response
|
||||||
|
const handler = this.requestHandlers.get(id)
|
||||||
|
|
||||||
|
if (handler) {
|
||||||
|
// 清除超时定时器
|
||||||
|
if (handler.timer) {
|
||||||
|
clearTimeout(handler.timer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === 'success') {
|
||||||
|
// 返回完整响应,包含 data、generation_id、execution_id 等
|
||||||
|
// 注意:需要检查 data 是否为 null,因为 typeof null === 'object'
|
||||||
|
// generation_id/execution_id 可能放在 data 中,需要兼容处理
|
||||||
|
// 注意:如果 data 是数组,不能展开,否则会破坏数组结构
|
||||||
|
const resolvedGenerationId = generation_id || (data && typeof data === 'object' && !Array.isArray(data) && data.generation_id)
|
||||||
|
const resolvedExecutionId = execution_id || (data && typeof data === 'object' && !Array.isArray(data) && data.execution_id)
|
||||||
|
|
||||||
|
// 直接返回 data,保持原始数据结构(数组或对象)
|
||||||
|
handler.resolve({
|
||||||
|
data,
|
||||||
|
generation_id: resolvedGenerationId,
|
||||||
|
execution_id: resolvedExecutionId
|
||||||
|
})
|
||||||
|
} 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听事件
|
||||||
|
*/
|
||||||
|
on(event: string, callback: (...args: any[]) => void): void {
|
||||||
|
if (this.socket) {
|
||||||
|
this.socket.on(event, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消监听事件
|
||||||
|
*/
|
||||||
|
off(event: string, callback?: (...args: any[]) => void): void {
|
||||||
|
if (this.socket) {
|
||||||
|
this.socket.off(event, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出单例
|
||||||
|
export default new WebSocketClient()
|
||||||