import { useEffect, useRef, useState } from 'react'; import { motion } from 'motion/react'; import { BookOpen, MessageSquarePlus, RotateCw, Send, ThumbsDown, ThumbsUp } from 'lucide-react'; import { api } from '@/api'; import { useAppData } from '@/state/AppData'; import { askStore, useAskState, type ChatMessage } from '@/state/askStore'; import { SeyarMascot } from '@/mascots/SeyarMascot'; import { useToast } from '@/components/Toast'; import { Screen, ScreenHeader } from '@/components/ui'; import { Diagram } from '@/diagram'; import { MathText } from '@/lib/MathText'; import { friendlyError } from '@/lib/errors'; import { toFa } from '@/lib/format'; const STARTERS = ['این فصل درباره‌ی چیه؟', 'یه مثالِ ساده برام حل کن', 'چطور برای امتحان تمرین کنم؟']; /** «از پی بپرس»: دستیارِ ریاضی که از روی کتابِ پایه‌ی کاربر جواب می‌دهد. */ export function AskScreen({ lessonId, questionId, prompt }: { lessonId?: number; questionId?: string; prompt?: string }) { const { profile } = useAppData(); const chat = useAskState(); const [input, setInput] = useState(''); const [busy, setBusy] = useState(false); const [quota, setQuota] = useState(null); const listEnd = useRef(null); const autoSent = useRef(false); // درسِ تازه یعنی گفتگوی تازه؛ وگرنه اگر گفتگوی قبلی هست، تاریخچه‌اش را بیاور. useEffect(() => { if (lessonId !== undefined && lessonId !== chat.lessonId) { askStore.reset(lessonId); return; } if (chat.historyLoaded) return; if (!chat.conversationId) { askStore.markHistoryLoaded(); return; } api.ai .history(chat.conversationId) .then((r) => askStore.setHistory( r.messages .filter((m) => m.role === 'user' || m.role === 'assistant') .map((m) => ({ role: m.role as ChatMessage['role'], text: m.text, messageId: m.messageId, diagram: m.diagram ?? null })), ), ) .catch(() => askStore.reset(null)); // فقط بارِ اولِ باز شدنِ صفحه؛ عمداً به chat وابسته نیست. }, []); useEffect(() => { if (prompt && !autoSent.current) { autoSent.current = true; void send(prompt, questionId); } }, [prompt, questionId]); // بعد از رندر اسکرول کن، تا پیشنهادهای ادامه که آخر از همه می‌آیند هم دیده شوند. useEffect(() => { const frame = requestAnimationFrame(() => listEnd.current?.scrollIntoView({ block: 'end' })); return () => cancelAnimationFrame(frame); }, [chat.messages]); async function send(text: string, forQuestionId?: string) { const question = text.trim(); if (!question || busy) return; setBusy(true); setInput(''); askStore.add({ role: 'user', text: question }); const replyId = askStore.add({ role: 'assistant', text: '' }); let answer = ''; try { const done = await api.ai.ask( { userQuestion: question, conversationId: askStore.get().conversationId ?? undefined, lessonId: askStore.get().lessonId ?? undefined, questionId: forQuestionId, }, (delta) => { answer += delta; askStore.update(replyId, { text: answer }); }, ); askStore.update(replyId, { text: answer, messageId: done.messageId, usedChunks: done.usedChunks, followUps: done.suggestedFollowUps, diagram: done.diagram ?? null, }); askStore.setConversation(done.conversationId); setQuota(done.remainingQuota); } catch (err) { askStore.update(replyId, { text: friendlyError(err, 'جوابی نرسید'), failedQuestion: question, }); } finally { setBusy(false); } } function retry(message: ChatMessage) { if (!message.failedQuestion) return; const all = askStore.get().messages; const index = all.findIndex((m) => m.localId === message.localId); const question = all[index - 1]; askStore.remove(message.localId); if (question?.role === 'user') askStore.remove(question.localId); void send(message.failedQuestion); } const empty = chat.messages.length === 0; return ( {!empty && ( )}
سلام {profile.name || 'دوستِ من'}! من پی‌ام 🙂 هر سوالی از ریاضیِ کتابت داری بپرس؛ قدم‌به‌قدم با هم حلش می‌کنیم. {empty && chat.historyLoaded && (
{STARTERS.map((s) => ( ))}
)} {chat.messages.map((m, i) => ( void send(q)} /> ))}
{ e.preventDefault(); void send(input); }} >