diff --git a/components/actionhistory.html b/components/actionhistory.html
index ef3bf73..9535130 100644
--- a/components/actionhistory.html
+++ b/components/actionhistory.html
@@ -1,5 +1,5 @@
{{define "actionhistory"}}
-
+
Backlog:
{{range .}}
@@ -14,4 +14,11 @@
{{end}}
+
{{end}}
diff --git a/components/cardcounter.html b/components/cardcounter.html
index 0eb3b43..15c1579 100644
--- a/components/cardcounter.html
+++ b/components/cardcounter.html
@@ -1,6 +1,9 @@
{{define "cardcounter"}}
-
Blue cards left: {{.BlueCounter}}
-
Red cards left: {{.RedCounter}}
+
Blue cards left: {{.BlueCounter}}
+
Red cards left: {{.RedCounter}}
+
+
Limit of cards to open: {{.ThisTurnLimit}}
+
Opened this turn: {{.OpenedThisTurn}}
{{end}}
diff --git a/components/room.html b/components/room.html
index df752a9..c7fc2b9 100644
--- a/components/room.html
+++ b/components/room.html
@@ -32,7 +32,6 @@
{{if .Room.IsRunning}}
{{template "cardcounter" .Room}}
{{end}}
-
{{if and (eq .State.Username .Room.CreatorName) (not .Room.IsRunning)}}
{{template "addbot" .}}
diff --git a/handlers/actions.go b/handlers/actions.go
index 9be7068..1ccace2 100644
--- a/handlers/actions.go
+++ b/handlers/actions.go
@@ -83,6 +83,7 @@ func saveFullInfo(fi *models.FullInfo) error {
func notifyBotIfNeeded(fi *models.FullInfo) {
if botName := fi.Room.WhichBotToMove(); botName != "" {
+ log.Debug("got botname", "name", botName)
llmapi.SignalChanMap[botName] <- true
}
log.Debug("no bot", "room_id", fi.Room.ID)
@@ -231,7 +232,7 @@ func joinTeam(ctx context.Context, role, team string) (*models.FullInfo, error)
}
// get all rooms
-func listPublicRooms() []*models.Room {
+func listRooms(allRooms bool) []*models.Room {
cacheMap := memcache.GetAll()
publicRooms := []*models.Room{}
// no way to know if room is public until unmarshal -_-;
@@ -243,7 +244,7 @@ func listPublicRooms() []*models.Room {
continue
}
log.Debug("consider room for list", "room", room, "key", key)
- if room.IsPublic {
+ if room.IsPublic || allRooms {
publicRooms = append(publicRooms, room)
}
}
@@ -251,6 +252,24 @@ func listPublicRooms() []*models.Room {
return publicRooms
}
+// get bots
+func listBots() map[string]map[string]string {
+ cacheMap := memcache.GetAll()
+ resp := make(map[string]map[string]string)
+ // no way to know if room is public until unmarshal -_-;
+ for key, value := range cacheMap {
+ if strings.HasPrefix(key, models.CacheBotPredix) {
+ botMap := make(map[string]string)
+ if err := json.Unmarshal(value, &botMap); err != nil {
+ log.Warn("failed to unmarshal bot", "error", err)
+ continue
+ }
+ resp[botMap["bot_name"]] = botMap
+ }
+ }
+ return resp
+}
+
func notify(event, msg string) {
Notifier.Notifier <- broker.NotificationEvent{
EventName: event,
@@ -271,3 +290,22 @@ func loadCards(room *models.Room) {
room.WCMap[card.Word] = card.Color
}
}
+
+func recoverBots() {
+ bots := listBots()
+ for botName, botMap := range bots {
+ if err := recoverBot(botMap); err != nil {
+ log.Warn("failed to recover bot", "botName", botName, "error", err)
+ }
+ }
+}
+
+func recoverBot(bm map[string]string) error {
+ // TODO: check if room still exists
+ log.Debug("recovering bot", "bot", bm)
+ _, err := llmapi.NewBot(bm["role"], bm["team"], bm["bot_name"], bm["room_id"], cfg)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/handlers/auth.go b/handlers/auth.go
index 81b73d0..9ebd8b9 100644
--- a/handlers/auth.go
+++ b/handlers/auth.go
@@ -107,7 +107,7 @@ func HandleFrontLogin(w http.ResponseWriter, r *http.Request) {
}
} else {
log.Debug("no room_id in login")
- fi.List = listPublicRooms()
+ fi.List = listRooms(false)
// save state to cache
if err := saveState(cleanName, userstate); err != nil {
// if err := saveFullInfo(fi); err != nil {
diff --git a/handlers/handlers.go b/handlers/handlers.go
index 119d60c..749ce60 100644
--- a/handlers/handlers.go
+++ b/handlers/handlers.go
@@ -28,6 +28,9 @@ func init() {
cfg = config.LoadConfigOrDefault("")
Notifier = broker.Notifier
cache.MemCache.StartBackupRoutine(15 * time.Second) // Reduced backup interval
+ // bot loader
+ // check the rooms if it has bot_{digits} in them, create bots if have
+ recoverBots()
}
func HandlePing(w http.ResponseWriter, r *http.Request) {
@@ -51,7 +54,7 @@ func HandleHome(w http.ResponseWriter, r *http.Request) {
}
}
if fi != nil && fi.Room == nil {
- fi.List = listPublicRooms()
+ fi.List = listRooms(false)
}
if err := tmpl.ExecuteTemplate(w, "base", fi); err != nil {
log.Error("failed to exec templ;", "error", err, "templ", "base")
@@ -92,7 +95,7 @@ func HandleExit(w http.ResponseWriter, r *http.Request) {
abortWithError(w, err.Error())
return
}
- fi.List = listPublicRooms()
+ fi.List = listRooms(false)
if err := tmpl.ExecuteTemplate(w, "base", fi); err != nil {
log.Error("failed to exec templ;", "error", err, "templ", "base")
}
diff --git a/llmapi/main.go b/llmapi/main.go
index 171abc5..08fb39f 100644
--- a/llmapi/main.go
+++ b/llmapi/main.go
@@ -37,18 +37,35 @@ type DSResp struct {
Object string `json:"object"`
}
+// type LLMResp struct {
+// Choices []struct {
+// FinishReason string `json:"finish_reason"`
+// Index int `json:"index"`
+// Message struct {
+// Content string `json:"content"`
+// Role string `json:"role"`
+// } `json:"message"`
+// } `json:"choices"`
+// Created int `json:"created"`
+// Model string `json:"model"`
+// Object string `json:"object"`
+// }
+
type LLMResp struct {
- Choices []struct {
- FinishReason string `json:"finish_reason"`
- Index int `json:"index"`
- Message struct {
- Content string `json:"content"`
- Role string `json:"role"`
- } `json:"message"`
- } `json:"choices"`
- Created int `json:"created"`
- Model string `json:"model"`
- Object string `json:"object"`
+ Index int `json:"index"`
+ Content string `json:"content"`
+ Tokens []any `json:"tokens"`
+ IDSlot int `json:"id_slot"`
+ Stop bool `json:"stop"`
+ Model string `json:"model"`
+ TokensPredicted int `json:"tokens_predicted"`
+ TokensEvaluated int `json:"tokens_evaluated"`
+ Prompt string `json:"prompt"`
+ HasNewLine bool `json:"has_new_line"`
+ Truncated bool `json:"truncated"`
+ StopType string `json:"stop_type"`
+ StoppingWord string `json:"stopping_word"`
+ TokensCached int `json:"tokens_cached"`
}
type MimeResp struct {
@@ -63,13 +80,13 @@ type GusserResp struct {
}
type Bot struct {
- Role string // gueeser | mime
- Team string
- cfg *config.Config
- RoomID string // can we get a room from here?
- BotName string
- log *slog.Logger
- LLMParser RespParser
+ Role string `json:"role"`
+ Team string `json:"team"`
+ cfg *config.Config `json:"-"`
+ RoomID string `json:"room_id"` // can we get a room from here?
+ BotName string `json:"bot_name"`
+ log *slog.Logger `json:"-"`
+ LLMParser RespParser `json:"-"`
// channels for communicaton
// channels are not serializable
// SignalsCh chan bool
@@ -271,7 +288,7 @@ func NewBot(role, team, name, roomID string, cfg *config.Config) (*Bot, error) {
}
func saveBot(bot *Bot) error {
- key := "botkey_" + bot.RoomID + bot.BotName
+ key := models.CacheBotPredix + bot.RoomID + bot.BotName
data, err := json.Marshal(bot)
if err != nil {
return err
diff --git a/llmapi/parser.go b/llmapi/parser.go
index 24a8253..5ee9003 100644
--- a/llmapi/parser.go
+++ b/llmapi/parser.go
@@ -66,12 +66,13 @@ func (p *lcpRespParser) ParseBytes(body []byte) (map[string]any, error) {
p.log.Error("failed to unmarshal", "error", err)
return nil, err
}
- if len(resp.Choices) == 0 {
- p.log.Error("empty choices", "resp", resp)
- err := errors.New("empty choices in resp")
- return nil, err
- }
- text := resp.Choices[0].Message.Content
+ // if len(resp.Choices) == 0 {
+ // p.log.Error("empty choices", "resp", resp)
+ // err := errors.New("empty choices in resp")
+ // return nil, err
+ // }
+ // text := resp.Choices[0].Message.Content
+ text := resp.Content
li := strings.Index(text, "{")
ri := strings.LastIndex(text, "}")
if li < 0 || ri < 1 {
diff --git a/main.go b/main.go
index b21ae34..97be504 100644
--- a/main.go
+++ b/main.go
@@ -69,7 +69,7 @@ func main() {
<-stop
slog.Info("Shutting down server...")
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
slog.Error("server shutdown failed", "error", err)
diff --git a/models/keys.go b/models/keys.go
index 967105a..ee4fc76 100644
--- a/models/keys.go
+++ b/models/keys.go
@@ -8,6 +8,7 @@ var (
// cache
CacheRoomPrefix = "room#"
CacheStatePrefix = "state-"
+ CacheBotPredix = "botkey_"
// sse
NotifyRoomListUpdate = "roomlistupdate"
NotifyRoomUpdatePrefix = "roomupdate_"
diff --git a/models/main.go b/models/main.go
index c74583c..019f1ba 100644
--- a/models/main.go
+++ b/models/main.go
@@ -116,7 +116,9 @@ func getGuesser(m map[string]BotPlayer, team UserTeam) string {
// WhichBotToMove returns bot name that have to move or empty string
func (r *Room) WhichBotToMove() string {
- fmt.Println("looking for bot to move", "team-turn", r.TeamTurn, "mime-done", r.MimeDone, "bot-map", r.BotMap, "is_running", r.IsRunning)
+ fmt.Println("looking for bot to move", "team-turn:", r.TeamTurn,
+ "mime-done:", r.MimeDone, "bot-map:", r.BotMap, "is_running:", r.IsRunning,
+ "blueMime:", r.BlueTeam.Mime, "redMime:", r.RedTeam.Mime)
if !r.IsRunning {
return ""
}
diff --git a/todos.md b/todos.md
index 3d6586c..94ece65 100644
--- a/todos.md
+++ b/todos.md
@@ -13,6 +13,9 @@
- needs resend to llm btn; +
- better styles and fluff;
- common auth system between sites;
+- autoscroll down backlog on update;
+- gameover to backlog;
+- ended turn action to backlog;
#### sse points
- clue sse update;
@@ -31,7 +34,8 @@
- 0 should mean without limit;
- sse hangs / fails connection which causes to wait for cards to open a few seconds (on local machine);
- after starting a new game (after old one) blue mime has no clue input;
-- gameover to backlog;
- remove verbs from word file;
- invite link gets cutoff;
-- mime rejoined the room: does not see colors; state save in store.json has empty role and team
+- mime rejoined the room: does not see colors; state save in store.json has empty role and team +
+- restart bot routines after server restart; +
+- guesser did not have same number of guesses (move ended after 1 guess); show how much guesses left on the page;