Files
agentu/internal/tui/theme.go
T
loveuer 86f5ca4aef v0.0.2: polish tui rendering
Add visible activity state for running turns.
Move model metadata below the input area.
Render assistant Markdown with theme-aware styling.
2026-06-23 09:06:20 +08:00

213 lines
5.1 KiB
Go

package tui
import (
"fmt"
"strings"
"github.com/charmbracelet/bubbles/textarea"
"github.com/charmbracelet/lipgloss"
)
type ThemeMode string
const (
ThemeLight ThemeMode = "light"
ThemeDark ThemeMode = "dark"
)
func ParseThemeMode(value string) (ThemeMode, error) {
switch ThemeMode(strings.ToLower(strings.TrimSpace(value))) {
case "", ThemeLight:
return ThemeLight, nil
case ThemeDark:
return ThemeDark, nil
default:
return "", fmt.Errorf("unsupported theme %q; expected light or dark", value)
}
}
type theme struct {
Mode ThemeMode
Background string
Surface string
SurfaceAlt string
Border string
Text string
Muted string
Title string
User string
Assistant string
Tool string
Error string
System string
}
type styles struct {
App lipgloss.Style
Header lipgloss.Style
Title lipgloss.Style
Muted lipgloss.Style
Activity lipgloss.Style
ModelMeta lipgloss.Style
Footer lipgloss.Style
Viewport lipgloss.Style
InputBox lipgloss.Style
CommandHint lipgloss.Style
UserLabel lipgloss.Style
AssistantLabel lipgloss.Style
ToolLabel lipgloss.Style
ErrorLabel lipgloss.Style
SystemLabel lipgloss.Style
UserMessage lipgloss.Style
AssistantMsg lipgloss.Style
ToolMessage lipgloss.Style
ErrorMessage lipgloss.Style
SystemMessage lipgloss.Style
}
func themeForMode(mode ThemeMode) theme {
if mode == ThemeDark {
return darkTheme()
}
return lightTheme()
}
func lightTheme() theme {
return theme{
Mode: ThemeLight,
Background: "#F8FAFC",
Surface: "#FFFFFF",
SurfaceAlt: "#EEF2F7",
Border: "#CBD5E1",
Text: "#111827",
Muted: "#64748B",
Title: "#0F766E",
User: "#2563EB",
Assistant: "#0F766E",
Tool: "#B45309",
Error: "#DC2626",
System: "#64748B",
}
}
func darkTheme() theme {
return theme{
Mode: ThemeDark,
Background: "#111827",
Surface: "#1F2937",
SurfaceAlt: "#0F172A",
Border: "#475569",
Text: "#E5E7EB",
Muted: "#94A3B8",
Title: "#5EEAD4",
User: "#93C5FD",
Assistant: "#5EEAD4",
Tool: "#FBBF24",
Error: "#FCA5A5",
System: "#94A3B8",
}
}
func (t theme) styles() styles {
return styles{
App: lipgloss.NewStyle().
Foreground(lipgloss.Color(t.Text)).
Background(lipgloss.Color(t.Background)),
Header: lipgloss.NewStyle().
Foreground(lipgloss.Color(t.Text)).
Background(lipgloss.Color(t.SurfaceAlt)).
Padding(0, 1),
Title: lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color(t.Title)),
Muted: lipgloss.NewStyle().
Foreground(lipgloss.Color(t.Muted)),
Activity: lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color(t.Background)).
Background(lipgloss.Color(t.Title)).
Padding(0, 1),
ModelMeta: lipgloss.NewStyle().
Foreground(lipgloss.Color(t.Text)).
Background(lipgloss.Color(t.SurfaceAlt)).
Padding(0, 1),
Footer: lipgloss.NewStyle().
Foreground(lipgloss.Color(t.Muted)).
Background(lipgloss.Color(t.Background)).
Padding(0, 1),
Viewport: lipgloss.NewStyle().
Foreground(lipgloss.Color(t.Text)).
Background(lipgloss.Color(t.Background)),
InputBox: lipgloss.NewStyle().
Foreground(lipgloss.Color(t.Text)).
Background(lipgloss.Color(t.Surface)).
Border(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color(t.Border)).
Padding(0, 1),
CommandHint: lipgloss.NewStyle().
Foreground(lipgloss.Color(t.Muted)).
Background(lipgloss.Color(t.SurfaceAlt)).
Padding(0, 1),
UserLabel: labelStyle(t.User),
AssistantLabel: labelStyle(t.Assistant),
ToolLabel: labelStyle(t.Tool),
ErrorLabel: labelStyle(t.Error),
SystemLabel: labelStyle(t.System),
UserMessage: messageStyle(t.Text, t.User),
AssistantMsg: messageStyle(t.Text, t.Assistant),
ToolMessage: messageStyle(t.Text, t.Tool),
ErrorMessage: messageStyle(t.Error, t.Error),
SystemMessage: lipgloss.NewStyle().Foreground(lipgloss.Color(t.System)).PaddingLeft(1),
}
}
func labelStyle(color string) lipgloss.Style {
return lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color(color))
}
func messageStyle(textColor, accentColor string) lipgloss.Style {
return lipgloss.NewStyle().
Foreground(lipgloss.Color(textColor)).
Border(lipgloss.ThickBorder(), false, false, false, true).
BorderForeground(lipgloss.Color(accentColor)).
PaddingLeft(1)
}
func applyTextareaTheme(input *textarea.Model, t theme) {
focused := textarea.Style{
Base: lipgloss.NewStyle().
Foreground(lipgloss.Color(t.Text)).
Background(lipgloss.Color(t.Surface)),
CursorLine: lipgloss.NewStyle().
Background(lipgloss.Color(t.Surface)),
Placeholder: lipgloss.NewStyle().
Foreground(lipgloss.Color(t.Muted)),
Prompt: lipgloss.NewStyle().
Foreground(lipgloss.Color(t.Muted)),
Text: lipgloss.NewStyle().
Foreground(lipgloss.Color(t.Text)),
EndOfBuffer: lipgloss.NewStyle().
Foreground(lipgloss.Color(t.Surface)),
}
blurred := focused
blurred.CursorLine = lipgloss.NewStyle().Background(lipgloss.Color(t.Surface))
input.FocusedStyle = focused
input.BlurredStyle = blurred
}