mirror of
https://github.com/ollama/ollama.git
synced 2026-07-16 13:19:40 -05:00
110 lines
3.6 KiB
Go
110 lines
3.6 KiB
Go
package launch
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// SpinnerFrames are the braille spinner frames used by the bubbletea TUIs in
|
|
// this codebase (sign-in, upgrade). StartSpinner uses the same frames for its
|
|
// fallback so the restart spinner matches the look of those flows.
|
|
var SpinnerFrames = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
|
|
|
|
// DefaultSpinner, when set, starts an animated spinner displaying message and
|
|
// returns a *Spinner. cmd/cmd.go registers a bubbletea implementation from
|
|
// cmd/tui; when unset (or when it returns nil, e.g. no TTY) StartSpinner falls
|
|
// back to a simple ANSI spinner using SpinnerFrames.
|
|
var DefaultSpinner func(message string) *Spinner
|
|
|
|
// Spinner is a handle on a running animated spinner. Stop halts the spinner
|
|
// and clears its line (it blocks until the spinner has fully stopped and is
|
|
// safe to call multiple times). Cancelled returns a channel that is closed if
|
|
// the user interrupts the spinner (e.g. with Ctrl+C); wait loops can select on
|
|
// it to abort early. For the non-interactive ANSI fallback the channel is never
|
|
// closed because Ctrl+C raises SIGINT and terminates the process directly.
|
|
type Spinner struct {
|
|
stop func()
|
|
cancelled chan struct{}
|
|
}
|
|
|
|
// NewSpinner builds a Spinner from a stop function and a cancellation channel.
|
|
// It is intended for implementations of DefaultSpinner (e.g. the bubbletea
|
|
// spinner in cmd/tui). stop must be safe to call multiple times; cancelled is
|
|
// closed by the implementation when the user interrupts the spinner, or left
|
|
// open when interruption is handled another way (e.g. SIGINT).
|
|
func NewSpinner(stop func(), cancelled chan struct{}) *Spinner {
|
|
return &Spinner{stop: stop, cancelled: cancelled}
|
|
}
|
|
|
|
// Stop halts the spinner and clears its line. It is a no-op when the spinner
|
|
// already stopped (for example after the user cancelled it).
|
|
func (s *Spinner) Stop() {
|
|
if s != nil && s.stop != nil {
|
|
s.stop()
|
|
}
|
|
}
|
|
|
|
// Cancelled returns a channel that is closed when the user interrupts the
|
|
// spinner. Callers may select on it to abort a blocking wait.
|
|
func (s *Spinner) Cancelled() <-chan struct{} {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
return s.cancelled
|
|
}
|
|
|
|
// StartSpinner begins an animated spinner displaying message and returns a
|
|
// *Spinner handle. It uses DefaultSpinner when available, otherwise a simple
|
|
// ANSI fallback that renders SpinnerFrames to stderr without requiring a TTY.
|
|
func StartSpinner(message string) *Spinner {
|
|
if DefaultSpinner != nil {
|
|
if s := DefaultSpinner(message); s != nil {
|
|
return s
|
|
}
|
|
}
|
|
return defaultSpinner(message)
|
|
}
|
|
|
|
// defaultSpinner renders SpinnerFrames to stderr without requiring a TTY. It
|
|
// runs in its own goroutine so it can animate while a caller polls; Stop
|
|
// signals the goroutine to exit, waits for it, and clears the spinner line.
|
|
func defaultSpinner(message string) *Spinner {
|
|
frames := SpinnerFrames
|
|
frame := 0
|
|
fmt.Fprintf(os.Stderr, "\r\033[90m%s %s\033[0m", message, frames[0])
|
|
|
|
done := make(chan struct{})
|
|
exited := make(chan struct{})
|
|
var once sync.Once
|
|
|
|
go func() {
|
|
ticker := time.NewTicker(100 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-done:
|
|
close(exited)
|
|
return
|
|
case <-ticker.C:
|
|
frame++
|
|
fmt.Fprintf(os.Stderr, "\r\033[90m%s %s\033[0m", message, frames[frame%len(frames)])
|
|
}
|
|
}
|
|
}()
|
|
|
|
stop := func() {
|
|
once.Do(func() {
|
|
close(done)
|
|
<-exited
|
|
fmt.Fprintf(os.Stderr, "\r\033[K")
|
|
})
|
|
}
|
|
|
|
// Ctrl+C in non-raw mode raises SIGINT and terminates the process by
|
|
// default (the launch flow installs no SIGINT handler), so this cancelled
|
|
// channel is intentionally never closed.
|
|
return &Spinner{stop: stop, cancelled: make(chan struct{})}
|
|
}
|