@@ -20,7 +20,10 @@ use ratatui::style::{Modifier, Style};
2020use ratatui:: text:: { Line , Span } ;
2121use ratatui:: widgets:: { Paragraph , Wrap } ;
2222
23- use crate :: app:: { KeyAction , PaneFocus } ;
23+ use rocm_dash_core:: metrics:: InstanceStatus ;
24+
25+ use crate :: app:: { AppState , KeyAction , PaneFocus } ;
26+ use crate :: ui:: format;
2427use crate :: ui:: panel:: { self , BoxRole } ;
2528use crate :: ui:: theme:: Theme ;
2629
@@ -82,12 +85,13 @@ pub fn draw(
8285 list_title : & str ,
8386 verbs : & [ Verb ] ,
8487 sel : usize ,
85- focus : PaneFocus ,
88+ state : & AppState ,
8689 theme : & Theme ,
8790) {
91+ let focus = state. pane_focus ;
8892 let cols = split_columns ( area) ;
8993 draw_verb_list ( f, cols[ 0 ] , list_title, verbs, sel, focus, theme) ;
90- draw_detail ( f, cols[ 1 ] , verbs, sel, focus, theme) ;
94+ draw_detail ( f, cols[ 1 ] , verbs, sel, focus, state , theme) ;
9195}
9296
9397/// Map a left-click in a domain tab's body to an action.
@@ -196,6 +200,7 @@ fn draw_detail(
196200 verbs : & [ Verb ] ,
197201 sel : usize ,
198202 focus : PaneFocus ,
203+ state : & AppState ,
199204 theme : & Theme ,
200205) {
201206 let sel = sel. min ( verbs. len ( ) . saturating_sub ( 1 ) ) ;
@@ -215,13 +220,22 @@ fn draw_detail(
215220 let mut lines: Vec < Line > = vec ! [
216221 Line :: from( Span :: styled( v. summary, Style :: default ( ) . fg( theme. fg) ) ) ,
217222 Line :: from( "" ) ,
218- Line :: from( Span :: styled(
219- "What you'll do" ,
220- Style :: default ( )
221- . fg( theme. muted)
222- . add_modifier( Modifier :: BOLD ) ,
223- ) ) ,
224223 ] ;
224+
225+ // State-aware block: what's actually installed / known / running for this
226+ // verb, so the pane reflects the system rather than fixed copy.
227+ let live = live_lines ( v. action , state, theme) ;
228+ if !live. is_empty ( ) {
229+ lines. extend ( live) ;
230+ lines. push ( Line :: from ( "" ) ) ;
231+ }
232+
233+ lines. push ( Line :: from ( Span :: styled (
234+ "What you'll do" ,
235+ Style :: default ( )
236+ . fg ( theme. muted )
237+ . add_modifier ( Modifier :: BOLD ) ,
238+ ) ) ) ;
225239 for ( i, step) in v. steps . iter ( ) . enumerate ( ) {
226240 lines. push ( Line :: from ( vec ! [
227241 Span :: styled(
@@ -281,3 +295,147 @@ fn draw_detail(
281295
282296 f. render_widget ( Paragraph :: new ( lines) . wrap ( Wrap { trim : false } ) , inner) ;
283297}
298+
299+ /// State-aware "current status" lines for a verb, or empty when the verb has no
300+ /// live view.
301+ ///
302+ /// Surfaces what's actually installed (ROCm/engines), known (model catalog), or
303+ /// running (instances) so the detail pane reflects the system — including the
304+ /// running-instances view inline, without opening the services popup.
305+ fn live_lines ( action : KeyAction , state : & AppState , theme : & Theme ) -> Vec < Line < ' static > > {
306+ let head = |t : String | {
307+ Line :: from ( Span :: styled (
308+ t,
309+ Style :: default ( )
310+ . fg ( theme. muted )
311+ . add_modifier ( Modifier :: BOLD ) ,
312+ ) )
313+ } ;
314+ let mark = |ok : bool | {
315+ if ok {
316+ Span :: styled ( "✓ " , Style :: default ( ) . fg ( theme. ok ) )
317+ } else {
318+ Span :: styled ( "· " , Style :: default ( ) . fg ( theme. muted ) )
319+ }
320+ } ;
321+ let info = || {
322+ state
323+ . latest
324+ . as_ref ( )
325+ . and_then ( |s| s. gpu_system_info . as_ref ( ) )
326+ } ;
327+
328+ match action {
329+ KeyAction :: OpenInstall => {
330+ let mut lines = vec ! [ head( "Installed now" . into( ) ) ] ;
331+ let rocm = info ( ) . and_then ( |i| i. rocm_version . clone ( ) ) ;
332+ lines. push ( Line :: from ( vec ! [
333+ mark( rocm. is_some( ) ) ,
334+ Span :: styled(
335+ rocm. map_or_else( || "ROCm — not detected" . into( ) , |v| format!( "ROCm {v}" ) ) ,
336+ Style :: default ( ) . fg( theme. fg) ,
337+ ) ,
338+ ] ) ) ;
339+ let driver = info ( ) . and_then ( |i| i. driver_version . clone ( ) ) ;
340+ lines. push ( Line :: from ( vec ! [
341+ mark( driver. is_some( ) ) ,
342+ Span :: styled(
343+ driver. map_or_else( || "Driver —" . into( ) , |v| format!( "Driver {v}" ) ) ,
344+ Style :: default ( ) . fg( theme. fg) ,
345+ ) ,
346+ ] ) ) ;
347+ if !state. runtimes . is_empty ( ) {
348+ lines. push ( head ( format ! ( "{} runtime(s)" , state. runtimes. len( ) ) ) ) ;
349+ for rt in state. runtimes . iter ( ) . take ( 4 ) {
350+ lines. push ( Line :: from ( vec ! [
351+ Span :: styled(
352+ if rt. active { "● " } else { " " } ,
353+ Style :: default ( ) . fg( theme. ok) ,
354+ ) ,
355+ Span :: styled(
356+ format!( "{} ({} {})" , rt. key, rt. channel, rt. version) ,
357+ Style :: default ( ) . fg( theme. fg) ,
358+ ) ,
359+ ] ) ) ;
360+ }
361+ }
362+ lines
363+ }
364+ KeyAction :: OpenEngineManager => {
365+ let mut lines = vec ! [ head( "Engines" . into( ) ) ] ;
366+ let lemon = info ( ) . and_then ( |i| i. lemond_version . clone ( ) ) ;
367+ lines. push ( Line :: from ( vec ! [
368+ mark( lemon. is_some( ) ) ,
369+ Span :: styled(
370+ lemon. map_or_else(
371+ || "Lemonade — not installed" . into( ) ,
372+ |v| format!( "Lemonade {v}" ) ,
373+ ) ,
374+ Style :: default ( ) . fg( theme. fg) ,
375+ ) ,
376+ ] ) ) ;
377+ // The daemon doesn't surface vLLM detection yet — open the manager to
378+ // check rather than claiming a status we don't have.
379+ lines. push ( Line :: from ( vec ! [
380+ Span :: styled( "· " , Style :: default ( ) . fg( theme. muted) ) ,
381+ Span :: styled( "vLLM — open to check" , Style :: default ( ) . fg( theme. muted) ) ,
382+ ] ) ) ;
383+ lines
384+ }
385+ KeyAction :: OpenServeWizard => {
386+ let n = state. model_recipes . len ( ) ;
387+ if n == 0 {
388+ return Vec :: new ( ) ;
389+ }
390+ let mut lines = vec ! [ head( format!( "Model catalog — {n} known" ) ) ] ;
391+ for r in state. model_recipes . iter ( ) . take ( 4 ) {
392+ lines. push ( Line :: from ( vec ! [
393+ Span :: styled( "• " , Style :: default ( ) . fg( theme. accent_2) ) ,
394+ Span :: styled( r. id. clone( ) , Style :: default ( ) . fg( theme. fg) ) ,
395+ ] ) ) ;
396+ }
397+ if n > 4 {
398+ lines. push ( Line :: from ( Span :: styled (
399+ format ! ( " …and {} more" , n - 4 ) ,
400+ Style :: default ( ) . fg ( theme. muted ) ,
401+ ) ) ) ;
402+ }
403+ lines
404+ }
405+ KeyAction :: OpenServices => {
406+ let running: Vec < _ > = state
407+ . instances
408+ . values ( )
409+ . filter ( |i| i. status == InstanceStatus :: Running )
410+ . collect ( ) ;
411+ let mut lines = vec ! [ head( format!( "Running now — {}" , running. len( ) ) ) ] ;
412+ if running. is_empty ( ) {
413+ lines. push ( Line :: from ( Span :: styled (
414+ " none running" ,
415+ Style :: default ( ) . fg ( theme. muted ) ,
416+ ) ) ) ;
417+ } else {
418+ for i in running. iter ( ) . take ( 5 ) {
419+ let port = i. port . map_or_else ( String :: new, |p| format ! ( " :{p}" ) ) ;
420+ lines. push ( Line :: from ( vec ! [
421+ Span :: styled( "● " , Style :: default ( ) . fg( theme. ok) ) ,
422+ Span :: styled( i. model_name. clone( ) , Style :: default ( ) . fg( theme. fg) ) ,
423+ Span :: styled( port, Style :: default ( ) . fg( theme. muted) ) ,
424+ Span :: styled(
425+ format!( " gen {}" , format:: tps_opt( i. gen_tps) ) ,
426+ Style :: default ( ) . fg( theme. muted) ,
427+ ) ,
428+ ] ) ) ;
429+ }
430+ if running. len ( ) > 5 {
431+ lines. push ( Line :: from ( Span :: styled (
432+ format ! ( " …and {} more" , running. len( ) - 5 ) ,
433+ Style :: default ( ) . fg ( theme. muted ) ,
434+ ) ) ) ;
435+ }
436+ }
437+ lines
438+ }
439+ _ => Vec :: new ( ) ,
440+ }
441+ }
0 commit comments