1
- import { JSONValue } from "ai" ;
2
- import React from "react" ;
3
- import { DocumentFile } from "." ;
4
- import { Button } from "../button" ;
5
- import { DocumentPreview } from "../document-preview" ;
6
- import FileUploader from "../file-uploader" ;
7
- import { Textarea } from "../textarea" ;
8
- import UploadImagePreview from "../upload-image-preview" ;
9
- import { ChatHandler } from "./chat.interface" ;
10
- import { useFile } from "./hooks/use-file" ;
11
- import { LlamaCloudSelector } from "./widgets/LlamaCloudSelector" ;
1
+ "use client" ;
12
2
13
- const ALLOWED_EXTENSIONS = [ "png" , "jpg" , "jpeg" , "csv" , "pdf" , "txt" , "docx" ] ;
3
+ import { ChatInput , useChatUI , useFile } from "@llamaindex/chat-ui" ;
4
+ import { DocumentPreview , ImagePreview } from "@llamaindex/chat-ui/widgets" ;
5
+ import { LlamaCloudSelector } from "./custom/llama-cloud-selector" ;
6
+ import { useClientConfig } from "./hooks/use-config" ;
14
7
15
- export default function ChatInput (
16
- props : Pick <
17
- ChatHandler ,
18
- | "isLoading"
19
- | "input"
20
- | "onFileUpload"
21
- | "onFileError"
22
- | "handleSubmit"
23
- | "handleInputChange"
24
- | "messages"
25
- | "setInput"
26
- | "append"
27
- > & {
28
- requestParams ?: any ;
29
- setRequestData ?: React . Dispatch < any > ;
30
- } ,
31
- ) {
8
+ export default function CustomChatInput ( ) {
9
+ const { requestData, isLoading, input } = useChatUI ( ) ;
10
+ const { backend } = useClientConfig ( ) ;
32
11
const {
33
12
imageUrl,
34
13
setImageUrl,
@@ -37,107 +16,65 @@ export default function ChatInput(
37
16
removeDoc,
38
17
reset,
39
18
getAnnotations,
40
- } = useFile ( ) ;
41
-
42
- // default submit function does not handle including annotations in the message
43
- // so we need to use append function to submit new message with annotations
44
- const handleSubmitWithAnnotations = (
45
- e : React . FormEvent < HTMLFormElement > ,
46
- annotations : JSONValue [ ] | undefined ,
47
- ) => {
48
- e . preventDefault ( ) ;
49
- props . append ! ( {
50
- content : props . input ,
51
- role : "user" ,
52
- createdAt : new Date ( ) ,
53
- annotations,
54
- } ) ;
55
- props . setInput ! ( "" ) ;
56
- } ;
57
-
58
- const onSubmit = ( e : React . FormEvent < HTMLFormElement > ) => {
59
- e . preventDefault ( ) ;
60
- const annotations = getAnnotations ( ) ;
61
- if ( annotations . length ) {
62
- handleSubmitWithAnnotations ( e , annotations ) ;
63
- return reset ( ) ;
64
- }
65
- props . handleSubmit ( e ) ;
66
- } ;
19
+ } = useFile ( { uploadAPI : `${ backend } /api/chat/upload` } ) ;
67
20
21
+ /**
22
+ * Handles file uploads. Overwrite to hook into the file upload behavior.
23
+ * @param file The file to upload
24
+ */
68
25
const handleUploadFile = async ( file : File ) => {
26
+ // There's already an image uploaded, only allow one image at a time
69
27
if ( imageUrl ) {
70
28
alert ( "You can only upload one image at a time." ) ;
71
29
return ;
72
30
}
31
+
73
32
try {
74
- await uploadFile ( file , props . requestParams ) ;
75
- props . onFileUpload ?. ( file ) ;
33
+ // Upload the file and send with it the current request data
34
+ await uploadFile ( file , requestData ) ;
76
35
} catch ( error : any ) {
77
- const onFileUploadError = props . onFileError || window . alert ;
78
- onFileUploadError ( error . message ) ;
36
+ // Show error message if upload fails
37
+ alert ( error . message ) ;
79
38
}
80
39
} ;
81
40
82
- const handleKeyDown = ( e : React . KeyboardEvent < HTMLTextAreaElement > ) => {
83
- if ( e . key === "Enter" && ! e . shiftKey ) {
84
- e . preventDefault ( ) ;
85
- onSubmit ( e as unknown as React . FormEvent < HTMLFormElement > ) ;
86
- }
87
- } ;
41
+ // Get references to the upload files in message annotations format, see https://github.com/run-llama/chat-ui/blob/main/packages/chat-ui/src/hook/use-file.tsx#L56
42
+ const annotations = getAnnotations ( ) ;
88
43
89
44
return (
90
- < form
91
- onSubmit = { onSubmit }
92
- className = "rounded-xl bg-white p-4 shadow-xl space-y-4 shrink-0"
45
+ < ChatInput
46
+ className = "shadow-xl rounded-xl"
47
+ resetUploadedFiles = { reset }
48
+ annotations = { annotations }
93
49
>
94
- { imageUrl && (
95
- < UploadImagePreview url = { imageUrl } onRemove = { ( ) => setImageUrl ( null ) } />
96
- ) }
97
- { files . length > 0 && (
98
- < div className = "flex gap-4 w-full overflow-auto py-2" >
99
- { files . map ( ( file : DocumentFile ) => (
100
- < DocumentPreview
101
- key = { file . id }
102
- file = { file }
103
- onRemove = { ( ) => removeDoc ( file ) }
104
- />
105
- ) ) }
106
- </ div >
107
- ) }
108
- < div className = "flex w-full items-start justify-between gap-4 " >
109
- < Textarea
110
- id = "chat-input"
111
- autoFocus
112
- name = "message"
113
- placeholder = "Type a message"
114
- className = "flex-1 min-h-0 h-[40px]"
115
- value = { props . input }
116
- onChange = { props . handleInputChange }
117
- onKeyDown = { handleKeyDown }
118
- />
119
- < FileUploader
120
- onFileUpload = { handleUploadFile }
121
- onFileError = { props . onFileError }
122
- config = { {
123
- allowedExtensions : ALLOWED_EXTENSIONS ,
124
- disabled : props . isLoading ,
125
- multiple : true ,
126
- } }
127
- />
128
- { process . env . NEXT_PUBLIC_USE_LLAMACLOUD === "true" &&
129
- props . setRequestData && (
130
- < LlamaCloudSelector setRequestData = { props . setRequestData } />
131
- ) }
132
- < Button
133
- type = "submit"
50
+ < div >
51
+ { /* Image preview section */ }
52
+ { imageUrl && (
53
+ < ImagePreview url = { imageUrl } onRemove = { ( ) => setImageUrl ( null ) } />
54
+ ) }
55
+ { /* Document previews section */ }
56
+ { files . length > 0 && (
57
+ < div className = "flex gap-4 w-full overflow-auto py-2" >
58
+ { files . map ( ( file ) => (
59
+ < DocumentPreview
60
+ key = { file . id }
61
+ file = { file }
62
+ onRemove = { ( ) => removeDoc ( file ) }
63
+ />
64
+ ) ) }
65
+ </ div >
66
+ ) }
67
+ </ div >
68
+ < ChatInput . Form >
69
+ < ChatInput . Field />
70
+ < ChatInput . Upload onUpload = { handleUploadFile } />
71
+ < LlamaCloudSelector />
72
+ < ChatInput . Submit
134
73
disabled = {
135
- props . isLoading || ( ! props . input . trim ( ) && files . length === 0 )
74
+ isLoading || ( ! input . trim ( ) && files . length === 0 && ! imageUrl )
136
75
}
137
- >
138
- Send message
139
- </ Button >
140
- </ div >
141
- </ form >
76
+ />
77
+ </ ChatInput . Form >
78
+ </ ChatInput >
142
79
) ;
143
80
}
0 commit comments