Spaces:
Build error
Build error
| import React, { useState, useRef, useEffect } from 'react'; | |
| const ChatInterface = () => { | |
| const [messages, setMessages] = useState([ | |
| { id: 1, role: 'assistant', content: 'Hello! I am your Dental Fine-Tuning Assistant. How can I help you with dental procedures, oral health, or medical queries today?' } | |
| ]); | |
| const [input, setInput] = useState(''); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const messagesEndRef = useRef(null); | |
| const scrollToBottom = () => { | |
| messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); | |
| }; | |
| useEffect(() => { | |
| scrollToBottom(); | |
| }, [messages]); | |
| const handleSend = async () => { | |
| if (!input.trim()) return; | |
| const userMessage = { id: Date.now(), role: 'user', content: input }; | |
| setMessages((prev) => [...prev, userMessage]); | |
| setInput(''); | |
| setIsLoading(true); | |
| try { | |
| // Simulating API call to MedGemma | |
| // In production, replace this with your actual API endpoint | |
| const response = await fetch('/api/chat', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ prompt: input }), | |
| }); | |
| const data = await response.json(); | |
| if (data.reply) { | |
| setMessages((prev) => [...prev, { id: Date.now() + 1, role: 'assistant', content: data.reply }]); | |
| } else { | |
| setMessages((prev) => [...prev, { id: Date.now() + 1, role: 'assistant', content: 'I apologize, but I am unable to generate a response right now.' }]); | |
| } | |
| } catch (error) { | |
| console.error('Error:', error); | |
| setMessages((prev) => [ | |
| ...prev, | |
| { id: Date.now() + 1, role: 'assistant', content: 'Sorry, there was an error connecting to the server.' } | |
| ]); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| const handleKeyPress = (e) => { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| handleSend(); | |
| } | |
| }; | |
| return ( | |
| <div className="flex flex-col h-screen bg-gray-50"> | |
| {/* Header */} | |
| <header className="bg-white border-b border-gray-200 px-6 py-4 shadow-sm flex items-center justify-between z-10"> | |
| <div className="flex items-center gap-3"> | |
| <div className="w-10 h-10 rounded-full bg-dental-100 flex items-center justify-center"> | |
| <svg className="w-6 h-6 text-dental-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" /> | |
| </svg> | |
| </div> | |
| <div> | |
| <h1 className="text-xl font-bold text-gray-800">Dental Fine-Tuning Assistant</h1> | |
| <p className="text-xs text-gray-500">Powered by MedGemma</p> | |
| </div> | |
| </div> | |
| <a | |
| href="https://huggingface.co/spaces/akhaliq/anycoder" | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="text-sm font-medium text-dental-600 hover:text-dental-800 transition-colors" | |
| > | |
| Built with anycoder | |
| </a> | |
| </header> | |
| {/* Messages Container */} | |
| <div className="flex-1 overflow-y-auto p-4 space-y-6"> | |
| {messages.map((message) => ( | |
| <div | |
| key={message.id} | |
| className={`flex w-full ${ | |
| message.role === 'user' ? 'justify-end' : 'justify-start' | |
| }`} | |
| > | |
| <div | |
| className={`flex max-w-[85%] rounded-2xl px-5 py-3 shadow-sm ${ | |
| message.role === 'user' | |
| ? 'bg-dental-600 text-white rounded-br-md' | |
| : 'bg-white border border-gray-200 text-gray-800 rounded-bl-md' | |
| }`} | |
| > | |
| {message.role === 'assistant' && ( | |
| <div className="w-8 h-8 rounded-full bg-dental-100 flex items-center justify-center mr-3 flex-shrink-0"> | |
| <svg className="w-5 h-5 text-dental-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19.428 15.428a2 2 0 00-1.022-.547l-2.384-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" /> | |
| </svg> | |
| </div> | |
| )} | |
| <div className="overflow-hidden"> | |
| <p className="text-sm font-medium mb-1"> | |
| {message.role === 'user' ? 'You' : 'Dental Assistant'} | |
| </p> | |
| <div className="prose prose-sm max-w-none"> | |
| <p className="text-base whitespace-pre-wrap break-words">{message.content}</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ))} | |
| {isLoading && ( | |
| <div className="flex justify-start"> | |
| <div className="bg-white border border-gray-200 rounded-2xl rounded-bl-md px-5 py-3 shadow-sm"> | |
| <div className="flex items-center gap-3"> | |
| <div className="w-8 h-8 rounded-full bg-dental-100 flex items-center justify-center"> | |
| <svg className="w-5 h-5 text-dental-600 animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> | |
| </svg> | |
| </div> | |
| <div className="flex space-x-1"> | |
| <span className="w-2 h-2 bg-dental-600 rounded-full animate-bounce"></span> | |
| <span className="w-2 h-2 bg-dental-600 rounded-full animate-bounce delay-100"></span> | |
| <span className="w-2 h-2 bg-dental-600 rounded-full animate-bounce delay-200"></span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| <div ref={messagesEndRef} /> | |
| </div> | |
| {/* Input Area */} | |
| <div className="bg-white border-t border-gray-200 p-4"> | |
| <div className="max-w-4xl mx-auto flex items-end gap-3"> | |
| <div className="flex-1 relative"> | |
| <textarea | |
| value={input} | |
| onChange={(e) => setInput(e.target.value)} | |
| onKeyDown={handleKeyPress} | |
| placeholder="Ask about dental procedures, oral health, or specific treatments..." | |
| className="w-full bg-gray-100 border-0 rounded-2xl px-5 py-3 pr-12 text-gray-800 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-dental-500 focus:bg-white transition-all resize-none" | |
| rows={1} | |
| style={{ minHeight: '48px', maxHeight: '120px' }} | |
| /> | |
| <div className="absolute right-3 top-1/2 transform -translate-y-1/2"> | |
| <button | |
| onClick={handleSend} | |
| disabled={isLoading || !input.trim()} | |
| className={`p-2 rounded-xl transition-all ${ | |
| isLoading || !input.trim() | |
| ? 'bg-gray-200 text-gray-400 cursor-not-allowed' | |
| : 'bg-dental-600 text-white hover:bg-dental-700 active:scale-95' | |
| }`} | |
| > | |
| <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" /> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default ChatInterface; |