Parent directory

main.go

2465 bytes
 1package main
 2
 3import (
 4	"context"
 5	"fmt"
 6	"os"
 7	"os/signal"
 8	"syscall"
 9	"time"
10
11	"github.com/charmbracelet/bubbletea"
12
13	"git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/adapters"
14	"git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/bootstrap"
15	"git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/config"
16	"git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/game"
17	"git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/stt"
18	"git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/tts"
19	"git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/ui"
20)
21
22// recordCap bounds a single push-to-talk capture. It is passed to the recorder
23// (which enforces it) and to the UI (which shows it as the cap indicator).
24const recordCap = 15 * time.Second
25
26// The capture command is fixed for this deployment; it is not user-configurable.
27const recordCommand = "arecord -r 16000 -f S16_LE -c 1"
28
29func main() {
30	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
31	defer stop()
32
33	cfg := config.ParseConfig()
34
35	svc, err := bootstrap.New(ctx, cfg)
36	if err != nil {
37		fatalf("%v", err)
38	}
39	defer svc.Close()
40
41	state := game.NewState(svc.Brief)
42	orch, speechIn := buildOrchestrator(svc, state)
43	defer speechIn.Close()
44
45	m := ui.NewModel(state, orch, int(recordCap.Seconds()))
46	p := tea.NewProgram(m)
47	go func() {
48		<-ctx.Done()
49		p.Quit()
50	}()
51	if _, err := p.Run(); err != nil {
52		svc.Close()
53		fatalf("%v", err)
54	}
55}
56
57func buildOrchestrator(svc *bootstrap.Services, state *game.State) (*game.Orchestrator, *adapters.SpeechInput) {
58	gameModel := &adapters.GameModel{Client: svc.Game}
59	compactor := &adapters.Compactor{Client: svc.Compaction}
60	judgeModel := &adapters.JudgeModel{Client: svc.Judge}
61	sheetModel := &adapters.SheetModel{Client: svc.Sheet}
62	askModel := &adapters.ScratchModel{Client: svc.Scratch}
63	flashModel := &adapters.FlashcardModel{Client: svc.Scratch}
64
65	speechIn := &adapters.SpeechInput{
66		Recorder: stt.NewRecorder(recordCommand, recordCap),
67		ASR:      stt.NewASRClient(svc.Cfg.AudioConfig.AudioBaseURL, svc.HTTP),
68	}
69	speechOut := &adapters.SpeechOutput{
70		Client: tts.NewClient(svc.Cfg.AudioConfig.AudioBaseURL, svc.HTTP),
71		Player: tts.NewPlayer(),
72	}
73
74	orch := game.NewOrchestrator(state, gameModel, judgeModel, compactor, sheetModel, askModel, flashModel, speechIn, speechOut)
75	return orch, speechIn
76}
77
78func fatalf(format string, args ...any) {
79	fmt.Fprintf(os.Stderr, "kaiwari: "+format+"\n", args...)
80	os.Exit(1)
81}