package tui import ( "fmt" "strings" "github.com/charmbracelet/bubbles/textarea" "github.com/charmbracelet/lipgloss" ) type ThemeMode string const ( ThemeLight ThemeMode = "light" ThemeDark ThemeMode = "dark" ) const ( iconReady = "✓" iconWorking = "◆" iconTool = "▣" iconError = "✕" iconCommand = "⌘" iconSession = "◌" iconContext = "◔" iconModel = "◉" ) 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 Muted lipgloss.Style Activity lipgloss.Style Header lipgloss.Style ModelMeta lipgloss.Style Footer lipgloss.Style Viewport lipgloss.Style InputBox lipgloss.Style CommandHint lipgloss.Style StatusReady lipgloss.Style StatusWarn lipgloss.Style StatusError lipgloss.Style StatusInfo lipgloss.Style Pill lipgloss.Style PillAccent lipgloss.Style Palette lipgloss.Style PaletteMatch lipgloss.Style ToolName lipgloss.Style ToolKey lipgloss.Style ToolValue lipgloss.Style UserLabel 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 appStyle(t theme) lipgloss.Style { style := lipgloss.NewStyle().Foreground(lipgloss.Color(t.Text)) if t.Mode == ThemeDark { style = style.Background(lipgloss.Color(t.Background)) } return style } func (t theme) styles() styles { return styles{ App: appStyle(t), Muted: lipgloss.NewStyle(). Foreground(lipgloss.Color(t.Muted)), Activity: lipgloss.NewStyle(). Foreground(lipgloss.Color(t.Muted)). Padding(0, 1), Header: lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color(t.Title)), ModelMeta: lipgloss.NewStyle(). Foreground(lipgloss.Color(t.Muted)). Padding(0, 1), Footer: lipgloss.NewStyle(). Foreground(lipgloss.Color(t.Muted)). Padding(0, 1), Viewport: lipgloss.NewStyle(). Foreground(lipgloss.Color(t.Text)), InputBox: lipgloss.NewStyle(). Foreground(lipgloss.Color(t.Text)). Border(lipgloss.ThickBorder()). BorderForeground(lipgloss.Color(t.Border)). Padding(0, 2), CommandHint: lipgloss.NewStyle(). Foreground(lipgloss.Color(t.Muted)). Padding(0, 1), StatusReady: lipgloss.NewStyle(). Foreground(lipgloss.Color(t.Assistant)), StatusWarn: lipgloss.NewStyle(). Foreground(lipgloss.Color(t.Tool)), StatusError: lipgloss.NewStyle(). Foreground(lipgloss.Color(t.Error)), StatusInfo: lipgloss.NewStyle(). Foreground(lipgloss.Color(t.User)), Pill: lipgloss.NewStyle(). Foreground(lipgloss.Color(t.Text)). Background(lipgloss.Color(t.SurfaceAlt)). Padding(0, 1), PillAccent: lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color(t.Title)). Background(lipgloss.Color(t.SurfaceAlt)). Padding(0, 1), Palette: lipgloss.NewStyle(). Foreground(lipgloss.Color(t.Text)). Border(lipgloss.ThickBorder()). BorderForeground(lipgloss.Color(t.Border)). Padding(0, 2), PaletteMatch: lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color(t.User)), ToolName: lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color(t.Tool)), ToolKey: lipgloss.NewStyle(). Foreground(lipgloss.Color(t.Muted)), ToolValue: lipgloss.NewStyle(). Foreground(lipgloss.Color(t.Text)), UserLabel: labelStyle(t.User), ToolLabel: labelStyle(t.Tool), ErrorLabel: labelStyle(t.Error), SystemLabel: labelStyle(t.System), UserMessage: bubbleMessageStyle(t.Text, t.SurfaceAlt), AssistantMsg: assistantMessageStyle(t.Text), ToolMessage: messageStyle(t.Text), ErrorMessage: messageStyle(t.Error), SystemMessage: lipgloss.NewStyle().Foreground(lipgloss.Color(t.System)).Padding(0, 1), } } func labelStyle(color string) lipgloss.Style { return lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color(color)) } func messageStyle(textColor string) lipgloss.Style { return lipgloss.NewStyle(). Foreground(lipgloss.Color(textColor)). Padding(0, 1) } func assistantMessageStyle(textColor string) lipgloss.Style { return lipgloss.NewStyle(). Foreground(lipgloss.Color(textColor)). Padding(1, 2) } func bubbleMessageStyle(textColor, backgroundColor string) lipgloss.Style { return lipgloss.NewStyle(). Foreground(lipgloss.Color(textColor)). Background(lipgloss.Color(backgroundColor)). Padding(1, 2) } func applyTextareaTheme(input *textarea.Model, t theme) { focused := textarea.Style{ Base: lipgloss.NewStyle(). Foreground(lipgloss.Color(t.Text)), CursorLine: lipgloss.NewStyle(), 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() input.FocusedStyle = focused input.BlurredStyle = blurred }