Spaces:
Build error
Build error
File size: 7,825 Bytes
17f4247 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
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; |