import type { ReactNode } from 'react'; import { useRef, useEffect } 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'; 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; onChatStart?: () => void; } export const AiChatPanel = ({ callerContext, onChatStart }: AiChatPanelProps) => { const { tokens } = useThemeTokens(); const quickActions = tokens.ai.quickActions; const messagesEndRef = useRef(null); const chatStartedRef = useRef(false); const token = localStorage.getItem('helix_access_token') ?? ''; const { messages, input, handleSubmit, handleInputChange, isLoading, append } = useChat({ api: `${API_URL}/api/ai/stream`, streamProtocol: 'text', headers: { 'Authorization': `Bearer ${token}`, }, body: { context: callerContext, }, }); 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 handleQuickAction = (prompt: string) => { append({ role: 'user', content: prompt }); }; return (
{messages.length === 0 && (

Ask me about doctors, clinics, packages, or patient info.

{quickActions.map((action) => ( ))}
)} {messages.map((msg) => (
{msg.role === 'assistant' && (
AI
)}
))} {isLoading && (
)}
); }; // Tool result cards will be added in Phase 2 when SDK versions are aligned for data stream protocol 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)}

; })}
); };