feat(ui): display agent name in message header
This commit is contained in:
@@ -529,6 +529,7 @@ export async function processMessage(
|
||||
text: result.text,
|
||||
hasToolCalls,
|
||||
messageCount: result.messages.length,
|
||||
agentName: options?.agentMode || 'build',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -95,6 +95,8 @@ export interface Message {
|
||||
model?: string;
|
||||
stepCount?: number;
|
||||
totalTokens?: number;
|
||||
/** 生成此消息的 Agent 名称 */
|
||||
agentName?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { useState, forwardRef } from 'react';
|
||||
import { cn } from '../utils/cn';
|
||||
import { fadeInUp, smoothTransition } from '../utils/animations';
|
||||
import { getAgentDisplayName } from '../utils/agent';
|
||||
import { Markdown } from './Markdown';
|
||||
import { FileMentionText } from './FileMentionTag';
|
||||
import type { Message, ToolCallInfo, ToolCallStatus, ToolMessagePart } from '../api/types.js';
|
||||
@@ -137,7 +138,7 @@ export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
|
||||
<div className="flex-1 min-w-0 overflow-hidden">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="text-sm text-fg-muted">
|
||||
{isUser ? 'You' : 'AI Assistant'}
|
||||
{isUser ? 'You' : getAgentDisplayName(message.metadata?.agentName)}
|
||||
</span>
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
@@ -156,9 +157,11 @@ export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
|
||||
|
||||
interface StreamingMessageProps {
|
||||
content: string;
|
||||
/** 当前 Agent 名称 */
|
||||
agentName?: string;
|
||||
}
|
||||
|
||||
export function StreamingMessage({ content }: StreamingMessageProps) {
|
||||
export function StreamingMessage({ content, agentName }: StreamingMessageProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
@@ -170,7 +173,7 @@ export function StreamingMessage({ content }: StreamingMessageProps) {
|
||||
<Bot size={18} />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0 overflow-hidden">
|
||||
<div className="text-sm text-fg-muted mb-1">AI Assistant</div>
|
||||
<div className="text-sm text-fg-muted mb-1">{getAgentDisplayName(agentName)}</div>
|
||||
<div className="message-content text-fg-secondary">
|
||||
<Markdown content={content} />
|
||||
<motion.span
|
||||
@@ -184,7 +187,12 @@ export function StreamingMessage({ content }: StreamingMessageProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export function TypingIndicator() {
|
||||
interface TypingIndicatorProps {
|
||||
/** 当前 Agent 名称 */
|
||||
agentName?: string;
|
||||
}
|
||||
|
||||
export function TypingIndicator({ agentName }: TypingIndicatorProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
@@ -196,7 +204,7 @@ export function TypingIndicator() {
|
||||
<Bot size={18} />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="text-sm text-fg-muted mb-1">AI Assistant</div>
|
||||
<div className="text-sm text-fg-muted mb-1">{getAgentDisplayName(agentName)}</div>
|
||||
<div className="flex items-center gap-1 h-6">
|
||||
{[0, 1, 2].map((i) => (
|
||||
<motion.span
|
||||
|
||||
@@ -36,6 +36,8 @@ interface ChatState {
|
||||
agentMode: AgentModeType;
|
||||
/** 是否自动授权文件写入/编辑 (会话级别) */
|
||||
autoApprove: boolean;
|
||||
/** 当前正在执行的 Agent 名称 */
|
||||
currentAgent: string;
|
||||
}
|
||||
|
||||
export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdated, onConfigError }: UseChatOptions) {
|
||||
@@ -47,6 +49,7 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate
|
||||
permissionRequest: null,
|
||||
agentMode: 'build',
|
||||
autoApprove: false,
|
||||
currentAgent: 'build',
|
||||
});
|
||||
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
@@ -138,6 +141,7 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate
|
||||
timestamp: new Date().toISOString(),
|
||||
parts: [] as MessagePart[],
|
||||
content: '',
|
||||
metadata: { agentName: prev.currentAgent },
|
||||
};
|
||||
|
||||
// 复制 parts 数组以进行修改
|
||||
@@ -180,6 +184,7 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate
|
||||
timestamp: new Date().toISOString(),
|
||||
parts: [] as MessagePart[],
|
||||
content: '',
|
||||
metadata: { agentName: prev.currentAgent },
|
||||
};
|
||||
|
||||
// 添加工具调用 part
|
||||
@@ -192,11 +197,19 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate
|
||||
arguments: payload.arguments,
|
||||
};
|
||||
|
||||
// 如果是 task 工具,切换到子 agent
|
||||
const newAgent =
|
||||
payload.toolName === 'task' && payload.arguments?.subagent_type
|
||||
? (payload.arguments.subagent_type as string)
|
||||
: prev.currentAgent;
|
||||
|
||||
return {
|
||||
...prev,
|
||||
currentAgent: newAgent,
|
||||
streamingMessage: {
|
||||
...streaming,
|
||||
parts: [...streaming.parts, toolPart],
|
||||
metadata: { ...streaming.metadata, agentName: newAgent },
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -222,11 +235,20 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate
|
||||
return part;
|
||||
});
|
||||
|
||||
// 查找完成的工具是否为 task,如果是则恢复主 agent
|
||||
const completedTool = prev.streamingMessage.parts.find(
|
||||
(part) => part.type === 'tool' && part.id === payload.id
|
||||
);
|
||||
const isTaskTool = completedTool?.type === 'tool' && completedTool.toolName === 'task';
|
||||
const newAgent = isTaskTool ? prev.agentMode : prev.currentAgent;
|
||||
|
||||
return {
|
||||
...prev,
|
||||
currentAgent: newAgent,
|
||||
streamingMessage: {
|
||||
...prev.streamingMessage,
|
||||
parts,
|
||||
metadata: { ...prev.streamingMessage.metadata, agentName: newAgent },
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -238,6 +260,8 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate
|
||||
// 使用流式消息或创建新消息
|
||||
const streaming = prev.streamingMessage;
|
||||
const content = message.payload?.content || streaming?.content || '';
|
||||
// 从服务器 payload 获取 agentName,或使用当前 agentMode
|
||||
const agentName = message.payload?.agentName || prev.agentMode;
|
||||
|
||||
const newMessage: Message = streaming
|
||||
? {
|
||||
@@ -245,6 +269,7 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate
|
||||
id: message.payload?.id || streaming.id,
|
||||
timestamp: message.payload?.timestamp || streaming.timestamp,
|
||||
content,
|
||||
metadata: { ...streaming.metadata, agentName },
|
||||
}
|
||||
: {
|
||||
id: message.payload?.id || `assistant-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
||||
@@ -252,6 +277,7 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate
|
||||
timestamp: message.payload?.timestamp || new Date().toISOString(),
|
||||
parts: [{ type: 'text', id: `text-${Date.now()}`, text: content }],
|
||||
content,
|
||||
metadata: { agentName },
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -259,6 +285,7 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate
|
||||
messages: [...prev.messages, newMessage],
|
||||
streamingMessage: null,
|
||||
isLoading: false,
|
||||
currentAgent: prev.agentMode, // 恢复为主 agent
|
||||
};
|
||||
});
|
||||
break;
|
||||
@@ -448,6 +475,7 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate
|
||||
permissionRequest: null,
|
||||
agentMode: 'build',
|
||||
autoApprove: false,
|
||||
currentAgent: 'build',
|
||||
});
|
||||
reconnectAttemptsRef.current = 0;
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Agent 工具函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* Agent 显示名称映射
|
||||
*/
|
||||
export const AGENT_DISPLAY_NAMES: Record<string, string> = {
|
||||
build: 'Build Agent',
|
||||
plan: 'Plan Agent',
|
||||
general: 'General Agent',
|
||||
explore: 'Explore Agent',
|
||||
'code-reviewer': 'Code Reviewer',
|
||||
vision: 'Vision Agent',
|
||||
summary: 'Summary Agent',
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取 Agent 显示名称
|
||||
* @param agentName Agent 名称(如 build、plan、explore 等)
|
||||
* @returns 显示名称
|
||||
*/
|
||||
export function getAgentDisplayName(agentName?: string): string {
|
||||
if (!agentName) return 'AI Assistant';
|
||||
return AGENT_DISPLAY_NAMES[agentName] || agentName;
|
||||
}
|
||||
@@ -62,6 +62,7 @@ export function ChatPage({
|
||||
autoApprove,
|
||||
setAgentMode,
|
||||
setAutoApprove,
|
||||
currentAgent,
|
||||
} = useChat({
|
||||
sessionId,
|
||||
onError: (error) => {
|
||||
@@ -297,7 +298,7 @@ export function ChatPage({
|
||||
<ChatMessage message={streamingMessage} isStreaming />
|
||||
)}
|
||||
|
||||
{isLoading && !streamingMessage && <TypingIndicator />}
|
||||
{isLoading && !streamingMessage && <TypingIndicator agentName={currentAgent} />}
|
||||
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user