diff options
| author | Grail Finder <wohilas@gmail.com> | 2026-01-03 09:59:33 +0300 |
|---|---|---|
| committer | Grail Finder <wohilas@gmail.com> | 2026-01-03 09:59:33 +0300 |
| commit | aeb2700d14c23c175376e2a5749295cd9a5d72c7 (patch) | |
| tree | e34a5860da644b2d4747e4091f23805a63ede180 | |
| parent | 6b875a2782e77afe6595b47d069a217fa2702eb1 (diff) | |
Refactor: building binary with no extra
| -rw-r--r-- | Makefile | 8 | ||||
| -rw-r--r-- | bot.go | 62 | ||||
| -rw-r--r-- | extra.go | 28 | ||||
| -rw-r--r-- | extra/cluedo.go | 73 | ||||
| -rw-r--r-- | extra/cluedo_test.go | 50 | ||||
| -rw-r--r-- | extra/stt.go | 3 | ||||
| -rw-r--r-- | extra/tts.go | 3 | ||||
| -rw-r--r-- | extra/twentyq.go | 11 | ||||
| -rw-r--r-- | extra/vad.go | 1 | ||||
| -rw-r--r-- | extra/websearch.go | 13 | ||||
| -rw-r--r-- | extra/whisper_binary.go | 3 | ||||
| -rw-r--r-- | noextra.go | 73 | ||||
| -rw-r--r-- | tools.go | 24 | ||||
| -rw-r--r-- | tui.go | 3 |
14 files changed, 142 insertions, 213 deletions
@@ -1,4 +1,4 @@ -.PHONY: setconfig run lint setup-whisper build-whisper download-whisper-model docker-up docker-down docker-logs +.PHONY: setconfig run lint setup-whisper build-whisper download-whisper-model docker-up docker-down docker-logs noextra-run noextra-server run: setconfig go build -o gf-lt && ./gf-lt @@ -6,6 +6,12 @@ run: setconfig server: setconfig go build -o gf-lt && ./gf-lt -port 3333 +noextra-run: setconfig + go build -tags '!extra' -o gf-lt && ./gf-lt + +noextra-server: setconfig + go build -tags '!extra' -o gf-lt && ./gf-lt -port 3333 + setconfig: find config.toml &>/dev/null || cp config.example.toml config.toml @@ -7,7 +7,6 @@ import ( "encoding/json" "fmt" "gf-lt/config" - "gf-lt/extra" "gf-lt/models" "gf-lt/rag" "gf-lt/storage" @@ -29,12 +28,10 @@ import ( ) var ( - httpClient = &http.Client{} - cluedoState *extra.CluedoRoundInfo // Current game state - playerOrder []string // Turn order tracking - cfg *config.Config - logger *slog.Logger - logLevel = new(slog.LevelVar) + httpClient = &http.Client{} + cfg *config.Config + logger *slog.Logger + logLevel = new(slog.LevelVar) ) var ( activeChatName string @@ -51,8 +48,8 @@ var ( chunkParser ChunkParser lastToolCall *models.FuncCall //nolint:unused // TTS_ENABLED conditionally uses this - orator extra.Orator - asr extra.STT + orator Orator + asr STT localModelsMu sync.RWMutex defaultLCPProps = map[string]float32{ "temperature": 0.8, @@ -600,32 +597,6 @@ func roleToIcon(role string) string { return "<" + role + ">: " } -// FIXME: it should not be here; move to extra -func checkGame(role string, tv *tview.TextView) { - // Handle Cluedo game flow - // should go before form msg, since formmsg takes chatBody and makes ioreader out of it - // role is almost always user, unless it's regen or resume - // cannot get in this block, since cluedoState is nil; - if cfg.EnableCluedo { - // Initialize Cluedo game if needed - if cluedoState == nil { - playerOrder = []string{cfg.UserRole, cfg.AssistantRole, cfg.CluedoRole2} - cluedoState = extra.CluedoPrepCards(playerOrder) - } - // notifyUser("got in cluedo", "yay") - currentPlayer := playerOrder[0] - playerOrder = append(playerOrder[1:], currentPlayer) // Rotate turns - if role == cfg.UserRole { - fmt.Fprintf(tv, "Your (%s) cards: %s\n", currentPlayer, cluedoState.GetPlayerCards(currentPlayer)) - } else { - chatBody.Messages = append(chatBody.Messages, models.RoleMsg{ - Role: cfg.ToolRole, - Content: cluedoState.GetPlayerCards(currentPlayer), - }) - } - } -} - func chatRound(userMsg, role string, tv *tview.TextView, regen, resume bool) { botRespMode = true botPersona := cfg.AssistantRole @@ -643,9 +614,6 @@ func chatRound(userMsg, role string, tv *tview.TextView, regen, resume bool) { return } } - if !resume { - checkGame(role, tv) - } choseChunkParser() reader, err := chunkParser.FormMsg(userMsg, role, resume) if reader == nil || err != nil { @@ -679,7 +647,7 @@ out: } // Send chunk to audio stream handler if cfg.TTS_ENABLED { - extra.TTSTextChan <- chunk + TTSTextChan <- chunk } case toolChunk := <-openAIToolChan: fmt.Fprint(tv, toolChunk) @@ -698,7 +666,7 @@ out: } // Send chunk to audio stream handler if cfg.TTS_ENABLED { - extra.TTSTextChan <- chunk + TTSTextChan <- chunk } } break out @@ -982,11 +950,6 @@ func addNewChat(chatName string) { func applyCharCard(cc *models.CharCard) { cfg.AssistantRole = cc.Role // FIXME: remove - // Initialize Cluedo if enabled and matching role - if cfg.EnableCluedo && cc.Role == "CluedoPlayer" { - playerOrder = []string{cfg.UserRole, cfg.AssistantRole, cfg.CluedoRole2} - cluedoState = extra.CluedoPrepCards(playerOrder) - } history, err := loadAgentsLastChat(cfg.AssistantRole) if err != nil { // too much action for err != nil; loadAgentsLastChat needs to be split up @@ -1123,18 +1086,13 @@ func init() { Stream: true, Messages: lastChat, } - // Initialize Cluedo if enabled and matching role - if cfg.EnableCluedo && cfg.AssistantRole == "CluedoPlayer" { - playerOrder = []string{cfg.UserRole, cfg.AssistantRole, cfg.CluedoRole2} - cluedoState = extra.CluedoPrepCards(playerOrder) - } choseChunkParser() httpClient = createClient(time.Second * 90) if cfg.TTS_ENABLED { - orator = extra.NewOrator(logger, cfg) + orator = NewOrator(logger, cfg) } if cfg.STT_ENABLED { - asr = extra.NewSTT(logger, cfg) + asr = NewSTT(logger, cfg) } // Initialize scrollToEndEnabled based on config scrollToEndEnabled = cfg.AutoScrollEnabled diff --git a/extra.go b/extra.go new file mode 100644 index 0000000..3ea63e2 --- /dev/null +++ b/extra.go @@ -0,0 +1,28 @@ +//go:build extra +// +build extra + +package main + +import ( + "gf-lt/config" + "gf-lt/extra" + "log/slog" +) + +// Interfaces and implementations when extra modules are included + +type Orator = extra.Orator +type STT = extra.STT + +func NewOrator(logger *slog.Logger, cfg *config.Config) Orator { + return extra.NewOrator(logger, cfg) +} + +func NewSTT(logger *slog.Logger, cfg *config.Config) STT { + return extra.NewSTT(logger, cfg) +} + +// TTS channels from extra package +var TTSTextChan = extra.TTSTextChan +var TTSFlushChan = extra.TTSFlushChan +var TTSDoneChan = extra.TTSDoneChan
\ No newline at end of file diff --git a/extra/cluedo.go b/extra/cluedo.go deleted file mode 100644 index 1ef11cc..0000000 --- a/extra/cluedo.go +++ /dev/null @@ -1,73 +0,0 @@ -package extra - -import ( - "math/rand" - "strings" -) - -var ( - rooms = []string{"HALL", "LOUNGE", "DINING ROOM", "KITCHEN", "BALLROOM", "CONSERVATORY", "BILLIARD ROOM", "LIBRARY", "STUDY"} - weapons = []string{"CANDLESTICK", "DAGGER", "LEAD PIPE", "REVOLVER", "ROPE", "SPANNER"} - people = []string{"Miss Scarlett", "Colonel Mustard", "Mrs. White", "Reverend Green", "Mrs. Peacock", "Professor Plum"} -) - -type MurderTrifecta struct { - Murderer string - Weapon string - Room string -} - -type CluedoRoundInfo struct { - Answer MurderTrifecta - PlayersCards map[string][]string -} - -func (c *CluedoRoundInfo) GetPlayerCards(player string) string { - // maybe format it a little - return "cards of " + player + "are " + strings.Join(c.PlayersCards[player], ",") -} - -func CluedoPrepCards(playerOrder []string) *CluedoRoundInfo { - res := &CluedoRoundInfo{} - // Select murder components - trifecta := MurderTrifecta{ - Murderer: people[rand.Intn(len(people))], - Weapon: weapons[rand.Intn(len(weapons))], - Room: rooms[rand.Intn(len(rooms))], - } - // Collect non-murder cards - var notInvolved []string - for _, room := range rooms { - if room != trifecta.Room { - notInvolved = append(notInvolved, room) - } - } - for _, weapon := range weapons { - if weapon != trifecta.Weapon { - notInvolved = append(notInvolved, weapon) - } - } - for _, person := range people { - if person != trifecta.Murderer { - notInvolved = append(notInvolved, person) - } - } - // Shuffle and distribute cards - rand.Shuffle(len(notInvolved), func(i, j int) { - notInvolved[i], notInvolved[j] = notInvolved[j], notInvolved[i] - }) - players := map[string][]string{} - cardsPerPlayer := len(notInvolved) / len(playerOrder) - // playerOrder := []string{"{{user}}", "{{char}}", "{{char2}}"} - for i, player := range playerOrder { - start := i * cardsPerPlayer - end := (i + 1) * cardsPerPlayer - if end > len(notInvolved) { - end = len(notInvolved) - } - players[player] = notInvolved[start:end] - } - res.Answer = trifecta - res.PlayersCards = players - return res -} diff --git a/extra/cluedo_test.go b/extra/cluedo_test.go deleted file mode 100644 index e7a53b1..0000000 --- a/extra/cluedo_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package extra - -import ( - "testing" -) - -func TestPrepCards(t *testing.T) { - // Run the function to get the murder combination and player cards - roundInfo := CluedoPrepCards([]string{"{{user}}", "{{char}}", "{{char2}}"}) - // Create a map to track all distributed cards - distributedCards := make(map[string]bool) - // Check that the murder combination cards are not distributed to players - murderCards := []string{roundInfo.Answer.Murderer, roundInfo.Answer.Weapon, roundInfo.Answer.Room} - for _, card := range murderCards { - if distributedCards[card] { - t.Errorf("Murder card %s was distributed to a player", card) - } - } - // Check each player's cards - for player, cards := range roundInfo.PlayersCards { - for _, card := range cards { - // Ensure the card is not part of the murder combination - for _, murderCard := range murderCards { - if card == murderCard { - t.Errorf("Player %s has a murder card: %s", player, card) - } - } - // Ensure the card is unique and not already distributed - if distributedCards[card] { - t.Errorf("Card %s is duplicated in player %s's hand", card, player) - } - distributedCards[card] = true - } - } - // Verify that all non-murder cards are distributed - allCards := append(append([]string{}, rooms...), weapons...) - allCards = append(allCards, people...) - for _, card := range allCards { - isMurderCard := false - for _, murderCard := range murderCards { - if card == murderCard { - isMurderCard = true - break - } - } - if !isMurderCard && !distributedCards[card] { - t.Errorf("Card %s was not distributed to any player", card) - } - } -} diff --git a/extra/stt.go b/extra/stt.go index e33a94d..86fcf9c 100644 --- a/extra/stt.go +++ b/extra/stt.go @@ -1,3 +1,6 @@ +//go:build extra +// +build extra + package extra import ( diff --git a/extra/tts.go b/extra/tts.go index c6f373a..c9ad59d 100644 --- a/extra/tts.go +++ b/extra/tts.go @@ -1,3 +1,6 @@ +//go:build extra +// +build extra + package extra import ( diff --git a/extra/twentyq.go b/extra/twentyq.go deleted file mode 100644 index 30c08cc..0000000 --- a/extra/twentyq.go +++ /dev/null @@ -1,11 +0,0 @@ -package extra - -import "math/rand" - -var ( - chars = []string{"Shrek", "Garfield", "Jack the Ripper"} -) - -func GetRandomChar() string { - return chars[rand.Intn(len(chars))] -} diff --git a/extra/vad.go b/extra/vad.go deleted file mode 100644 index 2a9e238..0000000 --- a/extra/vad.go +++ /dev/null @@ -1 +0,0 @@ -package extra diff --git a/extra/websearch.go b/extra/websearch.go deleted file mode 100644 index 99bc1b6..0000000 --- a/extra/websearch.go +++ /dev/null @@ -1,13 +0,0 @@ -package extra - -import "github.com/GrailFinder/searchagent/searcher" - -var WebSearcher searcher.WebSurfer - -func init() { - sa, err := searcher.NewWebSurfer(searcher.SearcherTypeScraper, "") - if err != nil { - panic("failed to init seachagent; error: " + err.Error()) - } - WebSearcher = sa -} diff --git a/extra/whisper_binary.go b/extra/whisper_binary.go index 31c083c..6b7ddc8 100644 --- a/extra/whisper_binary.go +++ b/extra/whisper_binary.go @@ -1,3 +1,6 @@ +//go:build extra +// +build extra + package extra import ( diff --git a/noextra.go b/noextra.go new file mode 100644 index 0000000..2ad138c --- /dev/null +++ b/noextra.go @@ -0,0 +1,73 @@ +//go:build !extra +// +build !extra + +package main + +import ( + "gf-lt/config" + "log/slog" +) + +// Interfaces and implementations when extra modules are not included + +type Orator interface { + Speak(text string) error + Stop() + GetLogger() *slog.Logger +} + +type STT interface { + StartRecording() error + StopRecording() (string, error) + IsRecording() bool +} + +// DefaultOrator is a no-op implementation when TTS is not available +type DefaultOrator struct { + logger *slog.Logger +} + +func NewOrator(logger *slog.Logger, cfg *config.Config) Orator { + return &DefaultOrator{logger: logger} +} + +func (d *DefaultOrator) Speak(text string) error { + d.logger.Debug("TTS not available - extra modules disabled") + return nil +} + +func (d *DefaultOrator) Stop() { + // No-op +} + +func (d *DefaultOrator) GetLogger() *slog.Logger { + return d.logger +} + +// DefaultSTT is a no-op implementation when STT is not available +type DefaultSTT struct { + logger *slog.Logger +} + +func NewSTT(logger *slog.Logger, cfg *config.Config) STT { + return &DefaultSTT{logger: logger} +} + +func (d *DefaultSTT) StartRecording() error { + d.logger.Debug("STT not available - extra modules disabled") + return nil +} + +func (d *DefaultSTT) StopRecording() (string, error) { + d.logger.Debug("STT not available - extra modules disabled") + return "", nil +} + +func (d *DefaultSTT) IsRecording() bool { + return false +} + +// TTS channels - no-op when extra is not available +var TTSTextChan = make(chan string, 10000) +var TTSFlushChan = make(chan bool, 1) +var TTSDoneChan = make(chan bool, 1)
\ No newline at end of file @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "gf-lt/agent" - "gf-lt/extra" "gf-lt/models" "io" "os" @@ -15,6 +14,8 @@ import ( "strings" "sync" "time" + + "github.com/GrailFinder/searchagent/searcher" ) var ( @@ -141,13 +142,6 @@ After that you are free to respond to the user. Role: "", FilePath: "", } - // toolCard = &models.CharCard{ - // SysPrompt: toolSysMsg, - // FirstMsg: defaultFirstMsg, - // Role: "", - // FilePath: "", - // } - // sysMap = map[string]string{"basic_sys": basicSysMsg, "tool_sys": toolSysMsg} sysMap = map[string]*models.CharCard{"basic_sys": basicCard} sysLabels = []string{"basic_sys"} @@ -156,6 +150,16 @@ After that you are free to respond to the user. webAgentsOnce sync.Once ) +var WebSearcher searcher.WebSurfer + +func init() { + sa, err := searcher.NewWebSurfer(searcher.SearcherTypeScraper, "") + if err != nil { + panic("failed to init seachagent; error: " + err.Error()) + } + WebSearcher = sa +} + // getWebAgentClient returns a singleton AgentClient for web agents. func getWebAgentClient() *agent.AgentClient { webAgentClientOnce.Do(func() { @@ -208,7 +212,7 @@ func websearch(args map[string]string) []byte { "limit_arg", limitS, "error", err) limit = 3 } - resp, err := extra.WebSearcher.Search(context.Background(), query, limit) + resp, err := WebSearcher.Search(context.Background(), query, limit) if err != nil { msg := "search tool failed; error: " + err.Error() logger.Error(msg) @@ -232,7 +236,7 @@ func readURL(args map[string]string) []byte { logger.Error(msg) return []byte(msg) } - resp, err := extra.WebSearcher.RetrieveFromLink(context.Background(), link) + resp, err := WebSearcher.RetrieveFromLink(context.Background(), link) if err != nil { msg := "search tool failed; error: " + err.Error() logger.Error(msg) @@ -2,7 +2,6 @@ package main import ( "fmt" - "gf-lt/extra" "gf-lt/models" "image" _ "image/jpeg" @@ -1147,7 +1146,7 @@ func init() { // textArea.SetText("pressed ctrl+A", true) if cfg.TTS_ENABLED { // audioStream.TextChan <- chunk - extra.TTSDoneChan <- true + TTSDoneChan <- true } } if event.Key() == tcell.KeyCtrlW { |
