Elshawaf1 commited on
Commit
17f4247
·
verified ·
1 Parent(s): ac5cf0f

Upload components/ChatInterface.jsx with huggingface_hub

Browse files
Files changed (1) hide show
  1. components/ChatInterface.jsx +180 -0
components/ChatInterface.jsx ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+
3
+ const ChatInterface = () => {
4
+ const [messages, setMessages] = useState([
5
+ { 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?' }
6
+ ]);
7
+ const [input, setInput] = useState('');
8
+ const [isLoading, setIsLoading] = useState(false);
9
+ const messagesEndRef = useRef(null);
10
+
11
+ const scrollToBottom = () => {
12
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
13
+ };
14
+
15
+ useEffect(() => {
16
+ scrollToBottom();
17
+ }, [messages]);
18
+
19
+ const handleSend = async () => {
20
+ if (!input.trim()) return;
21
+
22
+ const userMessage = { id: Date.now(), role: 'user', content: input };
23
+ setMessages((prev) => [...prev, userMessage]);
24
+ setInput('');
25
+ setIsLoading(true);
26
+
27
+ try {
28
+ // Simulating API call to MedGemma
29
+ // In production, replace this with your actual API endpoint
30
+ const response = await fetch('/api/chat', {
31
+ method: 'POST',
32
+ headers: {
33
+ 'Content-Type': 'application/json',
34
+ },
35
+ body: JSON.stringify({ prompt: input }),
36
+ });
37
+
38
+ const data = await response.json();
39
+
40
+ if (data.reply) {
41
+ setMessages((prev) => [...prev, { id: Date.now() + 1, role: 'assistant', content: data.reply }]);
42
+ } else {
43
+ setMessages((prev) => [...prev, { id: Date.now() + 1, role: 'assistant', content: 'I apologize, but I am unable to generate a response right now.' }]);
44
+ }
45
+ } catch (error) {
46
+ console.error('Error:', error);
47
+ setMessages((prev) => [
48
+ ...prev,
49
+ { id: Date.now() + 1, role: 'assistant', content: 'Sorry, there was an error connecting to the server.' }
50
+ ]);
51
+ } finally {
52
+ setIsLoading(false);
53
+ }
54
+ };
55
+
56
+ const handleKeyPress = (e) => {
57
+ if (e.key === 'Enter' && !e.shiftKey) {
58
+ e.preventDefault();
59
+ handleSend();
60
+ }
61
+ };
62
+
63
+ return (
64
+ <div className="flex flex-col h-screen bg-gray-50">
65
+ {/* Header */}
66
+ <header className="bg-white border-b border-gray-200 px-6 py-4 shadow-sm flex items-center justify-between z-10">
67
+ <div className="flex items-center gap-3">
68
+ <div className="w-10 h-10 rounded-full bg-dental-100 flex items-center justify-center">
69
+ <svg className="w-6 h-6 text-dental-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
70
+ <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" />
71
+ </svg>
72
+ </div>
73
+ <div>
74
+ <h1 className="text-xl font-bold text-gray-800">Dental Fine-Tuning Assistant</h1>
75
+ <p className="text-xs text-gray-500">Powered by MedGemma</p>
76
+ </div>
77
+ </div>
78
+ <a
79
+ href="https://huggingface.co/spaces/akhaliq/anycoder"
80
+ target="_blank"
81
+ rel="noopener noreferrer"
82
+ className="text-sm font-medium text-dental-600 hover:text-dental-800 transition-colors"
83
+ >
84
+ Built with anycoder
85
+ </a>
86
+ </header>
87
+
88
+ {/* Messages Container */}
89
+ <div className="flex-1 overflow-y-auto p-4 space-y-6">
90
+ {messages.map((message) => (
91
+ <div
92
+ key={message.id}
93
+ className={`flex w-full ${
94
+ message.role === 'user' ? 'justify-end' : 'justify-start'
95
+ }`}
96
+ >
97
+ <div
98
+ className={`flex max-w-[85%] rounded-2xl px-5 py-3 shadow-sm ${
99
+ message.role === 'user'
100
+ ? 'bg-dental-600 text-white rounded-br-md'
101
+ : 'bg-white border border-gray-200 text-gray-800 rounded-bl-md'
102
+ }`}
103
+ >
104
+ {message.role === 'assistant' && (
105
+ <div className="w-8 h-8 rounded-full bg-dental-100 flex items-center justify-center mr-3 flex-shrink-0">
106
+ <svg className="w-5 h-5 text-dental-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
107
+ <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" />
108
+ </svg>
109
+ </div>
110
+ )}
111
+ <div className="overflow-hidden">
112
+ <p className="text-sm font-medium mb-1">
113
+ {message.role === 'user' ? 'You' : 'Dental Assistant'}
114
+ </p>
115
+ <div className="prose prose-sm max-w-none">
116
+ <p className="text-base whitespace-pre-wrap break-words">{message.content}</p>
117
+ </div>
118
+ </div>
119
+ </div>
120
+ </div>
121
+ ))}
122
+
123
+ {isLoading && (
124
+ <div className="flex justify-start">
125
+ <div className="bg-white border border-gray-200 rounded-2xl rounded-bl-md px-5 py-3 shadow-sm">
126
+ <div className="flex items-center gap-3">
127
+ <div className="w-8 h-8 rounded-full bg-dental-100 flex items-center justify-center">
128
+ <svg className="w-5 h-5 text-dental-600 animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24">
129
+ <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" />
130
+ </svg>
131
+ </div>
132
+ <div className="flex space-x-1">
133
+ <span className="w-2 h-2 bg-dental-600 rounded-full animate-bounce"></span>
134
+ <span className="w-2 h-2 bg-dental-600 rounded-full animate-bounce delay-100"></span>
135
+ <span className="w-2 h-2 bg-dental-600 rounded-full animate-bounce delay-200"></span>
136
+ </div>
137
+ </div>
138
+ </div>
139
+ </div>
140
+ )}
141
+
142
+ <div ref={messagesEndRef} />
143
+ </div>
144
+
145
+ {/* Input Area */}
146
+ <div className="bg-white border-t border-gray-200 p-4">
147
+ <div className="max-w-4xl mx-auto flex items-end gap-3">
148
+ <div className="flex-1 relative">
149
+ <textarea
150
+ value={input}
151
+ onChange={(e) => setInput(e.target.value)}
152
+ onKeyDown={handleKeyPress}
153
+ placeholder="Ask about dental procedures, oral health, or specific treatments..."
154
+ 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"
155
+ rows={1}
156
+ style={{ minHeight: '48px', maxHeight: '120px' }}
157
+ />
158
+ <div className="absolute right-3 top-1/2 transform -translate-y-1/2">
159
+ <button
160
+ onClick={handleSend}
161
+ disabled={isLoading || !input.trim()}
162
+ className={`p-2 rounded-xl transition-all ${
163
+ isLoading || !input.trim()
164
+ ? 'bg-gray-200 text-gray-400 cursor-not-allowed'
165
+ : 'bg-dental-600 text-white hover:bg-dental-700 active:scale-95'
166
+ }`}
167
+ >
168
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
169
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
170
+ </svg>
171
+ </button>
172
+ </div>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ </div>
177
+ );
178
+ };
179
+
180
+ export default ChatInterface;