Fix: recover bot; llama.cpp fix
This commit is contained in:
		| @@ -1,5 +1,5 @@ | |||||||
| {{define "actionhistory"}} | {{define "actionhistory"}} | ||||||
| <div class="overflow-y-auto max-h-96 border-2 border-gray-300 p-4 rounded-lg space-y-2"> | <div id="actionHistoryContainer" class="overflow-y-auto max-h-96 border-2 border-gray-300 p-4 rounded-lg space-y-2"> | ||||||
|   Backlog: |   Backlog: | ||||||
|   {{range .}} |   {{range .}} | ||||||
|     <div class="flex items-center justify-between p-2 rounded"> |     <div class="flex items-center justify-between p-2 rounded"> | ||||||
| @@ -14,4 +14,11 @@ | |||||||
|     </div> |     </div> | ||||||
|   {{end}} |   {{end}} | ||||||
| </div> | </div> | ||||||
|  | <script> | ||||||
|  |   // Scroll to the bottom of the action history container | ||||||
|  |   const container = document.getElementById('actionHistoryContainer'); | ||||||
|  |   if (container) { | ||||||
|  |     container.scrollTop = container.scrollHeight; | ||||||
|  |   } | ||||||
|  | </script> | ||||||
| {{end}} | {{end}} | ||||||
|   | |||||||
| @@ -2,5 +2,8 @@ | |||||||
| <div class="flex justify-center"> | <div class="flex justify-center"> | ||||||
|     <p>Blue cards left: {{.BlueCounter}} </p> |     <p>Blue cards left: {{.BlueCounter}} </p> | ||||||
|     <p>Red cards left: {{.RedCounter}} </p> |     <p>Red cards left: {{.RedCounter}} </p> | ||||||
|  |     <hr> | ||||||
|  |     <p>Limit of cards to open: {{.ThisTurnLimit}} </p> | ||||||
|  |     <p>Opened this turn: {{.OpenedThisTurn}} </p> | ||||||
| </div> | </div> | ||||||
| {{end}} | {{end}} | ||||||
|   | |||||||
| @@ -32,7 +32,6 @@ | |||||||
|   {{if .Room.IsRunning}} |   {{if .Room.IsRunning}} | ||||||
|     {{template "cardcounter" .Room}} |     {{template "cardcounter" .Room}} | ||||||
|   {{end}} |   {{end}} | ||||||
|  |  | ||||||
|   <div id="addbot"> |   <div id="addbot"> | ||||||
|     {{if and (eq .State.Username .Room.CreatorName) (not .Room.IsRunning)}} |     {{if and (eq .State.Username .Room.CreatorName) (not .Room.IsRunning)}} | ||||||
|       {{template "addbot" .}} |       {{template "addbot" .}} | ||||||
|   | |||||||
| @@ -83,6 +83,7 @@ func saveFullInfo(fi *models.FullInfo) error { | |||||||
|  |  | ||||||
| func notifyBotIfNeeded(fi *models.FullInfo) { | func notifyBotIfNeeded(fi *models.FullInfo) { | ||||||
| 	if botName := fi.Room.WhichBotToMove(); botName != "" { | 	if botName := fi.Room.WhichBotToMove(); botName != "" { | ||||||
|  | 		log.Debug("got botname", "name", botName) | ||||||
| 		llmapi.SignalChanMap[botName] <- true | 		llmapi.SignalChanMap[botName] <- true | ||||||
| 	} | 	} | ||||||
| 	log.Debug("no bot", "room_id", fi.Room.ID) | 	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 | // get all rooms | ||||||
| func listPublicRooms() []*models.Room { | func listRooms(allRooms bool) []*models.Room { | ||||||
| 	cacheMap := memcache.GetAll() | 	cacheMap := memcache.GetAll() | ||||||
| 	publicRooms := []*models.Room{} | 	publicRooms := []*models.Room{} | ||||||
| 	// no way to know if room is public until unmarshal -_-; | 	// no way to know if room is public until unmarshal -_-; | ||||||
| @@ -243,7 +244,7 @@ func listPublicRooms() []*models.Room { | |||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 			log.Debug("consider room for list", "room", room, "key", key) | 			log.Debug("consider room for list", "room", room, "key", key) | ||||||
| 			if room.IsPublic { | 			if room.IsPublic || allRooms { | ||||||
| 				publicRooms = append(publicRooms, room) | 				publicRooms = append(publicRooms, room) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @@ -251,6 +252,24 @@ func listPublicRooms() []*models.Room { | |||||||
| 	return publicRooms | 	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) { | func notify(event, msg string) { | ||||||
| 	Notifier.Notifier <- broker.NotificationEvent{ | 	Notifier.Notifier <- broker.NotificationEvent{ | ||||||
| 		EventName: event, | 		EventName: event, | ||||||
| @@ -271,3 +290,22 @@ func loadCards(room *models.Room) { | |||||||
| 		room.WCMap[card.Word] = card.Color | 		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 | ||||||
|  | } | ||||||
|   | |||||||
| @@ -107,7 +107,7 @@ func HandleFrontLogin(w http.ResponseWriter, r *http.Request) { | |||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		log.Debug("no room_id in login") | 		log.Debug("no room_id in login") | ||||||
| 		fi.List = listPublicRooms() | 		fi.List = listRooms(false) | ||||||
| 		// save state to cache | 		// save state to cache | ||||||
| 		if err := saveState(cleanName, userstate); err != nil { | 		if err := saveState(cleanName, userstate); err != nil { | ||||||
| 			// if err := saveFullInfo(fi); err != nil { | 			// if err := saveFullInfo(fi); err != nil { | ||||||
|   | |||||||
| @@ -28,6 +28,9 @@ func init() { | |||||||
| 	cfg = config.LoadConfigOrDefault("") | 	cfg = config.LoadConfigOrDefault("") | ||||||
| 	Notifier = broker.Notifier | 	Notifier = broker.Notifier | ||||||
| 	cache.MemCache.StartBackupRoutine(15 * time.Second) // Reduced backup interval | 	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) { | 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 { | 	if fi != nil && fi.Room == nil { | ||||||
| 		fi.List = listPublicRooms() | 		fi.List = listRooms(false) | ||||||
| 	} | 	} | ||||||
| 	if err := tmpl.ExecuteTemplate(w, "base", fi); err != nil { | 	if err := tmpl.ExecuteTemplate(w, "base", fi); err != nil { | ||||||
| 		log.Error("failed to exec templ;", "error", err, "templ", "base") | 		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()) | 		abortWithError(w, err.Error()) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	fi.List = listPublicRooms() | 	fi.List = listRooms(false) | ||||||
| 	if err := tmpl.ExecuteTemplate(w, "base", fi); err != nil { | 	if err := tmpl.ExecuteTemplate(w, "base", fi); err != nil { | ||||||
| 		log.Error("failed to exec templ;", "error", err, "templ", "base") | 		log.Error("failed to exec templ;", "error", err, "templ", "base") | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -37,18 +37,35 @@ type DSResp struct { | |||||||
| 	Object            string `json:"object"` | 	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 { | type LLMResp struct { | ||||||
| 	Choices []struct { |  | ||||||
| 		FinishReason string `json:"finish_reason"` |  | ||||||
| 	Index           int    `json:"index"` | 	Index           int    `json:"index"` | ||||||
| 		Message      struct { |  | ||||||
| 	Content         string `json:"content"` | 	Content         string `json:"content"` | ||||||
| 			Role    string `json:"role"` | 	Tokens          []any  `json:"tokens"` | ||||||
| 		} `json:"message"` | 	IDSlot          int    `json:"id_slot"` | ||||||
| 	} `json:"choices"` | 	Stop            bool   `json:"stop"` | ||||||
| 	Created int    `json:"created"` |  | ||||||
| 	Model           string `json:"model"` | 	Model           string `json:"model"` | ||||||
| 	Object  string `json:"object"` | 	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 { | type MimeResp struct { | ||||||
| @@ -63,13 +80,13 @@ type GusserResp struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type Bot struct { | type Bot struct { | ||||||
| 	Role      string // gueeser | mime | 	Role      string         `json:"role"` | ||||||
| 	Team      string | 	Team      string         `json:"team"` | ||||||
| 	cfg       *config.Config | 	cfg       *config.Config `json:"-"` | ||||||
| 	RoomID    string // can we get a room from here? | 	RoomID    string         `json:"room_id"` // can we get a room from here? | ||||||
| 	BotName   string | 	BotName   string         `json:"bot_name"` | ||||||
| 	log       *slog.Logger | 	log       *slog.Logger   `json:"-"` | ||||||
| 	LLMParser RespParser | 	LLMParser RespParser     `json:"-"` | ||||||
| 	// channels for communicaton | 	// channels for communicaton | ||||||
| 	// channels are not serializable | 	// channels are not serializable | ||||||
| 	// SignalsCh chan bool | 	// SignalsCh chan bool | ||||||
| @@ -271,7 +288,7 @@ func NewBot(role, team, name, roomID string, cfg *config.Config) (*Bot, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func saveBot(bot *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) | 	data, err := json.Marshal(bot) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
|   | |||||||
| @@ -66,12 +66,13 @@ func (p *lcpRespParser) ParseBytes(body []byte) (map[string]any, error) { | |||||||
| 		p.log.Error("failed to unmarshal", "error", err) | 		p.log.Error("failed to unmarshal", "error", err) | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	if len(resp.Choices) == 0 { | 	// if len(resp.Choices) == 0 { | ||||||
| 		p.log.Error("empty choices", "resp", resp) | 	// 	p.log.Error("empty choices", "resp", resp) | ||||||
| 		err := errors.New("empty choices in resp") | 	// 	err := errors.New("empty choices in resp") | ||||||
| 		return nil, err | 	// 	return nil, err | ||||||
| 	} | 	// } | ||||||
| 	text := resp.Choices[0].Message.Content | 	// text := resp.Choices[0].Message.Content | ||||||
|  | 	text := resp.Content | ||||||
| 	li := strings.Index(text, "{") | 	li := strings.Index(text, "{") | ||||||
| 	ri := strings.LastIndex(text, "}") | 	ri := strings.LastIndex(text, "}") | ||||||
| 	if li < 0 || ri < 1 { | 	if li < 0 || ri < 1 { | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								main.go
									
									
									
									
									
								
							| @@ -69,7 +69,7 @@ func main() { | |||||||
|  |  | ||||||
| 	<-stop | 	<-stop | ||||||
| 	slog.Info("Shutting down server...") | 	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() | 	defer cancel() | ||||||
| 	if err := server.Shutdown(ctx); err != nil { | 	if err := server.Shutdown(ctx); err != nil { | ||||||
| 		slog.Error("server shutdown failed", "error", err) | 		slog.Error("server shutdown failed", "error", err) | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ var ( | |||||||
| 	// cache | 	// cache | ||||||
| 	CacheRoomPrefix  = "room#" | 	CacheRoomPrefix  = "room#" | ||||||
| 	CacheStatePrefix = "state-" | 	CacheStatePrefix = "state-" | ||||||
|  | 	CacheBotPredix   = "botkey_" | ||||||
| 	// sse | 	// sse | ||||||
| 	NotifyRoomListUpdate   = "roomlistupdate" | 	NotifyRoomListUpdate   = "roomlistupdate" | ||||||
| 	NotifyRoomUpdatePrefix = "roomupdate_" | 	NotifyRoomUpdatePrefix = "roomupdate_" | ||||||
|   | |||||||
| @@ -116,7 +116,9 @@ func getGuesser(m map[string]BotPlayer, team UserTeam) string { | |||||||
|  |  | ||||||
| // WhichBotToMove returns bot name that have to move or empty string | // WhichBotToMove returns bot name that have to move or empty string | ||||||
| func (r *Room) WhichBotToMove() 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 { | 	if !r.IsRunning { | ||||||
| 		return "" | 		return "" | ||||||
| 	} | 	} | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								todos.md
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								todos.md
									
									
									
									
									
								
							| @@ -13,6 +13,9 @@ | |||||||
| - needs resend to llm btn; + | - needs resend to llm btn; + | ||||||
| - better styles and fluff; | - better styles and fluff; | ||||||
| - common auth system between sites; | - common auth system between sites; | ||||||
|  | - autoscroll down backlog on update; | ||||||
|  | - gameover to backlog; | ||||||
|  | - ended turn action to backlog; | ||||||
|  |  | ||||||
| #### sse points | #### sse points | ||||||
| - clue sse update; | - clue sse update; | ||||||
| @@ -31,7 +34,8 @@ | |||||||
| - 0 should mean without limit; | - 0 should mean without limit; | ||||||
| - sse hangs / fails connection which causes to wait for cards to open a few seconds (on local machine); | - 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; | - after starting a new game (after old one) blue mime has no clue input; | ||||||
| - gameover to backlog; |  | ||||||
| - remove verbs from word file; | - remove verbs from word file; | ||||||
| - invite link gets cutoff; | - 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; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Grail Finder
					Grail Finder