feat(web): Task 卡片差异显示子代理模型名 + 子代理完成状态转发修复

- Task/tool 卡片头部在子代理模型与主代理不同(model 或 provider 任一不同)时显示模型徽章
- useChat 放行带 subagent_task_id 的 execution_completed 消息,更新卡片 running→completed 状态
This commit is contained in:
oudecheng 2026-08-15 15:35:32 +08:00
parent 53e2fb6dc6
commit 73c25e5a20
3 changed files with 31 additions and 1 deletions

View File

@ -89,6 +89,8 @@ interface MessageBubbleProps {
message: ChatMessage; message: ChatMessage;
onNavigateToSubAgent?: (taskId: string, description: string, subagentType?: string) => void; onNavigateToSubAgent?: (taskId: string, description: string, subagentType?: string) => void;
showThinking?: boolean; showThinking?: boolean;
/** 主代理当前生效模型Task 卡片差异显示:子代理模型不同才展示模型徽章) */
effectiveModel?: { provider: string; model: string } | null;
} }
function getAttachmentIcon(mediaType: string) { function getAttachmentIcon(mediaType: string) {
@ -352,6 +354,7 @@ export const MessageBubble = memo(function MessageBubble({
message, message,
onNavigateToSubAgent, onNavigateToSubAgent,
showThinking = true, showThinking = true,
effectiveModel,
}: MessageBubbleProps) { }: MessageBubbleProps) {
const isUser = message.role === 'user'; const isUser = message.role === 'user';
const isTool = message.role === 'tool'; const isTool = message.role === 'tool';
@ -484,6 +487,19 @@ export const MessageBubble = memo(function MessageBubble({
// 安全获取 task 状态配色,未知状态回退到默认(避免 undefined.borderColor 崩溃) // 安全获取 task 状态配色,未知状态回退到默认(避免 undefined.borderColor 崩溃)
const taskStyle = taskResult ? (taskStatusConfig[taskResult.status] ?? taskStatusConfig.failed) : null; const taskStyle = taskResult ? (taskStatusConfig[taskResult.status] ?? taskStatusConfig.failed) : null;
// 子代理模型徽章:结果携带模型信息,且(无主代理参照 或 model/provider 任一不同)时展示。
// provider 不同即使 model 同名也显示——不同 provider 的同名模型在路由/计费上已不同。
const modelDiffers =
!effectiveModel ||
effectiveModel.model !== taskResult?.model ||
effectiveModel.provider !== taskResult?.provider;
const subagentModelLabel =
taskResult?.model && modelDiffers
? effectiveModel && effectiveModel.provider === taskResult.provider
? taskResult.model
: `${taskResult.provider ?? '?'}/${taskResult.model}`
: null;
return ( return (
<div data-message-id={message.id} className="animate-slide-in"> <div data-message-id={message.id} className="animate-slide-in">
<div <div
@ -504,6 +520,14 @@ export const MessageBubble = memo(function MessageBubble({
? `${message.toolName || 'Tool'}${taskDescription ? ` · ${taskDescription}` : ''}` ? `${message.toolName || 'Tool'}${taskDescription ? ` · ${taskDescription}` : ''}`
: message.toolName || 'Tool'} : message.toolName || 'Tool'}
</span> </span>
{subagentModelLabel && (
<span
className="flex-shrink-0 rounded-full border border-[var(--border-color)] bg-[var(--bg-tertiary)] px-1.5 py-px text-[10px] leading-4 text-[var(--text-muted)] max-w-[160px] truncate"
title={`子代理模型:${taskResult?.provider ?? '?'} / ${taskResult?.model}`}
>
{subagentModelLabel}
</span>
)}
<span <span
className={`flex-shrink-0 transition-all duration-300 ${ className={`flex-shrink-0 transition-all duration-300 ${
taskStyle ? taskStyle.iconColor : statusConfig.iconColor taskStyle ? taskStyle.iconColor : statusConfig.iconColor

View File

@ -12,6 +12,8 @@ interface MessageListProps {
viewKey?: string; viewKey?: string;
/** 高亮的消息 ID点击待办项后滚动并高亮显示 */ /** 高亮的消息 ID点击待办项后滚动并高亮显示 */
highlightedMessageId?: string | null; highlightedMessageId?: string | null;
/** 主代理当前生效模型(透传给 MessageBubble 做 Task 卡片差异显示) */
effectiveModel?: { provider: string; model: string } | null;
} }
export function MessageList({ export function MessageList({
@ -20,6 +22,7 @@ export function MessageList({
showThinking = true, showThinking = true,
viewKey, viewKey,
highlightedMessageId, highlightedMessageId,
effectiveModel,
}: MessageListProps) { }: MessageListProps) {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const isAtBottomRef = useRef(true); const isAtBottomRef = useRef(true);
@ -274,6 +277,7 @@ export function MessageList({
message={message} message={message}
onNavigateToSubAgent={onNavigateToSubAgent} onNavigateToSubAgent={onNavigateToSubAgent}
showThinking={showThinking} showThinking={showThinking}
effectiveModel={effectiveModel}
/> />
</div> </div>
</div> </div>

View File

@ -182,7 +182,9 @@ export function useChat(): UseChatReturn {
// Tier 3: 主视图路由 // Tier 3: 主视图路由
// 3a: 带 subagent_task_id 的消息在主视图直接丢弃(已在 Tier 2 未命中) // 3a: 带 subagent_task_id 的消息在主视图直接丢弃(已在 Tier 2 未命中)
if (getSubagentTaskId(message)) return; // 例外execution_completed 携带 subagent_status需转发到 handleMainViewMessage
// 更新主视图 task 卡片占位状态running → completed 等),否则卡片永远显示运行中
if (getSubagentTaskId(message) && message.type !== 'execution_completed') return;
// 3b: 非 chat 消息的 case 分发 // 3b: 非 chat 消息的 case 分发
switch (message.type) { switch (message.type) {