@@ -7,6 +7,32 @@ import Contenteditable from '../../../pages/ProjectView/Contenteditable'
77import { debounce } from 'lodash'
88import api from '../../../service/api'
99import { 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'
1036
1137type ProjectDetails = {
1238 name : string
@@ -90,7 +116,6 @@ const Project: React.FC = () => {
90116 } ,
91117 } )
92118 const response_data = response . data
93- console . log ( 'projectResults:' , response_data . results )
94119 setProjectResults ( response_data . results )
95120 } catch ( error ) {
96121 console . error ( 'Error:' , error )
@@ -187,6 +212,85 @@ const Project: React.FC = () => {
187212 }
188213 }
189214
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+
190294 return (
191295 < main className = { `${ loading ? 'blur-sm' : '' } ` } >
192296 < div className = 'navbar bg-base-100 flex flex-col sm:flex-row' >
@@ -205,7 +309,77 @@ const Project: React.FC = () => {
205309 < div className = 'navbar-center text-center' >
206310 < div className = 'flex flex-row justify-center ' > { project . id } </ div >
207311 </ 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 >
209383 </ div >
210384 < hr > </ hr >
211385
0 commit comments