akhaliq HF Staff commited on
Commit
fd2c3f5
·
verified ·
1 Parent(s): 79fe52c

Upload pages/index.js with huggingface_hub

Browse files
Files changed (1) hide show
  1. pages/index.js +257 -0
pages/index.js ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useRef, useEffect } from 'react'
2
+ import ChatMessage from '../components/ChatMessage'
3
+ import ImageUpload from '../components/ImageUpload'
4
+ import { Send, Image as ImageIcon, Loader2 } from 'lucide-react'
5
+
6
+ export default function Home() {
7
+ const [messages, setMessages] = useState([])
8
+ const [input, setInput] = useState('')
9
+ const [isLoading, setIsLoading] = useState(false)
10
+ const [selectedImage, setSelectedImage] = useState(null)
11
+ const messagesEndRef = useRef(null)
12
+
13
+ const scrollToBottom = () => {
14
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
15
+ }
16
+
17
+ useEffect(scrollToBottom, [messages])
18
+
19
+ const handleSubmit = async (e) => {
20
+ e.preventDefault()
21
+ if (!input.trim() && !selectedImage) return
22
+
23
+ const userMessage = {
24
+ id: Date.now(),
25
+ role: 'user',
26
+ content: input.trim(),
27
+ image: selectedImage,
28
+ timestamp: new Date()
29
+ }
30
+
31
+ setMessages(prev => [...prev, userMessage])
32
+ setInput('')
33
+ setIsLoading(true)
34
+
35
+ // Prepare messages for API
36
+ const apiMessages = [...messages, userMessage].map(msg => {
37
+ const content = []
38
+
39
+ if (msg.content) {
40
+ content.push({
41
+ type: 'text',
42
+ text: msg.content
43
+ })
44
+ }
45
+
46
+ if (msg.image) {
47
+ content.push({
48
+ type: 'image_url',
49
+ image_url: {
50
+ url: msg.image
51
+ }
52
+ })
53
+ }
54
+
55
+ return {
56
+ role: msg.role,
57
+ content: content.length > 1 ? content : (content[0]?.text || '')
58
+ }
59
+ })
60
+
61
+ try {
62
+ const response = await fetch('/api/chat', {
63
+ method: 'POST',
64
+ headers: {
65
+ 'Content-Type': 'application/json',
66
+ },
67
+ body: JSON.stringify({
68
+ messages: apiMessages,
69
+ stream: true
70
+ }),
71
+ })
72
+
73
+ if (!response.ok) {
74
+ throw new Error(`HTTP error! status: ${response.status}`)
75
+ }
76
+
77
+ const reader = response.body.getReader()
78
+ const decoder = new TextDecoder()
79
+ let assistantMessage = {
80
+ id: Date.now() + 1,
81
+ role: 'assistant',
82
+ content: '',
83
+ timestamp: new Date()
84
+ }
85
+
86
+ setMessages(prev => [...prev, assistantMessage])
87
+
88
+ while (true) {
89
+ const { done, value } = await reader.read()
90
+ if (done) break
91
+
92
+ const chunk = decoder.decode(value)
93
+ const lines = chunk.split('\n')
94
+
95
+ for (const line of lines) {
96
+ if (line.startsWith('data: ')) {
97
+ const data = line.slice(6)
98
+ if (data === '[DONE]') return
99
+
100
+ try {
101
+ const parsed = JSON.parse(data)
102
+ const delta = parsed.choices?.[0]?.delta?.content
103
+ if (delta) {
104
+ assistantMessage.content += delta
105
+ setMessages(prev => {
106
+ const newMessages = [...prev]
107
+ const lastIndex = newMessages.length - 1
108
+ if (newMessages[lastIndex]?.id === assistantMessage.id) {
109
+ newMessages[lastIndex] = { ...assistantMessage }
110
+ }
111
+ return newMessages
112
+ })
113
+ }
114
+ } catch (e) {
115
+ // Skip invalid JSON
116
+ }
117
+ }
118
+ }
119
+ }
120
+ } catch (error) {
121
+ console.error('Error:', error)
122
+ const errorMessage = {
123
+ id: Date.now() + 1,
124
+ role: 'assistant',
125
+ content: 'Sorry, there was an error processing your request.',
126
+ timestamp: new Date(),
127
+ isError: true
128
+ }
129
+ setMessages(prev => [...prev, errorMessage])
130
+ } finally {
131
+ setIsLoading(false)
132
+ setSelectedImage(null)
133
+ }
134
+ }
135
+
136
+ const clearChat = () => {
137
+ setMessages([])
138
+ setSelectedImage(null)
139
+ }
140
+
141
+ return (
142
+ <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
143
+ {/* Header */}
144
+ <header className="bg-white/80 backdrop-blur-sm border-b border-gray-200 sticky top-0 z-10">
145
+ <div className="max-w-4xl mx-auto px-4 py-4">
146
+ <div className="flex items-center justify-between">
147
+ <div>
148
+ <h1 className="text-2xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
149
+ Multimodal Chat
150
+ </h1>
151
+ <p className="text-sm text-gray-600 mt-1">
152
+ Powered by Qwen3-VL-30B-A3B-Instruct
153
+ </p>
154
+ </div>
155
+ <div className="flex items-center gap-4">
156
+ {messages.length > 0 && (
157
+ <button
158
+ onClick={clearChat}
159
+ className="text-sm text-gray-500 hover:text-gray-700 px-3 py-1 rounded-md hover:bg-gray-100 transition-colors"
160
+ >
161
+ Clear Chat
162
+ </button>
163
+ )}
164
+ <a
165
+ href="https://huggingface.co/spaces/akhaliq/anycoder"
166
+ target="_blank"
167
+ rel="noopener noreferrer"
168
+ className="text-sm text-blue-600 hover:text-blue-700 hover:underline"
169
+ >
170
+ Built with anycoder
171
+ </a>
172
+ </div>
173
+ </div>
174
+ </div>
175
+ </header>
176
+
177
+ {/* Main Chat Area */}
178
+ <main className="max-w-4xl mx-auto px-4 py-6">
179
+ <div className="bg-white/70 backdrop-blur-sm rounded-2xl shadow-xl border border-gray-200 min-h-[600px] flex flex-col">
180
+ {/* Messages */}
181
+ <div className="flex-1 overflow-y-auto p-6 space-y-4">
182
+ {messages.length === 0 ? (
183
+ <div className="text-center text-gray-500 mt-20">
184
+ <ImageIcon className="mx-auto mb-4 w-12 h-12 text-gray-400" />
185
+ <h3 className="text-lg font-medium mb-2">Start a conversation</h3>
186
+ <p className="text-sm">
187
+ Send a message or upload an image to begin chatting with the AI
188
+ </p>
189
+ </div>
190
+ ) : (
191
+ messages.map((message) => (
192
+ <ChatMessage key={message.id} message={message} />
193
+ ))
194
+ )}
195
+ {isLoading && (
196
+ <div className="flex items-center gap-2 text-gray-500 p-4">
197
+ <Loader2 className="w-4 h-4 animate-spin" />
198
+ <span>AI is thinking...</span>
199
+ </div>
200
+ )}
201
+ <div ref={messagesEndRef} />
202
+ </div>
203
+
204
+ {/* Input Area */}
205
+ <div className="border-t border-gray-200 p-4">
206
+ {selectedImage && (
207
+ <div className="mb-4 p-3 bg-blue-50 rounded-lg border border-blue-200">
208
+ <div className="flex items-center justify-between">
209
+ <img
210
+ src={selectedImage}
211
+ alt="Selected"
212
+ className="w-16 h-16 object-cover rounded-lg"
213
+ />
214
+ <button
215
+ onClick={() => setSelectedImage(null)}
216
+ className="text-red-500 hover:text-red-700 p-1"
217
+ >
218
+
219
+ </button>
220
+ </div>
221
+ </div>
222
+ )}
223
+
224
+ <form onSubmit={handleSubmit} className="flex items-end gap-3">
225
+ <div className="flex-1">
226
+ <textarea
227
+ value={input}
228
+ onChange={(e) => setInput(e.target.value)}
229
+ placeholder="Type your message... (or ask about an image)"
230
+ className="w-full p-3 border border-gray-300 rounded-lg resize-none focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
231
+ rows="1"
232
+ style={{ minHeight: '48px', maxHeight: '120px' }}
233
+ onKeyDown={(e) => {
234
+ if (e.key === 'Enter' && !e.shiftKey) {
235
+ e.preventDefault()
236
+ handleSubmit(e)
237
+ }
238
+ }}
239
+ />
240
+ </div>
241
+
242
+ <ImageUpload onImageSelect={setSelectedImage} />
243
+
244
+ <button
245
+ type="submit"
246
+ disabled={(!input.trim() && !selectedImage) || isLoading}
247
+ className="bg-blue-600 hover:bg-blue-700 disabled:bg-gray-300 text-white p-3 rounded-lg transition-colors flex items-center justify-center"
248
+ >
249
+ <Send className="w-5 h-5" />
250
+ </button>
251
+ </form>
252
+ </div>
253
+ </div>
254
+ </main>
255
+ </div>
256
+ )
257
+ }