@@ -7,6 +7,32 @@ import Contenteditable from '../../../pages/ProjectView/Contenteditable'
7
7
import { debounce } from 'lodash'
8
8
import api from '../../../service/api'
9
9
import { toast } from 'sonner'
10
+ import { Button } from '@/components/ui/button'
11
+ import {
12
+ Dialog ,
13
+ DialogContent ,
14
+ DialogDescription ,
15
+ DialogFooter ,
16
+ DialogHeader ,
17
+ DialogTitle ,
18
+ DialogTrigger ,
19
+ } from '@/components/ui/dialog'
20
+ import { FolderDown } from 'lucide-react'
21
+ import { MultiSelect } from '@/components/ui/multi-select'
22
+ import {
23
+ Form ,
24
+ FormControl ,
25
+ FormDescription ,
26
+ FormField ,
27
+ FormItem ,
28
+ FormLabel ,
29
+ FormMessage ,
30
+ } from '@/components/ui/form'
31
+ import { useForm } from 'react-hook-form'
32
+ import { Separator } from '@/components/ui/separator'
33
+ import { Input } from '@/components/ui/input'
34
+ import { usePapaParse } from 'react-papaparse'
35
+ import { flattenObject , nestFlatKeys } from '../TableView/hooks/data-handler'
10
36
11
37
type ProjectDetails = {
12
38
name : string
@@ -90,7 +116,6 @@ const Project: React.FC = () => {
90
116
} ,
91
117
} )
92
118
const response_data = response . data
93
- console . log ( 'projectResults:' , response_data . results )
94
119
setProjectResults ( response_data . results )
95
120
} catch ( error ) {
96
121
console . error ( 'Error:' , error )
@@ -187,6 +212,85 @@ const Project: React.FC = () => {
187
212
}
188
213
}
189
214
215
+ const form = useForm ( )
216
+ const [ selectableHeaders , setSelectableHeaders ] = useState < string [ ] > ( [ ] )
217
+
218
+ useEffect ( ( ) => {
219
+ const allKeys = new Set < string > ( )
220
+ flattenObject ( projectResults ) . forEach ( ( obj ) => {
221
+ Object . keys ( obj ) . forEach ( ( key ) => allKeys . add ( key ) )
222
+ } )
223
+ setSelectableHeaders ( Array . from ( allKeys ) )
224
+ } , [ projectResults ] )
225
+
226
+ const { jsonToCSV, readString } = usePapaParse ( )
227
+
228
+ const handleJsonToCSV = ( ) => {
229
+ const selectedColumns = form . watch ( 'frameworks' ) || [ ]
230
+ const data = flattenObject ( projectResults )
231
+ if ( ! data . length ) return
232
+
233
+ const finalCols = selectableHeaders . reduce ( ( acc , col ) => {
234
+ acc . push ( col )
235
+ if ( selectedColumns . includes ( col ) ) acc . push ( `${ col } _truth` )
236
+ return acc
237
+ } , [ ] as string [ ] )
238
+
239
+ const finalData = data . map ( ( row ) =>
240
+ finalCols . reduce (
241
+ ( acc , col ) => {
242
+ acc [ col ] = row [ col ] ?? ( col . endsWith ( '_truth' ) ? 'Fill in ground truth' : 'NaN' )
243
+ return acc
244
+ } ,
245
+ { } as Record < string , unknown > ,
246
+ ) ,
247
+ )
248
+
249
+ const csvString = jsonToCSV ( finalData )
250
+ const blob = new Blob ( [ csvString ] , { type : 'text/csv;charset=utf-8;' } )
251
+ const url = URL . createObjectURL ( blob )
252
+ const link = document . createElement ( 'a' )
253
+ link . href = url
254
+ link . setAttribute ( 'download' , 'truth_template.csv' )
255
+ document . body . appendChild ( link )
256
+ link . click ( )
257
+ document . body . removeChild ( link )
258
+ }
259
+
260
+ // State and handlers for reading user’s CSV
261
+ const [ truthFile , setTruthFile ] = useState < File | null > ( null )
262
+
263
+ const handleFileChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
264
+ if ( ! e . target . files ?. length ) {
265
+ setTruthFile ( null )
266
+ return
267
+ }
268
+ setTruthFile ( e . target . files [ 0 ] )
269
+ }
270
+
271
+ const handleLoadCSV = ( ) => {
272
+ if ( ! truthFile ) return
273
+
274
+ // Read file contents
275
+ const reader = new FileReader ( )
276
+ reader . onload = ( e ) => {
277
+ if ( ! e . target ?. result ) return
278
+
279
+ const csvString = e . target . result as string
280
+ readString ( csvString , {
281
+ header : true ,
282
+ complete : ( results ) => {
283
+ results . data . forEach ( ( value : unknown ) => {
284
+ const row = value as Record < string , unknown >
285
+ const nestedRow = nestFlatKeys ( row )
286
+ console . log ( nestedRow )
287
+ } )
288
+ } ,
289
+ } )
290
+ }
291
+ reader . readAsText ( truthFile )
292
+ }
293
+
190
294
return (
191
295
< main className = { `${ loading ? 'blur-sm' : '' } ` } >
192
296
< div className = 'navbar bg-base-100 flex flex-col sm:flex-row' >
@@ -205,7 +309,77 @@ const Project: React.FC = () => {
205
309
< div className = 'navbar-center text-center' >
206
310
< div className = 'flex flex-row justify-center ' > { project . id } </ div >
207
311
</ div >
208
- < div className = 'md:navbar-end z-10 max-sm:pt-4' > </ div >
312
+ < div className = 'md:navbar-end z-10 max-md:pt-4' >
313
+ < Dialog >
314
+ < DialogTrigger asChild >
315
+ < Button >
316
+ < FolderDown />
317
+ Load truth
318
+ </ Button >
319
+ </ DialogTrigger >
320
+ < DialogContent className = 'sm:max-w-[600px]' >
321
+ < DialogHeader >
322
+ < DialogTitle > Create truth input</ DialogTitle >
323
+ < DialogDescription >
324
+ Select the columns you want to input a ground truth for and then export the CSV
325
+ file. After editing, upload your modified ground truth to obtain an accuracy score
326
+ on your features.
327
+ </ DialogDescription >
328
+ </ DialogHeader >
329
+
330
+ < Form { ...form } >
331
+ < form onSubmit = { form . handleSubmit ( ( ) => { } ) } className = 'space-y-8' >
332
+ < FormField
333
+ control = { form . control }
334
+ name = 'frameworks'
335
+ render = { ( { field } ) => (
336
+ < FormItem >
337
+ < FormLabel > Columns</ FormLabel >
338
+ < FormControl >
339
+ < MultiSelect
340
+ options = { selectableHeaders . map ( ( feature ) => ( {
341
+ label : feature . replace ( / \s / g, ' → ' ) . toLowerCase ( ) ,
342
+ value : feature ,
343
+ } ) ) }
344
+ onValueChange = { field . onChange }
345
+ defaultValue = { field . value }
346
+ placeholder = 'Select columns'
347
+ variant = 'inverted'
348
+ maxCount = { 3 }
349
+ />
350
+ </ FormControl >
351
+ < FormDescription >
352
+ Choose the columns/features you are interested in.
353
+ </ FormDescription >
354
+ < FormMessage />
355
+ </ FormItem >
356
+ ) }
357
+ />
358
+ < DialogFooter >
359
+ < Button type = 'submit' onClick = { handleJsonToCSV } >
360
+ Export CSV Base Template
361
+ </ Button >
362
+ </ DialogFooter >
363
+ </ form >
364
+ </ Form >
365
+
366
+ < Separator />
367
+
368
+ < div className = 'space-y-2' >
369
+ < div className = 'font-semibold' > Import Ground Truth</ div >
370
+ < p className = 'text-sm text-muted-foreground' >
371
+ Upload your modified ground truth CSV to score your features.
372
+ </ p >
373
+ < Input type = 'file' accept = '.csv' onChange = { handleFileChange } />
374
+ < DialogFooter >
375
+ < Button type = 'submit' onClick = { handleLoadCSV } >
376
+ Load CSV Ground Truth
377
+ </ Button >
378
+ </ DialogFooter >
379
+ </ div >
380
+ </ DialogContent >
381
+ </ Dialog >
382
+ </ div >
209
383
</ div >
210
384
< hr > </ hr >
211
385
0 commit comments