File size: 9,364 Bytes
a293444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77b6f68
 
a293444
 
 
 
 
 
 
 
 
 
 
4e357cc
a293444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77b6f68
a293444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77b6f68
 
a293444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4e357cc
 
 
 
a293444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
import json
import os
import random

import google.generativeai as genai
import gradio as gr
from jinja2 import Environment, FileSystemLoader

GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')

genai.configure(api_key=GOOGLE_API_KEY)

model = genai.GenerativeModel("gemini-1.5-flash-latest",
                              generation_config={"response_mime_type": "application/json"})


def merge_games(clues, num_merges=10):
    """Generates around 10 merges of words from the given clues.

    Args:
      clues: A list of clues, where each clue is a list containing the words, the answer, and the explanation.
      num_merges: The approximate number of merges to generate (default: 10).

    Returns:
      A list of tuples, where each tuple contains the merged words and the indices of the selected rows.
    """

    merged = []
    while len(merged) < num_merges:
        num_rows = random.choice([3, 4])
        selected_rows = random.sample(range(len(clues)), num_rows)
        words_list = [word for row in [clues[i][0] for i in selected_rows] for word in row]
        if len(words_list) in [8, 9]:
            merged.append((words_list, selected_rows))

    return merged


example_clues = [
    (['ARROW', 'TIE', 'HONOR'], 'BOW', 'such as a bow and arrow, a bow tie, or a bow as a sign of honor'),
    (['DOG', 'TREE'], 'BARK', 'such as the sound a dog makes, or a tree is made of bark'),
    (['MONEY', 'RIVER', 'ROB', 'BLOOD'], 'CRIME', 'such as money being stolen, a river being a potential crime scene, '
                                                  'robbery, or blood being a result of a violent crime'),
    (['BEEF', 'TURKEY', 'FIELD', 'GRASS'], 'GROUND',
     'such as ground beef, a turkey being a ground-dwelling bird, a field or grass being a type of ground'),
    (['BANK', 'GUITAR', 'LIBRARY'], 'NOTE',
     'such as a bank note, a musical note on a guitar, or a note being a written comment in a library book'),
    (['ROOM', 'PIANO', 'TYPEWRITER'], 'KEYS', 'such as a room key, piano keys, or typewriter keys'),
    (['TRAFFIC', 'RADAR', 'PHONE'], 'SIGNAL', 'such as traffic signals, radar signals, or phone signals'),
    (['FENCE', 'PICTURE', 'COOKIE'], 'FRAME',
     'such as a frame around a yard, a picture frame, or a cookie cutter being a type of frame'),
    (['YARN', 'VIOLIN', 'DRESS'], 'STRING', 'strings like material, instrument, clothing fastener'),
    (['JUMP', 'FLOWER', 'CLOCK'], 'SPRING',
     'such as jumping, flowers blooming in the spring, or a clock having a sprint component'),
    (['SPY', 'KNIFE'], 'WAR',
     'Both relate to aspects of war, such as spies being involved in war or knives being used as weapons'),
    (['STADIUM', 'SHOE', 'FIELD'], 'SPORT', 'Sports like venues, equipment, playing surfaces'),
    (['TEACHER', 'CLUB'], 'SCHOOL',
     'such as a teacher being a school staff member or a club being a type of school organization'),
    (['CYCLE', 'ARMY', 'COURT', 'FEES'], 'CHARGE', 'charges like electricity, battle, legal, payments'),
    (['FRUIT', 'MUSIC', 'TRAFFIC', 'STUCK'], 'JAM',
     'Jams such as fruit jam, a music jam session, traffic jam, or being stuck in a jam'),
    (['POLICE', 'DOG', 'THIEF'], 'CRIME',
     'such as police investigating crimes, dogs being used to detect crimes, or a thief committing a crime'),
    (['ARCTIC', 'SHUT', 'STAMP'], 'SEAL',
     'such as the Arctic being home to seals, or shutting a seal on an envelope, or a stamp being a type of seal'),
]

example_groupings = []
merges = merge_games(example_clues, 5)
for merged_words, indices in merges:
    groups = [{
        "words": example_clues[i][0],
        "clue": example_clues[i][1],
        "explanation": example_clues[i][2]
    } for i in indices]
    example_groupings.append((merged_words, json.dumps(groups, separators=(',', ':'))))


def render_jinja2_template(template, system_prompt, history, query):
    env = Environment(loader=FileSystemLoader('.'))
    template = env.from_string(template)
    return template.render(system_prompt=system_prompt, history=history, query=query)


def group_words(words):
    template = '''
    {% for example in history %}
    INPUT:
    {{ example[0] }}
    OUTPUT:
    {{ example[1] }}
    {% endfor %}
    INPUT:
    {{ query }}
    OUTPUT:

    {{ system }}

    Groups = {'words': list[str], 'clue': str, 'explanation': str}
    Return: Groups
    '''

    grouping_system_prompt = ("You are an assistant for the game Codenames. Your task is to help players by grouping a "
                              "given group of secrets into 3 to 4 groups of 2 to 4 words. Each group should consist of secrets that "
                              "share a common theme or other word connections such as homonym, hypernyms or synonyms. Avoid clues that are not too generic or not unique enough to be guessed easily")

    prompt = render_jinja2_template(template, grouping_system_prompt, example_groupings, words)
    # print(prompt)
    raw_response = model.generate_content(
        prompt,
        generation_config={'top_k': 3, 'temperature': 1.1})
    response = json.loads(raw_response.text)

    print("Grouping words:", words)

    print("Got groupings: ", json.dumps(response, indent=4))
    return response


def generate_clues(group):
    template = '''
    {% for example in history %}
    INPUT:
    {{ example[0] }}
    OUTPUT:
    { 'clue':{{ example[1] }}, 'explanation':{{ example[2] }} }
    {% endfor %}
    INPUT:
    {{ query }}
    OUTPUT:


    {{ system }}

    Clue = {'clue': str, 'explanation': str}
    Return: Clue
    '''

    clue_system_prompt = ("You are a codenames game companion. Your task is to give a single word clue related to "
                          "a given group of words. You will only respond with a single word clue. The clue can be a common theme or other word connections such as homonym, hypernyms or synonyms. Avoid clues that are not too generic or not unique enough to be guessed easily")

    prompt = render_jinja2_template(template, clue_system_prompt, example_clues, group)

    raw_response = model.generate_content(
        prompt,
        generation_config={'top_k': 3, 'temperature': 1.1})
    response = json.loads(raw_response.text)

    print("Generating clues for: ", group)

    print("Got clue: ", json.dumps(response, indent=4))
    return response


def process_image(img):
    raw_response = model.generate_content(['Identify the words in this game of Codenames. Provide only a list of '
                                           'words. Provide the words in capital letters only. Group these words into '
                                           '6 or 8 groups that can be guessed together using a single word clue in '
                                           'the game of codenames. Give a response as json of the form: {"Game": '
                                           '<list of words in the game>}', img], stream=True)
    raw_response.resolve()
    response = json.loads(raw_response.text)
    words = response['Game']
    return gr.update(choices=words, value=words)


with gr.Blocks() as demo:
    gr.Markdown("# *Codenames* clue generator")
    gr.Markdown("Provide a list of words to generate a clue")

    with gr.Row():
        game_image = gr.Image(type="pil")
        word_list_input = gr.Dropdown(label="Detected words",
                                      choices=[],
                                      multiselect=True,
                                      interactive=True)

    with gr.Row():
        detect_words_button = gr.Button("Detect Words")
        group_words_button = gr.Button("Group Words")

    dropdowns, buttons, outputs = [], [], []

    for i in range(4):
        with gr.Row():
            group_input = gr.Dropdown(label=f"Group {i + 1}",
                                      choices=[],
                                      allow_custom_value=True,
                                      multiselect=True,
                                      interactive=True)
            clue_button = gr.Button("Generate Clue", size='sm')
            clue_output = gr.Textbox(label=f"Clue {i + 1}")
            dropdowns.append(group_input)
            buttons.append(clue_button)
            outputs.append(clue_output)


    def pad_or_truncate(lst, n=4):
        # Ensure the length of the list is at most n
        truncated_lst = lst[:n]
        return truncated_lst + (n - len(truncated_lst)) * [None]


    def group_words_callback(words):
        groups = group_words(words)
        groups = pad_or_truncate(groups, 4)
        print("Got groups: ", groups, type(groups))
        return [gr.update(value=groups[i]["words"], choices=words, info=groups[i]["explanation"]) for i in range(4)]


    def generate_clues_callback(group):
        print("Generating clues: ", group)
        g = generate_clues(group)
        return gr.update(value=g['clue'], info=g['explanation'])


    detect_words_button.click(fn=process_image,
                              inputs=game_image,
                              outputs=[word_list_input])
    group_words_button.click(fn=group_words_callback,
                             inputs=word_list_input,
                             outputs=dropdowns)

    for i in range(4):
        buttons[i].click(generate_clues_callback, inputs=dropdowns[i], outputs=outputs[i])

demo.launch(share=True, debug=True)