diff --git a/src/api/chat.ts b/src/api/chat.ts index 85a877b..24e36ab 100644 --- a/src/api/chat.ts +++ b/src/api/chat.ts @@ -20,6 +20,7 @@ export interface ChatMessage { reasoning?: string | null; parentId?: string | null; createdAt: number; + agent_id?: string; meta?: { retrieved?: RetrievedSnippet[]; toolCalls?: ToolCallTrace[]; diff --git a/src/pages/chat/ChatPage.tsx b/src/pages/chat/ChatPage.tsx index dc671b1..2dc2c7b 100644 --- a/src/pages/chat/ChatPage.tsx +++ b/src/pages/chat/ChatPage.tsx @@ -158,6 +158,8 @@ export default function ChatPage() { url?.startsWith('http') || url?.startsWith(' export default function ChatBody(props: { bodyRef: React.RefObject; agent: Agent; + agentList: Agent[]; + currentAgentId: string; messages: ChatMessage[]; branches: Record; highlightId: string | null; @@ -20,7 +22,7 @@ export default function ChatBody(props: { onSwitchBranch: (userMsgId: string, branchId: string) => void; onCopy: (text: string, mode: CopyMode) => void; }) { - const { bodyRef, agent, messages, branches, highlightId, sending, streaming, onRegenerate, onSwitchBranch, onCopy } = props; + const { bodyRef, agent, agentList, currentAgentId, messages, branches, highlightId, sending, streaming, onRegenerate, onSwitchBranch, onCopy } = props; return (
@@ -55,6 +57,8 @@ export default function ChatBody(props: { + {(() => { + const streamingAgentId = streaming.targetAgentId || currentAgentId; + const streamingAgent = agentList.find(a => a.id === streamingAgentId); + if (streamingAgent) { + return ( +
+ + + {streamingAgent.name} + +
+ ); + } + return null; + })()}
{!!streaming.retryInfo?.message && ( diff --git a/src/pages/chat/components/messages/MessageItem.tsx b/src/pages/chat/components/messages/MessageItem.tsx index 21e2425..0bcffca 100644 --- a/src/pages/chat/components/messages/MessageItem.tsx +++ b/src/pages/chat/components/messages/MessageItem.tsx @@ -1,5 +1,6 @@ -import { Button, Dropdown, Space, Tag, Tooltip } from 'antd'; +import { Button, Dropdown, Space, Tag, Tooltip, Avatar } from 'antd'; import type { BranchInfo, ChatMessage } from '../../../../api'; +import type { Agent } from '../../../../api/agents'; import Markdown from '../../../../components/Markdown'; import { ProductCopyIcon } from '../../../../components/icons'; import type { CopyMode } from '../../utils/copy'; @@ -7,6 +8,8 @@ import { ReasoningView, RetrievedView, ToolCallView } from './MetaViews'; export default function MessageItem(props: { message: ChatMessage; + agentList: Agent[]; + currentAgentId: string; highlighted?: boolean; branch?: BranchInfo; busy?: boolean; @@ -14,7 +17,12 @@ export default function MessageItem(props: { onSwitchBranch?: (userMsgId: string, branchId: string) => void; onCopy?: (text: string, mode: CopyMode) => void; }) { - const { message, highlighted, branch, busy, onRegenerate, onSwitchBranch, onCopy } = props; + const { message, agentList, currentAgentId, highlighted, branch, busy, onRegenerate, onSwitchBranch, onCopy } = props; + + // 获取回答者 Agent 信息 + const answerAgentId = message.agent_id || (message.role === 'assistant' ? currentAgentId : undefined); + const answerAgent = answerAgentId ? agentList.find(a => a.id === answerAgentId) : undefined; + const hasAnswerAgent = !!answerAgent && message.role === 'assistant'; const hasBranches = !!branch && branch.total > 1; const activeIdx = branch?.activeIndex ?? 0; const total = branch?.total ?? 1; @@ -42,6 +50,24 @@ export default function MessageItem(props: { transition: 'background 0.4s, padding 0.4s' }} > + {hasAnswerAgent && ( +
+ + + {answerAgent!.name} + +
+ )}
{message.role === 'assistant' ? ( {message.content} diff --git a/src/pages/chat/hooks/useChatSender.ts b/src/pages/chat/hooks/useChatSender.ts index b45a74a..4a387ef 100644 --- a/src/pages/chat/hooks/useChatSender.ts +++ b/src/pages/chat/hooks/useChatSender.ts @@ -12,6 +12,7 @@ export interface StreamingState { retryInfo: any | null; retrieved: RetrievedSnippet[]; toolCalls: ToolCallTrace[]; + targetAgentId?: string; } // 解析文本中的 @AgentName,返回找到的目标 Agent @@ -30,7 +31,20 @@ export function parseMentionedAgent(text: string, agentList: Agent[]): Agent | n const startMatch = firstLine.match(/^@([^\s]+)/); const candidateName = startMatch ? startMatch[1] : lastMention; - if (!candidateName) return null; + console.log('[parseMentionedAgent]', { + text, + lastMention, + firstLine, + startMatch, + candidateName, + agentListCount: agentList.length, + agentList: agentList.map(a => ({ id: a.id, name: a.name })) + }); + + if (!candidateName) { + console.log('[parseMentionedAgent] no candidateName, return null'); + return null; + } // 模糊匹配(前缀匹配,不区分大小写) const lowerCandidate = candidateName.toLowerCase(); @@ -39,17 +53,21 @@ export function parseMentionedAgent(text: string, agentList: Agent[]): Agent | n for (const a of agentList) { const lowerName = a.name.toLowerCase(); + console.log('[parseMentionedAgent] checking', { candidate: lowerCandidate, agentName: lowerName, id: a.id }); if (lowerName === lowerCandidate) { + console.log('[parseMentionedAgent] exact match found', a); return a; // 精确匹配直接返回 } if (lowerName.startsWith(lowerCandidate)) { const score = candidateName.length / lowerName.length; + console.log('[parseMentionedAgent] prefix match', { score, agent: a }); if (score > bestScore) { bestScore = score; bestMatch = a; } } else if (lowerName.includes(lowerCandidate)) { const score = candidateName.length / lowerName.length * 0.8; // 包含匹配权重稍低 + console.log('[parseMentionedAgent] contains match', { score, agent: a }); if (score > bestScore) { bestScore = score; bestMatch = a; @@ -57,7 +75,9 @@ export function parseMentionedAgent(text: string, agentList: Agent[]): Agent | n } } - return bestScore > 0.3 ? bestMatch : null; + const result = bestScore > 0.3 ? bestMatch : null; + console.log('[parseMentionedAgent] final result', { bestScore, result }); + return result; } export function useChatSender(args: { @@ -89,7 +109,8 @@ export function useChatSender(args: { errorMessage: null, retryInfo: null, retrieved: [], - toolCalls: [] + toolCalls: [], + targetAgentId: undefined }); const [sessionRefresh, setSessionRefresh] = useState(0); @@ -103,19 +124,19 @@ export function useChatSender(args: { notify.error('会话未初始化,请稍后重试'); return; } + // 解析 @提及的目标 Agent + const targetAgent = parseMentionedAgent(text, agentList); + const targetAgentId = targetAgent?.id || agentId; + const tempUser: ChatMessage = { id: 'tmp-' + Date.now(), role: 'user', content: text, createdAt: Date.now() }; args.setMessages((m) => [...(m || []), tempUser]); - setStreaming({ active: true, reasoningText: '', answerText: '', errorMessage: null, retryInfo: null, retrieved: [], toolCalls: [] }); + setStreaming({ active: true, reasoningText: '', answerText: '', errorMessage: null, retryInfo: null, retrieved: [], toolCalls: [], targetAgentId }); scrollBottom(true); abortRef.current?.abort(); const ctrl = new AbortController(); abortRef.current = ctrl; - // 解析 @提及的目标 Agent - const targetAgent = parseMentionedAgent(text, agentList); - const targetAgentId = targetAgent?.id || agentId; - // 如果提及了其他 Agent,使用该 Agent 的第一个模型 let targetModel: string; let targetModelId: string;