// 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() }