feat(ui): 添加子 Agent 进度显示功能
当 build agent 调用 guide/explore 等子 agent 时, 用户可以在 web 页面实时看到子 agent 的执行进度 实现方案: - Core: 使用 EventEmitter 模式发射子 agent 事件 - Server: 订阅事件并转发到 WebSocket - UI: 处理事件并渲染 SubagentProgress 组件 新增文件: - packages/core/src/agent/events.ts - packages/ui/src/components/SubagentProgress.tsx 修改文件: - core: executor.ts, manager.ts, types.ts, task.ts - server: adapter.ts, types.ts - ui: useChat.ts, types.ts - web: Chat.tsx
This commit is contained in:
@@ -14,6 +14,13 @@ import type {
|
||||
MessagePart,
|
||||
ToolMessagePart,
|
||||
AgentModeType,
|
||||
SubagentStartPayload,
|
||||
SubagentEndPayload,
|
||||
SubagentStreamPayload,
|
||||
SubagentToolStartPayload,
|
||||
SubagentToolEndPayload,
|
||||
SubagentState,
|
||||
SubagentToolInfo,
|
||||
} from '../api/types.js';
|
||||
|
||||
interface UseChatOptions {
|
||||
@@ -38,6 +45,8 @@ interface ChatState {
|
||||
autoApprove: boolean;
|
||||
/** 当前正在执行的 Agent 名称 */
|
||||
currentAgent: string;
|
||||
/** 当前正在执行的子 Agent 状态 */
|
||||
currentSubagent: SubagentState | null;
|
||||
}
|
||||
|
||||
export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdated, onConfigError }: UseChatOptions) {
|
||||
@@ -50,6 +59,7 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate
|
||||
agentMode: 'build',
|
||||
autoApprove: false,
|
||||
currentAgent: 'build',
|
||||
currentSubagent: null,
|
||||
});
|
||||
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
@@ -334,6 +344,122 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate
|
||||
}));
|
||||
}
|
||||
break;
|
||||
|
||||
// ============ 子 Agent 事件处理 ============
|
||||
|
||||
case 'subagent:start': {
|
||||
const payload = message.payload as SubagentStartPayload;
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
currentAgent: payload.agentName,
|
||||
currentSubagent: {
|
||||
id: payload.agentId,
|
||||
name: payload.agentName,
|
||||
description: payload.description,
|
||||
status: 'running',
|
||||
tools: [],
|
||||
streamContent: '',
|
||||
},
|
||||
}));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'subagent:tool_start': {
|
||||
const payload = message.payload as SubagentToolStartPayload;
|
||||
setState((prev) => {
|
||||
if (!prev.currentSubagent || prev.currentSubagent.id !== payload.agentId) {
|
||||
return prev;
|
||||
}
|
||||
const newTool: SubagentToolInfo = {
|
||||
id: payload.toolCallId,
|
||||
toolName: payload.toolName,
|
||||
status: 'running',
|
||||
args: payload.args,
|
||||
};
|
||||
return {
|
||||
...prev,
|
||||
currentSubagent: {
|
||||
...prev.currentSubagent,
|
||||
tools: [...prev.currentSubagent.tools, newTool],
|
||||
},
|
||||
};
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'subagent:stream': {
|
||||
const payload = message.payload as SubagentStreamPayload;
|
||||
setState((prev) => {
|
||||
if (!prev.currentSubagent || prev.currentSubagent.id !== payload.agentId) {
|
||||
return prev;
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
currentSubagent: {
|
||||
...prev.currentSubagent,
|
||||
streamContent: prev.currentSubagent.streamContent + payload.content,
|
||||
},
|
||||
};
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'subagent:tool_end': {
|
||||
const payload = message.payload as SubagentToolEndPayload;
|
||||
setState((prev) => {
|
||||
if (!prev.currentSubagent || prev.currentSubagent.id !== payload.agentId) {
|
||||
return prev;
|
||||
}
|
||||
const updatedTools = prev.currentSubagent.tools.map((tool) => {
|
||||
if (tool.id === payload.toolCallId) {
|
||||
return {
|
||||
...tool,
|
||||
status: payload.status === 'completed' ? 'completed' : 'error',
|
||||
result: payload.result,
|
||||
error: payload.error,
|
||||
duration: payload.duration,
|
||||
} as SubagentToolInfo;
|
||||
}
|
||||
return tool;
|
||||
});
|
||||
return {
|
||||
...prev,
|
||||
currentSubagent: {
|
||||
...prev.currentSubagent,
|
||||
tools: updatedTools,
|
||||
},
|
||||
};
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'subagent:end': {
|
||||
const payload = message.payload as SubagentEndPayload;
|
||||
setState((prev) => {
|
||||
// 只有当 agentId 匹配时才处理
|
||||
if (!prev.currentSubagent || prev.currentSubagent.id !== payload.agentId) {
|
||||
return prev;
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
currentAgent: prev.agentMode, // 恢复为主 Agent
|
||||
currentSubagent: {
|
||||
...prev.currentSubagent,
|
||||
status: payload.success ? 'completed' : 'error',
|
||||
duration: payload.duration,
|
||||
error: payload.error,
|
||||
},
|
||||
};
|
||||
});
|
||||
// 完成后短暂延迟再清除,让 UI 能显示最终状态
|
||||
setTimeout(() => {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
currentSubagent: null,
|
||||
}));
|
||||
}, 1000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// 忽略解析错误
|
||||
@@ -476,6 +602,7 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate
|
||||
agentMode: 'build',
|
||||
autoApprove: false,
|
||||
currentAgent: 'build',
|
||||
currentSubagent: null,
|
||||
});
|
||||
reconnectAttemptsRef.current = 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user