ai-plays-jackbox 0.0.1__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ai-plays-jackbox might be problematic. Click here for more details.

Files changed (44) hide show
  1. ai_plays_jackbox/__init__.py +0 -1
  2. ai_plays_jackbox/bot/__init__.py +0 -1
  3. ai_plays_jackbox/bot/bot_base.py +42 -8
  4. ai_plays_jackbox/bot/bot_factory.py +18 -17
  5. ai_plays_jackbox/bot/bot_personality.py +0 -1
  6. ai_plays_jackbox/bot/jackbox5/__init__.py +0 -0
  7. ai_plays_jackbox/bot/jackbox5/bot_base.py +26 -0
  8. ai_plays_jackbox/bot/jackbox5/mad_verse_city.py +121 -0
  9. ai_plays_jackbox/bot/jackbox5/patently_stupid.py +167 -0
  10. ai_plays_jackbox/bot/jackbox6/bot_base.py +1 -1
  11. ai_plays_jackbox/bot/jackbox6/dictionarium.py +105 -0
  12. ai_plays_jackbox/bot/jackbox6/joke_boat.py +105 -0
  13. ai_plays_jackbox/bot/jackbox7/bot_base.py +1 -1
  14. ai_plays_jackbox/bot/jackbox7/quiplash3.py +8 -4
  15. ai_plays_jackbox/bot/jackbox8/__init__.py +0 -0
  16. ai_plays_jackbox/bot/jackbox8/bot_base.py +20 -0
  17. ai_plays_jackbox/bot/jackbox8/job_job.py +205 -0
  18. ai_plays_jackbox/bot/standalone/__init__.py +0 -0
  19. ai_plays_jackbox/bot/standalone/drawful2.py +159 -0
  20. ai_plays_jackbox/cli/__init__.py +0 -0
  21. ai_plays_jackbox/{cli.py → cli/main.py} +28 -15
  22. ai_plays_jackbox/constants.py +3 -0
  23. ai_plays_jackbox/llm/chat_model.py +20 -3
  24. ai_plays_jackbox/llm/chat_model_factory.py +24 -19
  25. ai_plays_jackbox/llm/gemini_model.py +86 -0
  26. ai_plays_jackbox/llm/ollama_model.py +19 -7
  27. ai_plays_jackbox/llm/openai_model.py +48 -8
  28. ai_plays_jackbox/room/__init__.py +0 -0
  29. ai_plays_jackbox/{room.py → room/room.py} +2 -5
  30. ai_plays_jackbox/run.py +8 -11
  31. ai_plays_jackbox/scripts/lint.py +18 -0
  32. ai_plays_jackbox/ui/main.py +12 -0
  33. ai_plays_jackbox/ui/startup.py +248 -0
  34. ai_plays_jackbox-0.2.0.dist-info/METADATA +156 -0
  35. ai_plays_jackbox-0.2.0.dist-info/RECORD +42 -0
  36. ai_plays_jackbox-0.2.0.dist-info/entry_points.txt +4 -0
  37. ai_plays_jackbox/llm/gemini_vertex_ai.py +0 -60
  38. ai_plays_jackbox/ui/create_ui.py +0 -169
  39. ai_plays_jackbox/web_ui.py +0 -12
  40. ai_plays_jackbox-0.0.1.dist-info/METADATA +0 -88
  41. ai_plays_jackbox-0.0.1.dist-info/RECORD +0 -28
  42. ai_plays_jackbox-0.0.1.dist-info/entry_points.txt +0 -5
  43. {ai_plays_jackbox-0.0.1.dist-info → ai_plays_jackbox-0.2.0.dist-info}/LICENSE +0 -0
  44. {ai_plays_jackbox-0.0.1.dist-info → ai_plays_jackbox-0.2.0.dist-info}/WHEEL +0 -0
@@ -1 +0,0 @@
1
- from .run import run
@@ -1 +0,0 @@
1
- from .bot_base import JackBoxBotBase
@@ -6,9 +6,10 @@ from typing import Optional
6
6
  from urllib import parse
7
7
  from uuid import uuid4
8
8
 
9
+ import cv2
9
10
  import html2text
11
+ import numpy as np
10
12
  from loguru import logger
11
- from ollama import Options, chat
12
13
  from pydantic import BaseModel, Field, field_validator
13
14
  from websocket import WebSocketApp
14
15
 
@@ -118,8 +119,8 @@ class JackBoxBotBase(ABC):
118
119
  elif server_message.opcode == "object" or server_message.opcode == "text":
119
120
  if server_message.opcode == "object":
120
121
  operation = ObjectOperation(**server_message.result)
121
- if server_message.opcode == "text":
122
- operation = TextOperation(**server_message.result)
122
+ elif server_message.opcode == "text":
123
+ operation = TextOperation(**server_message.result) # type: ignore
123
124
 
124
125
  if self._is_player_operation_key(operation.key):
125
126
  self._handle_player_operation(operation.json_data)
@@ -141,16 +142,49 @@ class JackBoxBotBase(ABC):
141
142
  def _send_ws(self, opcode: str, params: dict) -> None:
142
143
  self._message_sequence += 1
143
144
  message = {"seq": self._message_sequence, "opcode": opcode, "params": params}
144
- self._ws.send(json.dumps(message))
145
+ if self._ws is not None:
146
+ self._ws.send(json.dumps(message))
147
+ else:
148
+ raise Exception("Websocket connection has not been initialized")
145
149
 
146
150
  def _client_send(self, request: dict) -> None:
147
- self._message_sequence += 1
148
151
  params = {"from": self._player_id, "to": 1, "body": request}
149
152
  self._send_ws("client/send", params)
150
153
 
154
+ def _object_update(self, key: str, val: dict) -> None:
155
+ params = {"key": key, "val": val}
156
+ self._send_ws("object/update", params)
157
+
158
+ def _text_update(self, key: str, val: str) -> None:
159
+ params = {"key": key, "val": val}
160
+ self._send_ws("text/update", params)
161
+
151
162
  def _html_to_text(self, html: str) -> str:
152
163
  return html2text.html2text(html)
153
164
 
165
+ def _image_bytes_to_polylines(self, image_bytes: bytes, canvas_height: int, canvas_width: int) -> list[str]:
166
+ # Let's edge trace the outputted image to contours
167
+ image_array = np.frombuffer(image_bytes, dtype=np.uint8)
168
+ image = cv2.imdecode(image_array, flags=1)
169
+ gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
170
+ edges = cv2.Canny(gray_image, threshold1=100, threshold2=200)
171
+ contours, _ = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
172
+
173
+ # Figure out scaling factor
174
+ height, width = gray_image.shape
175
+ scale_x = canvas_width / width
176
+ scale_y = canvas_height / height
177
+ scale_factor = min(scale_x, scale_y)
178
+
179
+ # generate the polylines from the contours
180
+ polylines = []
181
+ for contour in contours:
182
+ if len(contour) > 1: # Only include contours with 2 or more points
183
+ polyline = [f"{int(point[0][0] * scale_factor)},{int(point[0][1] * scale_factor)}" for point in contour] # type: ignore
184
+ polylines.append("|".join(polyline))
185
+
186
+ return polylines
187
+
154
188
  def __del__(self):
155
189
  self.disconnect()
156
190
 
@@ -169,9 +203,9 @@ class TextOperation(BaseModel):
169
203
  value: str = Field(alias="val")
170
204
  version: int
171
205
 
172
- @field_validator("json_data")
206
+ @field_validator("json_data") # type: ignore
173
207
  def set_json_data(cls, value, values: dict):
174
- return json.loads(values.get("value"))
208
+ return json.loads(values.get("value", ""))
175
209
 
176
210
 
177
211
  class ObjectOperation(BaseModel):
@@ -181,6 +215,6 @@ class ObjectOperation(BaseModel):
181
215
  value: str = Field(default="")
182
216
  version: int
183
217
 
184
- @field_validator("value")
218
+ @field_validator("value") # type: ignore
185
219
  def set_value(cls, value, values: dict):
186
220
  return json.dumps(values.get("json_data"))
@@ -1,30 +1,31 @@
1
- from typing import Optional
2
-
3
1
  from ai_plays_jackbox.bot.bot_base import JackBoxBotBase
2
+ from ai_plays_jackbox.bot.jackbox5.mad_verse_city import MadVerseCityBot
3
+ from ai_plays_jackbox.bot.jackbox5.patently_stupid import PatentlyStupidBot
4
+ from ai_plays_jackbox.bot.jackbox6.dictionarium import DictionariumBot
5
+ from ai_plays_jackbox.bot.jackbox6.joke_boat import JokeBoatBot
4
6
  from ai_plays_jackbox.bot.jackbox7.quiplash3 import Quiplash3Bot
7
+ from ai_plays_jackbox.bot.standalone.drawful2 import Drawful2Bot
5
8
  from ai_plays_jackbox.llm.chat_model import ChatModel
6
- from ai_plays_jackbox.llm.ollama_model import OllamaModel
9
+
10
+ BOT_TYPES: dict[str, type[JackBoxBotBase]] = {
11
+ "quiplash3": Quiplash3Bot,
12
+ "patentlystupid": PatentlyStupidBot,
13
+ "drawful2international": Drawful2Bot,
14
+ "rapbattle": MadVerseCityBot,
15
+ "jokeboat": JokeBoatBot,
16
+ "ridictionary": DictionariumBot,
17
+ # "apply-yourself": JobJobBot,
18
+ }
7
19
 
8
20
 
9
21
  class JackBoxBotFactory:
10
22
  @staticmethod
11
23
  def get_bot(
12
24
  room_type: str,
25
+ chat_model: ChatModel,
13
26
  name: str = "FunnyBot",
14
27
  personality: str = "You are the funniest bot ever.",
15
- chat_model: Optional[ChatModel] = None,
16
28
  ) -> JackBoxBotBase:
17
- if chat_model is None:
18
- chat_model = OllamaModel()
19
- if room_type == "quiplash3":
20
- return Quiplash3Bot(name=name, personality=personality, chat_model=chat_model)
21
- # elif room_type == "ridictionary":
22
- # return DictionariumBot(name=name, personality=personality)
23
- # elif room_type == "patentlystupid":
24
- # return PatentlyStupidBot(name=name, personality=personality, model=model)
25
- # elif room_type == "fourbage":
26
- # return Fibbage4Bot(name=name, personality=personality, model=model)
27
- # elif room_type == "rapbattle":
28
- # return MadVerseCityBot(name=name, personality=personality, model=model)
29
- else:
29
+ if room_type not in BOT_TYPES.keys():
30
30
  raise ValueError(f"Unknown room type: {room_type}")
31
+ return BOT_TYPES[room_type](name=name, personality=personality, chat_model=chat_model)
@@ -27,7 +27,6 @@ class JackBoxBotVariant(Enum):
27
27
  SORRYBOT = JackBoxBotPersonality(
28
28
  name="SorryBot",
29
29
  personality="You are embarrassed by your answers and feel the need to apologize profusely to the rest of the group for them.",
30
- voice="Mouse",
31
30
  )
32
31
  HOSTAGEBOT = JackBoxBotPersonality(
33
32
  name="HostageBot",
File without changes
@@ -0,0 +1,26 @@
1
+ from abc import ABC
2
+ from typing import Optional
3
+
4
+ from ai_plays_jackbox.bot.bot_base import JackBoxBotBase
5
+
6
+
7
+ class JackBox5BotBase(JackBoxBotBase, ABC):
8
+ _actual_player_operation_key: Optional[str] = None
9
+
10
+ @property
11
+ def _player_operation_key(self):
12
+ return f"bc:customer:"
13
+
14
+ def _is_player_operation_key(self, operation_key: str) -> bool:
15
+ if self._actual_player_operation_key is None and self._player_operation_key in operation_key:
16
+ self._actual_player_operation_key = operation_key
17
+ return True
18
+ else:
19
+ return self._actual_player_operation_key == operation_key
20
+
21
+ @property
22
+ def _room_operation_key(self):
23
+ return "bc:room"
24
+
25
+ def _is_room_operation_key(self, operation_key: str) -> bool:
26
+ return operation_key == self._room_operation_key
@@ -0,0 +1,121 @@
1
+ import random
2
+ from typing import Optional
3
+
4
+ import demoji
5
+ from loguru import logger
6
+
7
+ from ai_plays_jackbox.bot.jackbox5.bot_base import JackBox5BotBase
8
+
9
+ _WORD_PROMPT_TEMPLATE = """
10
+ You are playing Mad Verse City. You are being asked to come up with a word.
11
+
12
+ {prompt}
13
+
14
+ When generating your response, follow these rules:
15
+ - You response must be {max_length} characters or less. It must be a singular word
16
+ - Your personality is: {personality}
17
+ - Do not include quotes in your response or any newlines, just the response itself.
18
+ - Some suggestions for the word are {suggestions}
19
+ """
20
+
21
+ _RHYME_PROMPT_TEMPLATE = """
22
+ You are playing Mad Verse City.
23
+
24
+ {prompt}
25
+
26
+ When generating your response, follow these rules:
27
+ - Your response must rhyme with {rhyme_word}. It cannot be the same word as that.
28
+ - You response must be {max_length} characters or less.
29
+ - Your personality is: {personality}
30
+ - Do not include quotes in your response or any newlines, just the response itself.
31
+ """
32
+
33
+
34
+ class MadVerseCityBot(JackBox5BotBase):
35
+ _actual_player_operation_key: Optional[str] = None
36
+ _current_word: str
37
+
38
+ def __init__(self, *args, **kwargs):
39
+ super().__init__(*args, **kwargs)
40
+
41
+ def _handle_welcome(self, data: dict):
42
+ pass
43
+
44
+ def _handle_player_operation(self, data: dict):
45
+ if not data:
46
+ return
47
+ room_state = data.get("state", None)
48
+ if not room_state:
49
+ return
50
+
51
+ prompt: dict[str, str] = data.get("prompt", {})
52
+ prompt_html = prompt.get("html", "")
53
+ clean_prompt = self._html_to_text(prompt_html)
54
+
55
+ max_length = data.get("maxLength", 40)
56
+ suggestions = data.get("suggestions", [])
57
+ if not suggestions:
58
+ suggestions = []
59
+ choices: list[dict] = data.get("choices", [])
60
+
61
+ match room_state:
62
+ case "EnterSingleText":
63
+ if "Give me a" in clean_prompt:
64
+ word = self._generate_word(clean_prompt, max_length - 10, suggestions)
65
+ self._current_word = demoji.replace(word, "")
66
+ self._client_send({"action": "write", "entry": word})
67
+
68
+ if "Now, write a line to rhyme with" in clean_prompt:
69
+ rhyme = self._generate_rhyme(clean_prompt, max_length - 10, self._current_word)
70
+ rhyme = demoji.replace(rhyme, "")
71
+ self._client_send({"action": "write", "entry": rhyme})
72
+
73
+ case "MakeSingleChoice":
74
+ if not choices:
75
+ pass
76
+ if "Who won this battle" in data.get("prompt", {}).get("text", "") and data.get("chosen", None) != 0:
77
+ choice_indexes = [i for i in range(0, len(choices))]
78
+ choice = random.choice(choice_indexes)
79
+ self._client_send({"action": "choose", "choice": choice})
80
+
81
+ if (
82
+ "Press this button to skip the tutorial" in data.get("prompt", {}).get("text", "")
83
+ and data.get("chosen", None) != 0
84
+ ):
85
+ self._client_send({"action": "choose", "choice": 0})
86
+
87
+ def _handle_room_operation(self, data: dict):
88
+ pass
89
+
90
+ def _generate_word(self, prompt: str, max_length: int, suggestions: list[str]) -> str:
91
+ logger.info("Generating word...")
92
+ formatted_prompt = _WORD_PROMPT_TEMPLATE.format(
93
+ personality=self._personality,
94
+ prompt=prompt,
95
+ max_length=max_length,
96
+ suggestions=", ".join(suggestions),
97
+ )
98
+
99
+ word = self._chat_model.generate_text(
100
+ formatted_prompt,
101
+ "",
102
+ temperature=self._chat_model._chat_model_temperature,
103
+ top_p=self._chat_model._chat_model_top_p,
104
+ )
105
+ return word
106
+
107
+ def _generate_rhyme(self, prompt: str, max_length: int, rhyme_word: str) -> str:
108
+ logger.info("Generating rhyme...")
109
+ formatted_prompt = _RHYME_PROMPT_TEMPLATE.format(
110
+ personality=self._personality,
111
+ prompt=prompt,
112
+ max_length=max_length,
113
+ rhyme_word=rhyme_word,
114
+ )
115
+ rhyme = self._chat_model.generate_text(
116
+ formatted_prompt,
117
+ "",
118
+ temperature=self._chat_model._chat_model_temperature,
119
+ top_p=self._chat_model._chat_model_top_p,
120
+ )
121
+ return rhyme
@@ -0,0 +1,167 @@
1
+ import random
2
+ from typing import Optional
3
+
4
+ from loguru import logger
5
+
6
+ from ai_plays_jackbox.bot.jackbox5.bot_base import JackBox5BotBase
7
+
8
+ _ISSUE_PROMPT_PROMPT_TEMPLATE = """
9
+ You are playing Patently Stupid. You need to fill in the given prompt.
10
+
11
+ When generating your response, follow these rules:
12
+ - Your personality is: {personality}
13
+ - You response must be 45 letters or less.
14
+ - Do not include quotes in your response.
15
+
16
+ Fill in the blank:
17
+
18
+ {prompt}
19
+ """
20
+
21
+ _SOLUTION_TITLE_PROMPT_TEMPLATE = """
22
+ You are playing Patently Stupid. The issue you are trying to solve is "{issue}"
23
+
24
+ I need you to generate a title for an invention that would solve the solution.
25
+
26
+ When generating your response, follow these rules:
27
+ - Your personality is: {personality}
28
+ - Your response must be 3 words or less.
29
+ - Do not include quotes in your response.
30
+ - Respond with only the title, nothing else. No newlines, etc.
31
+ """
32
+
33
+ _SOLUTION_TAGLINE_PROMPT_TEMPLATE = """
34
+ You are playing Patently Stupid. The issue you are trying to solve is "{issue}" and the invention that is going to solve it is called "{title}"
35
+
36
+ I need you to generate a tagline for the invention.
37
+
38
+ When generating your response, follow these rules:
39
+ - Your personality is: {personality}
40
+ - Your response must be 30 characters or less.
41
+ - Do not include quotes in your response.
42
+ - Respond with only the tagline, nothing else. No newlines, etc.
43
+ """
44
+
45
+ _SOLUTION_IMAGE_PROMPT_TEMPLATE = """
46
+ You are playing Patently Stupid. The issue you are trying to solve is "{issue}" and the invention that is going to solve it is called "{title}"
47
+
48
+ I need an drawing of this new invention.
49
+
50
+ When generating your response, follow these rules:
51
+ - The image must be a simple sketch
52
+ - The image must have a white background and use black for the lines
53
+ - Avoid intricate details
54
+ """
55
+
56
+
57
+ class PatentlyStupidBot(JackBox5BotBase):
58
+ _issue_to_solve: Optional[str] = None
59
+ _solution_title: Optional[str] = None
60
+ _solution_tagline: Optional[str] = None
61
+
62
+ def __init__(self, *args, **kwargs):
63
+ super().__init__(*args, **kwargs)
64
+
65
+ def _handle_welcome(self, data: dict):
66
+ pass
67
+
68
+ def _handle_player_operation(self, data: dict):
69
+ if not data:
70
+ return
71
+ room_state = data.get("state", None)
72
+ if not room_state:
73
+ return
74
+ entry = data.get("entry")
75
+ prompt: dict[str, str] = data.get("prompt", {})
76
+ prompt_html = prompt.get("html", "")
77
+ clean_prompt = self._html_to_text(prompt_html)
78
+ choices: list[dict] = data.get("choices", [])
79
+
80
+ if room_state == "EnterSingleText" and not bool(entry):
81
+ if "Write a title" in clean_prompt:
82
+ self._client_send({"action": "write", "entry": self._solution_title})
83
+ if "Write a tagline" in clean_prompt:
84
+ self._client_send({"action": "write", "entry": self._solution_tagline})
85
+ if "Fill in the Blank" in clean_prompt:
86
+ issue_fill_in = self._generate_issue(clean_prompt)
87
+ self._client_send({"action": "write", "entry": issue_fill_in})
88
+
89
+ elif room_state == "MakeSingleChoice":
90
+ if "Present your idea!" in clean_prompt:
91
+ done_text_html = data.get("doneText", {}).get("html", "")
92
+ if done_text_html == "":
93
+ self._client_send({"action": "choose", "choice": 1})
94
+ elif "Invest in the best!" in clean_prompt:
95
+ filtered_choices = [c for c in choices if not c.get("disabled", True)]
96
+ if filtered_choices:
97
+ choice = random.choice(filtered_choices)
98
+ self._client_send({"action": "choose", "choice": choice.get("index", 0)})
99
+ elif "Press to skip this presentation." in clean_prompt:
100
+ pass
101
+ else:
102
+ choice_indexes = [i for i in range(0, len(choices))]
103
+ choice = random.choice(choice_indexes) # type: ignore
104
+ self._client_send({"action": "choose", "choice": choice})
105
+
106
+ elif room_state == "Draw":
107
+ logger.info(data)
108
+ self._issue_to_solve = self._html_to_text(data["popup"]["html"])
109
+ self._solution_title = self._generate_title()
110
+ self._solution_tagline = self._generate_tagline()
111
+
112
+ lines = self._generate_drawing()
113
+ self._client_send(
114
+ {
115
+ "action": "submit",
116
+ "lines": [{"color": "#000000", "thickness": 6, "points": l} for l in lines],
117
+ },
118
+ )
119
+
120
+ def _handle_room_operation(self, data: dict):
121
+ pass
122
+
123
+ def _generate_issue(self, prompt: str) -> str:
124
+ formatted_prompt = _ISSUE_PROMPT_PROMPT_TEMPLATE.format(personality=self._personality, prompt=prompt)
125
+ issue = self._chat_model.generate_text(
126
+ formatted_prompt,
127
+ "",
128
+ max_tokens=10,
129
+ temperature=self._chat_model._chat_model_temperature,
130
+ top_p=self._chat_model._chat_model_top_p,
131
+ )
132
+ return issue
133
+
134
+ def _generate_title(self) -> str:
135
+ prompt = _SOLUTION_TITLE_PROMPT_TEMPLATE.format(personality=self._personality, issue=self._issue_to_solve)
136
+ title = self._chat_model.generate_text(
137
+ prompt,
138
+ "",
139
+ max_tokens=2,
140
+ temperature=self._chat_model._chat_model_temperature,
141
+ top_p=self._chat_model._chat_model_top_p,
142
+ )
143
+ return title
144
+
145
+ def _generate_tagline(self) -> str:
146
+ prompt = _SOLUTION_TAGLINE_PROMPT_TEMPLATE.format(
147
+ personality=self._personality, issue=self._issue_to_solve, title=self._solution_title
148
+ )
149
+ tagline = self._chat_model.generate_text(
150
+ prompt,
151
+ "",
152
+ max_tokens=12,
153
+ temperature=self._chat_model._chat_model_temperature,
154
+ top_p=self._chat_model._chat_model_top_p,
155
+ )
156
+ return tagline
157
+
158
+ def _generate_drawing(self) -> list[str]:
159
+ logger.info("Generating drawing...")
160
+ image_prompt = _SOLUTION_IMAGE_PROMPT_TEMPLATE.format(issue=self._issue_to_solve, title=self._solution_title)
161
+ image_bytes = self._chat_model.generate_sketch(
162
+ image_prompt,
163
+ "",
164
+ temperature=self._chat_model._chat_model_temperature,
165
+ top_p=self._chat_model._chat_model_top_p,
166
+ )
167
+ return self._image_bytes_to_polylines(image_bytes, 475, 475)
@@ -1,4 +1,4 @@
1
- from abc import ABC, abstractmethod
1
+ from abc import ABC
2
2
 
3
3
  from ai_plays_jackbox.bot.bot_base import JackBoxBotBase
4
4
 
@@ -0,0 +1,105 @@
1
+ import random
2
+
3
+ from loguru import logger
4
+
5
+ from ai_plays_jackbox.bot.jackbox6.bot_base import JackBox6BotBase
6
+
7
+ _DEFINITION_PROMPT_TEMPLATE = """
8
+ You are playing Dictionarium.
9
+
10
+ {prompt}
11
+
12
+ When generating your response, follow these rules:
13
+ - Your personality is: {personality}
14
+ - You response must be {max_length} characters or less.
15
+ - Do not include quotes in your response.
16
+ """
17
+
18
+ _SYNONYM_PROMPT_TEMPLATE = """
19
+ You are playing Dictionarium.
20
+
21
+ {prompt}
22
+
23
+ When generating your response, follow these rules:
24
+ - Your personality is: {personality}
25
+ - You response must be {max_length} characters or less.
26
+ - Do not include quotes in your response.
27
+ """
28
+
29
+ _SENTENCE_PROMPT_TEMPLATE = """
30
+ You are playing Dictionarium. You need to use a made up word in a sentence.
31
+
32
+ {prompt}
33
+
34
+ When generating your response, follow these rules:
35
+ - Your personality is: {personality}
36
+ - You response must be {max_length} characters or less.
37
+ - Do not include quotes in your response.
38
+ """
39
+
40
+
41
+ class DictionariumBot(JackBox6BotBase):
42
+ def __init__(self, *args, **kwargs):
43
+ super().__init__(*args, **kwargs)
44
+
45
+ def _handle_welcome(self, data: dict):
46
+ pass
47
+
48
+ def _handle_player_operation(self, data: dict):
49
+ if not data:
50
+ return
51
+ room_state = data.get("state", None)
52
+ if not room_state:
53
+ return
54
+
55
+ prompt = data.get("prompt", {})
56
+ prompt_html = prompt.get("html", "")
57
+ clean_prompt = self._html_to_text(prompt_html)
58
+ max_length = data.get("maxLength", 150)
59
+
60
+ match room_state:
61
+ case "EnterSingleText":
62
+ entry = data.get("entry", None)
63
+ entry_id = data.get("entryId", "")
64
+ print(data)
65
+ if not entry:
66
+ if entry_id == "Definition":
67
+ logger.info("Generating definition...")
68
+ template = _DEFINITION_PROMPT_TEMPLATE
69
+ elif entry_id == "Synonym":
70
+ logger.info("Generating synonym...")
71
+ template = _SYNONYM_PROMPT_TEMPLATE
72
+ elif entry_id == "Sentence":
73
+ logger.info("Generating sentence...")
74
+ template = _SENTENCE_PROMPT_TEMPLATE
75
+ else:
76
+ return
77
+
78
+ formatted_prompt = template.format(
79
+ personality=self._personality,
80
+ prompt=clean_prompt,
81
+ max_length=max_length,
82
+ )
83
+ submission = self._chat_model.generate_text(
84
+ formatted_prompt,
85
+ "",
86
+ temperature=self._chat_model._chat_model_temperature,
87
+ top_p=self._chat_model._chat_model_top_p,
88
+ )
89
+ submission = submission[: max_length - 1]
90
+ self._client_send({"action": "write", "entry": submission})
91
+
92
+ case "MakeSingleChoice":
93
+ choice_type = data.get("choiceType", "")
94
+ if (
95
+ choice_type == "ChooseDefinition"
96
+ or choice_type == "ChooseSynonym"
97
+ or choice_type == "ChooseSentence"
98
+ ):
99
+ choices: list[dict] = data.get("choices", [])
100
+ choice_indexes = [i for i in range(0, len(choices))]
101
+ selected_choice = random.choice(choice_indexes)
102
+ self._client_send({"action": "choose", "choice": selected_choice})
103
+
104
+ def _handle_room_operation(self, data: dict):
105
+ pass