ai-agent/internal/tui/toast.go
admin 8dc496b626
Some checks failed
CI / test (push) Has been cancelled
Release / release (push) Failing after 4m36s
first commit
2026-03-08 15:40:34 +07:00

195 lines
4.8 KiB
Go

package tui
import (
"strings"
"time"
"charm.land/lipgloss/v2"
)
// ToastKind represents the type of toast notification.
type ToastKind int
const (
ToastKindInfo ToastKind = iota
ToastKindSuccess
ToastKindWarning
ToastKindError
)
// Toast represents a transient notification message.
type Toast struct {
ID int
Kind ToastKind
Message string
CreatedAt time.Time
Duration time.Duration
ExpiresAt time.Time
}
// ToastManager manages the lifecycle of toast notifications.
type ToastManager struct {
toasts []Toast
nextID int
styles ToastStyles
maxToasts int
}
// ToastStyles holds styles for toast rendering.
type ToastStyles struct {
Info lipgloss.Style
Success lipgloss.Style
Warning lipgloss.Style
Error lipgloss.Style
Border lipgloss.Style
}
// NewToastManager creates a new toast manager.
func NewToastManager() *ToastManager {
return &ToastManager{
toasts: make([]Toast, 0),
nextID: 1,
maxToasts: 3,
}
}
// SetStyles applies styles to the manager.
func (tm *ToastManager) SetStyles(styles ToastStyles) {
tm.styles = styles
}
// Add creates a new toast with the given message and duration.
func (tm *ToastManager) Add(kind ToastKind, message string, duration time.Duration) int {
id := tm.nextID
tm.nextID++
toast := Toast{
ID: id,
Kind: kind,
Message: message,
CreatedAt: time.Now(),
Duration: duration,
ExpiresAt: time.Now().Add(duration),
}
tm.toasts = append(tm.toasts, toast)
// Limit number of toasts
if len(tm.toasts) > tm.maxToasts {
tm.toasts = tm.toasts[1:]
}
return id
}
// Info adds an info toast.
func (tm *ToastManager) Info(message string) int {
return tm.Add(ToastKindInfo, message, 3*time.Second)
}
// Success adds a success toast.
func (tm *ToastManager) Success(message string) int {
return tm.Add(ToastKindSuccess, message, 3*time.Second)
}
// Warning adds a warning toast.
func (tm *ToastManager) Warning(message string) int {
return tm.Add(ToastKindWarning, message, 5*time.Second)
}
// Error adds an error toast.
func (tm *ToastManager) Error(message string) int {
return tm.Add(ToastKindError, message, 5*time.Second)
}
// AddToast adds a toast with default duration based on kind.
func (tm *ToastManager) AddToast(toast Toast) int {
duration := 3 * time.Second
if toast.Kind == ToastKindWarning || toast.Kind == ToastKindError {
duration = 5 * time.Second
}
return tm.Add(toast.Kind, toast.Message, duration)
}
// Update removes expired toasts.
func (tm *ToastManager) Update() {
now := time.Now()
var active []Toast
for _, t := range tm.toasts {
if now.Before(t.ExpiresAt) {
active = append(active, t)
}
}
tm.toasts = active
}
// HasToasts returns true if there are active toasts.
func (tm *ToastManager) HasToasts() bool {
return len(tm.toasts) > 0
}
// Render renders all active toasts as a single string.
func (tm *ToastManager) Render(width int) string {
if len(tm.toasts) == 0 {
return ""
}
var b strings.Builder
for _, toast := range tm.toasts {
b.WriteString(tm.renderToast(toast, width))
b.WriteString("\n")
}
return strings.TrimRight(b.String(), "\n")
}
// renderToast renders a single toast.
func (tm *ToastManager) renderToast(toast Toast, width int) string {
icon := "○"
style := tm.styles.Info
switch toast.Kind {
case ToastKindSuccess:
icon = "✓"
style = tm.styles.Success
case ToastKindWarning:
icon = "⚠"
style = tm.styles.Warning
case ToastKindError:
icon = "✗"
style = tm.styles.Error
}
content := icon + " " + toast.Message
// Apply style and truncate if needed
maxW := width - 4
if maxW < 20 {
maxW = 20
}
rendered := style.Render(content)
if lipgloss.Width(rendered) > maxW {
rendered = style.Render(truncate(toast.Message, maxW-3))
}
return rendered
}
// DefaultToastStyles returns default styles for toasts based on theme.
func DefaultToastStyles(isDark bool) ToastStyles {
ld := lipgloss.LightDark(isDark)
colorInfo := ld(lipgloss.Color("#88c0d0"), lipgloss.Color("#5e81ac"))
colorSuccess := ld(lipgloss.Color("#a3be8c"), lipgloss.Color("#8fbc8f"))
colorWarning := ld(lipgloss.Color("#ebcb8b"), lipgloss.Color("#d08770"))
colorError := ld(lipgloss.Color("#bf616a"), lipgloss.Color("#bf616a"))
return ToastStyles{
Info: lipgloss.NewStyle().Foreground(colorInfo),
Success: lipgloss.NewStyle().Foreground(colorSuccess),
Warning: lipgloss.NewStyle().Foreground(colorWarning),
Error: lipgloss.NewStyle().Foreground(colorError),
}
}