Spaces:
Sleeping
Sleeping
| 'use client'; | |
| import { useState } from 'react'; | |
| import { X, Send, Loader2, MessageCircle } from 'lucide-react'; | |
| import clsx from 'clsx'; | |
| import ReactMarkdown from 'react-markdown'; | |
| import remarkGfm from 'remark-gfm'; | |
| interface AskQuestionModalProps { | |
| isOpen: boolean; | |
| onClose: () => void; | |
| context: string; // The original analyzed content | |
| } | |
| interface Message { | |
| role: 'user' | 'assistant'; | |
| content: string; | |
| } | |
| export default function AskQuestionModal({ isOpen, onClose, context }: AskQuestionModalProps) { | |
| const [messages, setMessages] = useState<Message[]>([]); | |
| const [input, setInput] = useState(''); | |
| const [loading, setLoading] = useState(false); | |
| const handleSubmit = async (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| if (!input.trim() || loading) return; | |
| const userMessage = input.trim(); | |
| setInput(''); | |
| setMessages(prev => [...prev, { role: 'user', content: userMessage }]); | |
| setLoading(true); | |
| try { | |
| const formData = new FormData(); | |
| formData.append('text', ` | |
| Based on this content I just learned: | |
| --- | |
| ${context.substring(0, 2000)} | |
| --- | |
| My question: ${userMessage} | |
| Please answer my question in a helpful, educational way. Use simple language and examples if needed. | |
| `); | |
| const res = await fetch('/api/analyze', { | |
| method: 'POST', | |
| body: formData, | |
| }); | |
| if (!res.ok) throw new Error('Failed to get response'); | |
| const data = await res.json(); | |
| const cleanResponse = data.result?.replace(/\[\[TOPICS?:.*?\]\]/gi, '').trim() || 'Sorry, I could not generate a response.'; | |
| setMessages(prev => [...prev, { role: 'assistant', content: cleanResponse }]); | |
| } catch (error) { | |
| setMessages(prev => [...prev, { role: 'assistant', content: 'Sorry, something went wrong. Please try again.' }]); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| if (!isOpen) return null; | |
| return ( | |
| <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm animate-fade-in-up"> | |
| <div className="w-full max-w-2xl bg-white dark:bg-neutral-900 rounded-2xl shadow-2xl border border-gray-200 dark:border-neutral-800 flex flex-col max-h-[80vh]"> | |
| {/* Header */} | |
| <div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-neutral-800"> | |
| <div className="flex items-center gap-2"> | |
| <MessageCircle size={20} className="text-sky-500" /> | |
| <h2 className="text-lg font-semibold text-gray-900 dark:text-white">Ask a Question</h2> | |
| </div> | |
| <button | |
| onClick={onClose} | |
| className="p-2 hover:bg-gray-100 dark:hover:bg-neutral-800 rounded-full transition-colors" | |
| > | |
| <X size={20} className="text-gray-500" /> | |
| </button> | |
| </div> | |
| {/* Messages */} | |
| <div className="flex-1 overflow-y-auto p-4 space-y-4"> | |
| {messages.length === 0 && ( | |
| <div className="text-center py-12 text-gray-400"> | |
| <MessageCircle size={48} className="mx-auto mb-4 opacity-50" /> | |
| <p>Ask any question about the content you just analyzed.</p> | |
| </div> | |
| )} | |
| {messages.map((msg, idx) => ( | |
| <div | |
| key={idx} | |
| className={clsx( | |
| "flex", | |
| msg.role === 'user' ? "justify-end" : "justify-start" | |
| )} | |
| > | |
| <div | |
| className={clsx( | |
| "max-w-[80%] rounded-2xl px-4 py-3", | |
| msg.role === 'user' | |
| ? "bg-sky-500 text-white rounded-br-md" | |
| : "bg-gray-100 dark:bg-neutral-800 text-gray-800 dark:text-gray-200 rounded-bl-md" | |
| )} | |
| > | |
| {msg.role === 'assistant' ? ( | |
| <div className="prose prose-sm dark:prose-invert max-w-none"> | |
| <ReactMarkdown remarkPlugins={[remarkGfm]}> | |
| {msg.content} | |
| </ReactMarkdown> | |
| </div> | |
| ) : ( | |
| <p>{msg.content}</p> | |
| )} | |
| </div> | |
| </div> | |
| ))} | |
| {loading && ( | |
| <div className="flex justify-start"> | |
| <div className="bg-gray-100 dark:bg-neutral-800 rounded-2xl rounded-bl-md px-4 py-3"> | |
| <Loader2 size={20} className="animate-spin text-gray-400" /> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| {/* Input */} | |
| <form onSubmit={handleSubmit} className="p-4 border-t border-gray-200 dark:border-neutral-800"> | |
| <div className="flex gap-3"> | |
| <input | |
| type="text" | |
| value={input} | |
| onChange={(e) => setInput(e.target.value)} | |
| placeholder="Type your question..." | |
| className="flex-1 px-4 py-3 bg-gray-100 dark:bg-neutral-800 border border-gray-200 dark:border-neutral-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-transparent text-gray-900 dark:text-white placeholder-gray-400" | |
| disabled={loading} | |
| /> | |
| <button | |
| type="submit" | |
| disabled={loading || !input.trim()} | |
| className={clsx( | |
| "px-4 py-3 rounded-xl font-medium transition-all flex items-center gap-2", | |
| loading || !input.trim() | |
| ? "bg-gray-200 dark:bg-neutral-700 text-gray-400 cursor-not-allowed" | |
| : "bg-sky-500 text-white hover:bg-sky-600" | |
| )} | |
| > | |
| {loading ? <Loader2 size={18} className="animate-spin" /> : <Send size={18} />} | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| ); | |
| } | |