fix: fix tool call views in share page

This commit is contained in:
Soumyadas15 2025-07-03 09:41:47 +05:30
parent 4ef84453d9
commit 0c66b05706
1 changed files with 11 additions and 70 deletions

View File

@ -31,8 +31,8 @@ import { useAgentStream } from '@/hooks/useAgentStream';
import { threadErrorCodeMessages } from '@/lib/constants/errorCodeMessages';
import { ThreadSkeleton } from '@/components/thread/content/ThreadSkeleton';
import { useAgent } from '@/hooks/react-query/agents/use-agents';
import { extractToolName } from '@/components/thread/tool-views/xml-parser';
// Extend the base Message type with the expected database fields
interface ApiMessageType extends BaseApiMessageType {
message_id?: string;
thread_id?: string;
@ -48,7 +48,6 @@ interface ApiMessageType extends BaseApiMessageType {
};
}
// Add a simple interface for streaming tool calls
interface StreamingToolCall {
id?: string;
name?: string;
@ -78,7 +77,6 @@ export default function ThreadPage({
const [currentToolIndex, setCurrentToolIndex] = useState<number>(0);
const [autoOpenedPanel, setAutoOpenedPanel] = useState(false);
// Playback control states
const [isPlaying, setIsPlaying] = useState(false);
const [currentMessageIndex, setCurrentMessageIndex] = useState(0);
const [streamingText, setStreamingText] = useState('');
@ -194,7 +192,7 @@ export default function ThreadPage({
(toolCall: StreamingToolCall | null) => {
if (!toolCall) return;
// Normalize the tool name by replacing underscores with hyphens
// Normalize the tool name like the project thread page does
const rawToolName = toolCall.name || toolCall.xml_tag_name || 'Unknown Tool';
const toolName = rawToolName.replace(/_/g, '-').toLowerCase();
@ -210,6 +208,7 @@ export default function ThreadPage({
// Format the arguments in a way that matches the expected XML format for each tool
// This ensures the specialized tool views render correctly
let formattedContent = toolArguments;
if (
toolName.includes('command') &&
!toolArguments.includes('<execute-command>')
@ -432,17 +431,10 @@ export default function ThreadPage({
return assistantMsg.content;
}
})();
// Try to extract tool name from content
const xmlMatch = assistantContent.match(
/<([a-zA-Z\-_]+)(?:\s+[^>]*)?>|<([a-zA-Z\-_]+)(?:\s+[^>]*)?\/>/,
);
if (xmlMatch) {
// Normalize tool name: replace underscores with hyphens and lowercase
const rawToolName = xmlMatch[1] || xmlMatch[2] || 'unknown';
toolName = rawToolName.replace(/_/g, '-').toLowerCase();
const extractedToolName = extractToolName(assistantContent);
if (extractedToolName) {
toolName = extractedToolName;
} else {
// Fallback to checking for tool_calls JSON structure
const assistantContentParsed = safeJsonParse<{
tool_calls?: Array<{ function?: { name?: string }; name?: string }>;
}>(assistantMsg.content, {});
@ -452,7 +444,6 @@ export default function ThreadPage({
) {
const firstToolCall = assistantContentParsed.tool_calls[0];
const rawName = firstToolCall.function?.name || firstToolCall.name || 'unknown';
// Normalize tool name here too
toolName = rawName.replace(/_/g, '-').toLowerCase();
}
}
@ -460,7 +451,6 @@ export default function ThreadPage({
let isSuccess = true;
try {
// Parse tool result content
const toolResultContent = (() => {
try {
const parsed = safeJsonParse<{ content?: string }>(resultMessage.content, {});
@ -469,15 +459,11 @@ export default function ThreadPage({
return resultMessage.content;
}
})();
// Check for ToolResult pattern first
if (toolResultContent && typeof toolResultContent === 'string') {
// Look for ToolResult(success=True/False) pattern
const toolResultMatch = toolResultContent.match(/ToolResult\s*\(\s*success\s*=\s*(True|False|true|false)/i);
if (toolResultMatch) {
isSuccess = toolResultMatch[1].toLowerCase() === 'true';
} else {
// Fallback: only check for error keywords if no ToolResult pattern found
const toolContent = toolResultContent.toLowerCase();
isSuccess = !(toolContent.includes('failed') ||
toolContent.includes('error') ||
@ -489,19 +475,17 @@ export default function ThreadPage({
historicalToolPairs.push({
assistantCall: {
name: toolName,
content: assistantMsg.content, // Store original content
content: assistantMsg.content,
timestamp: assistantMsg.created_at,
},
toolResult: {
content: resultMessage.content, // Store original content
content: resultMessage.content,
isSuccess: isSuccess,
timestamp: resultMessage.created_at,
},
});
}
});
// Sort the tool calls chronologically by timestamp
historicalToolPairs.sort((a, b) => {
const timeA = new Date(a.assistantCall.timestamp || '').getTime();
const timeB = new Date(b.assistantCall.timestamp || '').getTime();
@ -509,8 +493,6 @@ export default function ThreadPage({
});
setToolCalls(historicalToolPairs);
// When loading is complete, prepare for playback
initialLoadCompleted.current = true;
}
} catch (err) {
@ -525,9 +507,7 @@ export default function ThreadPage({
if (isMounted) setIsLoading(false);
}
}
loadData();
return () => {
isMounted = false;
};
@ -546,7 +526,6 @@ export default function ThreadPage({
messagesEndRef.current?.scrollIntoView({ behavior });
};
// Handle tool clicks
const handleToolClick = useCallback(
(clickedAssistantMessageId: string | null, clickedToolName: string) => {
if (!clickedAssistantMessageId) {
@ -557,7 +536,6 @@ export default function ThreadPage({
return;
}
// Reset user closed state when explicitly clicking a tool
userClosedPanelRef.current = false;
console.log(
@ -567,21 +545,16 @@ export default function ThreadPage({
clickedToolName,
);
// Find the index of the tool call associated with the clicked assistant message
const toolIndex = toolCalls.findIndex((tc) => {
// Check if the assistant message ID matches the one stored in the tool result's metadata
if (!tc.toolResult?.content || tc.toolResult.content === 'STREAMING')
return false; // Skip streaming or incomplete calls
return false;
// Find the original assistant message based on the ID
const assistantMessage = messages.find(
(m) =>
m.message_id === clickedAssistantMessageId &&
m.type === 'assistant',
);
if (!assistantMessage) return false;
// Find the corresponding tool message using metadata
const toolMessage = messages.find((m) => {
if (m.type !== 'tool' || !m.metadata) return false;
try {
@ -593,9 +566,6 @@ export default function ThreadPage({
return false;
}
});
// Check if the current toolCall 'tc' corresponds to this assistant/tool message pair
// Compare the original content directly without parsing
return (
tc.assistantCall?.content === assistantMessage.content &&
tc.toolResult?.content === toolMessage?.content
@ -608,7 +578,7 @@ export default function ThreadPage({
);
setExternalNavIndex(toolIndex);
setCurrentToolIndex(toolIndex);
setIsSidePanelOpen(true); // Explicitly open the panel
setIsSidePanelOpen(true);
setTimeout(() => setExternalNavIndex(undefined), 100);
} else {
@ -630,7 +600,6 @@ export default function ThreadPage({
setFileViewerOpen(true);
}, []);
// Initialize PlaybackControls
const playbackController: PlaybackController = PlaybackControls({
messages,
isSidePanelOpen,
@ -641,7 +610,6 @@ export default function ThreadPage({
projectName: projectName || 'Shared Conversation',
});
// Extract the playback state and functions
const {
playbackState,
renderHeader,
@ -652,21 +620,17 @@ export default function ThreadPage({
skipToEnd,
} = playbackController;
// Connect playbackState to component state
useEffect(() => {
// Keep the isPlaying state in sync with playbackState
setIsPlaying(playbackState.isPlaying);
setCurrentMessageIndex(playbackState.currentMessageIndex);
}, [playbackState.isPlaying, playbackState.currentMessageIndex]);
// Auto-scroll when new messages appear during playback
useEffect(() => {
if (playbackState.visibleMessages.length > 0 && !userHasScrolled) {
scrollToBottom('smooth');
}
}, [playbackState.visibleMessages, userHasScrolled]);
// Scroll button visibility
useEffect(() => {
if (!latestMessageRef.current || playbackState.visibleMessages.length === 0)
return;
@ -683,7 +647,6 @@ export default function ThreadPage({
`[PAGE] 🔄 Page AgentStatus: ${agentStatus}, Hook Status: ${streamHookStatus}, Target RunID: ${agentRunId || 'none'}, Hook RunID: ${currentHookRunId || 'none'}`,
);
// If the stream hook reports completion/stopping but our UI hasn't updated
if (
(streamHookStatus === 'completed' ||
streamHookStatus === 'stopped' ||
@ -700,7 +663,6 @@ export default function ThreadPage({
}
}, [agentStatus, streamHookStatus, agentRunId, currentHookRunId]);
// Auto-scroll function for use throughout the component
const autoScrollToBottom = useCallback(
(behavior: ScrollBehavior = 'smooth') => {
if (!userHasScrolled && messagesEndRef.current) {
@ -710,31 +672,21 @@ export default function ThreadPage({
[userHasScrolled],
);
// Very direct approach to update the tool index during message playback
useEffect(() => {
if (!isPlaying || currentMessageIndex <= 0 || !messages.length) return;
// Check if current message is a tool message
const currentMsg = messages[currentMessageIndex - 1]; // Look at previous message that just played
const currentMsg = messages[currentMessageIndex - 1];
if (currentMsg?.type === 'tool' && currentMsg.metadata) {
try {
const metadata = safeJsonParse<ParsedMetadata>(currentMsg.metadata, {});
const assistantId = metadata.assistant_message_id;
if (assistantId) {
// Find the tool call that matches this assistant message
const toolIndex = toolCalls.findIndex((tc) => {
// Find the assistant message
const assistantMessage = messages.find(
(m) => m.message_id === assistantId && m.type === 'assistant'
);
if (!assistantMessage) return false;
// Check if this tool call matches
return tc.assistantCall?.content === assistantMessage.content;
});
if (toolIndex !== -1) {
console.log(
`Direct mapping: Setting tool index to ${toolIndex} for message ${assistantId}`,
@ -748,28 +700,19 @@ export default function ThreadPage({
}
}, [currentMessageIndex, isPlaying, messages, toolCalls]);
// Force an explicit update to the tool panel based on the current message index
useEffect(() => {
// Skip if not playing or no messages
if (!isPlaying || messages.length === 0 || currentMessageIndex <= 0) return;
// Get all messages up to the current index
const currentMessages = messages.slice(0, currentMessageIndex);
// Find the most recent tool message to determine which panel to show
for (let i = currentMessages.length - 1; i >= 0; i--) {
const msg = currentMessages[i];
if (msg.type === 'tool' && msg.metadata) {
try {
const metadata = safeJsonParse<ParsedMetadata>(msg.metadata, {});
const assistantId = metadata.assistant_message_id;
if (assistantId) {
console.log(
`Looking for tool panel for assistant message ${assistantId}`,
);
// Scan for matching tool call
for (let j = 0; j < toolCalls.length; j++) {
const content = toolCalls[j].assistantCall?.content || '';
if (content.includes(assistantId)) {
@ -788,14 +731,12 @@ export default function ThreadPage({
}
}, [currentMessageIndex, isPlaying, messages, toolCalls]);
// Loading skeleton UI
if (isLoading && !initialLoadCompleted.current) {
return (
<ThreadSkeleton isSidePanelOpen={isSidePanelOpen} showHeader={true} />
);
}
// Error state UI
if (error) {
return (
<div className="flex h-screen">