11// This is a temporary chatbot component that is used to test the chatbot functionality.
22// LlamaIndex will replace it with better chatbot component.
33import { useState , useRef , useEffect , FormEvent , KeyboardEvent } from "react" ;
4- import { Send , Loader2 , Bot , User , MessageSquare , Trash2 , RefreshCw } from "lucide-react" ;
5- import { Button , Input , ScrollArea , Card , CardContent , cn , useWorkflowTaskCreate , useWorkflowTask } from "@llamaindex/ui" ;
4+ import {
5+ Send ,
6+ Loader2 ,
7+ Bot ,
8+ User ,
9+ MessageSquare ,
10+ Trash2 ,
11+ RefreshCw ,
12+ } from "lucide-react" ;
13+ import {
14+ Button ,
15+ Input ,
16+ ScrollArea ,
17+ Card ,
18+ CardContent ,
19+ cn ,
20+ useWorkflowTaskCreate ,
21+ useWorkflowTask ,
22+ } from "@llamaindex/ui" ;
623import { AGENT_NAME } from "../libs/config" ;
724import { toHumanResponseRawEvent } from "@/libs/utils" ;
825
@@ -28,19 +45,27 @@ export default function ChatBot() {
2845
2946 // Deployment + auth setup
3047 const deployment = AGENT_NAME || "document-qa" ;
31- const platformToken = ( import . meta as any ) . env ?. VITE_LLAMA_CLOUD_API_KEY as string | undefined ;
32- const projectId = ( import . meta as any ) . env ?. VITE_LLAMA_DEPLOY_PROJECT_ID as string | undefined ;
33- const defaultIndexName = ( import . meta as any ) . env ?. VITE_DEFAULT_INDEX_NAME || "document_qa_index" ;
34- const sessionIdRef = useRef < string > ( `chat-${ Math . random ( ) . toString ( 36 ) . slice ( 2 ) } -${ Date . now ( ) } ` ) ;
48+ const platformToken = ( import . meta as any ) . env ?. VITE_LLAMA_CLOUD_API_KEY as
49+ | string
50+ | undefined ;
51+ const projectId = ( import . meta as any ) . env ?. VITE_LLAMA_DEPLOY_PROJECT_ID as
52+ | string
53+ | undefined ;
54+ const defaultIndexName =
55+ ( import . meta as any ) . env ?. VITE_DEFAULT_INDEX_NAME || "document_qa_index" ;
56+ const sessionIdRef = useRef < string > (
57+ `chat-${ Math . random ( ) . toString ( 36 ) . slice ( 2 ) } -${ Date . now ( ) } ` ,
58+ ) ;
3559
3660 // UI text defaults
3761 const title = "AI Document Assistant" ;
3862 const placeholder = "Ask me anything about your documents..." ;
39- const welcomeMessage = "Welcome! 👋 Upload a document with the control above, then ask questions here." ;
63+ const welcomeMessage =
64+ "Welcome! 👋 Upload a document with the control above, then ask questions here." ;
4065
4166 // Helper functions for message management
4267 const appendMessage = ( role : Role , msg : string ) : void => {
43- setMessages ( prev => {
68+ setMessages ( ( prev ) => {
4469 const id = `${ role } -stream-${ Date . now ( ) } ` ;
4570 const idx = prev . length ;
4671 streamingMessageIndexRef . current = idx ;
@@ -57,7 +82,7 @@ export default function ChatBot() {
5782 } ;
5883
5984 const updateMessage = ( index : number , message : string ) => {
60- setMessages ( prev => {
85+ setMessages ( ( prev ) => {
6186 if ( index < 0 || index >= prev . length ) return prev ;
6287 const copy = [ ...prev ] ;
6388 const existing = copy [ index ] ;
@@ -73,7 +98,7 @@ export default function ChatBot() {
7398 id : "welcome" ,
7499 role : "assistant" ,
75100 content : welcomeMessage ,
76- timestamp : new Date ( )
101+ timestamp : new Date ( ) ,
77102 } ;
78103 setMessages ( [ welcomeMsg ] ) ;
79104 }
@@ -176,7 +201,7 @@ export default function ChatBot() {
176201 id : `user-${ Date . now ( ) } ` ,
177202 role : "user" ,
178203 content : trimmedInput ,
179- timestamp : new Date ( )
204+ timestamp : new Date ( ) ,
180205 } ;
181206
182207 const newMessages = [ ...messages , userMessage ] ;
@@ -202,11 +227,13 @@ export default function ChatBot() {
202227 ...getCommonHeaders ( ) ,
203228 } ,
204229 body : JSON . stringify ( {
205- event : JSON . stringify ( toHumanResponseRawEvent ( trimmedInput ) )
230+ event : JSON . stringify ( toHumanResponseRawEvent ( trimmedInput ) ) ,
206231 } ) ,
207232 } ) ;
208233 if ( ! postRes . ok ) {
209- throw new Error ( `Failed to send message: ${ postRes . status } ${ postRes . statusText } ` ) ;
234+ throw new Error (
235+ `Failed to send message: ${ postRes . status } ${ postRes . statusText } ` ,
236+ ) ;
210237 }
211238
212239 // The assistant reply will be streamed by useWorkflowTask and appended incrementally
@@ -219,10 +246,10 @@ export default function ChatBot() {
219246 role : "assistant" ,
220247 content : `Sorry, I encountered an error: ${ err instanceof Error ? err . message : "Unknown error" } . Please try again.` ,
221248 timestamp : new Date ( ) ,
222- error : true
249+ error : true ,
223250 } ;
224251
225- setMessages ( prev => [ ...prev , errorMessage ] ) ;
252+ setMessages ( ( prev ) => [ ...prev , errorMessage ] ) ;
226253 } finally {
227254 setIsLoading ( false ) ;
228255 // Focus back on input
@@ -232,7 +259,7 @@ export default function ChatBot() {
232259
233260 const handleKeyDown = ( e : KeyboardEvent < HTMLInputElement > ) => {
234261 // Submit on Enter (without Shift)
235- if ( e . key === ' Enter' && ! e . shiftKey ) {
262+ if ( e . key === " Enter" && ! e . shiftKey ) {
236263 e . preventDefault ( ) ;
237264 handleSubmit ( e as any ) ;
238265 }
@@ -244,40 +271,48 @@ export default function ChatBot() {
244271 id : "welcome" ,
245272 role : "assistant" as const ,
246273 content : welcomeMessage ,
247- timestamp : new Date ( )
248- }
274+ timestamp : new Date ( ) ,
275+ } ,
249276 ] ) ;
250277 setInput ( "" ) ;
251278 inputRef . current ?. focus ( ) ;
252279 } ;
253280
254281 const retryLastMessage = ( ) => {
255- const lastUserMessage = messages . filter ( m => m . role === "user" ) . pop ( ) ;
282+ const lastUserMessage = messages . filter ( ( m ) => m . role === "user" ) . pop ( ) ;
256283 if ( lastUserMessage ) {
257284 // Remove the last assistant message if it was an error
258285 const lastMessage = messages [ messages . length - 1 ] ;
259286 if ( lastMessage . role === "assistant" && lastMessage . error ) {
260- setMessages ( prev => prev . slice ( 0 , - 1 ) ) ;
287+ setMessages ( ( prev ) => prev . slice ( 0 , - 1 ) ) ;
261288 }
262289 setInput ( lastUserMessage . content ) ;
263290 inputRef . current ?. focus ( ) ;
264291 }
265292 } ;
266293
267294 return (
268- < div className = { cn ( "flex flex-col h-full bg-white dark:bg-gray-800 rounded-lg shadow-lg" ) } >
295+ < div
296+ className = { cn (
297+ "flex flex-col h-full bg-white dark:bg-gray-800 rounded-lg shadow-lg" ,
298+ ) }
299+ >
269300 { /* Header */ }
270301 < div className = "px-4 py-3 border-b dark:border-gray-700" >
271302 < div className = "flex items-center justify-between" >
272303 < div className = "flex items-center gap-2" >
273304 < MessageSquare className = "w-5 h-5 text-blue-600 dark:text-blue-400" />
274- < h3 className = "font-semibold text-gray-900 dark:text-white" > { title } </ h3 >
305+ < h3 className = "font-semibold text-gray-900 dark:text-white" >
306+ { title }
307+ </ h3 >
275308 { isLoading && (
276- < span className = "text-xs text-gray-500 dark:text-gray-400" > Thinking...</ span >
309+ < span className = "text-xs text-gray-500 dark:text-gray-400" >
310+ Thinking...
311+ </ span >
277312 ) }
278313 </ div >
279314 < div className = "flex items-center gap-2" >
280- { messages . some ( m => m . error ) && (
315+ { messages . some ( ( m ) => m . error ) && (
281316 < button
282317 onClick = { retryLastMessage }
283318 className = "p-1.5 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
@@ -320,51 +355,63 @@ export default function ChatBot() {
320355 key = { message . id }
321356 className = { cn (
322357 "flex gap-3" ,
323- message . role === "user" ? "justify-end" : "justify-start"
358+ message . role === "user" ? "justify-end" : "justify-start" ,
324359 ) }
325360 >
326361 { message . role !== "user" && (
327- < div className = { cn (
328- "w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0" ,
329- message . error
330- ? "bg-red-100 dark:bg-red-900"
331- : "bg-blue-100 dark:bg-blue-900"
332- ) } >
333- < Bot className = { cn (
334- "w-5 h-5" ,
362+ < div
363+ className = { cn (
364+ "w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0" ,
335365 message . error
336- ? "text-red-600 dark:text-red-400"
337- : "text-blue-600 dark:text-blue-400"
338- ) } />
366+ ? "bg-red-100 dark:bg-red-900"
367+ : "bg-blue-100 dark:bg-blue-900" ,
368+ ) }
369+ >
370+ < Bot
371+ className = { cn (
372+ "w-5 h-5" ,
373+ message . error
374+ ? "text-red-600 dark:text-red-400"
375+ : "text-blue-600 dark:text-blue-400" ,
376+ ) }
377+ />
339378 </ div >
340379 ) }
341- < div className = { cn (
342- "max-w-[70%]" ,
343- message . role === "user" ? "order-1" : "order-2"
344- ) } >
345- < Card className = { cn (
346- "py-0" ,
347- message . role === "user"
348- ? "bg-blue-600 text-white border-blue-600"
349- : message . error
350- ? "bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800"
351- : "bg-gray-50 dark:bg-gray-700"
352- ) } >
380+ < div
381+ className = { cn (
382+ "max-w-[70%]" ,
383+ message . role === "user" ? "order-1" : "order-2" ,
384+ ) }
385+ >
386+ < Card
387+ className = { cn (
388+ "py-0" ,
389+ message . role === "user"
390+ ? "bg-blue-600 text-white border-blue-600"
391+ : message . error
392+ ? "bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800"
393+ : "bg-gray-50 dark:bg-gray-700" ,
394+ ) }
395+ >
353396 < CardContent className = "p-3" >
354- < p className = { cn (
355- "whitespace-pre-wrap text-sm" ,
356- message . error && "text-red-700 dark:text-red-400"
357- ) } >
397+ < p
398+ className = { cn (
399+ "whitespace-pre-wrap text-sm" ,
400+ message . error && "text-red-700 dark:text-red-400" ,
401+ ) }
402+ >
358403 { message . content }
359404 </ p >
360- < p className = { cn (
361- "text-xs mt-1 opacity-70" ,
362- message . role === "user"
363- ? "text-blue-100"
364- : message . error
365- ? "text-red-500 dark:text-red-400"
366- : "text-gray-500 dark:text-gray-400"
367- ) } >
405+ < p
406+ className = { cn (
407+ "text-xs mt-1 opacity-70" ,
408+ message . role === "user"
409+ ? "text-blue-100"
410+ : message . error
411+ ? "text-red-500 dark:text-red-400"
412+ : "text-gray-500 dark:text-gray-400" ,
413+ ) }
414+ >
368415 { message . timestamp . toLocaleTimeString ( ) }
369416 </ p >
370417 </ CardContent >
@@ -387,9 +434,18 @@ export default function ChatBot() {
387434 < CardContent className = "p-3" >
388435 < div className = "flex items-center gap-2" >
389436 < div className = "flex gap-1" >
390- < span className = "w-2 h-2 bg-gray-500 rounded-full animate-bounce" style = { { animationDelay : '0ms' } } > </ span >
391- < span className = "w-2 h-2 bg-gray-500 rounded-full animate-bounce" style = { { animationDelay : '150ms' } } > </ span >
392- < span className = "w-2 h-2 bg-gray-500 rounded-full animate-bounce" style = { { animationDelay : '300ms' } } > </ span >
437+ < span
438+ className = "w-2 h-2 bg-gray-500 rounded-full animate-bounce"
439+ style = { { animationDelay : "0ms" } }
440+ > </ span >
441+ < span
442+ className = "w-2 h-2 bg-gray-500 rounded-full animate-bounce"
443+ style = { { animationDelay : "150ms" } }
444+ > </ span >
445+ < span
446+ className = "w-2 h-2 bg-gray-500 rounded-full animate-bounce"
447+ style = { { animationDelay : "300ms" } }
448+ > </ span >
393449 </ div >
394450 </ div >
395451 </ CardContent >
@@ -420,7 +476,7 @@ export default function ChatBot() {
420476 size = "icon"
421477 title = "Send message"
422478 >
423- { ( ! canSend || isLoading ) ? (
479+ { ! canSend || isLoading ? (
424480 < Loader2 className = "w-4 h-4 animate-spin" />
425481 ) : (
426482 < Send className = "w-4 h-4" />
@@ -433,4 +489,4 @@ export default function ChatBot() {
433489 </ div >
434490 </ div >
435491 ) ;
436- }
492+ }
0 commit comments