1
1
import React , { useState } from 'react'
2
- import { TextField , Button , Box , Typography } from '@mui/material'
2
+ import { TextField , Button , Box , Typography , Table , TableHead , TableBody , TableRow , TableCell , Paper , IconButton , Dialog , DialogTitle , styled } from '@mui/material'
3
3
import apiClient from '../util/apiClient'
4
+ import { useMutation , useQuery } from '@tanstack/react-query'
5
+ import type { RagIndexAttributes } from '../../server/db/models/ragIndex'
6
+ import { CloudUpload , Settings } from '@mui/icons-material'
4
7
5
8
type RagResponse = {
6
9
total : number
@@ -14,9 +17,82 @@ type RagResponse = {
14
17
} >
15
18
}
16
19
20
+ const useRagIndices = ( ) => {
21
+ const { data, ...rest } = useQuery < RagIndexAttributes [ ] > ( {
22
+ queryKey : [ 'ragIndices' ] ,
23
+ queryFn : async ( ) => {
24
+ const response = await apiClient . get ( '/rag/indices' )
25
+ return response . data
26
+ } ,
27
+ } )
28
+
29
+ return { data, ...rest }
30
+ }
31
+
32
+ const useCreateRagIndexMutation = ( ) => {
33
+ const mutation = useMutation ( {
34
+ mutationFn : async ( indexName : string ) => {
35
+ const response = await apiClient . post ( '/rag/indices' , { name : indexName } )
36
+ return response . data
37
+ } ,
38
+ } )
39
+ return mutation
40
+ }
41
+
42
+ const useDeleteRagIndexMutation = ( ) => {
43
+ const mutation = useMutation ( {
44
+ mutationFn : async ( indexId : number ) => {
45
+ const response = await apiClient . delete ( `/rag/indices/${ indexId } ` )
46
+ return response . data
47
+ } ,
48
+ } )
49
+ return mutation
50
+ }
51
+
52
+ const useUploadMutation = ( index : RagIndexAttributes | null ) => {
53
+ const mutation = useMutation ( {
54
+ mutationFn : async ( files : FileList ) => {
55
+ if ( ! index ) {
56
+ throw new Error ( 'Index is required' )
57
+ }
58
+ const formData = new FormData ( )
59
+ // Append each file individually
60
+ Array . from ( files ) . forEach ( ( file ) => {
61
+ formData . append ( 'files' , file )
62
+ } )
63
+ const response = await apiClient . post ( `/rag/indices/${ index . id } /upload` , formData , {
64
+ headers : {
65
+ 'Content-Type' : 'multipart/form-data' ,
66
+ } ,
67
+ } )
68
+ return response . data
69
+ } ,
70
+ } )
71
+ return mutation
72
+ }
73
+
74
+ const VisuallyHiddenInput = styled ( 'input' ) ( {
75
+ clip : 'rect(0 0 0 0)' ,
76
+ clipPath : 'inset(50%)' ,
77
+ height : 1 ,
78
+ overflow : 'hidden' ,
79
+ position : 'absolute' ,
80
+ bottom : 0 ,
81
+ left : 0 ,
82
+ whiteSpace : 'nowrap' ,
83
+ width : 1 ,
84
+ } )
85
+
17
86
const Rag : React . FC = ( ) => {
87
+ const { data : indices , refetch } = useRagIndices ( )
88
+ const createIndexMutation = useCreateRagIndexMutation ( )
89
+ const deleteIndexMutation = useDeleteRagIndexMutation ( )
90
+ const [ indexName , setIndexName ] = useState ( '' )
91
+ const [ selectedIndex , setSelectedIndex ] = useState < RagIndexAttributes > ( null )
18
92
const [ inputValue , setInputValue ] = useState ( '' )
19
93
const [ response , setResponse ] = useState < RagResponse | null > ( null )
94
+ const uploadMutation = useUploadMutation ( selectedIndex )
95
+ const [ modalOpen , setModalOpen ] = useState ( false )
20
96
21
97
const handleSubmit = async ( event : React . FormEvent ) => {
22
98
event . preventDefault ( )
@@ -30,42 +106,135 @@ const Rag: React.FC = () => {
30
106
}
31
107
32
108
return (
33
- < Box
34
- component = "form"
35
- onSubmit = { handleSubmit }
36
- sx = { {
37
- display : 'flex' ,
38
- flexDirection : 'column' ,
39
- gap : 2 ,
40
- width : '300px' ,
41
- margin : '0 auto' ,
42
- } }
43
- >
44
- < TextField label = "Enter text" variant = "outlined" value = { inputValue } onChange = { ( e ) => setInputValue ( e . target . value ) } fullWidth />
45
- < Button type = "submit" variant = "contained" color = "primary" >
46
- Submit
47
- </ Button >
48
- { response && (
49
- < Box
50
- sx = { {
51
- marginTop : 2 ,
52
- padding : 2 ,
53
- border : '1px solid #ccc' ,
54
- borderRadius : '4px' ,
55
- } }
56
- >
57
- < Typography variant = "h6" > Response:</ Typography >
58
- < Typography variant = "body1" > Total: { response . total } </ Typography >
59
- { response . documents . map ( ( doc ) => (
60
- < Box key = { doc . id } sx = { { marginBottom : 1 } } >
61
- < Typography variant = "subtitle1" > { doc . value . title } </ Typography >
62
- < Typography variant = "body2" > { doc . value . content } </ Typography >
63
- < Typography variant = "caption" > Score: { doc . value . score } </ Typography >
64
- </ Box >
65
- ) ) }
109
+ < Box sx = { { display : 'flex' , gap : 2 } } >
110
+ < Dialog open = { ! ! selectedIndex && modalOpen } onClose = { ( ) => setModalOpen ( false ) } >
111
+ < DialogTitle > Edit { selectedIndex ?. metadata ?. name } </ DialogTitle >
112
+ < Box sx = { { padding : 2 , display : 'flex' , gap : 2 } } >
113
+ < Button component = "label" role = { undefined } variant = "contained" tabIndex = { - 1 } startIcon = { < CloudUpload /> } >
114
+ Upload files
115
+ < VisuallyHiddenInput
116
+ type = "file"
117
+ onChange = { async ( event ) => {
118
+ const files = event . target . files
119
+ console . log ( 'Files selected:' , files )
120
+ if ( files && files . length > 0 ) {
121
+ await uploadMutation . mutateAsync ( files )
122
+ refetch ( )
123
+ }
124
+ } }
125
+ multiple
126
+ />
127
+ </ Button >
128
+ < Button
129
+ variant = "text"
130
+ color = "error"
131
+ onClick = { async ( ) => {
132
+ if ( selectedIndex && window . confirm ( `Are you sure you want to delete index ${ selectedIndex . metadata . name } ?` ) ) {
133
+ await deleteIndexMutation . mutateAsync ( selectedIndex . id )
134
+ setSelectedIndex ( null )
135
+ refetch ( )
136
+ }
137
+ } }
138
+ >
139
+ Delete Index
140
+ </ Button >
141
+ </ Box >
142
+ </ Dialog >
143
+ < Box >
144
+ < Typography variant = "h4" mb = "1rem" >
145
+ RAG Indices
146
+ </ Typography >
147
+ < Box sx = { { display : 'flex' , gap : 2 , marginBottom : 2 } } >
148
+ < TextField label = "Index Name" variant = "outlined" value = { indexName } onChange = { ( e ) => setIndexName ( e . target . value ) } fullWidth />
149
+ < Button
150
+ variant = "contained"
151
+ color = "primary"
152
+ onClick = { async ( ) => {
153
+ await createIndexMutation . mutateAsync ( indexName )
154
+ setIndexName ( '' )
155
+ refetch ( )
156
+ } }
157
+ >
158
+ Create Index
159
+ </ Button >
66
160
</ Box >
67
- ) }
68
- ss
161
+ { indices ?. map ( ( index ) => (
162
+ < Paper
163
+ key = { index . id }
164
+ sx = { {
165
+ mb : 2 ,
166
+ p : 1 ,
167
+ outline : selectedIndex ?. id === index . id ? '2px solid blue' : 'none' ,
168
+ } }
169
+ elevation = { selectedIndex ?. id === index . id ? 4 : 2 }
170
+ >
171
+ < Table sx = { { mb : 1 } } >
172
+ < TableHead >
173
+ < TableRow >
174
+ < TableCell > ID</ TableCell >
175
+ < TableCell > Name</ TableCell >
176
+ < TableCell > Dim</ TableCell >
177
+ </ TableRow >
178
+ </ TableHead >
179
+ < TableBody >
180
+ < TableRow >
181
+ < TableCell > { index . id } </ TableCell >
182
+ < TableCell > { index . metadata . name } </ TableCell >
183
+ < TableCell > { index . metadata . dim } </ TableCell >
184
+ </ TableRow >
185
+ </ TableBody >
186
+ </ Table >
187
+ < Button disabled = { selectedIndex ?. id === index . id } onClick = { ( ) => setSelectedIndex ( index ) } >
188
+ { selectedIndex ?. id === index . id ? 'Selected' : 'Select' }
189
+ </ Button >
190
+ < IconButton
191
+ onClick = { ( ) => {
192
+ setSelectedIndex ( index )
193
+ setModalOpen ( true )
194
+ } }
195
+ >
196
+ < Settings />
197
+ </ IconButton >
198
+ </ Paper >
199
+ ) ) }
200
+ </ Box >
201
+ < Box
202
+ component = "form"
203
+ onSubmit = { handleSubmit }
204
+ sx = { {
205
+ display : 'flex' ,
206
+ flexDirection : 'column' ,
207
+ gap : 2 ,
208
+ width : '300px' ,
209
+ margin : '0 auto' ,
210
+ } }
211
+ >
212
+ < TextField label = "Enter text" variant = "outlined" value = { inputValue } onChange = { ( e ) => setInputValue ( e . target . value ) } fullWidth />
213
+ < Button type = "submit" variant = "contained" color = "primary" >
214
+ Submit
215
+ </ Button >
216
+ { response && (
217
+ < Box
218
+ sx = { {
219
+ marginTop : 2 ,
220
+ padding : 2 ,
221
+ border : '1px solid #ccc' ,
222
+ borderRadius : '4px' ,
223
+ } }
224
+ >
225
+ < Typography variant = "h6" > Response:</ Typography >
226
+ < Typography variant = "body1" > Total: { response . total } </ Typography >
227
+ { response . documents . map ( ( doc ) => (
228
+ < Box key = { doc . id } sx = { { marginBottom : 1 } } >
229
+ < Typography variant = "subtitle1" > { doc . value . title } </ Typography >
230
+ < Typography variant = "body2" > { doc . value . content } </ Typography >
231
+ < Typography variant = "caption" > Score: { doc . value . score } </ Typography >
232
+ </ Box >
233
+ ) ) }
234
+ </ Box >
235
+ ) }
236
+ ss
237
+ </ Box >
69
238
</ Box >
70
239
)
71
240
}
0 commit comments