a431d1ec95
Add OpenAI-compatible providers, TUI, slash commands, and model switching. Add local file tools plus optional yolo shell execution. Document config, usage, and development workflow.
200 lines
4.8 KiB
Go
200 lines
4.8 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
|
|
Status 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)),
|
|
|
|
Status: lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color(t.Muted)).
|
|
Background(lipgloss.Color(t.SurfaceAlt)).
|
|
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
|
|
}
|