- Dashboard: tools, agents status, updates, quick actions - Studio: central chat + agents/workflows sidebar (Ctrl+S toggle) - Shell: terminal + AI assistant panel side-by-side (Ctrl+A toggle) - Config: profile, API keys, terminal/starship settings in 2 columns - New red/rose color scheme (#E8364F → #FF6B8A → #FFB3C6) - Animated header with visual tab bar and pulse loading - Remove old chat.go, agents.go, workflow_tab.go (merged into studio.go) - All tests pass, build clean 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
182 lines
5.4 KiB
Go
182 lines
5.4 KiB
Go
package tui
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
func (m Model) renderDashboard() string {
|
|
colWidth := m.width / 2
|
|
if colWidth < 30 {
|
|
colWidth = 30
|
|
}
|
|
|
|
var left, right strings.Builder
|
|
|
|
left.WriteString(renderSectionWithIcon("System", "◉"))
|
|
left.WriteString("\n")
|
|
if m.scanResult != nil {
|
|
sysInfo := m.scanResult.System.String()
|
|
left.WriteString(" ")
|
|
left.WriteString(lipgloss.NewStyle().Foreground(textColor).Render(sysInfo))
|
|
}
|
|
left.WriteString("\n\n")
|
|
|
|
left.WriteString(renderSectionWithIcon("Installed Tools", "◆"))
|
|
left.WriteString("\n")
|
|
if m.scanResult != nil {
|
|
installed := 0
|
|
total := len(m.scanResult.Tools)
|
|
for _, t := range m.scanResult.Tools {
|
|
if t.Installed {
|
|
installed++
|
|
left.WriteString(" ")
|
|
left.WriteString(itemOKStyle.Render("✓ "))
|
|
left.WriteString(lipgloss.NewStyle().Foreground(textColor).Render(t.Name))
|
|
left.WriteString(lipgloss.NewStyle().Foreground(dimColor).Render(fmt.Sprintf(" %s", extractVersion(t.Version))))
|
|
left.WriteString("\n")
|
|
} else {
|
|
left.WriteString(" ")
|
|
left.WriteString(itemMissingStyle.Render("✗ "))
|
|
left.WriteString(lipgloss.NewStyle().Foreground(mutedColor).Render(t.Name))
|
|
left.WriteString(itemPendingStyle.Render(" (missing)"))
|
|
left.WriteString("\n")
|
|
}
|
|
}
|
|
|
|
barWidth := 20
|
|
pct := 0
|
|
if total > 0 {
|
|
pct = (installed * barWidth) / total
|
|
}
|
|
bar := lipgloss.NewStyle().Foreground(primaryColor).Render(strings.Repeat("█", pct)) +
|
|
lipgloss.NewStyle().Foreground(dimColor).Render(strings.Repeat("░", barWidth-pct))
|
|
left.WriteString(fmt.Sprintf("\n %s %d/%d\n", bar, installed, total))
|
|
}
|
|
left.WriteString("\n")
|
|
|
|
if m.installing {
|
|
left.WriteString(renderSectionWithIcon("Installing", "⏳"))
|
|
left.WriteString("\n")
|
|
progBar := m.progressBar.View()
|
|
label := ""
|
|
if m.installTool != "" {
|
|
label = fmt.Sprintf(" %d/%d - %s", m.installCurrent+1, m.installTotal, m.installTool)
|
|
} else {
|
|
label = fmt.Sprintf(" %d/%d", m.installCurrent, m.installTotal)
|
|
}
|
|
left.WriteString(fmt.Sprintf(" %s%s\n", progBar, label))
|
|
left.WriteString("\n")
|
|
}
|
|
|
|
if len(m.installLog) > 0 {
|
|
left.WriteString(renderSectionWithIcon("Install Log", "📋"))
|
|
left.WriteString("\n")
|
|
for _, l := range m.installLog {
|
|
left.WriteString(l + "\n")
|
|
}
|
|
left.WriteString("\n")
|
|
}
|
|
|
|
right.WriteString(renderSectionWithIcon("Quick Actions", "⚡"))
|
|
right.WriteString("\n")
|
|
actions := []struct {
|
|
key string
|
|
desc string
|
|
color lipgloss.Color
|
|
}{
|
|
{"i", "Install missing tools", primaryColor},
|
|
{"u", "Check for updates", warmColor},
|
|
{"s", "Rescan system", roseColor},
|
|
{"l", "Scan LSP servers", accentColor},
|
|
{"m", "Configure MCP servers", roseLightColor},
|
|
}
|
|
for _, a := range actions {
|
|
right.WriteString(fmt.Sprintf(" %s %s\n",
|
|
lipgloss.NewStyle().Foreground(a.color).Bold(true).Render("["+a.key+"]"),
|
|
lipgloss.NewStyle().Foreground(textColor).Render(a.desc)))
|
|
}
|
|
right.WriteString("\n")
|
|
|
|
right.WriteString(renderSectionWithIcon("Active Agents", "◉"))
|
|
right.WriteString("\n")
|
|
|
|
agents := []struct {
|
|
name string
|
|
}{
|
|
{"Crush"},
|
|
{"Claude Code"},
|
|
}
|
|
for _, a := range agents {
|
|
right.WriteString(" ")
|
|
right.WriteString(lipgloss.NewStyle().Foreground(dimColor).Render("● "))
|
|
right.WriteString(lipgloss.NewStyle().Foreground(mutedColor).Render(a.name + " "))
|
|
right.WriteString(itemPendingStyle.Render("stopped"))
|
|
right.WriteString("\n")
|
|
}
|
|
right.WriteString("\n")
|
|
|
|
if len(m.updateStatus) > 0 {
|
|
right.WriteString(renderSectionWithIcon("Updates", "↻"))
|
|
right.WriteString("\n")
|
|
for _, s := range m.updateStatus {
|
|
if s.NeedsUpdate {
|
|
right.WriteString(" ")
|
|
right.WriteString(itemWarnStyle.Render("⚠ "))
|
|
right.WriteString(fmt.Sprintf("%s: %s → %s\n", s.Tool, s.Current, s.Latest))
|
|
} else if s.Error == "" {
|
|
right.WriteString(" ")
|
|
right.WriteString(itemOKStyle.Render("✓ "))
|
|
right.WriteString(fmt.Sprintf("%s: up to date\n", s.Tool))
|
|
}
|
|
}
|
|
right.WriteString("\n")
|
|
}
|
|
|
|
if len(m.lspServers) > 0 {
|
|
right.WriteString(renderSectionWithIcon("LSP Servers", "§"))
|
|
right.WriteString("\n")
|
|
lspInstalled := 0
|
|
for _, s := range m.lspServers {
|
|
if s.Installed {
|
|
lspInstalled++
|
|
right.WriteString(" ")
|
|
right.WriteString(itemOKStyle.Render("✓ "))
|
|
right.WriteString(fmt.Sprintf("%-22s (%s)\n", s.Name, s.Language))
|
|
} else {
|
|
right.WriteString(" ")
|
|
right.WriteString(itemPendingStyle.Render("○ "))
|
|
right.WriteString(fmt.Sprintf("%-22s (%s)\n", s.Name, s.Language))
|
|
}
|
|
}
|
|
right.WriteString(fmt.Sprintf("\n %d/%d available\n", lspInstalled, len(m.lspServers)))
|
|
right.WriteString("\n")
|
|
}
|
|
|
|
mcpStatus := itemPendingStyle.Render("○ not configured")
|
|
if m.mcpConfigured {
|
|
mcpStatus = itemOKStyle.Render("✓ configured")
|
|
}
|
|
right.WriteString(fmt.Sprintf(" MCP: %s\n", mcpStatus))
|
|
|
|
if m.daemon != nil {
|
|
daemonStatus := itemPendingStyle.Render("○ stopped")
|
|
if m.daemon.IsRunning() {
|
|
daemonStatus = itemOKStyle.Render("✓ running")
|
|
}
|
|
right.WriteString(fmt.Sprintf(" Daemon: %s\n", daemonStatus))
|
|
}
|
|
|
|
leftCol := lipgloss.NewStyle().Width(colWidth).Render(left.String())
|
|
rightCol := lipgloss.NewStyle().Width(colWidth).Render(right.String())
|
|
|
|
return lipgloss.JoinHorizontal(lipgloss.Top, leftCol, rightCol)
|
|
}
|
|
|
|
func renderSectionWithIcon(title string, icon string) string {
|
|
return lipgloss.NewStyle().Foreground(primaryColor).Render(icon+" ") +
|
|
sectionStyle.Render(title)
|
|
}
|