import type { ReactNode } from 'react'; import { useState, useRef, useEffect, useCallback } from 'react'; import { useThemeTokens } from '@/providers/theme-token-provider'; import { useChat } from '@ai-sdk/react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faPaperPlaneTop, faSparkles, faUserHeadset } from '@fortawesome/pro-duotone-svg-icons'; import { AiSummaryCard, type CallerSummary } from './ai-summary-card'; import { AiSuggestions, type Suggestion } from './ai-suggestions'; const API_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:4100'; type CallerContext = { type?: string; callerPhone?: string; leadId?: string; leadName?: string; }; interface AiChatPanelProps { callerContext?: CallerContext; callerSummary?: CallerSummary | null; onChatStart?: () => void; } const SUPERVISOR_QUICK_ACTIONS = [ { label: 'Agent performance', prompt: 'Show me agent performance this week.' }, { label: 'Call summary', prompt: 'Summarize call activity this week.' }, { label: 'Campaign stats', prompt: 'How are the campaigns performing?' }, { label: 'Who needs attention?', prompt: 'Which agents are underperforming or need attention?' }, ]; const SUPERVISOR_INTRO = 'Ask me about agent performance, call trends, or campaign stats.'; const parseAiResponse = (content: string): { message: string; suggestions: Suggestion[] } => { const trimmed = content.trim(); try { const parsed = JSON.parse(trimmed); if (parsed.message) { return { message: parsed.message, suggestions: Array.isArray(parsed.suggestions) ? parsed.suggestions : [], }; } } catch {} return { message: content, suggestions: [] }; }; export const AiChatPanel = ({ callerContext, callerSummary, onChatStart }: AiChatPanelProps) => { const { tokens } = useThemeTokens(); const isSupervisor = callerContext?.type === 'supervisor'; const quickActions = isSupervisor ? SUPERVISOR_QUICK_ACTIONS : tokens.ai.quickActions; const introText = isSupervisor ? SUPERVISOR_INTRO : 'Ask me about doctors, clinics, packages, or patient info.'; const messagesEndRef = useRef(null); const chatStartedRef = useRef(false); const [suggestions, setSuggestions] = useState([]); const token = localStorage.getItem('helix_access_token') ?? ''; const { messages, input, handleSubmit, handleInputChange, isLoading, append, setMessages } = useChat({ api: `${API_URL}/api/ai/stream`, streamProtocol: 'text', headers: { 'Authorization': `Bearer ${token}` }, body: { context: callerContext }, }); useEffect(() => { if (isLoading) return; const lastAssistant = [...messages].reverse().find(m => m.role === 'assistant'); if (lastAssistant) { const parsed = parseAiResponse(lastAssistant.content); if (parsed.suggestions.length > 0) { setSuggestions(parsed.suggestions); } } }, [messages, isLoading]); useEffect(() => { const el = messagesEndRef.current; if (el?.parentElement) { el.parentElement.scrollTop = el.parentElement.scrollHeight; } if (messages.length > 0 && !chatStartedRef.current) { chatStartedRef.current = true; onChatStart?.(); } }, [messages, onChatStart]); const autoFiredForLeadRef = useRef(null); useEffect(() => { const leadId = callerContext?.leadId ?? null; if (!leadId) { if (autoFiredForLeadRef.current !== null) { autoFiredForLeadRef.current = null; setMessages([]); setSuggestions([]); chatStartedRef.current = false; } return; } if (autoFiredForLeadRef.current === leadId) return; autoFiredForLeadRef.current = leadId; setMessages([]); setSuggestions([]); chatStartedRef.current = false; const name = callerContext?.leadName ?? 'this caller'; append({ role: 'user', content: `Give me a quick summary of ${name} and suggest relevant actions for this call.`, }); }, [callerContext?.leadId, callerContext?.leadName, append, setMessages]); const handleQuickAction = (prompt: string) => { append({ role: 'user', content: prompt }); }; const handleTellMeMore = useCallback((suggestion: Suggestion) => { append({ role: 'user', content: `Tell me more about "${suggestion.title}" — give me a detailed script and any relevant details.`, }); }, [append]); // Filter out the currently-streaming assistant message (shows raw JSON). // Only display completed assistant messages with parsed content. const displayMessages = messages .filter((msg, i) => { if (msg.role === 'assistant' && isLoading && i === messages.length - 1) return false; return true; }) .map(msg => { if (msg.role === 'assistant') { const parsed = parseAiResponse(msg.content); return { ...msg, content: parsed.message }; } return msg; }); return (
{!isSupervisor && }
{displayMessages.length === 0 && (

{introText}

{quickActions.map((action) => ( ))}
)} {displayMessages.map((msg) => (
{msg.role === 'assistant' && (
AI
)}
))} {isLoading && (
)}
{!isSupervisor && suggestions.length > 0 && ( )}
); }; const parseLine = (text: string): ReactNode[] => { const parts: ReactNode[] = []; const boldPattern = /\*\*(.+?)\*\*/g; let lastIndex = 0; let match: RegExpExecArray | null; while ((match = boldPattern.exec(text)) !== null) { if (match.index > lastIndex) parts.push(text.slice(lastIndex, match.index)); parts.push({match[1]}); lastIndex = boldPattern.lastIndex; } if (lastIndex < text.length) parts.push(text.slice(lastIndex)); return parts.length > 0 ? parts : [text]; }; const MessageContent = ({ content }: { content: string }) => { if (!content) return null; const lines = content.split('\n'); return (
{lines.map((line, i) => { if (line.trim().length === 0) return
; if (line.trimStart().startsWith('- ')) { return (
{parseLine(line.replace(/^\s*-\s*/, ''))}
); } return

{parseLine(line)}

; })}
); };