feat: correx-managed model lifecycle slice 5 — Go TUI model swap + telemetry
Go TUI decodes model.changed / model.list / resource.status and renders a status-bar VRAM/GPU%/RAM gauge plus a models overlay (m key / palette 'models'): lists configured models with the resident one marked, enter swaps (SwapModel), c clears the pin (ClearModelPin). All three new server messages are non-event-bearing control/gauge frames, applied immediately like provider.status_changed. Server addition required for the picker: ServerMessage.ModelList (model.list, NonEventMessage) sent once in the initial snapshot from ManagedInferenceRouter.availableModelIds()/currentModelId() — the TUI otherwise cannot enumerate swap targets. Completes the 5-slice model-lifecycle feature. ./gradlew check green; go build/vet clean. Plan: docs/plans/2026-05-31-model-lifecycle-management.md (slice 5 of 5).
This commit is contained in:
@@ -144,6 +144,8 @@ func (m Model) handleNormalKey(k tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
m.overlayEventIdx = 0
|
||||
case "t":
|
||||
m.overlay = OverlayToolPalette
|
||||
case "m":
|
||||
m.openModelsOverlay()
|
||||
case "w":
|
||||
if ds == StateIdle {
|
||||
m.wfVisible = !m.wfVisible
|
||||
@@ -333,10 +335,43 @@ func (m Model) handleOverlayKey(k tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
if runeIs(k, "t") {
|
||||
m.overlay = OverlayNone
|
||||
}
|
||||
case OverlayModels:
|
||||
switch {
|
||||
case k.Type == tea.KeyUp || runeIs(k, "k"):
|
||||
if m.modelsIndex > 0 {
|
||||
m.modelsIndex--
|
||||
}
|
||||
case k.Type == tea.KeyDown || runeIs(k, "j"):
|
||||
if m.modelsIndex < len(m.availableModels)-1 {
|
||||
m.modelsIndex++
|
||||
}
|
||||
case k.Type == tea.KeyEnter:
|
||||
if m.modelsIndex >= 0 && m.modelsIndex < len(m.availableModels) {
|
||||
m.client.Send(protocol.SwapModel(m.availableModels[m.modelsIndex]))
|
||||
m.overlay = OverlayNone
|
||||
}
|
||||
case runeIs(k, "c"):
|
||||
m.client.Send(protocol.ClearModelPin())
|
||||
m.overlay = OverlayNone
|
||||
case runeIs(k, "m"):
|
||||
m.overlay = OverlayNone
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// openModelsOverlay opens the model picker, pre-selecting the resident model.
|
||||
func (m *Model) openModelsOverlay() {
|
||||
m.overlay = OverlayModels
|
||||
m.modelsIndex = 0
|
||||
for i, id := range m.availableModels {
|
||||
if id == m.currentModel {
|
||||
m.modelsIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runeIs(k tea.KeyMsg, s string) bool {
|
||||
return k.Type == tea.KeyRunes && string(k.Runes) == s
|
||||
}
|
||||
@@ -374,6 +409,7 @@ func paletteCommands() []paletteCmd {
|
||||
return []paletteCmd{
|
||||
{"workflows", "start workflow", "open the workflow picker"},
|
||||
{"tools", "tool palette", "tools for the current stage"},
|
||||
{"models", "swap model", "pick / pin the local model"},
|
||||
{"events", "event inspector", "browse the event stream"},
|
||||
{"mode", "toggle mode", "switch chat / steering"},
|
||||
{"cancel", "cancel session", "stop the selected session"},
|
||||
@@ -404,6 +440,8 @@ func (m Model) execPalette(id string) (tea.Model, tea.Cmd) {
|
||||
m.wfIndex = 0
|
||||
case "tools":
|
||||
m.overlay = OverlayToolPalette
|
||||
case "models":
|
||||
m.openModelsOverlay()
|
||||
case "events":
|
||||
m.overlay = OverlayEventInspector
|
||||
m.overlayEventIdx = 0
|
||||
|
||||
Reference in New Issue
Block a user