feat(ui): display agent name in message header

This commit is contained in:
2025-12-16 10:43:02 +08:00
parent a6c1e792fa
commit e698ec2a64
6 changed files with 72 additions and 6 deletions
+1
View File
@@ -529,6 +529,7 @@ export async function processMessage(
text: result.text,
hasToolCalls,
messageCount: result.messages.length,
agentName: options?.agentMode || 'build',
},
});
+2
View File
@@ -95,6 +95,8 @@ export interface Message {
model?: string;
stepCount?: number;
totalTokens?: number;
/** 生成此消息的 Agent 名称 */
agentName?: string;
};
}
+13 -5
View File
@@ -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
+28
View File
@@ -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;
+26
View File
@@ -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;
}
+2 -1
View File
@@ -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>