Files
HxClaw/cmd/hxclaw/internal/spinner.go

97 lines
1.9 KiB
Go
Raw Normal View History

// Package internal 包含 hxclaw 的内部工具模块
// 提供配置管理、Markdown 渲染、输入读取等功能
package internal
import (
"fmt"
"os"
"time"
"charm.land/bubbles/v2/spinner"
"charm.land/lipgloss/v2"
)
// SpinnerState 表示加载动画的状态
type SpinnerState int
const (
StateThinking SpinnerState = iota // 思考中状态
StateAnswering // 回答中状态
StateDone // 完成状态
)
// Spinner 加载动画组件,用于显示思考状态
type Spinner struct {
text string // 显示的文本内容
state SpinnerState // 当前状态
spinner spinner.Model // 底层动画模型
stopCh chan struct{} // 停止信号通道
doneCh chan struct{} // 完成信号通道
}
// NewSpinner 创建一个新的加载动画实例
// text: 初始显示的文本内容
func NewSpinner(text string) *Spinner {
s := spinner.New(
spinner.WithSpinner(spinner.MiniDot),
spinner.WithStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("243"))),
)
return &Spinner{
text: text,
state: StateThinking,
spinner: s,
stopCh: make(chan struct{}),
doneCh: make(chan struct{}),
}
}
func (s *Spinner) Start() {
go s.run()
s.tick()
}
func (s *Spinner) Update(text string) {
s.text = text
s.state = StateAnswering
}
func (s *Spinner) Stop() {
close(s.stopCh)
<-s.doneCh
}
func (s *Spinner) tick() {
msg := s.spinner.Tick()
if msg, ok := msg.(spinner.TickMsg); ok {
s.spinner, _ = s.spinner.Update(msg)
}
}
func (s *Spinner) run() {
defer close(s.doneCh)
ticker := time.NewTicker(time.Second / 12)
defer ticker.Stop()
for {
select {
case <-ticker.C:
s.tick()
s.render()
case <-s.stopCh:
s.clear()
return
}
}
}
func (s *Spinner) render() {
fmt.Printf("\r %s %s", s.spinner.View(), s.text)
os.Stdout.Sync()
}
func (s *Spinner) clear() {
fmt.Printf("\r %s 思考完成.\n", s.spinner.View())
os.Stdout.Sync()
}