File size: 21,663 Bytes
5c9e936
 
623bf54
1c3bf10
cd82452
1c3bf10
cd82452
1c3bf10
cd82452
1c3bf10
cd82452
1c3bf10
cd82452
1c3bf10
cd82452
1c3bf10
cd82452
1c3bf10
cd82452
1c3bf10
 
623bf54
5c9e936
 
 
623bf54
5c9e936
 
c2a8f15
 
 
 
 
 
623bf54
 
c2a8f15
 
 
623bf54
5c9e936
623bf54
5c9e936
 
 
623bf54
5c9e936
 
 
 
 
 
c2a8f15
623bf54
c2a8f15
 
 
 
5c9e936
c2a8f15
 
 
5c9e936
 
c2a8f15
623bf54
c2a8f15
 
 
 
 
 
 
 
 
5c9e936
623bf54
5c9e936
c2a8f15
 
5c9e936
 
 
 
 
 
c2a8f15
 
 
 
 
623bf54
 
c2a8f15
 
623bf54
c2a8f15
 
623bf54
c2a8f15
 
 
623bf54
5c9e936
 
623bf54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5c9e936
623bf54
 
 
 
 
 
 
 
 
 
1c3bf10
 
 
 
 
 
 
 
623bf54
 
1c3bf10
 
623bf54
 
 
1c3bf10
623bf54
 
1c3bf10
 
 
 
 
 
 
 
 
5c9e936
 
 
623bf54
c2a8f15
 
 
 
 
 
623bf54
 
 
c2a8f15
 
 
 
 
 
 
 
 
623bf54
c2a8f15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
623bf54
 
c2a8f15
 
623bf54
 
 
 
 
 
 
 
 
 
 
 
 
c2a8f15
 
623bf54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5c9e936
 
 
 
 
 
 
9bc7cb6
5c9e936
 
9bc7cb6
5c9e936
623bf54
5c9e936
 
9bc7cb6
 
c2a8f15
 
 
 
 
 
9bc7cb6
 
 
c2a8f15
 
 
 
 
 
9bc7cb6
5c9e936
 
 
623bf54
5c9e936
 
 
623bf54
5c9e936
 
 
 
 
 
 
 
 
 
 
 
c2a8f15
 
623bf54
 
c2a8f15
 
623bf54
 
 
 
 
 
 
 
 
 
 
c2a8f15
623bf54
 
 
c2a8f15
 
 
 
 
623bf54
 
c2a8f15
 
 
 
 
 
 
 
623bf54
 
c2a8f15
 
 
 
 
 
 
 
623bf54
c2a8f15
 
623bf54
c2a8f15
 
5c9e936
623bf54
 
5c9e936
623bf54
1c3bf10
 
623bf54
 
 
5c9e936
 
 
 
623bf54
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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
from flask import Flask, render_template, request, redirect, url_for, flash, session, jsonify
from .agent import GroqAgent  

PERSONALITY_PROMPTS = {
    "vriendelijke_ouder": "You are taking part in a child-friendly guessing game. Keep your answer natural, short, and easy for a child to understand. You respond like a warm, ordinary parent. You are kind, calm, and encouraging. You give simple advice and sometimes add a gentle explanation. Keep answers short and natural, like something a parent might quickly say at home. Answer in 1 sentence (maximum 2), no more than 20 words . Communicate exclusively in Dutch. Do not say things like: great question!",
    
    "professor": "You are taking part in a child-friendly guessing game. Keep your answer natural, short, and easy for a child to understand. You respond like an enthusiastic professor who loves explaining things. You use slightly smarter words and give extra information. You may answer a bit longer than the others, but still keep it understandable for a child. Answer in 1 sentences (maximum 2), no more than 20 words . Communicate exclusively in Dutch. Do not say things like: great question!",
    
    "wetenschapper": "You are taking part in a child-friendly guessing game. Keep your answer natural, short, and easy for a child to understand. You respond like a curious scientist. You like evidence, experiments, and asking why. You explain things clearly and logically. Keep answers short unless the question needs more detail. Answer in 1 sentence (maximum 2), no more than 20 words . Communicate exclusively in Dutch. Do not say things like: great question!",
    
    "strenge_ouder": "You are taking part in a child-friendly guessing game. Keep your answer natural, short, and easy for a child to understand. You respond like a parent who values rules, responsibility, and good behaviour. You are not mean, but you are firm and practical. Keep answers short, clear, and sensible. Answer in 1 sentence (maximum 2), no more than 20 words . Communicate exclusively in Dutch. Do not say things like: great question!",
    
    "grappige_ouder": "You are taking part in a child-friendly guessing game. Keep your answer natural, short, and easy for a child to understand. You respond like a playful parent who likes making small jokes. You are cheerful, lighthearted, and a little silly, but still helpful. Keep answers short and avoid long explanations. Answer in 1 sentence (maximum 2), no more than 20 words. Communicate exclusively in Dutch. Do not say things like: great question!",
    
    "piraat": "You are taking part in a child-friendly guessing game. Keep your answer natural, short, and easy for a child to understand. You respond like a friendly pirate. You use pirate-style language such as ahoy or matey, but not so much that it becomes hard to understand. Keep answers playful and short. Answer in 1 sentence (maximum 2), no more than 20 words. Communicate exclusively in Dutch. Do not say things like: great question!",
    
    "alien": "You are taking part in a child-friendly guessing game. Keep your answer natural, short, and easy for a child to understand. You respond like a friendly alien visiting Earth. You find normal human things strange and interesting. You answer with curiosity and wonder. Keep answers short and funny. Answer in 1 sentence (maximum 2), no more than 20 words. Communicate exclusively in Dutch. Do not say things like: great question!",
    
    "tovenaar": "You are taking part in a child-friendly guessing game. Keep your answer natural, short, and easy for a child to understand. You respond like a wise but playful wizard. You use magical language and imaginative ideas, but still answer the child's question clearly. Keep it short and mysterious. Answer in 1 sentence (maximum 2), no more than 20 words. Communicate exclusively in Dutch. Do not say things like: great question!",
}

refresh_needed = False

class FlaskApp:
    def __init__(self, agents):
        self.agents = agents  # [None (admin), agent1, agent2, agent3]
        self.flask_app = Flask(__name__)
        self.flask_app.secret_key = "supersecretkey"
        self.difficulty_limits = {"easy": 5, "medium": 3, "hard": 1}
        self.difficulty = "easy"
        self.max_questions = self.difficulty_limits[self.difficulty]
        self.questions_asked = 0
        self.game_over = False
        self.last_result = None
        # screen_assignment maps screen position (1,2,3) to role (Human, AI1, AI2)
        self.screen_assignment = {"1": "AI1", "2": "Human", "3": "AI2"}
        self.rounds = []
        self.pending_round = None
        self.discarded_ai_responses = []
        self.routes_registered = False
        self.register_routes()
        self.first_guess = None

    def register_routes(self):
        if not self.routes_registered:

            @self.flask_app.route('/dashboard', methods=['GET', 'POST'])
            def user_dashboard():
                global refresh_needed
                if request.method == 'POST':
                    user_message = request.form.get('message')
                    if user_message:
                        if self.game_over or self.pending_round is not None:
                            flash('Wacht op het volgende spel of de admin-reactie.', 'warning')
                            return redirect(url_for('user_dashboard'))
                        if self.questions_asked >= self.max_questions:
                            flash('Je hebt je laatste vraag al gesteld. Maak je keuze.', 'warning')
                            return redirect(url_for('user_dashboard'))

                        ai_options = []
                        for agent_index, agent in enumerate(self.agents):
                            if agent is None:
                                continue
                            llm_response = agent.request_llm_response(user_message)
                            ai_options.append({
                                'agent': f'llm{agent_index}',
                                'content': llm_response
                            })

                        self.questions_asked += 1
                        self.pending_round = {
                            'question': user_message,
                            'ai_options': ai_options
                        }
                        flash('Je vraag is verstuurd. Wacht op de admin.', 'success')
                    return redirect(url_for('user_dashboard'))

                refresh_needed = True
                return render_template('dashboard.html')

            @self.flask_app.route('/admin_dashboard', methods=['GET', 'POST'])
            def admin_dashboard():
                global refresh_needed
                if request.method == 'POST':
                    action = request.form.get('action')

                    if action == 'send_round':
                        human_response = request.form.get('human_response')
                        if self.pending_round is None:
                            flash('Er is geen open vraag van de gebruiker.', 'warning')
                            return redirect(url_for('admin_dashboard'))
                        if not human_response:
                            flash('Schrijf eerst een menselijk antwoord.', 'warning')
                            return redirect(url_for('admin_dashboard'))

                        # Use current ai_options from pending_round
                        self.rounds.append({
                            'question': self.pending_round['question'],
                            'ai_options': self.pending_round['ai_options'],
                            'human_response': human_response
                        })
                        self.pending_round = None
                        flash('Antwoorden zijn verstuurd naar de gebruiker.', 'success')
                        return redirect(url_for('admin_dashboard'))

                    if action == 'reload_ai':
                        agent_index = int(request.form.get('agent_index'))
                        if self.pending_round is not None:
                            question = self.pending_round['question']
                            agent = self.agents[agent_index]
                            if agent is not None:
                                new_response = agent.request_llm_response(question)
                                for option in self.pending_round['ai_options']:
                                    if option['agent'] == f'llm{agent_index}':
                                        self.discarded_ai_responses.append({
                                            'question': question,
                                            'agent': option['agent'],
                                            'content': option['content']
                                        })
                                        option['content'] = new_response
                                        break
                        return redirect(url_for('admin_dashboard'))

                refresh_needed = True
                return render_template('admin_dashboard.html',
                                       pending_round=self.pending_round,
                                       discarded_ai_responses=self.discarded_ai_responses,
                                       screen_assignment=self.screen_assignment,
                                       difficulty=self.difficulty,
                                       questions_left=max(self.max_questions - self.questions_asked, 0),
                                       max_questions=self.max_questions)

            @self.flask_app.route('/setup_prompts', methods=['GET', 'POST'])
            def setup_prompts():
                if request.method == 'POST':
                    llm_1_key = request.form.get('llm_1_system_content')
                    llm_2_key = request.form.get('llm_2_system_content')
                    if llm_1_key in PERSONALITY_PROMPTS:
                        self.agents[1].system_content = PERSONALITY_PROMPTS[llm_1_key]
                    if llm_2_key in PERSONALITY_PROMPTS:
                        self.agents[2].system_content = PERSONALITY_PROMPTS[llm_2_key]
                    self.selected_llm_1 = llm_1_key
                    self.selected_llm_2 = llm_2_key
                    return redirect(url_for('setup_screens'))
                return render_template('setup_prompts.html',
                                    selected_llm_1=getattr(self, 'selected_llm_1', 'vriendelijke_ouder'),
                                    selected_llm_2=getattr(self, 'selected_llm_2', 'grappige_ouder'))

            @self.flask_app.route('/setup_screens', methods=['GET', 'POST'])
            def setup_screens():
                import random
                if request.method == 'POST':
                    return redirect(url_for('admin_dashboard'))
                
                roles = ["Human", "AI1", "AI2"]
                random.shuffle(roles)
                self.screen_assignment = {"1": roles[0], "2": roles[1], "3": roles[2]}
                human_screen = [k for k, v in self.screen_assignment.items() if v == "Human"][0]
                
                print("Human screen:", human_screen)  # add this to check in terminal
                
                return render_template('setup_screens.html', human_screen=human_screen)

            @self.flask_app.route('/reinitialize', methods=['POST'])
            def reinitialize_dashboards():
                session.clear()
                self.questions_asked = 0
                self.game_over = False
                self.last_result = None
                self.rounds = []
                self.pending_round = None
                self.discarded_ai_responses = []
                self.screen_assignment = {"1": "AI1", "2": "Human", "3": "AI2"}
                self.first_guess = None
                return redirect(url_for('admin_rules'))

            @self.flask_app.route('/reset_game', methods=['POST'])
            def reset_game():
                self.questions_asked = 0
                self.game_over = False
                self.last_result = None
                self.rounds = []
                self.pending_round = None
                self.discarded_ai_responses = []
                self.first_guess = None
                next_url = request.form.get('next')
                if next_url:
                    return redirect(next_url)
                return redirect(url_for('user_dashboard'))

            @self.flask_app.route('/set_difficulty', methods=['POST'])
            def set_difficulty():
                difficulty = request.form.get('difficulty')
                if difficulty in self.difficulty_limits and self.questions_asked == 0 and self.pending_round is None:
                    self.difficulty = difficulty
                    self.max_questions = self.difficulty_limits[difficulty]
                next_url = request.form.get('next')
                if next_url:
                    return redirect(next_url)
                return redirect(url_for('user_rules'))

            @self.flask_app.route('/submit_guess', methods=['POST'])
            def submit_guess():
                if self.game_over or self.pending_round is not None:
                    return jsonify({"ok": False, "reason": "not_ready"})
                if self.questions_asked == 0:
                    return jsonify({"ok": False, "reason": "no_questions"})

                screen = request.form.get('screen')
                if screen not in {"1", "2", "3"}:
                    return jsonify({"ok": False, "reason": "invalid"})

                # First guess
                if not hasattr(self, 'first_guess') or self.first_guess is None:
                    self.first_guess = screen
                    return jsonify({"ok": True, "status": "first_guess", "screen": screen})

                # Second guess — must be different screen
                if screen == self.first_guess:
                    return jsonify({"ok": False, "reason": "same_screen"})

                second_guess = screen
                guess1 = self.first_guess
                guess2 = second_guess
                self.first_guess = None
                self.game_over = True

                # Check results
                role1 = self.screen_assignment[guess1]
                role2 = self.screen_assignment[guess2]
                correct1 = role1 in ("AI1", "AI2")
                correct2 = role2 in ("AI1", "AI2")

                if correct1 and correct2:
                    result = "victory"
                elif correct1 or correct2:
                    result = "partial"
                else:
                    result = "defeat"

                # Find human screen
                human_screen = [k for k, v in self.screen_assignment.items() if v == "Human"][0]

                self.last_result = {
                    "result": result,
                    "guess1": guess1,
                    "guess2": guess2,
                    "correct1": correct1,
                    "correct2": correct2,
                    "human_screen": human_screen
                }

                return jsonify({"ok": True, "status": "done", "result": result,
                                "guess1": guess1, "guess2": guess2,
                                "correct1": correct1, "correct2": correct2,
                                "human_screen": human_screen})

            @self.flask_app.route('/login', methods=['GET', 'POST'])
            def login():
                if request.method == 'POST':
                    username = request.form.get('username')
                    if username == 'admin':
                        session['username'] = 'admin'
                        return redirect(url_for('admin_rules'))
                    elif username == 'user':
                        session['username'] = 'user'
                        return redirect(url_for('user_rules'))
                    else:
                        return "Ongeldige gebruikersnaam. Gebruik 'user' of 'admin'."
                return render_template('login.html')

            @self.flask_app.route('/rules/user')
            def user_rules():
                return render_template('rules_user.html',
                                       difficulty=self.difficulty,
                                       can_change_settings=self.questions_asked == 0 and self.pending_round is None,
                                       questions_left=max(self.max_questions - self.questions_asked, 0),
                                       max_questions=self.max_questions,
                                       game_over=self.game_over)

            @self.flask_app.route('/rules/admin')
            def admin_rules():
                return render_template('rules_admin.html',
                                       difficulty=self.difficulty,
                                       can_change_settings=self.questions_asked == 0 and self.pending_round is None,
                                       questions_left=max(self.max_questions - self.questions_asked, 0),
                                       max_questions=self.max_questions,
                                       game_over=self.game_over)

            @self.flask_app.route('/logout')
            def logout():
                session.clear()
                return redirect(url_for('login'))

            @self.flask_app.route('/')
            def index():
                return render_template('index.html')

            @self.flask_app.route('/check_refresh')
            def check_refresh():
                global refresh_needed
                return jsonify({"refresh_needed": refresh_needed})

            @self.flask_app.route('/reset_refresh', methods=['POST'])
            def reset_refresh():
                global refresh_needed
                refresh_needed = False
                return '', 204

            @self.flask_app.route('/get_game_state', methods=['GET'])
            def get_game_state():
                screen_history = {"1": [], "2": [], "3": []}
                screen_current = {"1": "Nog geen reacties.", "2": "Nog geen reacties.", "3": "Nog geen reacties."}

                for round_item in self.rounds:
                    for screen_num, role in self.screen_assignment.items():
                        if role == "Human":
                            answer = round_item["human_response"]
                        elif role == "AI1":
                            answer = round_item["ai_options"][0]["content"] if round_item["ai_options"] else ""
                        else:  # AI2
                            answer = round_item["ai_options"][1]["content"] if len(round_item["ai_options"]) > 1 else ""
                        screen_history[screen_num].append({
                            "question": round_item["question"],
                            "answer": answer
                        })

                for screen_num in ["1", "2", "3"]:
                    if screen_history[screen_num]:
                        screen_current[screen_num] = screen_history[screen_num][-1]["answer"]

                questions_left = max(self.max_questions - self.questions_asked, 0)
                pending = self.pending_round is not None

                return jsonify({
                    "current": screen_current,
                    "history": screen_history,
                    "questions_left": questions_left,
                    "questions_asked": self.questions_asked,
                    "max_questions": self.max_questions,
                    "pending": pending,
                    "game_over": self.game_over,
                    "result": self.last_result,
                    "can_ask": (not self.game_over) and (not pending) and (self.questions_asked < self.max_questions),
                    "can_guess": (not self.game_over) and (not pending) and (self.questions_asked > 0),
                    "ready_to_guess": (not self.game_over) and (not pending) and (self.questions_asked >= self.max_questions),
                    "first_guess": self.first_guess if hasattr(self, 'first_guess') else None
                })

            @self.flask_app.route('/get_admin_state', methods=['GET'])
            def get_admin_state():
                return jsonify({
                    "pending_round": self.pending_round,
                    "rounds": self.rounds,
                    "discarded": self.discarded_ai_responses,
                    "questions_left": max(self.max_questions - self.questions_asked, 0),
                    "max_questions": self.max_questions,
                    "difficulty": self.difficulty,
                    "screen_assignment": self.screen_assignment,
                    "can_change_settings": self.questions_asked == 0 and self.pending_round is None
                })

        self.routes_registered = True


if __name__ == '__main__':
    agent_1 = GroqAgent(PERSONALITY_PROMPTS["vriendelijke_ouder"])
    agent_2 = GroqAgent(PERSONALITY_PROMPTS["grappige_ouder"])
    admin = None

    agents = [admin, agent_1, agent_2]

    refresh_needed = False

    app = FlaskApp(agents)
    app.flask_app.run(debug=True)