@@ -33,7 +33,12 @@ import {
3333 ModalVariant ,
3434 ModalBody ,
3535 ModalFooter ,
36- ModalHeader
36+ ModalHeader ,
37+ Dropdown ,
38+ DropdownItem ,
39+ DropdownList ,
40+ MenuToggle ,
41+ MenuToggleElement
3742} from '@patternfly/react-core' ;
3843import {
3944 ExternalLinkAltIcon ,
@@ -42,7 +47,8 @@ import {
4247 CatalogIcon ,
4348 PencilAltIcon ,
4449 UploadIcon ,
45- TrashIcon
50+ TrashIcon ,
51+ BalanceScaleIcon
4652} from '@patternfly/react-icons' ;
4753import { ExpandableSection } from '@patternfly/react-core/dist/esm/components/ExpandableSection/ExpandableSection' ;
4854import { v4 as uuidv4 } from 'uuid' ;
@@ -77,6 +83,18 @@ const DashboardNative: React.FunctionComponent = () => {
7783 const [ isEditModalOpen , setIsEditModalOpen ] = React . useState ( false ) ;
7884 const [ expandedFiles , setExpandedFiles ] = React . useState < Record < string , boolean > > ( { } ) ;
7985
86+ // State Variables for Evaluate Checkpoint
87+ const [ isEvalModalOpen , setIsEvalModalOpen ] = React . useState < boolean > ( false ) ;
88+ const [ checkpoints , setCheckpoints ] = React . useState < string [ ] > ( [ ] ) ;
89+ const [ isCheckpointsLoading , setIsCheckpointsLoading ] = React . useState < boolean > ( false ) ;
90+ const [ checkpointsError , setCheckpointsError ] = React . useState < string | null > ( null ) ;
91+ const [ isDropdownOpen , setIsDropdownOpen ] = React . useState < boolean > ( false ) ;
92+ const [ selectedCheckpoint , setSelectedCheckpoint ] = React . useState < string | null > ( null ) ;
93+
94+ // QnA eval result
95+ const [ qnaEvalResult , setQnaEvalResult ] = React . useState < string > ( '' ) ;
96+ const [ isQnaLoading , setIsQnaLoading ] = React . useState < boolean > ( false ) ;
97+
8098 const router = useRouter ( ) ;
8199
82100 // Fetch branches from the API route
@@ -285,6 +303,100 @@ const DashboardNative: React.FunctionComponent = () => {
285303 } ) ) ;
286304 } ;
287305
306+ const handleOpenEvalModal = ( ) => {
307+ setIsEvalModalOpen ( true ) ;
308+ fetchCheckpoints ( ) ;
309+ } ;
310+
311+ const handleCloseEvalModal = ( ) => {
312+ setIsEvalModalOpen ( false ) ;
313+ setCheckpoints ( [ ] ) ;
314+ setCheckpointsError ( null ) ;
315+ setSelectedCheckpoint ( null ) ;
316+ setQnaEvalResult ( '' ) ;
317+ setIsQnaLoading ( false ) ;
318+ } ;
319+
320+ // **New Function to Fetch Checkpoints from API Route**
321+ const fetchCheckpoints = async ( ) => {
322+ setIsCheckpointsLoading ( true ) ;
323+ setCheckpointsError ( null ) ;
324+ try {
325+ const response = await fetch ( '/api/native/eval/checkpoints' ) ;
326+ console . log ( 'Response status:' , response . status ) ;
327+ const data = await response . json ( ) ;
328+ console . log ( 'Checkpoints data:' , data ) ;
329+
330+ if ( response . ok ) {
331+ // Assuming the API returns an array of checkpoints
332+ if ( Array . isArray ( data ) && data . length > 0 ) {
333+ setCheckpoints ( data ) ;
334+ console . log ( 'Checkpoints set successfully:' , data ) ;
335+ } else {
336+ setCheckpoints ( [ ] ) ;
337+ console . log ( 'No checkpoints returned from API.' ) ;
338+ }
339+ } else {
340+ setCheckpointsError ( data . error || 'Failed to fetch checkpoints.' ) ;
341+ console . error ( 'Error fetching checkpoints:' , data . error || 'Failed to fetch checkpoints.' ) ;
342+ }
343+ } catch ( error ) {
344+ console . error ( 'Error fetching checkpoints:' , error ) ;
345+ setCheckpointsError ( 'Unable to reach the checkpoints endpoint.' ) ;
346+ } finally {
347+ setIsCheckpointsLoading ( false ) ;
348+ }
349+ } ;
350+
351+ // Checkpoint select dropdown
352+ const onDropdownToggle = ( isOpen : boolean ) => setIsDropdownOpen ( isOpen ) ;
353+ const onSelectCheckpoint = ( event : React . MouseEvent < Element , MouseEvent > , selection : string ) => {
354+ setSelectedCheckpoint ( selection ) ;
355+ setIsDropdownOpen ( false ) ;
356+ } ;
357+
358+ const handleEvaluateQnA = async ( ) => {
359+ if ( ! selectedCheckpoint ) {
360+ addDangerAlert ( 'Please select a checkpoint to evaluate.' ) ;
361+ return ;
362+ }
363+
364+ setIsQnaLoading ( true ) ;
365+ setQnaEvalResult ( '' ) ;
366+
367+ // TODO: dynamically prepend the checkpoint path
368+ const selectedModelDir = '/var/home/cloud-user/.local/share/instructlab/checkpoints/hf_format/' + selectedCheckpoint ;
369+
370+ console . log ( '[CLIENT] Sending to /api/native/eval/qna:' , selectedModelDir ) ;
371+
372+ try {
373+ const res = await fetch ( '/api/native/eval/qna' , {
374+ method : 'POST' ,
375+ headers : { 'Content-Type' : 'application/json' } ,
376+ body : JSON . stringify ( { selectedModelDir } )
377+ } ) ;
378+
379+ const data = await res . json ( ) ;
380+ console . log ( '[CLIENT] Response from /api/native/eval/qna:' , data ) ;
381+
382+ if ( ! res . ok ) {
383+ addDangerAlert ( data . error || 'Failed to evaluate QnA.' ) ;
384+ } else {
385+ if ( data . result ) {
386+ setQnaEvalResult ( data . result ) ;
387+ addSuccessAlert ( 'QnA Evaluation succeeded!' ) ;
388+ } else {
389+ setQnaEvalResult ( 'Evaluation completed (no result field).' ) ;
390+ }
391+ }
392+ } catch ( error ) {
393+ console . error ( 'Error evaluating QnA:' , error ) ;
394+ addDangerAlert ( 'Error evaluating QnA.' ) ;
395+ } finally {
396+ setIsQnaLoading ( false ) ;
397+ }
398+ } ;
399+
288400 return (
289401 < div >
290402 < PageBreadcrumb hasBodyWrapper = { false } >
@@ -397,6 +509,9 @@ const DashboardNative: React.FunctionComponent = () => {
397509 < Tooltip aria = "none" aria-live = "polite" content = { < div > Delete</ div > } >
398510 < Button icon = { < TrashIcon /> } variant = "plain" aria-label = "delete" onClick = { ( ) => handleDeleteContribution ( branch . name ) } />
399511 </ Tooltip >
512+ < Tooltip aria = "none" aria-live = "polite" content = { < div > Evaluate QnA Checkpoint</ div > } >
513+ < Button icon = { < BalanceScaleIcon /> } variant = "plain" aria-label = "evaluate" onClick = { handleOpenEvalModal } />
514+ </ Tooltip >
400515 </ FlexItem >
401516 </ Flex >
402517 </ CardBody >
@@ -412,6 +527,84 @@ const DashboardNative: React.FunctionComponent = () => {
412527 </ PageSection >
413528 ) }
414529
530+ { /* Evaluate Checkpoint Modal */ }
531+ < Modal
532+ variant = { ModalVariant . medium }
533+ title = "Evaluate Checkpoint"
534+ isOpen = { isEvalModalOpen }
535+ onClose = { handleCloseEvalModal }
536+ aria-labelledby = "evaluate-checkpoint-modal-title"
537+ aria-describedby = "evaluate-checkpoint-modal-body"
538+ >
539+ < ModalHeader title = "Evaluate Checkpoint" />
540+ < ModalBody id = "evaluate-checkpoint-modal-body" >
541+ { isCheckpointsLoading ? (
542+ < Spinner size = "lg" aria-label = "Loading checkpoints" />
543+ ) : checkpointsError ? (
544+ < Alert variant = "danger" title = { checkpointsError } isInline />
545+ ) : (
546+ < >
547+ < div style = { { marginBottom : '1rem' } } >
548+ < label style = { { display : 'block' , marginBottom : '0.4rem' } } > Select a Checkpoint:</ label >
549+ < Dropdown
550+ isOpen = { isDropdownOpen }
551+ onSelect = { onSelectCheckpoint }
552+ onOpenChange = { onDropdownToggle }
553+ toggle = { ( toggleRef : React . Ref < MenuToggleElement > ) => (
554+ < MenuToggle ref = { toggleRef } onClick = { ( ) => onDropdownToggle ( ! isDropdownOpen ) } isExpanded = { isDropdownOpen } >
555+ { selectedCheckpoint || 'Select a Checkpoint' }
556+ </ MenuToggle >
557+ ) }
558+ ouiaId = "EvaluateCheckpointDropdown"
559+ shouldFocusToggleOnSelect
560+ >
561+ < DropdownList >
562+ { checkpoints . length > 0 ? (
563+ checkpoints . map ( ( checkpoint ) => (
564+ < DropdownItem key = { checkpoint } value = { checkpoint } >
565+ { checkpoint }
566+ </ DropdownItem >
567+ ) )
568+ ) : (
569+ < DropdownItem key = "no-checkpoints" isDisabled >
570+ No checkpoints available
571+ </ DropdownItem >
572+ ) }
573+ </ DropdownList >
574+ </ Dropdown >
575+ </ div >
576+
577+ { /* Display the evaluation result */ }
578+ { qnaEvalResult && (
579+ < div style = { { marginTop : '1rem' } } >
580+ < b > Evaluation Output:</ b >
581+ < pre
582+ style = { {
583+ marginTop : '0.5rem' ,
584+ backgroundColor : '#f5f5f5' ,
585+ padding : '1rem' ,
586+ borderRadius : '4px' ,
587+ maxHeight : '300px' ,
588+ overflowY : 'auto'
589+ } }
590+ >
591+ { qnaEvalResult }
592+ </ pre >
593+ </ div >
594+ ) }
595+ </ >
596+ ) }
597+ </ ModalBody >
598+ < ModalFooter >
599+ < Button key = "evaluateQnA" variant = "primary" onClick = { handleEvaluateQnA } isDisabled = { ! selectedCheckpoint || isQnaLoading } >
600+ { isQnaLoading ? 'Evaluating...' : 'Evaluate' }
601+ </ Button >
602+ < Button key = "cancel" variant = "secondary" onClick = { handleCloseEvalModal } >
603+ Cancel
604+ </ Button >
605+ </ ModalFooter >
606+ </ Modal >
607+
415608 < Modal
416609 variant = { ModalVariant . medium }
417610 title = { `Files Contained in Branch: ${ diffData ?. branch } ` }
@@ -486,7 +679,7 @@ const DashboardNative: React.FunctionComponent = () => {
486679 >
487680 < ModalHeader title = "Deleting Contribution" labelId = "delete-contribution-modal-title" titleIconVariant = "warning" />
488681 < ModalBody id = "delete-contribution-body-variant" >
489- < p > are you sure you want to delete this contribution?</ p >
682+ < p > Are you sure you want to delete this contribution?</ p >
490683 </ ModalBody >
491684 < ModalFooter >
492685 < Button key = "confirm" variant = "primary" onClick = { ( ) => handleDeleteContributionConfirm ( ) } >
@@ -509,7 +702,7 @@ const DashboardNative: React.FunctionComponent = () => {
509702 >
510703 < ModalHeader title = "Publishing Contribution" labelId = "publish-contribution-modal-title" titleIconVariant = "warning" />
511704 < ModalBody id = "publish-contribution-body-variant" >
512- < p > are you sure you want to publish contribution to remote taxonomy repository present at : { taxonomyRepoDir } ?</ p >
705+ < p > Are you sure you want to publish contribution to remote taxonomy repository present at : { taxonomyRepoDir } ?</ p >
513706 </ ModalBody >
514707 < ModalFooter >
515708 < Button key = "confirm" variant = "primary" onClick = { ( ) => handlePublishContributionConfirm ( ) } >
0 commit comments