import React, { useState, useRef, useEffect } from 'react'; import { Send, Bot, User, Sparkles, Loader2, X, Maximize2, Minimize2, AlertCircle, Wrench, ChevronDown, ChevronUp, Trash2 } from 'lucide-react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { useStudy } from '../context/StudyContext'; interface Message { id: string; role: 'user' | 'assistant'; content: string; timestamp: Date; toolCalls?: Array<{ tool: string; input: Record; result_preview: string; }>; } interface ClaudeChatProps { isExpanded?: boolean; onToggleExpand?: () => void; onClose?: () => void; } export const ClaudeChat: React.FC = ({ isExpanded = false, onToggleExpand, onClose }) => { const { selectedStudy } = useStudy(); const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [apiAvailable, setApiAvailable] = useState(null); const [suggestions, setSuggestions] = useState([]); const [expandedTools, setExpandedTools] = useState>(new Set()); const messagesEndRef = useRef(null); const inputRef = useRef(null); // Check API status on mount useEffect(() => { checkApiStatus(); }, []); // Load suggestions when study changes useEffect(() => { loadSuggestions(); }, [selectedStudy]); // Scroll to bottom when messages change useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); const checkApiStatus = async () => { try { const response = await fetch('/api/claude/status'); const data = await response.json(); setApiAvailable(data.available); if (!data.available) { setError(data.message); } } catch (err) { setApiAvailable(false); setError('Could not connect to Claude API'); } }; const loadSuggestions = async () => { try { const url = selectedStudy ? `/api/claude/suggestions?study_id=${selectedStudy.id}` : '/api/claude/suggestions'; const response = await fetch(url); const data = await response.json(); setSuggestions(data.suggestions || []); } catch (err) { setSuggestions([]); } }; const sendMessage = async (messageText?: string) => { const text = messageText || input.trim(); if (!text || isLoading) return; setError(null); const userMessage: Message = { id: `user-${Date.now()}`, role: 'user', content: text, timestamp: new Date() }; setMessages(prev => [...prev, userMessage]); setInput(''); setIsLoading(true); try { const response = await fetch('/api/claude/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: text, study_id: selectedStudy?.id, conversation_history: messages.map(m => ({ role: m.role, content: m.content })) }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || 'Failed to get response'); } const data = await response.json(); const assistantMessage: Message = { id: `assistant-${Date.now()}`, role: 'assistant', content: data.response, timestamp: new Date(), toolCalls: data.tool_calls }; setMessages(prev => [...prev, assistantMessage]); } catch (err: any) { setError(err.message || 'Failed to send message'); } finally { setIsLoading(false); inputRef.current?.focus(); } }; const clearConversation = () => { setMessages([]); setError(null); }; const toggleToolExpand = (toolId: string) => { setExpandedTools(prev => { const newSet = new Set(prev); if (newSet.has(toolId)) { newSet.delete(toolId); } else { newSet.add(toolId); } return newSet; }); }; // Render tool call indicator const renderToolCalls = (toolCalls: Message['toolCalls'], messageId: string) => { if (!toolCalls || toolCalls.length === 0) return null; return (
{toolCalls.map((tool, index) => { const toolId = `${messageId}-tool-${index}`; const isExpanded = expandedTools.has(toolId); return (
{isExpanded && (
Input:
                    {JSON.stringify(tool.input, null, 2)}
                  
Result preview:
                    {tool.result_preview}
                  
)}
); })}
); }; return (
{/* Header */}
Claude Code {selectedStudy && ( {selectedStudy.id} )}
{messages.length > 0 && ( )} {onToggleExpand && ( )} {onClose && ( )}
{/* API Status Warning */} {apiAvailable === false && (
{error || 'Claude API not available'}
)} {/* Messages */}
{messages.length === 0 ? (

Ask me anything about your optimization

I can analyze results, explain concepts, and help you improve your designs.

{/* Suggestions */} {suggestions.length > 0 && (
{suggestions.slice(0, 6).map((suggestion, i) => ( ))}
)}
) : ( messages.map((msg) => (
{msg.role === 'assistant' && (
)}
{msg.role === 'assistant' ? ( <>

{children}

, ul: ({ children }) =>
    {children}
, ol: ({ children }) =>
    {children}
, li: ({ children }) =>
  • {children}
  • , code: ({ inline, children }: any) => inline ? ( {children} ) : (
                                    {children}
                                  
    ), table: ({ children }) => (
    {children}
    ), th: ({ children }) => ( {children} ), td: ({ children }) => ( {children} ), strong: ({ children }) => {children}, h1: ({ children }) =>

    {children}

    , h2: ({ children }) =>

    {children}

    , h3: ({ children }) =>

    {children}

    , }} > {msg.content}
    {renderToolCalls(msg.toolCalls, msg.id)} ) : (

    {msg.content}

    )}
    {msg.role === 'user' && (
    )}
    )) )} {/* Loading indicator */} {isLoading && (
    Thinking
    )} {/* Error message */} {error && !isLoading && (
    {error}
    )}
    {/* Input */}
    setInput(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }} placeholder={apiAvailable === false ? 'API not available...' : 'Ask about your optimization...'} disabled={isLoading || apiAvailable === false} className="flex-1 px-4 py-2.5 bg-dark-700 border border-dark-600 rounded-lg text-white placeholder-dark-400 focus:outline-none focus:border-primary-500 disabled:opacity-50 disabled:cursor-not-allowed" />

    Claude can query your study data, analyze results, and help improve your optimization.

    ); }; export default ClaudeChat;