ai-plays-jackbox 0.0.1__py3-none-any.whl → 0.1.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.
- ai_plays_jackbox/__init__.py +0 -1
- ai_plays_jackbox/bot/__init__.py +0 -1
- ai_plays_jackbox/bot/bot_base.py +42 -8
- ai_plays_jackbox/bot/bot_factory.py +15 -17
- ai_plays_jackbox/bot/bot_personality.py +0 -1
- ai_plays_jackbox/bot/jackbox5/__init__.py +0 -0
- ai_plays_jackbox/bot/jackbox5/bot_base.py +26 -0
- ai_plays_jackbox/bot/jackbox5/mad_verse_city.py +121 -0
- ai_plays_jackbox/bot/jackbox5/patently_stupid.py +167 -0
- ai_plays_jackbox/bot/jackbox6/bot_base.py +1 -1
- ai_plays_jackbox/bot/jackbox6/joke_boat.py +105 -0
- ai_plays_jackbox/bot/jackbox7/bot_base.py +1 -1
- ai_plays_jackbox/bot/jackbox7/quiplash3.py +8 -4
- ai_plays_jackbox/bot/standalone/__init__.py +0 -0
- ai_plays_jackbox/bot/standalone/drawful2.py +159 -0
- ai_plays_jackbox/cli.py +26 -13
- ai_plays_jackbox/constants.py +3 -0
- ai_plays_jackbox/llm/chat_model.py +20 -3
- ai_plays_jackbox/llm/chat_model_factory.py +24 -19
- ai_plays_jackbox/llm/gemini_model.py +86 -0
- ai_plays_jackbox/llm/ollama_model.py +19 -7
- ai_plays_jackbox/llm/openai_model.py +48 -8
- ai_plays_jackbox/room.py +2 -5
- ai_plays_jackbox/run.py +7 -10
- ai_plays_jackbox/ui/create_ui.py +44 -16
- ai_plays_jackbox/web_ui.py +1 -1
- ai_plays_jackbox-0.1.0.dist-info/METADATA +154 -0
- ai_plays_jackbox-0.1.0.dist-info/RECORD +35 -0
- {ai_plays_jackbox-0.0.1.dist-info → ai_plays_jackbox-0.1.0.dist-info}/entry_points.txt +0 -1
- ai_plays_jackbox/llm/gemini_vertex_ai.py +0 -60
- ai_plays_jackbox-0.0.1.dist-info/METADATA +0 -88
- ai_plays_jackbox-0.0.1.dist-info/RECORD +0 -28
- {ai_plays_jackbox-0.0.1.dist-info → ai_plays_jackbox-0.1.0.dist-info}/LICENSE +0 -0
- {ai_plays_jackbox-0.0.1.dist-info → ai_plays_jackbox-0.1.0.dist-info}/WHEEL +0 -0
ai_plays_jackbox/__init__.py
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .run import run
|
ai_plays_jackbox/bot/__init__.py
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .bot_base import JackBoxBotBase
|
ai_plays_jackbox/bot/bot_base.py
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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,28 @@
|
|
|
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.joke_boat import JokeBoatBot
|
|
4
5
|
from ai_plays_jackbox.bot.jackbox7.quiplash3 import Quiplash3Bot
|
|
6
|
+
from ai_plays_jackbox.bot.standalone.drawful2 import Drawful2Bot
|
|
5
7
|
from ai_plays_jackbox.llm.chat_model import ChatModel
|
|
6
|
-
|
|
8
|
+
|
|
9
|
+
BOT_TYPES: dict[str, type[JackBoxBotBase]] = {
|
|
10
|
+
"quiplash3": Quiplash3Bot,
|
|
11
|
+
"patentlystupid": PatentlyStupidBot,
|
|
12
|
+
"drawful2international": Drawful2Bot,
|
|
13
|
+
"rapbattle": MadVerseCityBot,
|
|
14
|
+
"jokeboat": JokeBoatBot,
|
|
15
|
+
}
|
|
7
16
|
|
|
8
17
|
|
|
9
18
|
class JackBoxBotFactory:
|
|
10
19
|
@staticmethod
|
|
11
20
|
def get_bot(
|
|
12
21
|
room_type: str,
|
|
22
|
+
chat_model: ChatModel,
|
|
13
23
|
name: str = "FunnyBot",
|
|
14
24
|
personality: str = "You are the funniest bot ever.",
|
|
15
|
-
chat_model: Optional[ChatModel] = None,
|
|
16
25
|
) -> JackBoxBotBase:
|
|
17
|
-
if
|
|
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:
|
|
26
|
+
if room_type not in BOT_TYPES.keys():
|
|
30
27
|
raise ValueError(f"Unknown room type: {room_type}")
|
|
28
|
+
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)
|
|
@@ -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
|
+
_TOPIC_PROMPT_TEMPLATE = """
|
|
8
|
+
You are playing Joke Boat.
|
|
9
|
+
|
|
10
|
+
You are being asked to come up with a topic that is {placeholder}.
|
|
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
|
+
- Your response should be a single word.
|
|
16
|
+
- Do not include quotes in your response or any newlines, just the response itself
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
_PUNCHLINE_INSTRUCTIONS_TEMPLATE = """
|
|
20
|
+
You are playing Joke Boat. You need to fill in the given prompt with a punchline.
|
|
21
|
+
|
|
22
|
+
When generating your response, follow these rules:
|
|
23
|
+
- Your personality is: {personality}
|
|
24
|
+
- You response must be {max_length} characters or less
|
|
25
|
+
- Do not include quotes in your response or any newlines, just the response itself
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class JokeBoatBot(JackBox6BotBase):
|
|
30
|
+
def __init__(self, *args, **kwargs):
|
|
31
|
+
super().__init__(*args, **kwargs)
|
|
32
|
+
|
|
33
|
+
def _handle_welcome(self, data: dict):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def _handle_player_operation(self, data: dict):
|
|
37
|
+
if not data:
|
|
38
|
+
return
|
|
39
|
+
room_state = data.get("state", None)
|
|
40
|
+
if not room_state:
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
prompt = data.get("prompt", {})
|
|
44
|
+
prompt_html = prompt.get("html", "")
|
|
45
|
+
clean_prompt = self._html_to_text(prompt_html)
|
|
46
|
+
|
|
47
|
+
match room_state:
|
|
48
|
+
case "MakeSingleChoice":
|
|
49
|
+
choices: list[dict] = data.get("choices", [])
|
|
50
|
+
choice_type = data.get("choiceType", "")
|
|
51
|
+
choice_indexes = [i for i in range(0, len(choices))]
|
|
52
|
+
selected_choice = random.choice(choice_indexes)
|
|
53
|
+
|
|
54
|
+
if choice_type == "ChooseAuthorReady":
|
|
55
|
+
selected_choice = 1
|
|
56
|
+
if choice_type == "Skip":
|
|
57
|
+
selected_choice = 0
|
|
58
|
+
|
|
59
|
+
if data.get("chosen", None) is None:
|
|
60
|
+
self._client_send({"action": "choose", "choice": selected_choice})
|
|
61
|
+
|
|
62
|
+
case "EnterSingleText":
|
|
63
|
+
if "Write as many topics as you can." in clean_prompt:
|
|
64
|
+
placeholder = data.get("placeholder", "")
|
|
65
|
+
max_length = data.get("maxLength", 42)
|
|
66
|
+
topic = self._generate_topic(placeholder, max_length)
|
|
67
|
+
self._client_send({"action": "write", "entry": topic})
|
|
68
|
+
if "Write your punchline" in clean_prompt or "Write the punchline to this joke" in clean_prompt:
|
|
69
|
+
max_length = data.get("maxLength", 80)
|
|
70
|
+
punchline = self._generate_punchline(clean_prompt, max_length)
|
|
71
|
+
self._client_send({"action": "write", "entry": punchline})
|
|
72
|
+
|
|
73
|
+
def _handle_room_operation(self, data: dict):
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
def _generate_topic(self, placeholder: str, max_length: int) -> str:
|
|
77
|
+
logger.info("Generating topic...")
|
|
78
|
+
formatted_prompt = _TOPIC_PROMPT_TEMPLATE.format(
|
|
79
|
+
personality=self._personality,
|
|
80
|
+
placeholder=placeholder,
|
|
81
|
+
max_length=max_length,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
topic = self._chat_model.generate_text(
|
|
85
|
+
formatted_prompt,
|
|
86
|
+
"",
|
|
87
|
+
temperature=self._chat_model._chat_model_temperature,
|
|
88
|
+
top_p=self._chat_model._chat_model_top_p,
|
|
89
|
+
)
|
|
90
|
+
return topic[:max_length]
|
|
91
|
+
|
|
92
|
+
def _generate_punchline(self, prompt: str, max_length: int) -> str:
|
|
93
|
+
logger.info("Generating punchline...")
|
|
94
|
+
formatted_instructions = _PUNCHLINE_INSTRUCTIONS_TEMPLATE.format(
|
|
95
|
+
personality=self._personality,
|
|
96
|
+
prompt=prompt,
|
|
97
|
+
max_length=max_length,
|
|
98
|
+
)
|
|
99
|
+
punchline = self._chat_model.generate_text(
|
|
100
|
+
prompt,
|
|
101
|
+
formatted_instructions,
|
|
102
|
+
temperature=self._chat_model._chat_model_temperature,
|
|
103
|
+
top_p=self._chat_model._chat_model_top_p,
|
|
104
|
+
)
|
|
105
|
+
return punchline[:max_length]
|
|
@@ -75,7 +75,11 @@ class Quiplash3Bot(JackBox7BotBase):
|
|
|
75
75
|
max_tokens = 32
|
|
76
76
|
instructions = _FINAL_QUIP_PROMPT_INSTRUCTIONS_TEMPLATE.format(personality=self._personality)
|
|
77
77
|
quip = self._chat_model.generate_text(
|
|
78
|
-
prompt,
|
|
78
|
+
prompt,
|
|
79
|
+
instructions,
|
|
80
|
+
max_tokens=max_tokens,
|
|
81
|
+
temperature=self._chat_model._chat_model_temperature,
|
|
82
|
+
top_p=self._chat_model._chat_model_top_p,
|
|
79
83
|
)
|
|
80
84
|
return quip
|
|
81
85
|
|
|
@@ -84,7 +88,7 @@ class Quiplash3Bot(JackBox7BotBase):
|
|
|
84
88
|
instructions = _QUIP_CHOICE_PROMPT_INSTRUCTIONS_TEMPLATE.format(prompt=prompt)
|
|
85
89
|
response = self._chat_model.generate_text(
|
|
86
90
|
f"Vote for your favorite response. Your options are: {choices_str}",
|
|
87
|
-
instructions
|
|
91
|
+
instructions,
|
|
88
92
|
max_tokens=1,
|
|
89
93
|
)
|
|
90
94
|
try:
|
|
@@ -100,5 +104,5 @@ class Quiplash3Bot(JackBox7BotBase):
|
|
|
100
104
|
return choosen_prompt - 1
|
|
101
105
|
|
|
102
106
|
def _choose_random_favorite(self, choices: list[dict]) -> int:
|
|
103
|
-
|
|
104
|
-
return random.choice(
|
|
107
|
+
choices_as_ints = [i for i in range(0, len(choices))]
|
|
108
|
+
return random.choice(choices_as_ints)
|
|
File without changes
|