"use client" import { useState, useRef, useEffect, useCallback } from "react" import { motion, AnimatePresence } from "framer-motion" import type { AnalysisResult } from "@/app/page" import { VoiceRecorder } from "./voice-recorder" import { apiGet, apiPost } from "@/lib/api" const WELCOME = "مرحباً! أنا مساعدك الطبي. كيف يمكنني مساعدتك اليوم؟" const faqQuestions = [ { id: 1, text: "ما سبب الشعور بالتعب المستمر؟", color: "primary" }, { id: 2, text: "كيف أرفع تحليلي؟", color: "secondary" }, { id: 3, text: "ماذا تنصحني لتحسين صحتي؟", color: "muted" }, ] interface ChatBotProps { analysisResult?: AnalysisResult | null sessionId?: string } type Message = { role: "assistant" | "user"; content: string } export function ChatBot({ analysisResult, sessionId = "anonymous" }: ChatBotProps) { const [isOpen, setIsOpen] = useState(false) const [messages, setMessages] = useState([{ role: "assistant", content: WELCOME }]) const [inputValue, setInputValue] = useState("") const [isTyping, setIsTyping] = useState(false) const [historyLoaded, setHistoryLoaded] = useState(false) const messagesEndRef = useRef(null) const lastAssistantMsg = [...messages].reverse().find((m) => m.role === "assistant")?.content ?? "" useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }) }, [messages, isTyping]) // ── Load chat history when panel first opens ────────────────── useEffect(() => { if (!isOpen || historyLoaded || sessionId === "anonymous") return setHistoryLoaded(true) apiGet(`/api/chat/history/${encodeURIComponent(sessionId)}`, { limit: 30 }) .then((r) => r.json()) .then(({ messages: saved }: { messages: Message[] }) => { if (saved && saved.length > 0) { setMessages([{ role: "assistant", content: WELCOME }, ...saved]) } }) .catch(() => {}) }, [isOpen, historyLoaded, sessionId]) // ── Persist exchange to backend ─────────────────────────────── const saveExchange = useCallback( (userMsg: string, assistantMsg: string) => { if (sessionId === "anonymous" || !assistantMsg.trim()) return apiPost("/api/chat/save", { session_id: sessionId, messages: [ { role: "user", content: userMsg }, { role: "assistant", content: assistantMsg }, ], }).catch(() => {}) }, [sessionId] ) const sendMessage = async (text: string) => { if (!text.trim() || isTyping) return const userMessage: Message = { role: "user", content: text } const updatedMessages = [...messages, userMessage] setMessages(updatedMessages) setInputValue("") setIsTyping(true) const history = updatedMessages.slice(0, -1).map((m) => ({ role: m.role, content: m.content, })) let assistantReply = "" try { const res = await apiPost("/api/chat", { query: text, history, analysis_context: analysisResult ? JSON.stringify(analysisResult) : "", }) if (!res.ok || !res.body) throw new Error("no stream") setMessages((prev) => [...prev, { role: "assistant", content: "" }]) setIsTyping(false) const reader = res.body.getReader() const decoder = new TextDecoder() while (true) { const { done, value } = await reader.read() if (done) break const chunk = decoder.decode(value, { stream: true }) assistantReply += chunk setMessages((prev) => { const updated = [...prev] updated[updated.length - 1] = { role: "assistant", content: updated[updated.length - 1].content + chunk, } return updated }) } saveExchange(text, assistantReply) } catch { setIsTyping(false) const errMsg = "تعذّر الاتصال بالخادم. تأكد من تشغيل الباكند." setMessages((prev) => [...prev, { role: "assistant", content: errMsg }]) } } return ( <> {/* ── Trigger button ── */} setIsOpen(!isOpen)} className="fixed bottom-6 right-6 z-50 w-14 h-14 rounded-full shadow-glow-primary flex items-center justify-center overflow-hidden gradient-primary" aria-label="فتح المساعد الذكي" > {isOpen ? ( ) : ( )} {!isOpen && ( )} {/* ── Chat panel ── */} {isOpen && ( {/* Header */}
المساعد الذكي

مساعد طبي ذكي

{/* Messages */}
{messages.map((msg, i) => (

{msg.content}

))}
{isTyping && (
{[0, 0.2, 0.4].map((delay, i) => ( ))}
)}
{/* FAQ quick questions */}
{faqQuestions.map((faq, i) => ( sendMessage(faq.text)} disabled={isTyping} className={`px-3 py-1.5 rounded-full text-[11px] font-medium text-foreground transition-all duration-200 disabled:opacity-50 border ${ faq.color === "primary" ? "bg-primary/10 border-primary/20 hover:bg-primary/15" : faq.color === "secondary" ? "bg-secondary/30 border-border/50 hover:bg-secondary/40" : "bg-muted border-border/50 hover:bg-muted/70" }`} > {faq.text} ))}
{/* Input */}
sendMessage(text)} ttsText={lastAssistantMsg} disabled={isTyping} /> setInputValue(e.target.value)} onKeyDown={(e) => e.key === "Enter" && sendMessage(inputValue)} placeholder="اكتب أو انقر الميكروفون..." disabled={isTyping} className="flex-1 px-3 py-2 bg-transparent text-[13px] text-foreground placeholder:text-muted-foreground focus:outline-none disabled:opacity-50" /> sendMessage(inputValue)} disabled={isTyping || !inputValue.trim()} className="w-8 h-8 rounded-full flex items-center justify-center shrink-0 disabled:opacity-50 gradient-primary shadow-glow-primary" aria-label="إرسال" >
)} ) }