Some checks failed
Stable Release / stable (push) Failing after 22s
- Dark theme with red accents (cyberpunk aesthetic) - Epuré cyberpunk style: clean dark backgrounds, sharp red highlights - Full cyberpunk animations: glitch effect, scan line, typewriter - Mixed Unicode + ASCII icons - Rounded borders (╭ ╮ ╯ ╰) on cards and panels - ASCII art block titles (■) with red styling - Header: MUYUE branding, status indicators, live clock - Footer: shortcuts, version, update indicator - Tab transitions: glitch → scan → typewriter sequence - Extracted header.go, footer.go, animations.go as new files Controls unchanged: ctrl+t tabs, ctrl+s sidebar, ctrl+a AI panel file changes: - styles.go: new color palette (cyberRed, bgVoid, dimRed), block titles - types.go: added transition state, clock tick, glitch/scan/done messages - animations.go: new file with glitch, scan, typewriter, hex stream effects - header.go: new file with logo, tabs, status dots, live clock - footer.go: new file with shortcuts, version, update indicator - model.go: integrated transition state machine, clock updates - dashboard.go, studio.go, terminal.go, config_tab.go: updated icons/styles Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land> Co-authored-by: Augustin <muyue@legion-muyue.fr> Reviewed-on: #1
179 lines
4.8 KiB
Go
179 lines
4.8 KiB
Go
package tui
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
"github.com/muyue/muyue/internal/version"
|
|
)
|
|
|
|
func (m Model) renderHeader() string {
|
|
var tabs []string
|
|
for i, name := range tabNames {
|
|
icon := tabIcons[i]
|
|
if tab(i) == m.activeTab {
|
|
tabStyle := lipgloss.NewStyle().
|
|
Background(cyberRed).
|
|
Foreground(lipgloss.Color("#FFFFFF")).
|
|
Bold(true).
|
|
Padding(0, 2)
|
|
tabs = append(tabs, tabStyle.Render(icon+" "+name))
|
|
} else {
|
|
tabStyle := lipgloss.NewStyle().
|
|
Background(bgSurface).
|
|
Foreground(textDim).
|
|
Padding(0, 2)
|
|
tabs = append(tabs, tabStyle.Render(icon+" "+name))
|
|
}
|
|
}
|
|
|
|
tabLine := tabBarStyle.Render(lipgloss.JoinHorizontal(lipgloss.Bottom, tabs...))
|
|
|
|
timeStr := ""
|
|
if !m.currentTime.IsZero() {
|
|
timeStr = m.currentTime.Format("15:04:05")
|
|
}
|
|
|
|
dateStr := ""
|
|
if !m.currentTime.IsZero() {
|
|
dateStr = m.currentTime.Format("02/01/2006")
|
|
}
|
|
|
|
rightInfo := lipgloss.NewStyle().Background(bgSurface).Padding(0, 1).Render(
|
|
lipgloss.JoinHorizontal(lipgloss.Center,
|
|
lipgloss.NewStyle().Foreground(textDim).Render(dateStr+" "),
|
|
lipgloss.NewStyle().Foreground(cyberRed).Bold(true).Render(timeStr),
|
|
lipgloss.NewStyle().Foreground(textMuted).Render(" "+getAnimFrame(m.animationFrame)),
|
|
),
|
|
)
|
|
|
|
statusDots := ""
|
|
if m.config != nil {
|
|
hasAI := false
|
|
for _, p := range m.config.AI.Providers {
|
|
if p.Active && p.APIKey != "" {
|
|
hasAI = true
|
|
break
|
|
}
|
|
}
|
|
if hasAI {
|
|
statusDots += lipgloss.NewStyle().Foreground(successGreen).Render("●")
|
|
} else {
|
|
statusDots += lipgloss.NewStyle().Foreground(errorRed).Render("●")
|
|
}
|
|
} else {
|
|
statusDots += lipgloss.NewStyle().Foreground(warnAmber).Render("●")
|
|
}
|
|
|
|
statusDots += lipgloss.NewStyle().Foreground(textMuted).Render(" ")
|
|
|
|
if m.mcpConfigured {
|
|
statusDots += lipgloss.NewStyle().Foreground(successGreen).Render("●")
|
|
} else {
|
|
statusDots += lipgloss.NewStyle().Foreground(warnAmber).Render("●")
|
|
}
|
|
|
|
statusInfo := lipgloss.NewStyle().Background(bgSurface).Padding(0, 1).Render(
|
|
lipgloss.JoinHorizontal(lipgloss.Center,
|
|
lipgloss.NewStyle().Foreground(textDim).Render("SYS "),
|
|
statusDots,
|
|
),
|
|
)
|
|
|
|
badge := lipgloss.NewStyle().Foreground(cyberRed).Bold(true).Render("MUYUE")
|
|
versionBadge := lipgloss.NewStyle().Foreground(dimRed).Render("v" + version.Version)
|
|
|
|
logoLine := lipgloss.NewStyle().Background(bgVoid).Padding(0, 1).Render(
|
|
lipgloss.JoinHorizontal(lipgloss.Center, badge, " ", versionBadge),
|
|
)
|
|
|
|
topLine := lipgloss.JoinHorizontal(lipgloss.Bottom,
|
|
logoLine,
|
|
strings.Repeat(" ", max(0, m.width-lipgloss.Width(logoLine)-lipgloss.Width(rightInfo)-lipgloss.Width(statusInfo))),
|
|
statusInfo,
|
|
rightInfo,
|
|
)
|
|
|
|
return lipgloss.JoinVertical(lipgloss.Left, topLine, tabLine)
|
|
}
|
|
|
|
func (m Model) renderTabMenuOverlay() string {
|
|
menuStyle := lipgloss.NewStyle().
|
|
Border(lipgloss.RoundedBorder()).
|
|
BorderForeground(cyberRed).
|
|
Background(bgCard).
|
|
Padding(1, 3)
|
|
|
|
tabItemStyle := lipgloss.NewStyle().
|
|
Foreground(textDim).
|
|
Padding(0, 2)
|
|
|
|
tabItemActiveStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FFFFFF")).
|
|
Background(cyberRed).
|
|
Bold(true).
|
|
Padding(0, 2)
|
|
|
|
descs := []string{
|
|
"tools, updates & system status",
|
|
"chat, agents & workflows",
|
|
"terminal + AI assistant",
|
|
"profile, API keys & settings",
|
|
}
|
|
|
|
var items []string
|
|
for i, name := range tabNames {
|
|
num := lipgloss.NewStyle().Foreground(dimRed).Render(fmt.Sprintf(" %d.", i+1))
|
|
icon := tabIcons[i] + " "
|
|
if i == m.tabMenuCursor {
|
|
item := fmt.Sprintf("%s %s%-10s %s", num, icon, name, lipgloss.NewStyle().Foreground(cyberRose).Render(descs[i]))
|
|
items = append(items, tabItemActiveStyle.Render(">"+item))
|
|
} else {
|
|
item := fmt.Sprintf("%s %s%-10s %s", num, icon, name, lipgloss.NewStyle().Foreground(textMuted).Render(descs[i]))
|
|
items = append(items, tabItemStyle.Render(" "+item))
|
|
}
|
|
}
|
|
|
|
header := lipgloss.NewStyle().Foreground(cyberRed).Bold(true).Render("SWITCH TAB")
|
|
content := header + "\n\n" +
|
|
strings.Join(items, "\n") +
|
|
"\n\n" +
|
|
lipgloss.NewStyle().Foreground(textMuted).Render("up/down navigate | enter select | esc cancel")
|
|
|
|
box := menuStyle.Render(content)
|
|
|
|
return lipgloss.Place(m.width, m.height,
|
|
0.5, 0.5,
|
|
box,
|
|
lipgloss.WithWhitespaceBackground(bgVoid),
|
|
lipgloss.WithWhitespaceForeground(textMuted),
|
|
)
|
|
}
|
|
|
|
func (m Model) renderQuitOverlay() string {
|
|
yesStyle := confirmNoStyle
|
|
noStyle := confirmYesStyle
|
|
if m.confirmCursor == 0 {
|
|
yesStyle = confirmYesStyle
|
|
noStyle = confirmNoStyle
|
|
}
|
|
|
|
frame := lipgloss.NewStyle().Foreground(cyberRed).Render(getAnimFrame(m.animationFrame))
|
|
|
|
box := fmt.Sprintf("\n\n %s Quit muyue?\n\n %s %s",
|
|
frame,
|
|
yesStyle.Render("[ Yes ]"),
|
|
noStyle.Render("[ No ]"),
|
|
)
|
|
|
|
content := confirmBoxStyle.Render(box)
|
|
|
|
return lipgloss.Place(m.width, m.height,
|
|
0.5, 0.5,
|
|
content,
|
|
lipgloss.WithWhitespaceBackground(bgVoid),
|
|
lipgloss.WithWhitespaceForeground(textMuted),
|
|
)
|
|
}
|