fix: improve and simplify tool call chain rendering (#5704)
This commit is contained in:
@@ -4,12 +4,6 @@ import { extractImagePaths, removeImagePathsFromText } from '../utils/imageUtils
|
|||||||
import { formatMessageTimestamp } from '../utils/timeUtils';
|
import { formatMessageTimestamp } from '../utils/timeUtils';
|
||||||
import MarkdownContent from './MarkdownContent';
|
import MarkdownContent from './MarkdownContent';
|
||||||
import ToolCallWithResponse from './ToolCallWithResponse';
|
import ToolCallWithResponse from './ToolCallWithResponse';
|
||||||
import ToolCallChain from './ToolCallChain';
|
|
||||||
import {
|
|
||||||
identifyConsecutiveToolCalls,
|
|
||||||
shouldHideMessage,
|
|
||||||
getChainForMessage,
|
|
||||||
} from '../utils/toolCallChaining';
|
|
||||||
import {
|
import {
|
||||||
getTextContent,
|
getTextContent,
|
||||||
getToolRequests,
|
getToolRequests,
|
||||||
@@ -22,6 +16,7 @@ import ToolCallConfirmation from './ToolCallConfirmation';
|
|||||||
import MessageCopyLink from './MessageCopyLink';
|
import MessageCopyLink from './MessageCopyLink';
|
||||||
import { NotificationEvent } from '../hooks/useMessageStream';
|
import { NotificationEvent } from '../hooks/useMessageStream';
|
||||||
import { cn } from '../utils';
|
import { cn } from '../utils';
|
||||||
|
import { identifyConsecutiveToolCalls, shouldHideTimestamp } from '../utils/toolCallChaining';
|
||||||
|
|
||||||
interface GooseMessageProps {
|
interface GooseMessageProps {
|
||||||
// messages up to this index are presumed to be "history" from a resumed session, this is used to track older tool confirmation requests
|
// messages up to this index are presumed to be "history" from a resumed session, this is used to track older tool confirmation requests
|
||||||
@@ -48,15 +43,10 @@ export default function GooseMessage({
|
|||||||
isStreaming = false,
|
isStreaming = false,
|
||||||
}: GooseMessageProps) {
|
}: GooseMessageProps) {
|
||||||
const contentRef = useRef<HTMLDivElement | null>(null);
|
const contentRef = useRef<HTMLDivElement | null>(null);
|
||||||
// Track which tool confirmations we've already handled to prevent infinite loops
|
|
||||||
const handledToolConfirmations = useRef<Set<string>>(new Set());
|
const handledToolConfirmations = useRef<Set<string>>(new Set());
|
||||||
|
|
||||||
// Extract text content from the message
|
|
||||||
let textContent = getTextContent(message);
|
let textContent = getTextContent(message);
|
||||||
|
|
||||||
// Utility to split Chain-of-Thought (CoT) from the visible assistant response.
|
|
||||||
// If the text contains a <think>...</think> block, everything inside is treated as the
|
|
||||||
// CoT and removed from the user-visible text.
|
|
||||||
const splitChainOfThought = (text: string): { visibleText: string; cotText: string | null } => {
|
const splitChainOfThought = (text: string): { visibleText: string; cotText: string | null } => {
|
||||||
const regex = /<think>([\s\S]*?)<\/think>/i;
|
const regex = /<think>([\s\S]*?)<\/think>/i;
|
||||||
const match = text.match(regex);
|
const match = text.match(regex);
|
||||||
@@ -73,78 +63,30 @@ export default function GooseMessage({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Split out Chain-of-Thought
|
|
||||||
const { visibleText, cotText } = splitChainOfThought(textContent);
|
const { visibleText, cotText } = splitChainOfThought(textContent);
|
||||||
|
|
||||||
// Extract image paths from the message content
|
|
||||||
const imagePaths = extractImagePaths(visibleText);
|
const imagePaths = extractImagePaths(visibleText);
|
||||||
|
|
||||||
// Remove image paths from text for display
|
|
||||||
const displayText =
|
const displayText =
|
||||||
imagePaths.length > 0 ? removeImagePathsFromText(visibleText, imagePaths) : visibleText;
|
imagePaths.length > 0 ? removeImagePathsFromText(visibleText, imagePaths) : visibleText;
|
||||||
|
|
||||||
// Memoize the timestamp
|
|
||||||
const timestamp = useMemo(() => formatMessageTimestamp(message.created), [message.created]);
|
const timestamp = useMemo(() => formatMessageTimestamp(message.created), [message.created]);
|
||||||
|
|
||||||
// Get tool requests from the message
|
|
||||||
const toolRequests = getToolRequests(message);
|
const toolRequests = getToolRequests(message);
|
||||||
|
|
||||||
// Get current message index
|
|
||||||
const messageIndex = messages.findIndex((msg) => msg.id === message.id);
|
const messageIndex = messages.findIndex((msg) => msg.id === message.id);
|
||||||
|
|
||||||
// Enhanced chain detection that works during streaming
|
|
||||||
const toolCallChains = useMemo(() => {
|
|
||||||
// Always run chain detection, but handle streaming messages specially
|
|
||||||
const chains = identifyConsecutiveToolCalls(messages);
|
|
||||||
|
|
||||||
// If this message is streaming and has tool calls but no text,
|
|
||||||
// check if it should extend an existing chain
|
|
||||||
if (isStreaming && toolRequests.length > 0 && !displayText.trim()) {
|
|
||||||
// Look for an existing chain that this message could extend
|
|
||||||
const previousMessage = messageIndex > 0 ? messages[messageIndex - 1] : null;
|
|
||||||
if (previousMessage) {
|
|
||||||
const prevToolRequests = getToolRequests(previousMessage);
|
|
||||||
|
|
||||||
// If previous message has tool calls (with or without text), extend its chain
|
|
||||||
if (prevToolRequests.length > 0) {
|
|
||||||
// Find if previous message is part of a chain
|
|
||||||
const prevChain = chains.find((chain) => chain.includes(messageIndex - 1));
|
|
||||||
if (prevChain) {
|
|
||||||
// Extend the existing chain to include this streaming message
|
|
||||||
const extendedChains = chains.map((chain) =>
|
|
||||||
chain === prevChain ? [...chain, messageIndex] : chain
|
|
||||||
);
|
|
||||||
return extendedChains;
|
|
||||||
} else {
|
|
||||||
// Create a new chain with previous and current message
|
|
||||||
return [...chains, [messageIndex - 1, messageIndex]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return chains;
|
|
||||||
}, [messages, isStreaming, messageIndex, toolRequests, displayText]);
|
|
||||||
|
|
||||||
// Check if this message should be hidden (part of chain but not first)
|
|
||||||
const shouldHide = shouldHideMessage(messageIndex, toolCallChains);
|
|
||||||
|
|
||||||
// Get the chain this message belongs to
|
|
||||||
const messageChain = getChainForMessage(messageIndex, toolCallChains);
|
|
||||||
const toolConfirmationContent = getToolConfirmationContent(message);
|
const toolConfirmationContent = getToolConfirmationContent(message);
|
||||||
|
const toolCallChains = useMemo(() => identifyConsecutiveToolCalls(messages), [messages]);
|
||||||
|
const hideTimestamp = useMemo(
|
||||||
|
() => shouldHideTimestamp(messageIndex, toolCallChains),
|
||||||
|
[messageIndex, toolCallChains]
|
||||||
|
);
|
||||||
const hasToolConfirmation = toolConfirmationContent !== undefined;
|
const hasToolConfirmation = toolConfirmationContent !== undefined;
|
||||||
|
|
||||||
// Find tool responses that correspond to the tool requests in this message
|
|
||||||
const toolResponsesMap = useMemo(() => {
|
const toolResponsesMap = useMemo(() => {
|
||||||
const responseMap = new Map();
|
const responseMap = new Map();
|
||||||
|
|
||||||
// Look for tool responses in subsequent messages
|
|
||||||
if (messageIndex !== undefined && messageIndex >= 0) {
|
if (messageIndex !== undefined && messageIndex >= 0) {
|
||||||
for (let i = messageIndex + 1; i < messages.length; i++) {
|
for (let i = messageIndex + 1; i < messages.length; i++) {
|
||||||
const responses = getToolResponses(messages[i]);
|
const responses = getToolResponses(messages[i]);
|
||||||
|
|
||||||
for (const response of responses) {
|
for (const response of responses) {
|
||||||
// Check if this response matches any of our tool requests
|
|
||||||
const matchingRequest = toolRequests.find((req) => req.id === response.id);
|
const matchingRequest = toolRequests.find((req) => req.id === response.id);
|
||||||
if (matchingRequest) {
|
if (matchingRequest) {
|
||||||
responseMap.set(response.id, response);
|
responseMap.set(response.id, response);
|
||||||
@@ -157,21 +99,17 @@ export default function GooseMessage({
|
|||||||
}, [messages, messageIndex, toolRequests]);
|
}, [messages, messageIndex, toolRequests]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// If the message is the last message in the resumed session and has tool confirmation, it means the tool confirmation
|
|
||||||
// is broken or cancelled, to contonue use the session, we need to append a tool response to avoid mismatch tool result error.
|
|
||||||
if (
|
if (
|
||||||
messageIndex === messageHistoryIndex - 1 &&
|
messageIndex === messageHistoryIndex - 1 &&
|
||||||
hasToolConfirmation &&
|
hasToolConfirmation &&
|
||||||
toolConfirmationContent &&
|
toolConfirmationContent &&
|
||||||
!handledToolConfirmations.current.has(toolConfirmationContent.id)
|
!handledToolConfirmations.current.has(toolConfirmationContent.id)
|
||||||
) {
|
) {
|
||||||
// Only append the error message if there isn't already a response for this tool confirmation
|
|
||||||
const hasExistingResponse = messages.some((msg) =>
|
const hasExistingResponse = messages.some((msg) =>
|
||||||
getToolResponses(msg).some((response) => response.id === toolConfirmationContent.id)
|
getToolResponses(msg).some((response) => response.id === toolConfirmationContent.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!hasExistingResponse) {
|
if (!hasExistingResponse) {
|
||||||
// Mark this tool confirmation as handled to prevent infinite loop
|
|
||||||
handledToolConfirmations.current.add(toolConfirmationContent.id);
|
handledToolConfirmations.current.add(toolConfirmationContent.id);
|
||||||
|
|
||||||
appendMessage(
|
appendMessage(
|
||||||
@@ -188,14 +126,6 @@ export default function GooseMessage({
|
|||||||
appendMessage,
|
appendMessage,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// If this message should be hidden (part of chain but not first), don't render it
|
|
||||||
if (shouldHide) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine rendering logic based on chain membership and content
|
|
||||||
const isFirstInChain = messageChain && messageChain[0] === messageIndex;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="goose-message flex w-[90%] justify-start min-w-0">
|
<div className="goose-message flex w-[90%] justify-start min-w-0">
|
||||||
<div className="flex flex-col w-full min-w-0">
|
<div className="flex flex-col w-full min-w-0">
|
||||||
@@ -216,7 +146,6 @@ export default function GooseMessage({
|
|||||||
<MarkdownContent content={displayText} />
|
<MarkdownContent content={displayText} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Image previews */}
|
|
||||||
{imagePaths.length > 0 && (
|
{imagePaths.length > 0 && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
{imagePaths.map((imagePath, index) => (
|
{imagePaths.map((imagePath, index) => (
|
||||||
@@ -244,39 +173,28 @@ export default function GooseMessage({
|
|||||||
|
|
||||||
{toolRequests.length > 0 && (
|
{toolRequests.length > 0 && (
|
||||||
<div className={cn(displayText && 'mt-2')}>
|
<div className={cn(displayText && 'mt-2')}>
|
||||||
{isFirstInChain ? (
|
<div className="relative flex flex-col w-full">
|
||||||
<ToolCallChain
|
<div className="flex flex-col gap-3">
|
||||||
messages={messages}
|
{toolRequests.map((toolRequest) => (
|
||||||
chainIndices={messageChain}
|
<div className="goose-message-tool" key={toolRequest.id}>
|
||||||
toolCallNotifications={toolCallNotifications}
|
<ToolCallWithResponse
|
||||||
toolResponsesMap={toolResponsesMap}
|
isCancelledMessage={
|
||||||
messageHistoryIndex={messageHistoryIndex}
|
messageIndex < messageHistoryIndex &&
|
||||||
isStreaming={isStreaming}
|
toolResponsesMap.get(toolRequest.id) == undefined
|
||||||
/>
|
}
|
||||||
) : !messageChain ? (
|
toolRequest={toolRequest}
|
||||||
<div className="relative flex flex-col w-full">
|
toolResponse={toolResponsesMap.get(toolRequest.id)}
|
||||||
<div className="flex flex-col gap-3">
|
notifications={toolCallNotifications.get(toolRequest.id)}
|
||||||
{toolRequests.map((toolRequest) => (
|
isStreamingMessage={isStreaming}
|
||||||
<div className="goose-message-tool" key={toolRequest.id}>
|
append={append}
|
||||||
<ToolCallWithResponse
|
/>
|
||||||
isCancelledMessage={
|
</div>
|
||||||
messageIndex < messageHistoryIndex &&
|
))}
|
||||||
toolResponsesMap.get(toolRequest.id) == undefined
|
|
||||||
}
|
|
||||||
toolRequest={toolRequest}
|
|
||||||
toolResponse={toolResponsesMap.get(toolRequest.id)}
|
|
||||||
notifications={toolCallNotifications.get(toolRequest.id)}
|
|
||||||
isStreamingMessage={isStreaming}
|
|
||||||
append={append}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-text-muted pt-1 transition-all duration-200 group-hover:-translate-y-4 group-hover:opacity-0">
|
|
||||||
{!isStreaming && timestamp}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
<div className="text-xs text-text-muted transition-all duration-200 group-hover:-translate-y-4 group-hover:opacity-0 pt-1">
|
||||||
|
{!isStreaming && !hideTimestamp && timestamp}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* - Configurable batch size and delay
|
* - Configurable batch size and delay
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Message } from '../api';
|
import { Message } from '../api';
|
||||||
import GooseMessage from './GooseMessage';
|
import GooseMessage from './GooseMessage';
|
||||||
import UserMessage from './UserMessage';
|
import UserMessage from './UserMessage';
|
||||||
@@ -22,6 +22,7 @@ import { SystemNotificationInline } from './context_management/SystemNotificatio
|
|||||||
import { NotificationEvent } from '../hooks/useMessageStream';
|
import { NotificationEvent } from '../hooks/useMessageStream';
|
||||||
import LoadingGoose from './LoadingGoose';
|
import LoadingGoose from './LoadingGoose';
|
||||||
import { ChatType } from '../types/chat';
|
import { ChatType } from '../types/chat';
|
||||||
|
import { identifyConsecutiveToolCalls, isInChain } from '../utils/toolCallChaining';
|
||||||
|
|
||||||
interface ProgressiveMessageListProps {
|
interface ProgressiveMessageListProps {
|
||||||
messages: Message[];
|
messages: Message[];
|
||||||
@@ -161,6 +162,9 @@ export default function ProgressiveMessageList({
|
|||||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
}, [isLoading, messages.length]);
|
}, [isLoading, messages.length]);
|
||||||
|
|
||||||
|
// Detect tool call chains
|
||||||
|
const toolCallChains = useMemo(() => identifyConsecutiveToolCalls(messages), [messages]);
|
||||||
|
|
||||||
// Render messages up to the current rendered count
|
// Render messages up to the current rendered count
|
||||||
const renderMessages = useCallback(() => {
|
const renderMessages = useCallback(() => {
|
||||||
const messagesToRender = messages.slice(0, renderedCount);
|
const messagesToRender = messages.slice(0, renderedCount);
|
||||||
@@ -195,11 +199,12 @@ export default function ProgressiveMessageList({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isUser = isUserMessage(message);
|
const isUser = isUserMessage(message);
|
||||||
|
const messageIsInChain = isInChain(index, toolCallChains);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={message.id && `${message.id}-${message.content.length}`}
|
key={message.id && `${message.id}-${message.content.length}`}
|
||||||
className={`relative ${index === 0 ? 'mt-0' : 'mt-4'} ${isUser ? 'user' : 'assistant'}`}
|
className={`relative ${index === 0 ? 'mt-0' : 'mt-4'} ${isUser ? 'user' : 'assistant'} ${messageIsInChain ? 'in-chain' : ''}`}
|
||||||
data-testid="message-container"
|
data-testid="message-container"
|
||||||
>
|
>
|
||||||
{isUser ? (
|
{isUser ? (
|
||||||
@@ -238,6 +243,7 @@ export default function ProgressiveMessageList({
|
|||||||
toolCallNotifications,
|
toolCallNotifications,
|
||||||
isStreamingMessage,
|
isStreamingMessage,
|
||||||
onMessageUpdate,
|
onMessageUpdate,
|
||||||
|
toolCallChains,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
import { formatMessageTimestamp } from '../utils/timeUtils';
|
|
||||||
import { Message } from '../api';
|
|
||||||
import { getToolRequests } from '../types/message';
|
|
||||||
import { NotificationEvent } from '../hooks/useMessageStream';
|
|
||||||
import ToolCallWithResponse from './ToolCallWithResponse';
|
|
||||||
|
|
||||||
interface ToolCallChainProps {
|
|
||||||
messages: Message[];
|
|
||||||
chainIndices: number[];
|
|
||||||
toolCallNotifications: Map<string, NotificationEvent[]>;
|
|
||||||
toolResponsesMap: Map<string, import('../types/message').ToolResponseMessageContent>;
|
|
||||||
messageHistoryIndex: number;
|
|
||||||
isStreaming?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ToolCallChain({
|
|
||||||
messages,
|
|
||||||
chainIndices,
|
|
||||||
toolCallNotifications,
|
|
||||||
toolResponsesMap,
|
|
||||||
messageHistoryIndex,
|
|
||||||
isStreaming = false,
|
|
||||||
}: ToolCallChainProps) {
|
|
||||||
const lastMessageIndex = chainIndices[chainIndices.length - 1];
|
|
||||||
const lastMessage = messages[lastMessageIndex];
|
|
||||||
const timestamp = lastMessage ? formatMessageTimestamp(lastMessage.created) : '';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative flex flex-col w-full">
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
{chainIndices.map((messageIndex) => {
|
|
||||||
const message = messages[messageIndex];
|
|
||||||
const toolRequests = getToolRequests(message);
|
|
||||||
|
|
||||||
return toolRequests.map((toolRequest) => (
|
|
||||||
<div key={toolRequest.id} className="goose-message-tool">
|
|
||||||
<ToolCallWithResponse
|
|
||||||
isCancelledMessage={
|
|
||||||
messageIndex < messageHistoryIndex &&
|
|
||||||
toolResponsesMap.get(toolRequest.id) == undefined
|
|
||||||
}
|
|
||||||
toolRequest={toolRequest}
|
|
||||||
toolResponse={toolResponsesMap.get(toolRequest.id)}
|
|
||||||
notifications={toolCallNotifications.get(toolRequest.id)}
|
|
||||||
isStreamingMessage={isStreaming}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
));
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-xs text-text-muted pt-1 transition-all duration-200 group-hover:-translate-y-4 group-hover:opacity-0">
|
|
||||||
{!isStreaming && timestamp}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -247,24 +247,12 @@ function ToolCallView({
|
|||||||
}
|
}
|
||||||
}, [toolResponse, startTime]);
|
}, [toolResponse, startTime]);
|
||||||
|
|
||||||
const toolResults: { result: Content; isExpandToolResults: boolean }[] =
|
const toolResults: Content[] =
|
||||||
loadingStatus === 'success' && Array.isArray(toolResponse?.toolResult.value)
|
loadingStatus === 'success' && Array.isArray(toolResponse?.toolResult.value)
|
||||||
? toolResponse!.toolResult.value
|
? toolResponse!.toolResult.value.filter((item) => {
|
||||||
.filter((item) => {
|
const audience = item.annotations?.audience as string[] | undefined;
|
||||||
const audience = item.annotations?.audience as string[] | undefined;
|
return !audience || audience.includes('user');
|
||||||
return !audience || audience.includes('user');
|
})
|
||||||
})
|
|
||||||
.map((item) => {
|
|
||||||
// Use user preference for detailed/concise, but still respect high priority items
|
|
||||||
const priority = (item.annotations?.priority as number | undefined) ?? -1;
|
|
||||||
const isHighPriority = priority >= 0.5;
|
|
||||||
const shouldExpandBasedOnStyle = responseStyle === 'detailed' || responseStyle === null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
result: item,
|
|
||||||
isExpandToolResults: isHighPriority || shouldExpandBasedOnStyle,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const logs = notifications
|
const logs = notifications
|
||||||
@@ -290,17 +278,6 @@ function ToolCallView({
|
|||||||
const isRenderingProgress =
|
const isRenderingProgress =
|
||||||
loadingStatus === 'loading' && (progressEntries.length > 0 || (logs || []).length > 0);
|
loadingStatus === 'loading' && (progressEntries.length > 0 || (logs || []).length > 0);
|
||||||
|
|
||||||
// Determine if the main tool call should be expanded
|
|
||||||
const isShouldExpand = (() => {
|
|
||||||
// Always expand if there are high priority results that need to be shown
|
|
||||||
const hasHighPriorityResults = toolResults.some((v) => v.isExpandToolResults);
|
|
||||||
|
|
||||||
// Also expand based on user preference for detailed mode
|
|
||||||
const shouldExpandBasedOnStyle = responseStyle === 'detailed' || responseStyle === null;
|
|
||||||
|
|
||||||
return hasHighPriorityResults || shouldExpandBasedOnStyle;
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Function to create a descriptive representation of what the tool is doing
|
// Function to create a descriptive representation of what the tool is doing
|
||||||
const getToolDescription = (): string | null => {
|
const getToolDescription = (): string | null => {
|
||||||
const args = toolCall.arguments as Record<string, ToolCallArgumentValue>;
|
const args = toolCall.arguments as Record<string, ToolCallArgumentValue>;
|
||||||
@@ -488,7 +465,7 @@ function ToolCallView({
|
|||||||
return (
|
return (
|
||||||
<ToolCallExpandable
|
<ToolCallExpandable
|
||||||
isStartExpanded={isRenderingProgress}
|
isStartExpanded={isRenderingProgress}
|
||||||
isForceExpand={isShouldExpand}
|
isForceExpand={false}
|
||||||
label={
|
label={
|
||||||
extensionTooltip ? (
|
extensionTooltip ? (
|
||||||
<TooltipWrapper tooltipContent={extensionTooltip} side="top" align="start">
|
<TooltipWrapper tooltipContent={extensionTooltip} side="top" align="start">
|
||||||
@@ -529,13 +506,11 @@ function ToolCallView({
|
|||||||
{/* Tool Output */}
|
{/* Tool Output */}
|
||||||
{!isCancelledMessage && (
|
{!isCancelledMessage && (
|
||||||
<>
|
<>
|
||||||
{toolResults.map(({ result, isExpandToolResults }, index) => {
|
{toolResults.map((result, index) => (
|
||||||
return (
|
<div key={index} className={cn('border-t border-borderSubtle')}>
|
||||||
<div key={index} className={cn('border-t border-borderSubtle')}>
|
<ToolResultView result={result} isStartExpanded={false} />
|
||||||
<ToolResultView result={result} isStartExpanded={isExpandToolResults} />
|
</div>
|
||||||
</div>
|
))}
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</ToolCallExpandable>
|
</ToolCallExpandable>
|
||||||
|
|||||||
@@ -555,7 +555,7 @@ p > code.bg-inline-code {
|
|||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
}
|
}
|
||||||
|
|
||||||
.assistant:has(+ .user) .goose-message {
|
.assistant:has(+ .user):not(.in-chain) .goose-message {
|
||||||
padding-bottom: 24px;
|
padding-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,20 @@ export function shouldHideMessage(messageIndex: number, chains: number[][]): boo
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shouldHideTimestamp(messageIndex: number, chains: number[][]): boolean {
|
||||||
|
for (const chain of chains) {
|
||||||
|
if (chain.includes(messageIndex)) {
|
||||||
|
// Hide timestamp for all but the last message in the chain
|
||||||
|
return chain[chain.length - 1] !== messageIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isInChain(messageIndex: number, chains: number[][]): boolean {
|
||||||
|
return chains.some((chain) => chain.includes(messageIndex));
|
||||||
|
}
|
||||||
|
|
||||||
export function getChainForMessage(messageIndex: number, chains: number[][]): number[] | null {
|
export function getChainForMessage(messageIndex: number, chains: number[][]): number[] | null {
|
||||||
return chains.find((chain) => chain.includes(messageIndex)) || null;
|
return chains.find((chain) => chain.includes(messageIndex)) || null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user