import { useState, useRef, useEffect, useCallback } from 'react' import { X, Send, Sparkles, Loader2 } from 'lucide-react' import { MarkdownContent } from '@/components/ui/MarkdownContent' import { copilotApi } from '@/api/copilot' import { SuggestedFlowCard } from '@/components/assistant/SuggestedFlowCard' import type { CopilotMessage, SuggestedFlow } from '@/types/copilot' interface CopilotPanelProps { isOpen: boolean onClose: () => void treeId: string sessionId?: string currentNodeId?: string } export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId }: CopilotPanelProps) { const [conversationId, setConversationId] = useState(null) const [messages, setMessages] = useState([]) const [suggestedFlows, setSuggestedFlows] = useState([]) const [input, setInput] = useState('') const [loading, setLoading] = useState(false) const [initializing, setInitializing] = useState(false) const messagesEndRef = useRef(null) const inputRef = useRef(null) const startConversation = useCallback(async () => { setInitializing(true) try { const response = await copilotApi.startConversation({ tree_id: treeId, session_id: sessionId, current_node_id: currentNodeId, }) setConversationId(response.conversation_id) setMessages([{ role: 'assistant', content: response.greeting }]) } catch { setMessages([{ role: 'assistant', content: 'Failed to start copilot. Please try again.' }]) } finally { setInitializing(false) } }, [treeId, sessionId, currentNodeId]) // Start conversation when panel opens or treeId changes useEffect(() => { if (isOpen && !conversationId && !initializing) { startConversation() } }, [isOpen, treeId, startConversation, conversationId, initializing]) // Auto-scroll to bottom useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) }, [messages]) const handleSend = async () => { if (!input.trim() || !conversationId || loading) return const userMessage = input.trim() setInput('') setMessages(prev => [...prev, { role: 'user', content: userMessage }]) setLoading(true) try { const response = await copilotApi.sendMessage(conversationId, { message: userMessage, current_node_id: currentNodeId, }) setMessages(prev => [...prev, { role: 'assistant', content: response.content }]) if (response.suggested_flows.length > 0) { setSuggestedFlows(response.suggested_flows) } } catch { setMessages(prev => [...prev, { role: 'assistant', content: 'Sorry, something went wrong. Please try again.' }]) } finally { setLoading(false) requestAnimationFrame(() => inputRef.current?.focus()) } } const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() handleSend() } } if (!isOpen) return null return (
{/* Header */}
AI Copilot
{/* Messages */}
{messages.map((msg, i) => (
))} {loading && (
)} {/* Suggested flows */} {suggestedFlows.length > 0 && (
Related Flows {suggestedFlows.map(flow => ( ))}
)}
{/* Input */}