Feat: remove bot

This commit is contained in:
Grail Finder
2025-06-27 07:35:25 +03:00
parent 12fe92b261
commit 86b1ecf82e
9 changed files with 165 additions and 100 deletions

View File

@ -1,18 +1,31 @@
{{ define "addbot" }} {{ define "addbot" }}
{{$botName := ""}}
<div> <div>
{{$botName = .Room.FindBotByTeamRole "blue" "mime"}}
{{ if eq .Room.BlueTeam.Mime "" }} {{ if eq .Room.BlueTeam.Mime "" }}
<button hx-get="/add-bot?team=blue&role=mime" hx-target="#addbot" class="bg-blue-400 text-black px-4 py-2 rounded">Add Bot Mime</button> <button hx-get="/add-bot?team=blue&role=mime" hx-target="#addbot" class="bg-blue-400 text-black px-4 py-2 rounded">Add Bot Mime</button>
{{ else if ne $botName "" }}
<button hx-get="/remove-bot?bot={{$botName}}" hx-target="#addbot" class="bg-blue-400 text-black px-4 py-2 rounded">Remove {{$botName}}</button>
{{ end }} {{ end }}
{{$botName = .Room.FindBotByTeamRole "red" "mime"}}
{{ if eq .Room.RedTeam.Mime "" }} {{ if eq .Room.RedTeam.Mime "" }}
<button hx-get="/add-bot?team=red&role=mime" hx-target="#addbot" class="bg-red-400 text-black px-4 py-2 rounded">Add Bot Mime</button> <button hx-get="/add-bot?team=red&role=mime" hx-target="#addbot" class="bg-red-400 text-black px-4 py-2 rounded">Add Bot Mime</button>
{{ else if ne $botName "" }}
<button hx-get="/remove-bot?bot={{$botName}}" hx-target="#addbot" class="bg-red-400 text-black px-4 py-2 rounded">Remove {{$botName}}</button>
{{ end }} {{ end }}
</div> </div>
{{$botName = .Room.FindBotByTeamRole "blue" "guesser"}}
<div> <div>
{{ if not .Room.BlueTeam.Guessers }} {{ if not .Room.BlueTeam.Guessers }}
<button hx-get="/add-bot?team=blue&role=guesser" hx-target="#addbot" class="bg-blue-300 text-black px-4 py-2 rounded">Add Bot Guesser</button> <button hx-get="/add-bot?team=blue&role=guesser" hx-target="#addbot" class="bg-blue-300 text-black px-4 py-2 rounded">Add Bot Guesser</button>
{{ else if ne $botName "" }}
<button hx-get="/remove-bot?bot={{$botName}}" hx-target="#addbot" class="bg-blue-300 text-black px-4 py-2 rounded">Remove {{$botName}}</button>
{{ end }} {{ end }}
{{$botName = .Room.FindBotByTeamRole "red" "guesser"}}
{{ if not .Room.RedTeam.Guessers }} {{ if not .Room.RedTeam.Guessers }}
<button hx-get="/add-bot?team=red&role=guesser" hx-target="#addbot" class="bg-red-300 text-black px-4 py-2 rounded">Add Bot Guesser</button> <button hx-get="/add-bot?team=red&role=guesser" hx-target="#addbot" class="bg-red-300 text-black px-4 py-2 rounded">Add Bot Guesser</button>
{{ else if ne $botName "" }}
<button hx-get="/remove-bot?bot={{$botName}}" hx-target="#addbot" class="bg-red-300 text-black px-4 py-2 rounded">Remove {{$botName}}</button>
{{ end }} {{ end }}
</div> </div>
{{end}} {{end}}

View File

@ -1,16 +1,16 @@
{{define "cardword"}} {{define "cardword"}}
{{if .Revealed}} {{if .Revealed}}
{{if eq .Color "amber"}} {{if eq .Color "amber"}}
<div id="card-{{.Word}}" class="bg-{{.Color}}-100 p-4 rounded-lg min-w-[100px] text-center text-white cursor-pointer" <div id="card-{{.Word}}" class="bg-{{.Color}}-100 border border-gray-500 p-4 rounded-lg min-w-[100px] text-center text-white cursor-pointer"
style="text-shadow: 0 2px 4px rgba(0,0,0,0.9);"> {{.Word}} style="text-shadow: 0 2px 4px rgba(0,0,0,0.9);"> {{.Word}}
</div> </div>
{{else}} {{else}}
<div id="card-{{.Word}}" class="bg-{{.Color}}-600 p-4 rounded-lg min-w-[100px] text-center text-white cursor-pointer" <div id="card-{{.Word}}" class="bg-{{.Color}}-600 border border-gray-500 p-4 rounded-lg min-w-[100px] text-center text-white cursor-pointer"
style="text-shadow: 0 2px 4px rgba(0,0,0,0.9);"> {{.Word}} style="text-shadow: 0 2px 4px rgba(0,0,0,0.9);"> {{.Word}}
</div> </div>
{{end}} {{end}}
{{else}} {{else}}
<div id="card-{{.Word}}" class="bg-stone-600 p-4 rounded-lg min-w-[100px] text-center text-white cursor-pointer" <div id="card-{{.Word}}" class="bg-stone-600 border border-gray-500 p-4 rounded-lg min-w-[100px] text-center text-white cursor-pointer"
style="text-shadow: 0 2px 4px rgba(0,0,0,0.8);" style="text-shadow: 0 2px 4px rgba(0,0,0,0.8);"
hx-get="/word/show-color?word={{.Word}}" hx-trigger="click" hx-swap="outerHTML transition:true swap:.05s"> hx-get="/word/show-color?word={{.Word}}" hx-trigger="click" hx-swap="outerHTML transition:true swap:.05s">
{{.Word}} {{.Word}}

View File

@ -194,3 +194,18 @@ func HandleAddBot(w http.ResponseWriter, r *http.Request) {
// go bot.StartBot() // go bot.StartBot()
notify(models.NotifyRoomUpdatePrefix+fi.Room.ID, "") notify(models.NotifyRoomUpdatePrefix+fi.Room.ID, "")
} }
func HandleRemoveBot(w http.ResponseWriter, r *http.Request) {
botName := r.URL.Query().Get("bot")
log.Debug("got remove-bot request", "bot_name", botName)
fi, err := getFullInfoByCtx(r.Context())
if err != nil {
abortWithError(w, err.Error())
return
}
if err := llmapi.RemoveBot(botName, fi.Room); err != nil {
abortWithError(w, err.Error())
return
}
notify(models.NotifyRoomUpdatePrefix+fi.Room.ID, "")
}

View File

@ -8,6 +8,7 @@ import (
"html/template" "html/template"
"net/http" "net/http"
"strconv" "strconv"
"strings"
) )
func HandleCreateRoom(w http.ResponseWriter, r *http.Request) { func HandleCreateRoom(w http.ResponseWriter, r *http.Request) {
@ -248,12 +249,20 @@ func HandleGiveClue(w http.ResponseWriter, r *http.Request) {
abortWithError(w, "your team already has a clue") abortWithError(w, "your team already has a clue")
return return
} }
// check if the clue is the same as one of the existing words
for _, card := range fi.Room.Cards {
if strings.EqualFold(card.Word, clue) {
msg := fmt.Sprintf("cannot use existing word (%s) as a clue", clue)
abortWithError(w, msg)
return
}
}
// === // ===
action := models.Action{ action := models.Action{
Actor: fi.State.Username, Actor: fi.State.Username,
ActorColor: string(fi.State.Team), ActorColor: string(fi.State.Team),
WordColor: string(fi.State.Team), WordColor: string(fi.State.Team),
Action: "gave clue", Action: models.ActionTypeClue,
Word: clue, Word: clue,
Number: num, Number: num,
} }

View File

@ -77,7 +77,7 @@ func (b *Bot) checkGuess(word string, room *models.Room) error {
Actor: b.BotName, Actor: b.BotName,
ActorColor: b.Team, ActorColor: b.Team,
WordColor: string(color), WordColor: string(color),
Action: "guessed", Action: models.ActionTypeGuess,
Word: word, Word: word,
} }
room.ActionHistory = append(room.ActionHistory, action) room.ActionHistory = append(room.ActionHistory, action)
@ -115,12 +115,7 @@ func (b *Bot) checkGuess(word string, room *models.Room) error {
return nil return nil
} }
// StartBot func (b *Bot) BotMove() {
func (b *Bot) StartBot() {
for {
select {
case <-SignalChanMap[b.BotName]:
func() {
// botJournalName := models.NotifyJournalPrefix + b.RoomID // botJournalName := models.NotifyJournalPrefix + b.RoomID
b.log.Debug("got signal", "bot-team", b.Team, "bot-role", b.Role) b.log.Debug("got signal", "bot-team", b.Team, "bot-role", b.Role)
// get room cards and actions // get room cards and actions
@ -168,7 +163,7 @@ func (b *Bot) StartBot() {
Actor: b.BotName, Actor: b.BotName,
ActorColor: b.Team, ActorColor: b.Team,
WordColor: b.Team, WordColor: b.Team,
Action: "gave clue", Action: models.ActionTypeClue,
Word: mimeResp.Clue, Word: mimeResp.Clue,
Number: mimeResp.Number, Number: mimeResp.Number,
} }
@ -213,7 +208,14 @@ func (b *Bot) StartBot() {
b.log.Debug("notifying bot", "name", botName) b.log.Debug("notifying bot", "name", botName)
SignalChanMap[botName] <- true SignalChanMap[botName] <- true
} }
}() }
// StartBot
func (b *Bot) StartBot() {
for {
select {
case <-SignalChanMap[b.BotName]:
b.BotMove()
case <-DoneChanMap[b.BotName]: case <-DoneChanMap[b.BotName]:
b.log.Debug("got done signal", "bot-name", b.BotName) b.log.Debug("got done signal", "bot-name", b.BotName)
return return
@ -221,6 +223,18 @@ func (b *Bot) StartBot() {
} }
} }
func RemoveBot(botName string, room *models.Room) error {
// channels
DoneChanMap[botName] <- true
close(DoneChanMap[botName])
close(SignalChanMap[botName])
// maps
delete(room.BotMap, botName)
delete(DoneChanMap, botName)
delete(SignalChanMap, botName)
return saveRoom(room)
}
// EndBot // EndBot
func NewBot(role, team, name, roomID string, cfg *config.Config, recovery bool) (*Bot, error) { func NewBot(role, team, name, roomID string, cfg *config.Config, recovery bool) (*Bot, error) {

View File

@ -160,8 +160,10 @@ func (p *openRouterParser) ParseBytes(body []byte) (map[string]any, error) {
} }
func (p *openRouterParser) MakePayload(prompt string) io.Reader { func (p *openRouterParser) MakePayload(prompt string) io.Reader {
// "model": "deepseek/deepseek-chat-v3-0324:free",
// TODO: set list of models an option to pick on the frontend
strPayload := fmt.Sprintf(`{ strPayload := fmt.Sprintf(`{
"model": "deepseek/deepseek-chat-v3-0324:free", "model": "google/gemini-2.0-flash-exp:free",
"messages": [ "messages": [
{ {
"role": "user", "role": "user",

View File

@ -47,6 +47,7 @@ func ListenToRequests(port string) *http.Server {
mux.HandleFunc("GET /word/show-color", handlers.HandleShowColor) mux.HandleFunc("GET /word/show-color", handlers.HandleShowColor)
mux.HandleFunc("POST /check/name", handlers.HandleNameCheck) mux.HandleFunc("POST /check/name", handlers.HandleNameCheck)
mux.HandleFunc("GET /add-bot", handlers.HandleAddBot) mux.HandleFunc("GET /add-bot", handlers.HandleAddBot)
mux.HandleFunc("GET /remove-bot", handlers.HandleRemoveBot)
// special // special
mux.HandleFunc("GET /renotify-bot", handlers.HandleRenotifyBot) mux.HandleFunc("GET /renotify-bot", handlers.HandleRenotifyBot)
// sse // sse

View File

@ -97,6 +97,16 @@ type Room struct {
LogJournal []string LogJournal []string
} }
// FindBotByTeamRole returns bot name if found; otherwise empty string
func (r *Room) FindBotByTeamRole(team, role string) string {
for bn, b := range r.BotMap {
if b.Role == StrToUserRole(role) && b.Team == StrToUserTeam(team) {
return bn
}
}
return ""
}
func (r *Room) FetchLastClue() (*Action, error) { func (r *Room) FetchLastClue() (*Action, error) {
for i := len(r.ActionHistory) - 1; i >= 0; i-- { for i := len(r.ActionHistory) - 1; i >= 0; i-- {
if r.ActionHistory[i].Action == string(ActionTypeClue) { if r.ActionHistory[i].Action == string(ActionTypeClue) {

View File

@ -18,6 +18,7 @@
- clear indication that model (llm) is thinking / answered; - clear indication that model (llm) is thinking / answered;
- instead of guessing all words at ones, ask only for 1 word to be open. - instead of guessing all words at ones, ask only for 1 word to be open.
- ways to remove bots from teams; - ways to remove bots from teams;
- check if clue word is the same as one of the cards and return err if it is; +
#### sse points #### sse points
- clue sse update; - clue sse update;