Files
agents_and_robots/pkg/tui/view.go
T
egutierrez b6fe4f9135 feat: ejecutar tests desde el dashboard TUI
Se añade opción "Run Tests" al menú del servidor en el dashboard TUI.
Ejecuta `go test -tags goolm -count=1 ./...` y muestra los resultados
en una pantalla dedicada (ScreenTestOutput) con scroll y opción de re-ejecutar.

Cambios:
- pkg/tui: nuevo MsgTestsDone, ScreenTestOutput, IntentRunTests, updateTestOutput
- pkg/tui/view.go: viewTestOutput con scroll y controles (↑↓ r 0)
- shell/tui/adapter.go: runTests() ejecuta go test con el env del manager
- shell/process/manager.go: buildEnv → BuildEnv (exportado) para que el adapter
  pueda construir el env completo con las variables de .env

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 02:21:17 +00:00

288 lines
6.2 KiB
Go

package tui
import (
"fmt"
"strings"
)
// View is PURE: Model → string. No side effects.
func View(model Model) string {
switch model.Screen {
case ScreenMain:
return viewMain(model)
case ScreenAgentList:
return viewAgentList(model)
case ScreenAgentActions:
return viewAgentActions(model)
case ScreenLogs:
return viewLogs(model)
case ScreenServer:
return viewServer(model)
case ScreenTestOutput:
return viewTestOutput(model)
default:
return ""
}
}
func viewMain(m Model) string {
var b strings.Builder
b.WriteString("\n Bot Server Dashboard\n")
b.WriteString(" " + strings.Repeat("─", 36) + "\n")
// Summary
running, stopped, disabled := countStatuses(m.Agents)
total := len(m.Agents)
if total > 0 {
b.WriteString(fmt.Sprintf(" %d agents (%d running, %d stopped, %d disabled)\n\n",
total, running, stopped, disabled))
} else {
b.WriteString(" Loading...\n\n")
}
// Menu
for i, opt := range MainMenuOptions() {
cursor := " "
if i == m.Cursor {
cursor = "> "
}
b.WriteString(fmt.Sprintf(" %s%-16s %s\n", cursor, opt.Label, opt.Desc))
}
b.WriteString("\n ↑↓ navegar enter seleccionar q salir\n")
return b.String()
}
func viewAgentList(m Model) string {
var b strings.Builder
b.WriteString("\n Agents\n")
b.WriteString(" " + strings.Repeat("─", 60) + "\n")
if len(m.Agents) == 0 {
b.WriteString(" No agents found.\n")
}
for i, a := range m.Agents {
cursor := " "
if i == m.Cursor {
cursor = "> "
}
icon := "○"
status := "stopped"
if !a.Enabled {
icon = " "
status = "disabled"
} else if a.Running {
icon = "●"
if a.Instances > 1 {
status = fmt.Sprintf("running %d instances", a.Instances)
} else {
status = fmt.Sprintf("running PID %d", a.PID)
}
}
b.WriteString(fmt.Sprintf(" %s%s %-20s %-8s %s\n",
cursor, icon, a.ID, a.Version, status))
}
if m.StatusMsg != "" {
b.WriteString("\n " + m.StatusMsg + "\n")
}
b.WriteString("\n ↑↓ navegar enter acciones 0 volver\n")
return b.String()
}
func viewAgentActions(m Model) string {
var b strings.Builder
if m.Selected == nil {
return " No agent selected.\n"
}
a := m.Selected
var icon string
switch {
case !a.Enabled:
icon = " disabled"
case a.Running:
icon = "● enabled (running)"
default:
icon = "○ enabled (stopped)"
}
b.WriteString(fmt.Sprintf("\n %s %s\n", a.ID, icon))
b.WriteString(" " + strings.Repeat("─", 44) + "\n")
if a.Desc != "" {
b.WriteString(" " + a.Desc + "\n")
}
b.WriteString("\n")
opts := AgentActionOptions(a.Enabled)
for i, opt := range opts {
cursor := " "
if i == m.Cursor {
cursor = "> "
}
b.WriteString(fmt.Sprintf(" %s%-16s %s\n", cursor, opt.Label, opt.Desc))
}
if m.StatusMsg != "" {
b.WriteString("\n " + m.StatusMsg + "\n")
}
b.WriteString("\n ↑↓ navegar enter ejecutar 0 volver\n")
return b.String()
}
func viewLogs(m Model) string {
var b strings.Builder
agentID := "Launcher"
if m.Selected != nil {
agentID = m.Selected.ID
}
b.WriteString(fmt.Sprintf("\n %s — Logs\n", agentID))
b.WriteString(" " + strings.Repeat("─", 60) + "\n")
if len(m.LogLines) == 0 {
b.WriteString(" (no log data)\n")
} else {
visible := visibleLogLines(m)
end := m.LogScroll + visible
if end > len(m.LogLines) {
end = len(m.LogLines)
}
start := m.LogScroll
if start >= len(m.LogLines) {
start = max(0, len(m.LogLines)-1)
}
for _, line := range m.LogLines[start:end] {
// Truncate long lines
if len(line) > m.WindowWidth-4 && m.WindowWidth > 10 {
line = line[:m.WindowWidth-7] + "..."
}
b.WriteString(" " + line + "\n")
}
}
b.WriteString("\n ↑↓ scroll r recargar 0 volver\n")
return b.String()
}
func viewServer(m Model) string {
var b strings.Builder
b.WriteString("\n Launcher Management\n")
b.WriteString(" " + strings.Repeat("─", 44) + "\n")
// Launcher status
if m.LauncherRunning {
b.WriteString(fmt.Sprintf(" ● Launcher running PID %d\n", m.LauncherPID))
parts := []string{}
if m.LauncherUptime != "" {
parts = append(parts, "uptime: "+m.LauncherUptime)
}
if m.LauncherMemory != "" {
parts = append(parts, "mem: "+m.LauncherMemory)
}
if m.LauncherCPU != "" {
parts = append(parts, "cpu: "+m.LauncherCPU)
}
if m.LauncherLogSize != "" {
parts = append(parts, "log: "+m.LauncherLogSize)
}
if len(parts) > 0 {
b.WriteString(" " + strings.Join(parts, " ") + "\n")
}
} else {
b.WriteString(" ○ Launcher stopped\n")
}
// Agent summary
_, _, disabled := countStatuses(m.Agents)
enabled := len(m.Agents) - disabled
if len(m.Agents) > 0 {
b.WriteString(fmt.Sprintf("\n %d agents (%d enabled, %d disabled)\n", len(m.Agents), enabled, disabled))
for _, a := range m.Agents {
icon := "●"
if !a.Enabled {
icon = "○"
}
b.WriteString(fmt.Sprintf(" %s %s\n", icon, a.ID))
}
}
b.WriteString("\n")
// Action menu
for i, opt := range ServerMenuOptions(m.LauncherRunning) {
cursor := " "
if i == m.Cursor {
cursor = "> "
}
b.WriteString(fmt.Sprintf(" %s%-20s %s\n", cursor, opt.Label, opt.Desc))
}
if m.StatusMsg != "" {
b.WriteString("\n " + m.StatusMsg + "\n")
}
b.WriteString("\n ↑↓ navegar enter ejecutar 0 volver\n")
return b.String()
}
func viewTestOutput(m Model) string {
var b strings.Builder
b.WriteString("\n Test Results\n")
b.WriteString(" " + strings.Repeat("─", 60) + "\n")
if m.StatusMsg != "" {
b.WriteString(" " + m.StatusMsg + "\n\n")
}
if len(m.LogLines) == 0 {
b.WriteString(" Running tests...\n")
} else {
visible := visibleLogLines(m)
end := m.LogScroll + visible
if end > len(m.LogLines) {
end = len(m.LogLines)
}
start := m.LogScroll
if start >= len(m.LogLines) {
start = max(0, len(m.LogLines)-1)
}
for _, line := range m.LogLines[start:end] {
if len(line) > m.WindowWidth-4 && m.WindowWidth > 10 {
line = line[:m.WindowWidth-7] + "..."
}
b.WriteString(" " + line + "\n")
}
}
b.WriteString("\n ↑↓ scroll r re-ejecutar 0 volver\n")
return b.String()
}
func countStatuses(agents []AgentView) (running, stopped, disabled int) {
for _, a := range agents {
switch {
case !a.Enabled:
disabled++
case a.Running:
running++
default:
stopped++
}
}
return
}