fix: simplify tool call badges
This commit is contained in:
@@ -38,60 +38,47 @@ function CheckIcon() {
|
||||
);
|
||||
}
|
||||
|
||||
function ToolDoneIcon() {
|
||||
function ToolActivityIcon({ spinning }: { spinning: boolean }) {
|
||||
return (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M5 12.5l4.5 4.5L19 7.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<span className={`tool-badge-spinner${spinning ? ' is-spinning' : ''}`} aria-hidden="true">
|
||||
<span />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function resolveToolCallName(
|
||||
tool: Extract<Message['content'][number], { type: 'toolRequest' }>,
|
||||
): string {
|
||||
const toolCall = tool.toolCall as {
|
||||
functionName?: string;
|
||||
value?: { name?: string };
|
||||
};
|
||||
return String(toolCall.functionName ?? toolCall.value?.name ?? '').trim();
|
||||
function ToolBadgeItem({ spinning }: { spinning: boolean }) {
|
||||
return (
|
||||
<span className="tool-badge">
|
||||
<ToolActivityIcon spinning={spinning} />
|
||||
<span>工具调用</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function ToolBadge({ message }: { message: Message }) {
|
||||
function hasToolActivity(message: Message) {
|
||||
return message.content.some((c) => c.type === 'toolRequest' || c.type === 'toolResponse');
|
||||
}
|
||||
|
||||
function isToolOnlyAssistantMessage(message: Message) {
|
||||
return (
|
||||
message.role === 'assistant' &&
|
||||
hasToolActivity(message) &&
|
||||
!getDisplayText(message).trim() &&
|
||||
!getThinking(message) &&
|
||||
getImageUrls(message).length === 0
|
||||
);
|
||||
}
|
||||
|
||||
function ToolBadge({ message, active }: { message: Message; active: boolean }) {
|
||||
if (message.role !== 'assistant') return null;
|
||||
const tools = message.content.filter((c) => c.type === 'toolRequest' || c.type === 'toolResponse');
|
||||
if (tools.length === 0) return null;
|
||||
|
||||
const completed = tools.filter((t) => t.type === 'toolResponse').length;
|
||||
const total = tools.length;
|
||||
|
||||
return (
|
||||
<div className="tool-badges">
|
||||
{tools.map((tool, i) => {
|
||||
const isRequest = tool.type === 'toolRequest';
|
||||
const toolName = isRequest ? resolveToolCallName(tool) : '';
|
||||
|
||||
let label: string;
|
||||
if (isRequest && !toolName) {
|
||||
label = `正在加载 tools (${completed}/${total})`;
|
||||
} else {
|
||||
label = isRequest ? toolName : '工具完成';
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
key={`${message.id}-${i}`}
|
||||
className={`tool-badge${isRequest ? ' is-pending' : ' is-complete'}`}
|
||||
>
|
||||
{!isRequest ? <ToolDoneIcon /> : null}
|
||||
<span>{label}</span>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
{tools.map((tool, i) => (
|
||||
<ToolBadgeItem key={`${message.id}-${tool.id}-${i}`} spinning={active} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -198,6 +185,7 @@ function MessageRow({
|
||||
publishUsername,
|
||||
sessionId,
|
||||
compact = false,
|
||||
activeToolMessage = false,
|
||||
}: {
|
||||
message: Message;
|
||||
avatarUrl: string | null;
|
||||
@@ -208,6 +196,7 @@ function MessageRow({
|
||||
publishUsername?: string;
|
||||
sessionId?: string;
|
||||
compact?: boolean;
|
||||
activeToolMessage?: boolean;
|
||||
}) {
|
||||
const [actionsOpen, setActionsOpen] = useState(false);
|
||||
const rawText = getDisplayText(message);
|
||||
@@ -224,7 +213,7 @@ function MessageRow({
|
||||
<div className={`msg-row msg-row-assistant`}>
|
||||
<TKMindAvatar />
|
||||
<div className="msg-content">
|
||||
<ToolBadge message={message} />
|
||||
<ToolBadge message={message} active={activeToolMessage} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -273,7 +262,7 @@ function MessageRow({
|
||||
dangerouslySetInnerHTML={{ __html: renderMarkdown(text) }}
|
||||
/>
|
||||
)}
|
||||
<ToolBadge message={message} />
|
||||
<ToolBadge message={message} active={activeToolMessage} />
|
||||
</div>
|
||||
{showActionsToggle && (
|
||||
<button
|
||||
@@ -361,11 +350,22 @@ export function MessageList({
|
||||
compact?: boolean;
|
||||
}) {
|
||||
const { avatarUrl } = useUserAvatar();
|
||||
const renderableMessages = messages.filter(shouldShowChatMessage);
|
||||
const lastRenderableIndex = renderableMessages.length - 1;
|
||||
const activeToolMessage =
|
||||
streaming &&
|
||||
lastRenderableIndex >= 0 &&
|
||||
hasToolActivity(renderableMessages[lastRenderableIndex])
|
||||
? renderableMessages[lastRenderableIndex]
|
||||
: null;
|
||||
const visibleMessages = renderableMessages.filter(
|
||||
(message) => !isToolOnlyAssistantMessage(message) || message === activeToolMessage,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="message-list">
|
||||
{messages.length === 0 && <ChatWelcomePanel compact={compact} />}
|
||||
{messages.filter(shouldShowChatMessage).map((message, index, visibleMessages) => {
|
||||
{visibleMessages.map((message, index) => {
|
||||
const prev = index > 0 ? visibleMessages[index - 1] : undefined;
|
||||
const showTime = shouldShowTimestamp(message.created, prev?.created);
|
||||
const anchorId = message.id ?? `msg-${index}-${message.role}-${message.created}`;
|
||||
@@ -386,6 +386,7 @@ export function MessageList({
|
||||
publishUsername={publishUsername}
|
||||
sessionId={sessionId}
|
||||
compact={compact}
|
||||
activeToolMessage={message === activeToolMessage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useChat } from '../context/ChatProvider';
|
||||
import { buildSelectedAssetGreeting } from '../utils/mindspaceChatContext';
|
||||
import { shouldShowChatMessage } from '../utils/message';
|
||||
import type { Message, MindSpaceChatContext, MindSpaceSaveCategory, PortalUser } from '../types';
|
||||
import { SpaceChatFab } from './SpaceChatFab';
|
||||
import { SpaceChatPanel } from './SpaceChatPanel';
|
||||
@@ -90,7 +91,7 @@ export function MindSpaceSpaceChat({
|
||||
|
||||
const prefillMessages = useMemo(() => {
|
||||
if (chatBridge || !open) return [];
|
||||
if (messages.some((message) => message.role === 'user')) return [];
|
||||
if (messages.some((message) => message.role === 'user' && shouldShowChatMessage(message))) return [];
|
||||
const selected = context.selectedAssets ?? [];
|
||||
if (selected.length !== 1) return [];
|
||||
return [buildSelectedAssetGreetingMessage(selected[0])];
|
||||
|
||||
@@ -4,6 +4,7 @@ import { INSUFFICIENT_BALANCE_NOTICE } from '../hooks/useTKMindChat';
|
||||
import type { PageEditSubChatBridge } from '../hooks/usePageEditSubChat';
|
||||
import type { MindSpaceChatContext, MindSpaceSaveCategory, PortalUser, Message } from '../types';
|
||||
import { formatContextChip } from '../utils/mindspaceChatContext';
|
||||
import { shouldShowChatMessage } from '../utils/message';
|
||||
import { ChatPanel } from './ChatPanel';
|
||||
import { TKMindAvatar } from './TKMindAvatar';
|
||||
|
||||
@@ -64,7 +65,7 @@ export function SpaceChatPanel({
|
||||
|
||||
const displayMessages = useMemo(() => {
|
||||
if (!prefillMessages.length) return messages;
|
||||
if (messages.some((message) => message.role === 'user')) return messages;
|
||||
if (messages.some((message) => message.role === 'user' && shouldShowChatMessage(message))) return messages;
|
||||
const extras = prefillMessages.filter(
|
||||
(item) => !messages.some((message) => message.id && message.id === item.id),
|
||||
);
|
||||
|
||||
+3
-8
@@ -1948,14 +1948,6 @@ body,
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.tool-badge.is-pending {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.tool-badge.is-complete {
|
||||
color: var(--color-success, #7dd3a7);
|
||||
}
|
||||
|
||||
.tool-badge-spinner {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
@@ -1971,6 +1963,9 @@ body,
|
||||
border-radius: 999px;
|
||||
border: 1.5px solid currentColor;
|
||||
border-right-color: transparent;
|
||||
}
|
||||
|
||||
.tool-badge-spinner.is-spinning span {
|
||||
animation: tool-badge-spin 0.75s linear infinite;
|
||||
}
|
||||
|
||||
|
||||
@@ -191,10 +191,11 @@ export function getDisplayText(message: Message): string {
|
||||
}
|
||||
|
||||
export function shouldShowChatMessage(message: Message): boolean {
|
||||
if (message.role === 'user') return true;
|
||||
if (getDisplayText(message).trim()) return true;
|
||||
if (message.role === 'user' && getImageUrls(message).length > 0) return true;
|
||||
if (getThinking(message)) return true;
|
||||
if (getSystemNotificationText(message)) return true;
|
||||
if (message.role !== 'assistant') return false;
|
||||
return message.content.some(
|
||||
(item) => item.type === 'toolRequest' || item.type === 'toolResponse' || item.type === 'actionRequired',
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user