Files
magpie/internal/gui/backend.go
T

89 lines
2.5 KiB
Go

package gui
import (
"context"
"log"
"sync/atomic"
"time"
"github.com/yetone/magpie/internal/agent"
"github.com/yetone/magpie/internal/catalog"
"github.com/yetone/magpie/internal/gateway"
"github.com/yetone/magpie/internal/provider"
)
// served is the gateway this process serves, nil while another magpie
// has it.
var served atomic.Pointer[gateway.Server]
// startBackend starts what serves the page and the agents: the gateway,
// unless another magpie has it (then that one serves and this one only
// shows its status, and gw is nil — until that one is gone: a magpie left
// running from before an update, a magpie serve in a terminal), and the
// model lists kept warm.
func startBackend() (gw *gateway.Server) {
gateway.Window = true // the routing this process serves is shown on its page
gw = serveGateway()
go watchGateway()
// Model lists are fetched, never compiled in: whatever the agents can see
// comes from the models.dev catalog plus each vendor's own /models answer.
// Keep both halves warm without making the user click anything.
go func() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
if catalog.Stale() {
if err := catalog.Sync(ctx); err != nil {
log.Println("catalog:", err)
}
}
cancel()
// A signed-in agent's list exists only at the vendor; fill it in the
// first time so the picker never shows a stale snapshot.
for _, p := range provider.All() {
if p.Account == nil || !p.Ready() {
continue
}
if _, ok := p.Fetched(); ok {
continue
}
c, cancel := context.WithTimeout(context.Background(), 20*time.Second)
if _, err := p.Fetch(c); err != nil {
log.Println(p.ID + ": " + err.Error())
}
cancel()
}
// lists an older magpie wrote into agents' files, without what
// it has learnt since (context windows, providers added)
agent.SyncCatalog()
}()
return gw
}
var gatewayWatch = 15 * time.Second
// watchGateway takes the gateway up once the magpie that had it is gone.
func watchGateway() {
for {
time.Sleep(gatewayWatch)
if served.Load() == nil && !gateway.Running() {
serveGateway()
}
}
}
// serveGateway starts the gateway here when no magpie has it: the one
// started, or nil.
func serveGateway() *gateway.Server {
if gateway.Running() {
return nil
}
gw := gateway.New()
served.Store(gw)
go func() {
if err := gw.ListenAndServe(context.Background()); err != nil {
log.Println("gateway:", err)
served.CompareAndSwap(gw, nil) // another took the port first
}
}()
return gw
}