summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrail Finder <wohilas@gmail.com>2026-01-03 09:59:33 +0300
committerGrail Finder <wohilas@gmail.com>2026-01-03 09:59:33 +0300
commitaeb2700d14c23c175376e2a5749295cd9a5d72c7 (patch)
treee34a5860da644b2d4747e4091f23805a63ede180
parent6b875a2782e77afe6595b47d069a217fa2702eb1 (diff)
Refactor: building binary with no extra
-rw-r--r--Makefile8
-rw-r--r--bot.go62
-rw-r--r--extra.go28
-rw-r--r--extra/cluedo.go73
-rw-r--r--extra/cluedo_test.go50
-rw-r--r--extra/stt.go3
-rw-r--r--extra/tts.go3
-rw-r--r--extra/twentyq.go11
-rw-r--r--extra/vad.go1
-rw-r--r--extra/websearch.go13
-rw-r--r--extra/whisper_binary.go3
-rw-r--r--noextra.go73
-rw-r--r--tools.go24
-rw-r--r--tui.go3
14 files changed, 142 insertions, 213 deletions
diff --git a/Makefile b/Makefile
index b845e6f..fff1acf 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/bot.go b/bot.go
index 123bf75..7a70fba 100644
--- a/bot.go
+++ b/bot.go
@@ -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
diff --git a/tools.go b/tools.go
index 8ac1c29..7861fe6 100644
--- a/tools.go
+++ b/tools.go
@@ -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)
diff --git a/tui.go b/tui.go
index 1fc9ddd..f1029b7 100644
--- a/tui.go
+++ b/tui.go
@@ -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 {