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