Something went wrong. Try again.
small gleam coding and persistent agent daemon with a detachable cli and a canvas ui forked from okami.mom/albedo
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125package tui
import "fmt"
// A mood is a class of moment in a turn. Each class owns its own faces, so a// face always means something: what albedo is doing, or how a turn ended.type mood string
const ( moodPreparing mood = "preparing" moodThinking mood = "thinking" moodResponding mood = "responding" moodWorking mood = "working" moodCompacting mood = "compacting" moodStopping mood = "stopping"
moodDone mood = "done" moodQuick mood = "quick" moodLong mood = "long" moodTired mood = "tired" moodFailed mood = "failed" moodStopped mood = "stopped")
// Faces avoid combining marks and right-to-left letters, which terminals// disagree on.//// A phase owns a pool of animations and each turn plays one of them. A face// keeps its parentheses in place across frames: only eyes, mouth, and what// follows the face move.var animations = map[mood][][]string{ moodPreparing: {{"(・・ )", "( ・・)"}}, moodThinking: { {"( ˘ω˘ )", "( ˘ω˘ ).", "( ˘ω˘ )..", "( ˘ω˘ )..."}, {"(・ω・ )", "( ・ω・)", "(・ω・ )?", "( ・ω・)?"}, {"(´-ω-`)", "(´・ω・`)", "(´-ω-`)", "(´・ω・`)!"}, }, moodResponding: { {"(ˊᗜˋ )", "(ˊ-ˋ )", "( ˊᗜˋ)", "( ˊ-ˋ)"}, {"(˶ᵔ ᗜ ᵔ˶)", "(˶ᵔ ᵕ ᵔ˶)"}, {"(・ᗜ・)", "(・-・)", "(・ᗜ・)ノ", "(・-・)ノ"}, }, moodWorking: { {"(˶•ᴗ•)カタ", "(˶•ᴗ•)カタカタ", "(˶•ᴗ•)カタカタカタ"}, {"(・_・ )", "( ・_・)", "(・ᴗ・ )✧"}, {"(っ•ᴗ•)っ・", "(っ•ᴗ•)っ ・", "(っ•ᴗ•)っ ・"}, {"ヽ(・ω・)ノ", "ヾ(・ω・)ノ"}, {"(๑•ᴗ•)", "(๑•ᴗ•)✧", "(๑•ᴗ•) ✧"}, }, moodCompacting: {{"(>_<)", "(>.<)"}}, moodStopping: {{"(・_・;)"}},}
// An outcome owns a pool of faces and each turn shows one of them.var faces = map[mood][]string{ moodDone: {"(˶ᵔ ᵕ ᵔ˶)", "(๑˃ᴗ˂)", "ヽ(・∀・)ノ", "(ᵔᴥᵔ)", "(っ˘ω˘ς)"}, moodQuick: {"(・ω・)ノ", "(`・ω・´)", "(^_−)☆"}, moodLong: {"(ง ˃ᴗ˂)ง", "(๑˃ᴗ˂)✧", "(ˊᗜˋ*)✧"}, moodTired: {"( ̄ー ̄;)ゞ", "(・ω・;)ゞ", "( ˘ᴗ˘ )フゥ", "(´-ω-`)zzZ", "_(:3 」∠)_"}, moodFailed: {"(╥﹏╥)", "(。•︿•。)", "(っ- ‸ – ς)"}, moodStopped: {"(・_・;)", "(°ロ°)", "( ̄□ ̄;)"},}
// pick chooses from n by seed, salted by the mood so one turn's picks for// different moods do not move in step.func (m mood) pick(seed int64, n int) int { return int(fnv1a(uint64(seed), string(m)) % uint64(n))}
// face picks a stable face for seed, so a rebuilt transcript keeps its faces.func (m mood) face(seed int64) string { set := faces[m] return set[m.pick(seed, len(set))]}
// framesPerFace slows faces against the 80ms spinner tick, about 0.5s each.const framesPerFace = 6
// frame is the turn's animation at spinner tick.func (m mood) frame(seed int64, tick int) string { pool := animations[m] set := pool[m.pick(seed, len(pool))] return set[(tick/framesPerFace)%len(set)]}
// outcome classes a finished turn by how it ended and how long it took.func outcome(failed, stopped bool, elapsedMs int64) mood { switch { case stopped: return moodStopped case failed: return moodFailed case elapsedMs > 10*60_000: return moodTired case elapsedMs >= 3*60_000: return moodLong case elapsedMs > 0 && elapsedMs < 4_000: return moodQuick } return moodDone}
func formatElapsed(ms int64) string { s := ms / 1000 switch { case s < 60: return fmt.Sprintf("%ds", s) case s < 3600: return fmt.Sprintf("%dm %ds", s/60, s%60) } return fmt.Sprintf("%dh %dm", s/3600, s/60%60)}
// formatGap is how long you were away, in the largest whole unit.func formatGap(ms int64) string { m := ms / 60_000 switch { case m < 60: return fmt.Sprintf("%dm later", m) case m < 48*60: return fmt.Sprintf("%dh later", m/60) } return fmt.Sprintf("%dd later", m/(24*60))}