Files
2026-08-14 23:41:57 +08:00

106 lines
4.6 KiB
Go
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package tui
import (
"fmt"
"strings"
"charm.land/lipgloss/v2"
"github.com/smallnest/pigo/internal/provider"
"github.com/smallnest/pigo/internal/selfupdate"
)
// This file builds the startup splash shown at the top of the transcript: the
// pigo braille logo painted in a vertical rainbow gradient, with the session's
// basic configuration (model, provider, protocol, thinking effort, directory)
// laid out beside it. It is seeded once by withSession so it scrolls up as the
// conversation grows, like a shell's login banner.
// logoLines is the pigo braille-art logo, one string per row.
var logoLines = []string{
"⣿⣿⣿⣿⡿⠟⠛⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠙⣿",
"⣿⣿⡿⠋⠀⠀⣼",
"⣿⠋⠀⠀⠀⠀⠀⣀⡀⠀⠀⠀⠀⢠⣤⣤⣤⠀⠀⠀⠀⠀⣤⣤⣤⣤⣤⣤⣾⣿",
"⣧⠀⠀⠀⠀⣠⣾⣿⡇⠀⠀⠀⠀⣿⣿⣿⣿⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿",
"⣿⣶⣤⣤⣾⣿⣿⣿⡇⠀⠀⠀⠀⣿⣿⣿⣿⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿",
"⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⢀⣿⣿⣿⣿⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿",
"⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀⠀⠀⢸⣿⣿⣿⣿⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿",
"⣿⣿⣿⣿⣿⣿⣿⠇⠀⠀⠀⠀⣾⣿⣿⣿⣿⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿",
"⣿⣿⣿⣿⣿⣿⡟⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⣿⣿⣿⡿⠛⠛⢿⣿",
"⣿⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⣾⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⣿⣿⡟⠀⠀⠀⠀⢻",
"⣿⣿⣿⣿⡟⠁⠀⠀⠀⠀⣸⣿⣿⣿⣿⣿⣿⡀⠀⠀⠀⠀⠛⠛⠁⠀⠀⠀⠀⣾",
"⣿⣿⣿⡏⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣿⣿⣿⣧⡀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⣿",
"⣿⣿⣿⣿⣄⣀⣀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣄⣀⣀⣀⣀⣠⣴⣿⣿⣿",
}
// logoColors is the top-to-bottom rainbow ramp painted across the logo rows
// (ANSI 256-color cube): red → orange → yellow → green → cyan → blue.
var logoColors = []string{
"196", "202", "208", "214", "220", "190", "118",
"46", "48", "50", "45", "39", "33",
}
// renderBanner paints the logo gradient and joins it with a config panel showing
// the session basics. Its only I/O is a single cheap read of the local
// update-check cache (no network — CachedLatest); it never panics, so it is safe
// to build eagerly at startup.
func renderBanner(theme Theme, opts Options, cwd string) string {
var logo strings.Builder
for i, line := range logoLines {
if i > 0 {
logo.WriteByte('\n')
}
c := logoColors[i%len(logoColors)]
logo.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color(c)).Render(line))
}
title := lipgloss.NewStyle().Foreground(lipgloss.Color(colorAccent)).Bold(true)
label := lipgloss.NewStyle().Foreground(lipgloss.Color(colorGray))
value := lipgloss.NewStyle().Foreground(lipgloss.Color(colorUser)).Bold(true)
rows := [][2]string{
{"Version", firstNonEmpty(opts.Version, "dev")},
{"Model", firstNonEmpty(opts.Model, "—")},
{"Provider", firstNonEmpty(opts.ProviderName, "—")},
{"Protocol", firstNonEmpty(provider.ProtocolLabel(opts.Protocol), "—")},
{"Thinking", firstNonEmpty(string(opts.ThinkingLevel), "off")},
{"Directory", firstNonEmpty(cwd, "—")},
}
// When the cached latest-release check says a newer version exists, append a
// highlighted "→ vX.Y.Z" and an upgrade hint to the Version row. The check is
// read from the local cache only (no network here); a background refresh keeps
// it current for the next launch. dev/unparseable versions never trigger this.
upgradeHint := ""
if latest, _ := selfupdate.CachedLatest(); latest != "" {
if avail, comparable := selfupdate.UpdateAvailable(opts.Version, latest); comparable && avail {
newVer := lipgloss.NewStyle().Foreground(lipgloss.Color("214")).Bold(true).Render(latest)
rows[0][1] = rows[0][1] + " → " + newVer
upgradeHint = label.Render(strings.Repeat(" ", 11)) +
lipgloss.NewStyle().Foreground(lipgloss.Color("214")).Render("Run pigo update to upgrade")
}
}
var info strings.Builder
info.WriteString(title.Render("pigo") + " " + theme.System.Render("Terminal AI coding assistant") + "\n\n")
for i, r := range rows {
if i > 0 {
info.WriteByte('\n')
}
info.WriteString(label.Render(fmt.Sprintf("%-10s ", r[0])) + value.Render(r[1]))
}
if upgradeHint != "" {
info.WriteString("\n" + upgradeHint)
}
return lipgloss.JoinHorizontal(lipgloss.Center, logo.String(), " ", info.String())
}
// firstNonEmpty returns s when it is non-empty, otherwise the fallback.
func firstNonEmpty(s, fallback string) string {
if strings.TrimSpace(s) == "" {
return fallback
}
return s
}