import { useState, useRef, useCallback } from 'react' import { ToolResult } from '@/lib/services/tool-executor' // Removed StreamingActivity interface - now using React state directly export interface UseChatStreamingReturn { // Streaming states isStreaming: boolean streamingMessage: string aiPlanning: string currentToolExecution: { name: string description: string args?: any } | null toolExecutionResults: Array<{ name: string result: string success: boolean }> // Control functions startStreaming: () => void stopStreaming: () => void resetStreaming: () => void updateStreamingContent: (content: string) => void updateAiPlanning: (planning: string) => void updateCurrentExecution: (execution: { name: string description: string args?: any } | null) => void addToolResult: (result: { name: string result: string success: boolean }) => void // Abort controller abortControllerRef: React.MutableRefObject currentStreamingContentRef: React.MutableRefObject // Utilities stopExecution: () => void processStreamChunk: (parsed: any, accumulatedToolResults: ToolResult[]) => void } export function useChatStreaming(): UseChatStreamingReturn { // Streaming states const [isStreaming, setIsStreaming] = useState(false) const [streamingMessage, setStreamingMessage] = useState('') const [aiPlanning, setAiPlanning] = useState('') const [currentToolExecution, setCurrentToolExecution] = useState<{ name: string description: string args?: any } | null>(null) const [toolExecutionResults, setToolExecutionResults] = useState>([]) // Refs for reliable streaming state management const abortControllerRef = useRef(null) const currentStreamingContentRef = useRef('') // Remove streamingActivityRef - it duplicates React state // Control functions const startStreaming = useCallback(() => { setIsStreaming(true) setStreamingMessage('') setAiPlanning('') setCurrentToolExecution(null) setToolExecutionResults([]) currentStreamingContentRef.current = '' // Create new abort controller abortControllerRef.current = new AbortController() }, []) const stopStreaming = useCallback(() => { setIsStreaming(false) }, []) const resetStreaming = useCallback(() => { setIsStreaming(false) setStreamingMessage('') setAiPlanning('') setCurrentToolExecution(null) setToolExecutionResults([]) currentStreamingContentRef.current = '' // Clear abort controller abortControllerRef.current = null }, []) const updateStreamingContent = useCallback((content: string) => { setStreamingMessage(content) currentStreamingContentRef.current = content }, []) const updateAiPlanning = useCallback((planning: string) => { console.log('Setting AI planning:', planning) setAiPlanning(planning) }, []) const updateCurrentExecution = useCallback((execution: { name: string description: string args?: any } | null) => { if (execution) { console.log('Setting current tool execution:', execution.name, execution.description) } setCurrentToolExecution(execution) }, []) const addToolResult = useCallback((result: { name: string result: string success: boolean }) => { console.log('Adding tool result:', result.name, result.result) setCurrentToolExecution(null) // Clear current execution setToolExecutionResults(prev => { const newResults = [...prev, result] console.log('Updated tool results:', newResults) return newResults }) }, []) const stopExecution = useCallback(() => { if (abortControllerRef.current) { console.log('Stopping AI execution, current streaming message:', streamingMessage) abortControllerRef.current.abort() abortControllerRef.current = null } }, [streamingMessage]) const processStreamChunk = useCallback((parsed: any, accumulatedToolResults: ToolResult[]) => { if (parsed.type) { switch (parsed.type) { case 'thinking': // Show thinking state to user updateAiPlanning(`🤔 ${parsed.content || 'Thinking...'}`) break case 'planning': updateAiPlanning(parsed.content || '') break case 'executing': const execution = { name: parsed.tool_name || 'Unknown Tool', description: parsed.content || 'Executing...', args: parsed.tool_args } updateCurrentExecution(execution) break case 'tool_result': const toolResult = { name: parsed.tool_name || 'Unknown Tool', result: parsed.content || 'Completed', success: !parsed.content?.includes('Error') } addToolResult(toolResult) // Store for legacy compatibility if (parsed.tool_result) { accumulatedToolResults.push({ role: 'tool', tool_call_id: parsed.tool_name || 'unknown', name: parsed.tool_name || 'unknown', content: JSON.stringify(parsed.tool_result) } as ToolResult) } break case 'content': // Add proper line breaks when transitioning from tool phases to content let newContent = currentStreamingContentRef.current || '' // If we're starting content after tools/planning, add line breaks if (newContent === '' && (aiPlanning || currentToolExecution || toolExecutionResults.length > 0)) { newContent += '\n\n' } newContent += (parsed.content || '') updateStreamingContent(newContent) break case 'error': console.error('Stream error:', parsed.content) updateCurrentExecution(null) updateAiPlanning('') break case 'status': if (parsed.status === 'completed') { console.log('Stream completed, final states:', { planning: aiPlanning, toolResults: toolExecutionResults, currentExecution: currentToolExecution }) } else if (parsed.content) { console.log('Status update:', parsed.content) } break } } }, [updateAiPlanning, updateCurrentExecution, addToolResult, updateStreamingContent, aiPlanning, toolExecutionResults, currentToolExecution]) return { // States isStreaming, streamingMessage, aiPlanning, currentToolExecution, toolExecutionResults, // Control functions startStreaming, stopStreaming, resetStreaming, updateStreamingContent, updateAiPlanning, updateCurrentExecution, addToolResult, // Refs abortControllerRef, currentStreamingContentRef, // Utilities stopExecution, processStreamChunk } }