repos
/ orchard main

orchard

mirror

Every site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.

blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics

1.9 KB · 66 lines · Go Raw History
 1package tools
 2
 3import "sync"
 4
 5// A widget is the structured half of an answer. The model still writes prose
 6// about a ticker or a forecast, but the numbers behind it read far better as a
 7// chart than as a sentence, and the model is not good at either drawing one or
 8// at reciting fourteen figures without dropping one.
 9//
10// What is recorded here is the subject and not the data. The frontend fetches
11// the readings itself from /api/widget/..., so switching a chart from a day to
12// a year does not need another turn, and reopening a week old conversation
13// draws today's price rather than the one that was on screen when it was asked.
14type Widget struct {
15	Kind string `json:"kind"`
16
17	// ticker
18	Symbol string `json:"symbol,omitempty"`
19
20	// weather. Zip is what pollen.com is keyed on and Country decides whether
21	// there is any pollen to ask for at all.
22	Place   string  `json:"place,omitempty"`
23	Lat     float64 `json:"lat,omitempty"`
24	Lon     float64 `json:"lon,omitempty"`
25	Zip     string  `json:"zip,omitempty"`
26	Country string  `json:"country,omitempty"`
27
28	Label string `json:"label,omitempty"`
29}
30
31// Sink collects the widgets one turn produced. A turn runs its tools on
32// goroutines, so this locks, and it is created per turn and hung on the Deps
33// copy rather than on the shared one, for the same reason the session is.
34type Sink struct {
35	mu   sync.Mutex
36	list []Widget
37	seen map[string]bool
38}
39
40func NewSink() *Sink { return &Sink{seen: map[string]bool{}} }
41
42// Add ignores a repeat. A model that asks for VTI twice in one turn should not
43// get two identical charts stacked up.
44func (s *Sink) Add(w Widget) {
45	if s == nil {
46		return
47	}
48	s.mu.Lock()
49	defer s.mu.Unlock()
50	key := w.Kind + "\x00" + w.Symbol + "\x00" + w.Place
51	if s.seen[key] {
52		return
53	}
54	s.seen[key] = true
55	s.list = append(s.list, w)
56}
57
58func (s *Sink) List() []Widget {
59	if s == nil {
60		return nil
61	}
62	s.mu.Lock()
63	defer s.mu.Unlock()
64	return append([]Widget(nil), s.list...)
65}