fix: refactor playback controls and consolidate XML tag constants

- Remove commented-out playback logic from share page
- Simplify tool call handling in PlaybackControls by removing toolPlaybackIndex state
- Fix tool navigation initialization and synchronization issues
- Consolidate HIDE_STREAMING_XML_TAGS constant from multiple files into utils.ts
- Add 'create-tasks' to hidden streaming XML tags list
- Improve tool call side panel index synchronization
- Add cursor pointer styling to slider components for better UX
- Remove unused toolName tracking in chunk processing
- Fix tool index initialization to start at 0 instead of -1

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
yeyan1996 2025-08-09 12:59:55 -07:00
parent a1ef96b245
commit 176182e2e9
6 changed files with 63 additions and 138 deletions

View File

@ -288,33 +288,6 @@ export default function ThreadPage({
[], [],
); );
// useEffect(() => {
// if (!isPlaying || messages.length === 0) return;
// let playbackTimeout: NodeJS.Timeout;
// const playbackNextMessage = async () => {
// if (currentMessageIndex >= messages.length) {
// setIsPlaying(false);
// return;
// }
// const currentMessage = messages[currentMessageIndex];
// console.log(
// `Playing message ${currentMessageIndex}:`,
// currentMessage.type,
// currentMessage.message_id,
// );
// setCurrentMessageIndex((prevIndex) => prevIndex + 1);
// };
// playbackTimeout = setTimeout(playbackNextMessage, 500);
// return () => {
// clearTimeout(playbackTimeout);
// };
// }, [isPlaying, currentMessageIndex, messages]);
const { const {
status: streamHookStatus, status: streamHookStatus,
toolCall: streamingToolCall, toolCall: streamingToolCall,

View File

@ -11,42 +11,15 @@ import {
import { UnifiedMessage } from '@/components/thread/types'; import { UnifiedMessage } from '@/components/thread/types';
import { safeJsonParse } from '@/components/thread/utils'; import { safeJsonParse } from '@/components/thread/utils';
import Link from 'next/link'; import Link from 'next/link';
import { parseXmlToolCalls } from '../tool-views/xml-parser';
// Define the set of tags whose raw XML should be hidden during streaming import { HIDE_STREAMING_XML_TAGS } from '@/components/thread/utils';
const HIDE_STREAMING_XML_TAGS = new Set([
'execute-command',
'create-file',
'delete-file',
'full-file-rewrite',
'edit-file',
'str-replace',
'browser-click-element',
'browser-close-tab',
'browser-drag-drop',
'browser-get-dropdown-options',
'browser-go-back',
'browser-input-text',
'browser-navigate-to',
'browser-scroll-down',
'browser-scroll-to-text',
'browser-scroll-up',
'browser-select-dropdown-option',
'browser-send-keys',
'browser-switch-tab',
'browser-wait',
'deploy',
'ask',
'complete',
'crawl-webpage',
'web-search',
]);
export interface PlaybackControlsProps { export interface PlaybackControlsProps {
messages: UnifiedMessage[]; messages: UnifiedMessage[];
isSidePanelOpen: boolean; isSidePanelOpen: boolean;
onToggleSidePanel: () => void; onToggleSidePanel: () => void;
toolCalls: any[]; toolCalls: any[];
setCurrentToolIndex: (index: number) => void; setCurrentToolIndex: React.Dispatch<React.SetStateAction<number>>;
onFileViewerOpen: () => void; onFileViewerOpen: () => void;
projectName?: string; projectName?: string;
} }
@ -58,7 +31,6 @@ export interface PlaybackState {
streamingText: string; streamingText: string;
isStreamingText: boolean; isStreamingText: boolean;
currentToolCall: any | null; currentToolCall: any | null;
toolPlaybackIndex: number;
} }
export interface PlaybackController { export interface PlaybackController {
@ -88,7 +60,6 @@ export const PlaybackControls = ({
streamingText: '', streamingText: '',
isStreamingText: false, isStreamingText: false,
currentToolCall: null, currentToolCall: null,
toolPlaybackIndex: -1,
}); });
// Extract state variables for easier access // Extract state variables for easier access
@ -99,10 +70,10 @@ export const PlaybackControls = ({
streamingText, streamingText,
isStreamingText, isStreamingText,
currentToolCall, currentToolCall,
toolPlaybackIndex,
} = playbackState; } = playbackState;
const playbackTimeout = useRef<NodeJS.Timeout | null>(null); const playbackTimeout = useRef<NodeJS.Timeout | null>(null);
const [isToolInitialized, setIsToolInitialized] = useState(false);
// Helper function to update playback state // Helper function to update playback state
const updatePlaybackState = useCallback((updates: Partial<PlaybackState>) => { const updatePlaybackState = useCallback((updates: Partial<PlaybackState>) => {
@ -129,9 +100,9 @@ export const PlaybackControls = ({
streamingText: '', streamingText: '',
isStreamingText: false, isStreamingText: false,
currentToolCall: null, currentToolCall: null,
toolPlaybackIndex: -1,
}); });
setCurrentToolIndex(-1); setCurrentToolIndex(0);
setIsToolInitialized(false);
if (playbackTimeout.current) { if (playbackTimeout.current) {
clearTimeout(playbackTimeout.current); clearTimeout(playbackTimeout.current);
@ -192,7 +163,6 @@ export const PlaybackControls = ({
streamingText: '', streamingText: '',
isStreamingText: false, isStreamingText: false,
currentToolCall: null, currentToolCall: null,
toolPlaybackIndex: toolCalls.length - 1,
}); });
if (toolCalls.length > 0) { if (toolCalls.length > 0) {
@ -242,11 +212,9 @@ export const PlaybackControls = ({
} }
// Add the tool call // Add the tool call
const toolName = match[1] || match[2];
chunks.push({ chunks.push({
text: match[0], text: match[0],
isTool: true, isTool: true,
toolName,
}); });
lastIndex = toolCallRegex.lastIndex; lastIndex = toolCallRegex.lastIndex;
@ -306,28 +274,12 @@ export const PlaybackControls = ({
// If this is a tool call chunk and we're at the start of it // If this is a tool call chunk and we're at the start of it
if (currentChunk.isTool && currentIndex === 0) { if (currentChunk.isTool && currentIndex === 0) {
// For tool calls, check if they should be hidden during streaming // For tool calls, check if they should be hidden during streaming
if ( if (isToolInitialized) {
currentChunk.toolName && setCurrentToolIndex((prev) => prev + 1);
HIDE_STREAMING_XML_TAGS.has(currentChunk.toolName) } else {
) { setIsToolInitialized(true);
// Instead of showing the XML, create a tool call object
const toolCall = {
name: currentChunk.toolName,
arguments: currentChunk.text,
xml_tag_name: currentChunk.toolName,
};
updatePlaybackState({
currentToolCall: toolCall,
toolPlaybackIndex: toolPlaybackIndex + 1,
});
if (!isSidePanelOpen) {
onToggleSidePanel();
} }
setCurrentToolIndex(toolPlaybackIndex + 1);
// Pause streaming briefly while showing the tool // Pause streaming briefly while showing the tool
isPaused = true; isPaused = true;
setTimeout(() => { setTimeout(() => {
@ -340,7 +292,6 @@ export const PlaybackControls = ({
return; return;
} }
}
// Handle normal text streaming for non-tool chunks or visible tool chunks // Handle normal text streaming for non-tool chunks or visible tool chunks
if (currentIndex < currentChunk.text.length) { if (currentIndex < currentChunk.text.length) {
@ -387,7 +338,6 @@ export const PlaybackControls = ({
isPlaying, isPlaying,
messages, messages,
currentMessageIndex, currentMessageIndex,
toolPlaybackIndex,
setCurrentToolIndex, setCurrentToolIndex,
isSidePanelOpen, isSidePanelOpen,
onToggleSidePanel, onToggleSidePanel,

View File

@ -16,40 +16,8 @@ import { AgentLoader } from './loader';
import { parseXmlToolCalls, isNewXmlFormat } from '@/components/thread/tool-views/xml-parser'; import { parseXmlToolCalls, isNewXmlFormat } from '@/components/thread/tool-views/xml-parser';
import { ShowToolStream } from './ShowToolStream'; import { ShowToolStream } from './ShowToolStream';
import { ComposioUrlDetector } from './composio-url-detector'; import { ComposioUrlDetector } from './composio-url-detector';
import { HIDE_STREAMING_XML_TAGS } from '@/components/thread/utils';
const HIDE_STREAMING_XML_TAGS = new Set([
'execute-command',
'create-file',
'delete-file',
'full-file-rewrite',
'edit-file',
'str-replace',
'browser-click-element',
'browser-close-tab',
'browser-drag-drop',
'browser-get-dropdown-options',
'browser-go-back',
'browser-input-text',
'browser-navigate-to',
'browser-scroll-down',
'browser-scroll-to-text',
'browser-scroll-up',
'browser-select-dropdown-option',
'browser-send-keys',
'browser-switch-tab',
'browser-wait',
'deploy',
'ask',
'complete',
'crawl-webpage',
'web-search',
'see-image',
'execute_data_provider_call',
'execute_data_provider_endpoint',
'execute-data-provider-call',
'execute-data-provider-endpoint',
]);
// Helper function to render attachments (keeping original implementation for now) // Helper function to render attachments (keeping original implementation for now)
export function renderAttachments(attachments: string[], fileViewerHandler?: (filePath?: string, filePathList?: string[]) => void, sandboxId?: string, project?: Project) { export function renderAttachments(attachments: string[], fileViewerHandler?: (filePath?: string, filePathList?: string[]) => void, sandboxId?: string, project?: Project) {

View File

@ -151,10 +151,9 @@ export function ToolCallSidePanel({
}, [toolCalls, navigationMode, toolCallSnapshots.length, isInitialized]); }, [toolCalls, navigationMode, toolCallSnapshots.length, isInitialized]);
React.useEffect(() => { React.useEffect(() => {
if (isOpen && !isInitialized && toolCallSnapshots.length > 0) { // This is used to sync the internal index to the current index
setInternalIndex(Math.min(currentIndex, toolCallSnapshots.length - 1)); setInternalIndex(Math.min(currentIndex, toolCallSnapshots.length - 1));
} }, [currentIndex, toolCallSnapshots.length]);
}, [isOpen, currentIndex, isInitialized, toolCallSnapshots.length]);
const safeInternalIndex = Math.min(internalIndex, Math.max(0, toolCallSnapshots.length - 1)); const safeInternalIndex = Math.min(internalIndex, Math.max(0, toolCallSnapshots.length - 1));
const currentSnapshot = toolCallSnapshots[safeInternalIndex]; const currentSnapshot = toolCallSnapshots[safeInternalIndex];

View File

@ -494,3 +494,38 @@ export function getUserFriendlyToolName(toolName: string): string {
} }
return TOOL_DISPLAY_NAMES.get(toolName) || toolName; return TOOL_DISPLAY_NAMES.get(toolName) || toolName;
} }
export const HIDE_STREAMING_XML_TAGS = new Set([
'create-tasks',
'execute-command',
'create-file',
'delete-file',
'full-file-rewrite',
'edit-file',
'str-replace',
'browser-click-element',
'browser-close-tab',
'browser-drag-drop',
'browser-get-dropdown-options',
'browser-go-back',
'browser-input-text',
'browser-navigate-to',
'browser-scroll-down',
'browser-scroll-to-text',
'browser-scroll-up',
'browser-select-dropdown-option',
'browser-send-keys',
'browser-switch-tab',
'browser-wait',
'deploy',
'ask',
'complete',
'crawl-webpage',
'web-search',
'see-image',
'execute_data_provider_call',
'execute_data_provider_endpoint',
'execute-data-provider-call',
'execute-data-provider-endpoint',
]);

View File

@ -45,7 +45,7 @@ function Slider({
<SliderPrimitive.Range <SliderPrimitive.Range
data-slot="slider-range" data-slot="slider-range"
className={cn( className={cn(
'bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full', 'cursor-pointer bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full',
)} )}
/> />
</SliderPrimitive.Track> </SliderPrimitive.Track>
@ -53,7 +53,7 @@ function Slider({
<SliderPrimitive.Thumb <SliderPrimitive.Thumb
data-slot="slider-thumb" data-slot="slider-thumb"
key={index} key={index}
className="border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50" className="cursor-pointer border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
/> />
))} ))}
</SliderPrimitive.Root> </SliderPrimitive.Root>