akhaliq HF Staff commited on
Commit
38af7f4
·
verified ·
1 Parent(s): 4a11dd7

Upload components/ChatInterface.jsx with huggingface_hub

Browse files
Files changed (1) hide show
  1. components/ChatInterface.jsx +168 -0
components/ChatInterface.jsx ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useRef, useEffect } from 'react'
2
+ import MessageBubble from './MessageBubble'
3
+ import ChatInput from './ChatInput'
4
+ import TypingIndicator from './TypingIndicator'
5
+
6
+ export default function ChatInterface() {
7
+ const [messages, setMessages] = useState([
8
+ {
9
+ role: 'assistant',
10
+ content: 'Hello! I can analyze images and answer questions about them. Upload an image and ask me anything!',
11
+ },
12
+ ])
13
+ const [input, setInput] = useState('')
14
+ const [uploadedImage, setUploadedImage] = useState(null)
15
+ const [isLoading, setIsLoading] = useState(false)
16
+ const [error, setError] = useState(null)
17
+ const messagesEndRef = useRef(null)
18
+
19
+ const scrollToBottom = () => {
20
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
21
+ }
22
+
23
+ useEffect(() => {
24
+ scrollToBottom()
25
+ }, [messages, isLoading])
26
+
27
+ const handleImageUpload = (file) => {
28
+ if (!file.type.startsWith('image/')) {
29
+ setError('Please upload a valid image file')
30
+ return
31
+ }
32
+
33
+ if (file.size > 10 * 1024 * 1024) {
34
+ setError('Image size must be less than 10MB')
35
+ return
36
+ }
37
+
38
+ const reader = new FileReader()
39
+ reader.onload = (e) => {
40
+ setUploadedImage({
41
+ file,
42
+ preview: e.target.result,
43
+ base64: e.target.result.split(',')[1],
44
+ })
45
+ setError(null)
46
+ }
47
+ reader.readAsDataURL(file)
48
+ }
49
+
50
+ const handleSubmit = async () => {
51
+ if (!input.trim() && !uploadedImage) {
52
+ setError('Please enter a message or upload an image')
53
+ return
54
+ }
55
+
56
+ setError(null)
57
+ setIsLoading(true)
58
+
59
+ const userMessage = {
60
+ role: 'user',
61
+ content: [],
62
+ }
63
+
64
+ if (input.trim()) {
65
+ userMessage.content.push({
66
+ type: 'text',
67
+ text: input,
68
+ })
69
+ }
70
+
71
+ if (uploadedImage) {
72
+ userMessage.content.push({
73
+ type: 'image_url',
74
+ image_url: {
75
+ url: `data:image/jpeg;base64,${uploadedImage.base64}`,
76
+ },
77
+ })
78
+ }
79
+
80
+ setMessages(prev => [...prev, userMessage])
81
+ setInput('')
82
+ setUploadedImage(null)
83
+
84
+ try {
85
+ const response = await fetch('/api/chat', {
86
+ method: 'POST',
87
+ headers: {
88
+ 'Content-Type': 'application/json',
89
+ },
90
+ body: JSON.stringify({
91
+ messages: [...messages, userMessage],
92
+ }),
93
+ })
94
+
95
+ if (!response.ok) {
96
+ throw new Error('Failed to get response from the AI')
97
+ }
98
+
99
+ const reader = response.body.getReader()
100
+ const decoder = new TextDecoder()
101
+ let accumulatedContent = ''
102
+
103
+ setMessages(prev => [...prev, { role: 'assistant', content: '' }])
104
+
105
+ while (true) {
106
+ const { done, value } = await reader.read()
107
+ if (done) break
108
+
109
+ const chunk = decoder.decode(value, { stream: true })
110
+ accumulatedContent += chunk
111
+
112
+ setMessages(prev => {
113
+ const newMessages = [...prev]
114
+ const lastMessage = newMessages[newMessages.length - 1]
115
+ if (lastMessage.role === 'assistant') {
116
+ lastMessage.content = accumulatedContent
117
+ }
118
+ return newMessages
119
+ })
120
+ }
121
+ } catch (err) {
122
+ console.error('Chat error:', err)
123
+ setError(err.message || 'An error occurred. Please try again.')
124
+ } finally {
125
+ setIsLoading(false)
126
+ }
127
+ }
128
+
129
+ const handleKeyPress = (e) => {
130
+ if (e.key === 'Enter' && !e.shiftKey) {
131
+ e.preventDefault()
132
+ handleSubmit()
133
+ }
134
+ }
135
+
136
+ return (
137
+ <div className="flex flex-col flex-1 overflow-hidden">
138
+ <div className="flex-1 overflow-y-auto p-4 space-y-4">
139
+ {messages.map((message, index) => (
140
+ <MessageBubble
141
+ key={index}
142
+ message={message}
143
+ isLast={index === messages.length - 1}
144
+ />
145
+ ))}
146
+ {isLoading && <TypingIndicator />}
147
+ <div ref={messagesEndRef} />
148
+ </div>
149
+
150
+ {error && (
151
+ <div className="mx-4 mb-2 p-3 bg-red-100 border border-red-400 text-red-700 rounded-lg">
152
+ {error}
153
+ </div>
154
+ )}
155
+
156
+ <ChatInput
157
+ input={input}
158
+ setInput={setInput}
159
+ uploadedImage={uploadedImage}
160
+ setUploadedImage={setUploadedImage}
161
+ onImageUpload={handleImageUpload}
162
+ onSubmit={handleSubmit}
163
+ onKeyPress={handleKeyPress}
164
+ isLoading={isLoading}
165
+ />
166
+ </div>
167
+ )
168
+ }