Initilaize
This commit is contained in:
255
internal/ui/statusbar/bar.go
Normal file
255
internal/ui/statusbar/bar.go
Normal file
@@ -0,0 +1,255 @@
|
||||
package statusbar
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"lazykimi/pkg/theme"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/v2/spinner"
|
||||
tea "github.com/charmbracelet/bubbletea/v2"
|
||||
"github.com/charmbracelet/lipgloss/v2"
|
||||
)
|
||||
|
||||
type FlashType string
|
||||
|
||||
const (
|
||||
FlashTypeError FlashType = "error"
|
||||
FlashTypeSuccess FlashType = "success"
|
||||
)
|
||||
|
||||
func FlashSuccess(msg string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
return EventFlashSuccess(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func FlashError(msg string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
return EventFlashError(msg)
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
EventFlashSuccess string
|
||||
EventFlashError string
|
||||
)
|
||||
|
||||
// Model represents the application status bar
|
||||
type Model struct {
|
||||
// Layout
|
||||
width int
|
||||
styles Style
|
||||
|
||||
// Left sections
|
||||
messagesCount int
|
||||
modelName string
|
||||
markdownMode bool
|
||||
|
||||
// Right sections
|
||||
Spinner spinner.Model
|
||||
isGenerating bool
|
||||
startTime time.Time
|
||||
flashType FlashType
|
||||
flashMessage string
|
||||
}
|
||||
|
||||
// Style represents the styling for the status bar
|
||||
type Style struct {
|
||||
base lipgloss.Style
|
||||
divider lipgloss.Style
|
||||
message lipgloss.Style
|
||||
model lipgloss.Style
|
||||
mode lipgloss.Style
|
||||
flash map[FlashType]lipgloss.Style
|
||||
bar lipgloss.Style
|
||||
}
|
||||
|
||||
// New creates a new status bar component
|
||||
func New(width int) Model {
|
||||
s := spinner.New()
|
||||
s.Spinner = spinner.Moon
|
||||
|
||||
// Initialize base styles
|
||||
baseStyle := lipgloss.NewStyle().Bold(true).Padding(0, 1)
|
||||
|
||||
return Model{
|
||||
width: width,
|
||||
Spinner: s,
|
||||
styles: Style{
|
||||
base: baseStyle,
|
||||
divider: lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#666666")).
|
||||
Padding(0, 1),
|
||||
message: baseStyle.Foreground(theme.Yellow),
|
||||
model: baseStyle.Foreground(theme.Blue),
|
||||
mode: baseStyle.Foreground(theme.Green),
|
||||
flash: map[FlashType]lipgloss.Style{
|
||||
FlashTypeError: baseStyle.Foreground(theme.Red),
|
||||
FlashTypeSuccess: baseStyle.Foreground(theme.Green),
|
||||
},
|
||||
bar: lipgloss.NewStyle().
|
||||
BorderStyle(lipgloss.NormalBorder()).
|
||||
BorderForeground(theme.Gray).
|
||||
BorderTop(true).
|
||||
Width(width),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SetWidth updates the width of the status bar
|
||||
func (s *Model) SetWidth(width int) {
|
||||
s.width = width
|
||||
}
|
||||
|
||||
// SetLoading sets loading state
|
||||
func (s *Model) SetGenerating(loading bool) {
|
||||
s.isGenerating = loading
|
||||
if loading {
|
||||
s.startTime = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Model) ClearFlashMessage() {
|
||||
s.flashType = ""
|
||||
s.flashMessage = ""
|
||||
}
|
||||
|
||||
// SetError sets an error message
|
||||
func (s *Model) setFlashMessage(t FlashType, msg string) {
|
||||
s.flashType = t
|
||||
s.flashMessage = msg
|
||||
}
|
||||
|
||||
// SetMessageCount sets the message count
|
||||
func (s *Model) SetMessageCount(count int) {
|
||||
s.messagesCount = count
|
||||
}
|
||||
|
||||
// SetModelName sets the model name
|
||||
func (s *Model) SetModelName(name string) {
|
||||
s.modelName = name
|
||||
}
|
||||
|
||||
// SetMarkdownMode sets the markdown mode
|
||||
func (s *Model) SetMarkdownMode(enable bool) {
|
||||
s.markdownMode = enable
|
||||
}
|
||||
|
||||
// Init implements tea.Model
|
||||
func (s *Model) Init() tea.Cmd {
|
||||
return s.Spinner.Tick
|
||||
}
|
||||
|
||||
// Update implements tea.Model
|
||||
func (s Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
var cmds []tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
s.SetWidth(msg.Width)
|
||||
case spinner.TickMsg:
|
||||
s.Spinner, cmd = s.Spinner.Update(msg)
|
||||
cmds = append(cmds, cmd)
|
||||
case EventFlashError:
|
||||
s.setFlashMessage(FlashTypeError, string(msg))
|
||||
case EventFlashSuccess:
|
||||
s.setFlashMessage(FlashTypeSuccess, string(msg))
|
||||
}
|
||||
return s, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
// View implements tea.Model
|
||||
func (s *Model) View() string {
|
||||
// Render left and right sections
|
||||
leftBar := s.joinSections(s.getLeftSections())
|
||||
rightBar := s.joinSections(s.getRightSections())
|
||||
|
||||
// Calculate and handle available space
|
||||
availableWidth := s.width
|
||||
leftWidth := lipgloss.Width(leftBar)
|
||||
rightWidth := lipgloss.Width(rightBar)
|
||||
spacerWidth := availableWidth - leftWidth - rightWidth
|
||||
|
||||
var statusBar string
|
||||
// Handle different space scenarios
|
||||
switch {
|
||||
case spacerWidth >= 0:
|
||||
// Enough space for everything
|
||||
spacer := strings.Repeat(" ", spacerWidth)
|
||||
statusBar = fmt.Sprintf("%s%s%s", leftBar, spacer, rightBar)
|
||||
case availableWidth >= leftWidth:
|
||||
// Only show left bar
|
||||
statusBar = leftBar
|
||||
default:
|
||||
// Not enough space - truncate left bar
|
||||
statusBar = lipgloss.NewStyle().
|
||||
MaxWidth(availableWidth).
|
||||
Render(leftBar)
|
||||
}
|
||||
|
||||
// Apply the final bar styling
|
||||
return s.styles.bar.Width(s.width).Render(statusBar)
|
||||
}
|
||||
|
||||
// getLeftSections returns the rendered left sections of the status bar
|
||||
func (s *Model) getLeftSections() []string {
|
||||
mode := "PLAIN"
|
||||
if s.markdownMode {
|
||||
mode = "MARKDOWN"
|
||||
}
|
||||
|
||||
return []string{
|
||||
s.styles.message.Render(fmt.Sprintf("💬 %d", s.messagesCount)),
|
||||
s.styles.model.Render(fmt.Sprintf("🤖 %s", s.modelName)),
|
||||
s.styles.mode.Render(mode),
|
||||
}
|
||||
}
|
||||
|
||||
// getRightSections returns the rendered right sections of the status bar
|
||||
func (s *Model) getRightSections() []string {
|
||||
var sections []string
|
||||
|
||||
// Generation status
|
||||
if s.isGenerating {
|
||||
duration := time.Since(s.startTime).Round(time.Millisecond)
|
||||
sections = append(sections,
|
||||
s.styles.flash[FlashTypeSuccess].Render(
|
||||
fmt.Sprintf("⚡ %s %.1fs", s.Spinner.View(), duration.Seconds()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Flash message
|
||||
if s.flashMessage != "" {
|
||||
var icon string
|
||||
switch s.flashType {
|
||||
case FlashTypeError:
|
||||
icon = "×"
|
||||
case FlashTypeSuccess:
|
||||
icon = "✓"
|
||||
}
|
||||
sections = append(sections,
|
||||
s.styles.flash[s.flashType].Render(
|
||||
fmt.Sprintf("%s %s", icon, s.flashMessage),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return sections
|
||||
}
|
||||
|
||||
// joinSections joins sections with a dot separator
|
||||
func (s *Model) joinSections(sections []string) string {
|
||||
if len(sections) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
separator := s.styles.divider.SetString("┃").String()
|
||||
result := sections[0]
|
||||
for _, section := range sections[1:] {
|
||||
result = lipgloss.JoinHorizontal(lipgloss.Center, result, separator, section)
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user