@@ -34,23 +34,6 @@ interface ExperimentGroupViewProps {
3434}
3535
3636// Utility functions
37- function abbreviateStatus ( status : string ) : string {
38- const statusMap : Record < string , string > = {
39- running : 'R' ,
40- stopped : 'S' ,
41- pending : 'P' ,
42- starting : 'P' , // Pending (starting up)
43- failed : 'F' ,
44- succeeded : 'D' , // Done
45- init : 'S' , // Stopped
46- terminated : 'T' ,
47- cancelled : 'S' , // Stopped
48- unknown : '?' ,
49- } ;
50- const lower = status . toLowerCase ( ) ;
51- return statusMap [ lower ] || status . charAt ( 0 ) . toUpperCase ( ) ;
52- }
53-
5437function formatLargeNumber ( value : number ) : string {
5538 const absValue = Math . abs ( value ) ;
5639 if ( absValue >= 1_000_000_000 ) {
@@ -235,30 +218,31 @@ function StateTransition({ current, desired, experimentId, onSetDesiredState }:
235218 ) ;
236219 }
237220
238- const currentAbbrev = abbreviateStatus ( currentLower ) ;
239- const desiredAbbrev = abbreviateStatus ( desiredLower ) ;
221+ // Use full status text with smaller font
222+ const currentText = currentLower ;
223+ const desiredText = desiredLower ;
240224
241225 if ( currentLower === desiredLower ) {
242226 return (
243227 < span onClick = { handleClick } style = { { cursor : 'pointer' } } >
244- < span className = { `status-badge ${ currentLower } ` } title = { current } > { currentAbbrev } </ span >
228+ < span className = { `status-badge ${ currentLower } ` } style = { { fontSize : '10px' } } title = { current } > { currentText } </ span >
245229 </ span >
246230 ) ;
247231 }
248232
249233 if ( currentLower === 'failed' && desiredLower === 'stopped' ) {
250234 return (
251235 < span onClick = { handleClick } style = { { cursor : 'pointer' } } >
252- < span className = { `status-badge ${ currentLower } ` } title = { current } > { currentAbbrev } </ span >
236+ < span className = { `status-badge ${ currentLower } ` } style = { { fontSize : '10px' } } title = { current } > { currentText } </ span >
253237 </ span >
254238 ) ;
255239 }
256240
257241 return (
258242 < span onClick = { handleClick } style = { { cursor : 'pointer' } } >
259- < span className = { `status-badge ${ currentLower } ` } title = { current } > { currentAbbrev } </ span >
243+ < span className = { `status-badge ${ currentLower } ` } style = { { fontSize : '10px' } } title = { current } > { currentText } </ span >
260244 { ' → ' }
261- < span className = { `status-badge ${ desiredLower } ` } title = { desired } > { desiredAbbrev } </ span >
245+ < span className = { `status-badge ${ desiredLower } ` } style = { { fontSize : '10px' } } title = { desired } > { desiredText } </ span >
262246 </ span >
263247 ) ;
264248}
@@ -304,6 +288,8 @@ export function ExperimentGroupView({
304288 const [ flagDefinitions , setFlagDefinitions ] = useState < Array < { flag : string ; type : string ; default : unknown ; required : boolean } > > ( [ ] ) ;
305289 const [ editingResource , setEditingResource ] = useState < 'nodes' | 'gpus' | null > ( null ) ;
306290 const [ editedResourceValue , setEditedResourceValue ] = useState < string > ( '' ) ;
291+ const [ isEditingNamePrefix , setIsEditingNamePrefix ] = useState ( false ) ;
292+ const [ editedNamePrefix , setEditedNamePrefix ] = useState < string > ( '' ) ;
307293 const [ isEditingToolPath , setIsEditingToolPath ] = useState ( false ) ;
308294 const [ editedToolPath , setEditedToolPath ] = useState < string > ( '' ) ;
309295 const [ confirmingDelete , setConfirmingDelete ] = useState ( false ) ;
@@ -425,6 +411,22 @@ export function ExperimentGroupView({
425411 }
426412 } , [ experiments , apiCall , showNotification , onRefreshData ] ) ;
427413
414+ // Update group name_prefix
415+ const updateGroupNamePrefix = useCallback ( async ( value : string ) => {
416+ if ( ! group ) return ;
417+ try {
418+ await apiCall ( `/groups/${ group . id } ` , {
419+ method : 'PATCH' ,
420+ body : JSON . stringify ( { name_prefix : value || null } ) ,
421+ } ) ;
422+ showNotification ( `Updated name prefix for group` , 'success' ) ;
423+ onRefreshData ( ) ;
424+ } catch ( error ) {
425+ console . error ( 'Error updating name prefix:' , error ) ;
426+ showNotification ( 'Error updating name prefix' , 'error' ) ;
427+ }
428+ } , [ group , apiCall , showNotification , onRefreshData ] ) ;
429+
428430 // Delete a flag from all experiments in the group
429431 const deleteFlagForAll = useCallback ( async ( flagKey : string ) => {
430432 try {
@@ -790,7 +792,109 @@ export function ExperimentGroupView({
790792 { /* Common flags display - editable */ }
791793 { ( Object . keys ( commonFlags ) . length > 0 || commonNodes !== null || commonGpus !== null || commonToolPath !== null || ! isUngrouped ) && (
792794 < div style = { { display : 'flex' , flexWrap : 'wrap' , gap : '4px' , marginLeft : '8px' , alignItems : 'center' } } >
793- { /* Tool path first - editable */ }
795+ { /* Name prefix - editable */ }
796+ { ! isUngrouped && group && (
797+ isEditingNamePrefix ? (
798+ < span
799+ style = { {
800+ display : 'inline-flex' ,
801+ alignItems : 'center' ,
802+ gap : '4px' ,
803+ padding : '4px 8px' ,
804+ backgroundColor : '#fff' ,
805+ border : '1px solid #ff9800' ,
806+ borderRadius : '4px' ,
807+ fontSize : '11px' ,
808+ fontFamily : 'monospace' ,
809+ } }
810+ >
811+ < input
812+ type = "text"
813+ value = { editedNamePrefix }
814+ onChange = { e => setEditedNamePrefix ( e . target . value ) }
815+ onKeyDown = { e => {
816+ if ( e . key === 'Enter' ) {
817+ updateGroupNamePrefix ( editedNamePrefix ) ;
818+ setIsEditingNamePrefix ( false ) ;
819+ setEditedNamePrefix ( '' ) ;
820+ }
821+ if ( e . key === 'Escape' ) { setIsEditingNamePrefix ( false ) ; setEditedNamePrefix ( '' ) ; }
822+ } }
823+ autoFocus
824+ placeholder = "name prefix"
825+ style = { {
826+ border : 'none' ,
827+ outline : 'none' ,
828+ width : '150px' ,
829+ fontSize : '11px' ,
830+ fontFamily : 'monospace' ,
831+ padding : '2px 4px' ,
832+ backgroundColor : '#f5f5f5' ,
833+ borderRadius : '2px' ,
834+ } }
835+ />
836+ < span
837+ onClick = { ( ) => {
838+ updateGroupNamePrefix ( editedNamePrefix ) ;
839+ setIsEditingNamePrefix ( false ) ;
840+ setEditedNamePrefix ( '' ) ;
841+ } }
842+ style = { { cursor : 'pointer' , color : '#4CAF50' , fontWeight : 'bold' , padding : '0 2px' } }
843+ title = "Save"
844+ >
845+ ✓
846+ </ span >
847+ < span
848+ onClick = { ( ) => { setIsEditingNamePrefix ( false ) ; setEditedNamePrefix ( '' ) ; } }
849+ style = { { cursor : 'pointer' , color : '#999' , padding : '0 2px' } }
850+ title = "Cancel"
851+ >
852+ ✕
853+ </ span >
854+ </ span >
855+ ) : group . name_prefix ? (
856+ < span
857+ onClick = { ( ) => { setIsEditingNamePrefix ( true ) ; setEditedNamePrefix ( group . name_prefix || '' ) ; } }
858+ style = { {
859+ display : 'inline-block' ,
860+ padding : '6px 10px' ,
861+ backgroundColor : '#fff3e0' ,
862+ borderRadius : '4px' ,
863+ fontSize : '11px' ,
864+ fontFamily : 'monospace' ,
865+ color : '#e65100' ,
866+ whiteSpace : 'nowrap' ,
867+ cursor : 'pointer' ,
868+ transition : 'background-color 0.15s' ,
869+ } }
870+ onMouseEnter = { e => ( e . currentTarget . style . backgroundColor = '#ffe0b2' ) }
871+ onMouseLeave = { e => ( e . currentTarget . style . backgroundColor = '#fff3e0' ) }
872+ title = "Click to edit name prefix"
873+ >
874+ prefix: { group . name_prefix }
875+ </ span >
876+ ) : (
877+ < span
878+ onClick = { ( ) => { setIsEditingNamePrefix ( true ) ; setEditedNamePrefix ( '' ) ; } }
879+ style = { {
880+ display : 'inline-flex' ,
881+ alignItems : 'center' ,
882+ justifyContent : 'center' ,
883+ padding : '6px 10px' ,
884+ backgroundColor : '#fff3e0' ,
885+ borderRadius : '4px' ,
886+ fontSize : '11px' ,
887+ color : '#ff9800' ,
888+ cursor : 'pointer' ,
889+ fontStyle : 'italic' ,
890+ } }
891+ title = "Add name prefix"
892+ >
893+ + prefix
894+ </ span >
895+ )
896+ ) }
897+ { /* Tool path - editable */ }
794898 { commonToolPath !== null && ! isUngrouped && (
795899 isEditingToolPath ? (
796900 < span
0 commit comments