ollama source for Momentry Core verification
This commit is contained in:
216
progress/bar.go
Normal file
216
progress/bar.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package progress
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/term"
|
||||
|
||||
"github.com/ollama/ollama/format"
|
||||
)
|
||||
|
||||
type Bar struct {
|
||||
message string
|
||||
messageWidth int
|
||||
|
||||
maxValue int64
|
||||
initialValue int64
|
||||
currentValue int64
|
||||
|
||||
started time.Time
|
||||
stopped time.Time
|
||||
|
||||
maxBuckets int
|
||||
buckets []bucket
|
||||
}
|
||||
|
||||
type bucket struct {
|
||||
updated time.Time
|
||||
value int64
|
||||
}
|
||||
|
||||
func NewBar(message string, maxValue, initialValue int64) *Bar {
|
||||
b := Bar{
|
||||
message: message,
|
||||
messageWidth: -1,
|
||||
maxValue: maxValue,
|
||||
initialValue: initialValue,
|
||||
currentValue: initialValue,
|
||||
started: time.Now(),
|
||||
maxBuckets: 10,
|
||||
}
|
||||
|
||||
if initialValue >= maxValue {
|
||||
b.stopped = time.Now()
|
||||
}
|
||||
|
||||
return &b
|
||||
}
|
||||
|
||||
// formatDuration limits the rendering of a time.Duration to 2 units
|
||||
func formatDuration(d time.Duration) string {
|
||||
switch {
|
||||
case d >= 100*time.Hour:
|
||||
return "99h+"
|
||||
case d >= time.Hour:
|
||||
return fmt.Sprintf("%dh%dm", int(d.Hours()), int(d.Minutes())%60)
|
||||
default:
|
||||
return d.Round(time.Second).String()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bar) String() string {
|
||||
termWidth, _, err := term.GetSize(int(os.Stderr.Fd()))
|
||||
if err != nil {
|
||||
termWidth = defaultTermWidth
|
||||
}
|
||||
|
||||
var pre strings.Builder
|
||||
if len(b.message) > 0 {
|
||||
message := strings.TrimSpace(b.message)
|
||||
if b.messageWidth > 0 && len(message) > b.messageWidth {
|
||||
message = message[:b.messageWidth]
|
||||
}
|
||||
|
||||
fmt.Fprintf(&pre, "%s", message)
|
||||
if padding := b.messageWidth - pre.Len(); padding > 0 {
|
||||
pre.WriteString(repeat(" ", padding))
|
||||
}
|
||||
|
||||
pre.WriteString(" ")
|
||||
}
|
||||
|
||||
fmt.Fprintf(&pre, "%3.0f%%", b.percent())
|
||||
|
||||
var suf strings.Builder
|
||||
// max 13 characters: "999 MB/999 MB"
|
||||
if b.stopped.IsZero() {
|
||||
curValue := format.HumanBytes(b.currentValue)
|
||||
suf.WriteString(repeat(" ", 6-len(curValue)))
|
||||
suf.WriteString(curValue)
|
||||
suf.WriteString("/")
|
||||
|
||||
maxValue := format.HumanBytes(b.maxValue)
|
||||
suf.WriteString(repeat(" ", 6-len(maxValue)))
|
||||
suf.WriteString(maxValue)
|
||||
} else {
|
||||
maxValue := format.HumanBytes(b.maxValue)
|
||||
suf.WriteString(repeat(" ", 6-len(maxValue)))
|
||||
suf.WriteString(maxValue)
|
||||
suf.WriteString(repeat(" ", 7))
|
||||
}
|
||||
|
||||
rate := b.rate()
|
||||
// max 10 characters: " 999 MB/s"
|
||||
if b.stopped.IsZero() && rate > 0 {
|
||||
suf.WriteString(" ")
|
||||
humanRate := format.HumanBytes(int64(rate))
|
||||
suf.WriteString(repeat(" ", 6-len(humanRate)))
|
||||
suf.WriteString(humanRate)
|
||||
suf.WriteString("/s")
|
||||
} else {
|
||||
suf.WriteString(repeat(" ", 10))
|
||||
}
|
||||
|
||||
// max 8 characters: " 59m59s"
|
||||
if b.stopped.IsZero() && rate > 0 {
|
||||
suf.WriteString(" ")
|
||||
var remaining time.Duration
|
||||
if rate > 0 {
|
||||
remaining = time.Duration(int64(float64(b.maxValue-b.currentValue)/rate)) * time.Second
|
||||
}
|
||||
|
||||
humanRemaining := formatDuration(remaining)
|
||||
suf.WriteString(repeat(" ", 6-len(humanRemaining)))
|
||||
suf.WriteString(humanRemaining)
|
||||
} else {
|
||||
suf.WriteString(repeat(" ", 8))
|
||||
}
|
||||
|
||||
var mid strings.Builder
|
||||
// add 5 extra spaces: 2 boundary characters and 1 space at each end
|
||||
f := termWidth - pre.Len() - suf.Len() - 5
|
||||
n := int(float64(f) * b.percent() / 100)
|
||||
|
||||
mid.WriteString(" ▕")
|
||||
|
||||
if n > 0 {
|
||||
mid.WriteString(repeat("█", n))
|
||||
}
|
||||
|
||||
if f-n > 0 {
|
||||
mid.WriteString(repeat(" ", f-n))
|
||||
}
|
||||
|
||||
mid.WriteString("▏ ")
|
||||
|
||||
return pre.String() + mid.String() + suf.String()
|
||||
}
|
||||
|
||||
func (b *Bar) Set(value int64) {
|
||||
if value >= b.maxValue {
|
||||
value = b.maxValue
|
||||
}
|
||||
|
||||
b.currentValue = value
|
||||
if b.currentValue >= b.maxValue {
|
||||
b.stopped = time.Now()
|
||||
}
|
||||
|
||||
// throttle bucket updates to 1 per second
|
||||
if len(b.buckets) == 0 || time.Since(b.buckets[len(b.buckets)-1].updated) > time.Second {
|
||||
b.buckets = append(b.buckets, bucket{
|
||||
updated: time.Now(),
|
||||
value: value,
|
||||
})
|
||||
|
||||
if len(b.buckets) > b.maxBuckets {
|
||||
b.buckets = b.buckets[1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bar) percent() float64 {
|
||||
if b.maxValue > 0 {
|
||||
return float64(b.currentValue) / float64(b.maxValue) * 100
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (b *Bar) rate() float64 {
|
||||
var numerator, denominator float64
|
||||
|
||||
if !b.stopped.IsZero() {
|
||||
numerator = float64(b.currentValue - b.initialValue)
|
||||
denominator = b.stopped.Sub(b.started).Round(time.Second).Seconds()
|
||||
} else {
|
||||
switch len(b.buckets) {
|
||||
case 0:
|
||||
// noop
|
||||
case 1:
|
||||
numerator = float64(b.buckets[0].value - b.initialValue)
|
||||
denominator = b.buckets[0].updated.Sub(b.started).Round(time.Second).Seconds()
|
||||
default:
|
||||
first, last := b.buckets[0], b.buckets[len(b.buckets)-1]
|
||||
numerator = float64(last.value - first.value)
|
||||
denominator = last.updated.Sub(first.updated).Round(time.Second).Seconds()
|
||||
}
|
||||
}
|
||||
|
||||
if denominator != 0 {
|
||||
return numerator / denominator
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func repeat(s string, n int) string {
|
||||
if n > 0 {
|
||||
return strings.Repeat(s, n)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
134
progress/progress.go
Normal file
134
progress/progress.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package progress
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTermWidth = 80
|
||||
defaultTermHeight = 24
|
||||
)
|
||||
|
||||
type State interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
type Progress struct {
|
||||
mu sync.Mutex
|
||||
// buffer output to minimize flickering on all terminals
|
||||
w *bufio.Writer
|
||||
|
||||
pos int
|
||||
|
||||
ticker *time.Ticker
|
||||
states []State
|
||||
}
|
||||
|
||||
func NewProgress(w io.Writer) *Progress {
|
||||
p := &Progress{w: bufio.NewWriter(w)}
|
||||
go p.start()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Progress) stop() bool {
|
||||
for _, state := range p.states {
|
||||
if spinner, ok := state.(*Spinner); ok {
|
||||
spinner.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
if p.ticker != nil {
|
||||
p.ticker.Stop()
|
||||
p.ticker = nil
|
||||
p.render()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Progress) Stop() bool {
|
||||
stopped := p.stop()
|
||||
if stopped {
|
||||
fmt.Fprint(p.w, "\n")
|
||||
p.w.Flush()
|
||||
}
|
||||
return stopped
|
||||
}
|
||||
|
||||
func (p *Progress) StopAndClear() bool {
|
||||
defer p.w.Flush()
|
||||
|
||||
fmt.Fprint(p.w, "\033[?25l")
|
||||
defer fmt.Fprint(p.w, "\033[?25h")
|
||||
|
||||
stopped := p.stop()
|
||||
if stopped {
|
||||
// clear all progress lines
|
||||
for i := range p.pos {
|
||||
if i > 0 {
|
||||
fmt.Fprint(p.w, "\033[A")
|
||||
}
|
||||
fmt.Fprint(p.w, "\033[2K\033[1G")
|
||||
}
|
||||
}
|
||||
|
||||
return stopped
|
||||
}
|
||||
|
||||
func (p *Progress) Add(key string, state State) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
p.states = append(p.states, state)
|
||||
}
|
||||
|
||||
func (p *Progress) render() {
|
||||
_, termHeight, err := term.GetSize(int(os.Stderr.Fd()))
|
||||
if err != nil {
|
||||
termHeight = defaultTermHeight
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
defer p.w.Flush()
|
||||
|
||||
// eliminate flickering on terminals that support synchronized output
|
||||
fmt.Fprint(p.w, "\033[?2026h")
|
||||
defer fmt.Fprint(p.w, "\033[?2026l")
|
||||
|
||||
fmt.Fprint(p.w, "\033[?25l")
|
||||
defer fmt.Fprint(p.w, "\033[?25h")
|
||||
|
||||
// move the cursor back to the beginning
|
||||
for range p.pos - 1 {
|
||||
fmt.Fprint(p.w, "\033[A")
|
||||
}
|
||||
fmt.Fprint(p.w, "\033[1G")
|
||||
|
||||
// render progress lines
|
||||
maxHeight := min(len(p.states), termHeight)
|
||||
for i := len(p.states) - maxHeight; i < len(p.states); i++ {
|
||||
fmt.Fprint(p.w, p.states[i].String(), "\033[K")
|
||||
if i < len(p.states)-1 {
|
||||
fmt.Fprint(p.w, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
p.pos = len(p.states)
|
||||
}
|
||||
|
||||
func (p *Progress) start() {
|
||||
p.ticker = time.NewTicker(100 * time.Millisecond)
|
||||
for range p.ticker.C {
|
||||
p.render()
|
||||
}
|
||||
}
|
||||
79
progress/spinner.go
Normal file
79
progress/spinner.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package progress
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Spinner struct {
|
||||
message atomic.Value
|
||||
messageWidth int
|
||||
|
||||
parts []string
|
||||
|
||||
value int
|
||||
|
||||
ticker *time.Ticker
|
||||
started time.Time
|
||||
stopped time.Time
|
||||
}
|
||||
|
||||
func NewSpinner(message string) *Spinner {
|
||||
s := &Spinner{
|
||||
parts: []string{
|
||||
"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏",
|
||||
},
|
||||
started: time.Now(),
|
||||
}
|
||||
s.SetMessage(message)
|
||||
go s.start()
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Spinner) SetMessage(message string) {
|
||||
s.message.Store(message)
|
||||
}
|
||||
|
||||
func (s *Spinner) String() string {
|
||||
var sb strings.Builder
|
||||
|
||||
if message, ok := s.message.Load().(string); ok && len(message) > 0 {
|
||||
message := strings.TrimSpace(message)
|
||||
if s.messageWidth > 0 && len(message) > s.messageWidth {
|
||||
message = message[:s.messageWidth]
|
||||
}
|
||||
|
||||
fmt.Fprintf(&sb, "%s", message)
|
||||
if padding := s.messageWidth - sb.Len(); padding > 0 {
|
||||
sb.WriteString(strings.Repeat(" ", padding))
|
||||
}
|
||||
|
||||
sb.WriteString(" ")
|
||||
}
|
||||
|
||||
if s.stopped.IsZero() {
|
||||
spinner := s.parts[s.value]
|
||||
sb.WriteString(spinner)
|
||||
sb.WriteString(" ")
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (s *Spinner) start() {
|
||||
s.ticker = time.NewTicker(100 * time.Millisecond)
|
||||
for range s.ticker.C {
|
||||
s.value = (s.value + 1) % len(s.parts)
|
||||
if !s.stopped.IsZero() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Spinner) Stop() {
|
||||
if s.stopped.IsZero() {
|
||||
s.stopped = time.Now()
|
||||
}
|
||||
}
|
||||
33
progress/stepbar.go
Normal file
33
progress/stepbar.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package progress
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StepBar displays step-based progress (e.g., for image generation steps).
|
||||
type StepBar struct {
|
||||
message string
|
||||
current int
|
||||
total int
|
||||
}
|
||||
|
||||
func NewStepBar(message string, total int) *StepBar {
|
||||
return &StepBar{message: message, total: total}
|
||||
}
|
||||
|
||||
func (s *StepBar) Set(current int) {
|
||||
s.current = current
|
||||
}
|
||||
|
||||
func (s *StepBar) String() string {
|
||||
percent := float64(s.current) / float64(s.total) * 100
|
||||
barWidth := s.total
|
||||
empty := barWidth - s.current
|
||||
|
||||
// "Generating 0% ▕ ▏ 0/9"
|
||||
return fmt.Sprintf("%s %3.0f%% ▕%s%s▏ %d/%d",
|
||||
s.message, percent,
|
||||
strings.Repeat("█", s.current), strings.Repeat(" ", empty),
|
||||
s.current, s.total)
|
||||
}
|
||||
Reference in New Issue
Block a user