bethanie05 commited on
Commit
737f332
·
verified ·
1 Parent(s): 87c7ace

Remove old main.py file.

Browse files
Files changed (1) hide show
  1. chat_application/main.py +0 -620
chat_application/main.py DELETED
@@ -1,620 +0,0 @@
1
- from flask import Flask, request, render_template, redirect, url_for, session, make_response, render_template_string
2
- from flask_socketio import SocketIO, join_room, leave_room, send
3
- from pymongo import MongoClient
4
- from datetime import datetime, timedelta
5
- import random
6
- import time
7
- import math
8
- import google.auth
9
- from google.auth.transport.requests import AuthorizedSession
10
- from vertexai.tuning import sft
11
- from vertexai.generative_models import GenerativeModel
12
- import re
13
- import concurrent.futures
14
- from text_corruption import corrupt
15
- from humanizing import humanize
16
- from quote_removal import remove_quotes
17
- from weird_char_removal import remove_weird_characters
18
- from duplicate_detection import duplicate_check
19
-
20
- #controls
21
- CHAT_CONTEXT = 20 #how many messages from chat history to append to inference prompt
22
- #minimum number of chars where we start checking for duplicate messages
23
- DUP_LEN = 25 #since short messages may reasonably be the same
24
-
25
- app = Flask(__name__)
26
- app.config["SECRET_KEY"] = "supersecretkey"
27
- socketio = SocketIO(app)
28
-
29
- # Setup for Vertex API calls
30
- credentials, _ = google.auth.default(
31
- scopes=["https://www.googleapis.com/auth/cloud-platform"]
32
- )
33
- google_session = AuthorizedSession(credentials)
34
-
35
- # Initialize the bots
36
- pirate_tuning_job_name = f"projects/frozone-475719/locations/us-central1/tuningJobs/3296615187565510656"
37
- tuning_job_frobot = f"projects/frozone-475719/locations/us-central1/tuningJobs/1280259296294076416"
38
- tuning_job_hotbot = f"projects/frozone-475719/locations/us-central1/tuningJobs/4961166390611410944"
39
- tuning_job_coolbot = f"projects/frozone-475719/locations/us-central1/tuningJobs/4112237860852072448"
40
-
41
- hottj = sft.SupervisedTuningJob(tuning_job_hotbot)
42
- cooltj = sft.SupervisedTuningJob(tuning_job_coolbot)
43
- frotj = sft.SupervisedTuningJob(tuning_job_frobot)
44
- # Create the bot models
45
- hotbot = GenerativeModel(hottj.tuned_model_endpoint_name)
46
- coolbot = GenerativeModel(cooltj.tuned_model_endpoint_name)
47
- frobot = GenerativeModel(frotj.tuned_model_endpoint_name)
48
-
49
- # MongoDB setup
50
- client = MongoClient("mongodb://localhost:27017/")
51
- db = client["experimentData"]
52
- rooms_collection = db.rooms
53
-
54
- # List of fruits to choose display names from
55
- FRUIT_NAMES = ["blueberry", "strawberry", "orange", "cherry"]
56
- aliases = {"watermelon":"W", "apple":"L", "banana":"B", "blueberry":"C", "strawberry":"D", "orange":"E", "grape":"G", "cherry":"H"}
57
- reverse_aliases = { value:key for key,value in aliases.items() }
58
- # List of discussion topics
59
- TOPICS_LIST = [
60
- {
61
- "title": "Abortion",
62
- "text": "Since the Supreme Court overturned Roe vs. Wade in 2022, there has been an increase in patients crossing state lines to receive abortions in less restrictive states. Pro-choice advocates argue that these restrictions exacerbate unequal access to healthcare due to financial strain and other factors and believe that a patient should be able to make personal medical decisions about their own body and future. Pro-life advocates argue that abortion legislation should be left to the states and believe that abortion is amoral and tantamount to murder. Both sides disagree on how to handle cases of rape, incest, terminal medical conditions, and risks to the mother’s life and health. What stance do you take on abortion and why?",
63
- "post": "Idk its hard bc both sides have good points. People should be able to make their own decisions about their own body but theres also moral stuff to think about too you know"
64
- },
65
- {
66
- "title": "Gun Rights/Control",
67
- "text": "Gun rights advocates argue that the right to bear arms is a protected second amendment right necessary for self-defense. Meanwhile, gun control advocates argue that stricter regulations are necessary to reduce gun violence. Potential reforms include stricter background checks, banning assault weapons, enacting red flag laws, and increasing the minimum age to purchase a gun. What stance do you take on gun rights vs. gun control and why?",
68
- "post": "i think people should be able to own guns but there has to be some check like background stuff so crazy people dont get them"
69
- },
70
- {
71
- "title": "Education and Trans Students",
72
- "text": "Laws and policies affecting trans people are highly contested, especially those involving education. Several states have passed laws restricting the use of preferred pronouns and names in schools, limiting transgender athletes' ability to participate in sports, and banning books containing LGBTQ+ content from school libraries. How do you think decisions on school policies regarding trans students should be made and why?",
73
- "post": "I dont think its that big a deal to use different pronouns but also trans athletes should be playing with the gender they were born as. I know thats an unpopular opinion but its the only way its fair."
74
- },
75
- {
76
- "title": "Immigration and ICE Activity",
77
- "text": "The current year has seen an increase in ICE (U.S. Immigration and Customs Enforcement) activity, including raids at workplaces, courthouses, schools, churches, and hospitals. Some argue that ICE is going too far and is violating the Constitutional due process rights of both immigrants and citizens. Others argue that these actions are necessary to maintain national security and enforce immigration law. What stance do you take on recent ICE activity and why?",
78
- "post": "I think ice is doing their job they're literally immigration enforcement. It sucks but if you come here illegally youre going to face the consequence."
79
- },
80
- {
81
- "title": "Universal Healthcare",
82
- "text": "Some argue that universal healthcare is necessary to ensure everyone has access to lifesaving medical treatments and a minimum standard of living, regardless of income or employment. Others argue that the choice of how to access healthcare is a private responsibility and that it is more efficient for the government to limit intervention. What stance do you take on government involvement in providing healthcare and why?",
83
- "post": "I think people should handle their own healthcare. the government is slow plus competition means more innovation. i dont trust the idea of one size fits all"
84
- }
85
- ]
86
-
87
- # FroBot Prompt
88
- with open("../data/inference_prompts/frobot_prompt_main.txt") as f:
89
- FROBOT_PROMPT = f.read()
90
-
91
- # HotBot Prompt
92
- with open("../data/inference_prompts/hotbot_prompt_main.txt") as h:
93
- HOTBOT_PROMPT = h.read()
94
-
95
- # CoolBot Prompt
96
- with open("../data/inference_prompts/coolbot_prompt_main.txt") as c:
97
- COOLBOT_PROMPT = c.read()
98
-
99
- # Randomly select fruits to use for display names
100
- def choose_names(n):
101
- # Return n unique random fruit names
102
- return random.sample(FRUIT_NAMES, n)
103
-
104
- # Send initial watermelon post
105
- def send_initial_post(room_id, delay):
106
- # Wait 1 second before sending
107
- time.sleep(delay)
108
- # Get the inital post for this topic
109
- room_doc = rooms_collection.find_one({"_id": room_id})
110
- topic_title = room_doc["topic"]
111
- topic_info = next((t for t in TOPICS_LIST if t["title"] == topic_title), None)
112
- if not topic_info:
113
- return
114
- initialPost = topic_info["post"]
115
- # Store the initial post in the database
116
- db_msg = {
117
- "sender": "watermelon",
118
- "message": initialPost,
119
- "timestamp": datetime.utcnow()
120
- }
121
- rooms_collection.update_one(
122
- {"_id": room_id},
123
- {"$push": {"messages": db_msg}}
124
- )
125
- # Send to the client (must use emit when in background thread)
126
- socketio.emit("message", {"sender": "watermelon", "message": initialPost}, to=room_id)
127
-
128
- #send to the bots
129
- socketio.start_background_task(ask_bot_round, room_id)
130
-
131
- # Send message that a bot joined the room
132
- def send_bot_joined(room_id, bot_name, delay):
133
- # Wait 1 second before sending
134
- time.sleep(delay)
135
- socketio.emit("message", {"sender": "", "message": f"{bot_name} has entered the chat"}, to=room_id)
136
-
137
- # Trigger a round of bot calls if user has been inactive for a while
138
- def user_inactivity_tracker(room_id, timeout_seconds=180):
139
- print(f"Started user inactivity tracker for Room ID#{room_id}")
140
- while True:
141
- room_doc = rooms_collection.find_one({"_id": room_id})
142
- # Stop if this room's chat has ended
143
- if not room_doc or room_doc.get("ended", False):
144
- print(f"User inactivity tracker stopping for Room ID#{room_id}")
145
- return
146
- lastTime = room_doc.get("last_activity")
147
- if lastTime:
148
- if datetime.utcnow() - lastTime > timedelta(seconds=timeout_seconds):
149
- print(f"User has been inactive in Room ID#{room_id} - triggering new round of bot calls.")
150
- socketio.start_background_task(ask_bot_round, room_id)
151
- # Prevent multiple bot call triggers due to inactivity
152
- rooms_collection.update_one(
153
- {"_id": room_id},
154
- {"$set": {"last_activity": datetime.utcnow()}}
155
- )
156
- time.sleep(5) # re-check inactivity every 5s
157
-
158
- def let_to_name(room_id, text):
159
- named_response = str(text)
160
- letters = [aliases[name] for name in (FRUIT_NAMES + ["watermelon"])] # makes a copy, rather than directly modifying
161
- for letter in set(re.findall(r"\b[A-Z]\b", named_response)):
162
- if letter in letters:
163
- named_response = re.sub(r"\b" + letter + r"\b", reverse_aliases[letter], named_response)
164
- return named_response
165
-
166
- def name_to_let(room_id, text):
167
- named_response = str(text)
168
- names = FRUIT_NAMES + ["watermelon"] # makes a copy, rather than directly modifying
169
- for name in names:
170
- if name in text:
171
- text = re.sub(r"\b" + name + r"\b", aliases[name], text, flags=re.I)
172
- return text
173
-
174
- def replace_semicolons(text, probability=0.80):
175
- modified_text = []
176
- for char in text:
177
- if char == ';' and random.random() <= probability:
178
- modified_text.append(',')
179
- else:
180
- modified_text.append(char)
181
- return ''.join(modified_text)
182
-
183
- def get_response_delay(response):
184
- baseDelay = 1 # standard delay for thinking
185
- randFactor = random.uniform(2, 12.)
186
- perCharacterDelay = 0.12
187
- # was .25 -> average speed: 3.33 characters/second = 0.3
188
- maxDelay = 150 # maximum cap of 2.5 minutes (so the bots don't take too long)
189
- # Add total delay
190
- totalDelay = baseDelay + perCharacterDelay * len(response) + randFactor
191
- return min(totalDelay, maxDelay)
192
-
193
- # Ask a bot for its response, store in DB, and send to client
194
- # Returns true if the bot passed
195
- def ask_bot(room_id, bot, bot_display_name, initial_prompt):
196
- # Prevents crashing if bot model did not load
197
- if bot is None:
198
- return False
199
- # Get the full chat room history
200
- room_doc = rooms_collection.find_one({"_id": room_id})
201
- # Do not proceed if the chat has ended
202
- if not room_doc or room_doc.get("ended", False):
203
- return False
204
- history = room_doc["messages"]
205
- # Build the LLM prompt
206
- prompt = re.sub(r"<RE>", aliases[bot_display_name], initial_prompt)
207
- context = list() #get the context sent to bot for duplicate_check
208
- for message in history[-CHAT_CONTEXT:]:
209
- prompt += f"{aliases[message['sender']]}: {message['message']}\n"
210
- context.append(message['message'])
211
-
212
- prompt = name_to_let(room_id, prompt) #sub fruit names to letters to give to bots
213
-
214
- print("\n")
215
- print("=================================prompt")
216
- print(prompt)
217
-
218
- # Get the bot's response
219
- try:
220
- response = bot.generate_content(prompt)
221
- parsed_response = response.candidates[0].content.parts[0].text.strip()
222
- except Exception as e:
223
- print("Error in bot response: ", e)
224
- print("Treating this bot's response as a pass.")
225
- # Do not store/send messages if the chat has ended
226
- room_doc = rooms_collection.find_one({"_id": room_id})
227
- if not room_doc or room_doc.get("ended", False):
228
- return False
229
- # Store the error response in the database
230
- bot_message = {
231
- "sender": bot_display_name,
232
- "message": "ERROR in bot response - treated as a (pass)",
233
- "timestamp": datetime.utcnow()
234
- }
235
- rooms_collection.update_one(
236
- {"_id": room_id},
237
- {"$push": {"messages": bot_message}}
238
- )
239
- return True
240
-
241
- #remove bot formatting like <i></i> <b></b> that will render on the page
242
- parsed_response = re.sub(r"<([a-zA-Z]+)>(?=.*</\1>)", "", parsed_response)
243
- parsed_response = re.sub(r"</([a-zA-Z]+)>", "", parsed_response)
244
- #fix any escaped \\n --> \n so they are actual newlines
245
- parsed_response = re.sub(r"\\n", "\n", parsed_response).strip()
246
- #remove bot heading ("C: ...")
247
- if re.search(r"\b" + aliases[bot_display_name] + r"\b:",
248
- parsed_response):
249
- parsed_response = re.sub(r"\b"
250
- + aliases[bot_display_name]
251
- + r"\b:\s?", '', parsed_response)
252
-
253
- # Check for if the bot passed (i.e. response = "(pass)")
254
- if ("(pass)" in parsed_response) or (parsed_response == ""):
255
- # Do not store/send messages if the chat has ended
256
- room_doc = rooms_collection.find_one({"_id": room_id})
257
- if not room_doc or room_doc.get("ended", False):
258
- return False
259
- # Store the pass in the database
260
- bot_message = {
261
- "sender": bot_display_name,
262
- "message": parsed_response,
263
- "timestamp": datetime.utcnow()
264
- }
265
- rooms_collection.update_one(
266
- {"_id": room_id},
267
- {"$push": {"messages": bot_message}}
268
- )
269
-
270
- print("PASSED")
271
- return True # a pass is still recorded in the database, but not sent to the client
272
-
273
- #remove encapsulating quotes
274
- no_quotes = remove_quotes(parsed_response)
275
- #humanize the response (remove obvious AI formatting styles)
276
- humanized_response = humanize(no_quotes)
277
- #replace most semicolons
278
- less_semicolons_response = replace_semicolons(humanized_response)
279
- #corrupt the response (add some typos and misspellings)
280
- corrupted_response = corrupt(less_semicolons_response)
281
- #remove weird chars
282
- no_weird_chars = remove_weird_characters(corrupted_response)
283
- #sub letters for names, so if the bot addressed A -> Apple
284
- named_response = let_to_name(room_id, no_weird_chars)
285
-
286
- #check that there are no reccent duplicate messages
287
- if len(named_response) > DUP_LEN and duplicate_check(named_response, context):
288
- print("****DUPLICATE MESSAGE DETECTED")
289
- print("Treating this bot's response as a pass.")
290
- # Do not store/send messages if the chat has ended
291
- room_doc = rooms_collection.find_one({"_id": room_id})
292
- if not room_doc or room_doc.get("ended", False):
293
- return False
294
- # Store the error response in the database
295
- bot_message = {
296
- "sender": bot_display_name,
297
- "message": f"DUPLICATE message detected - treated as a (pass) : {named_response}",
298
- "timestamp": datetime.utcnow()
299
- }
300
- rooms_collection.update_one(
301
- {"_id": room_id},
302
- {"$push": {"messages": bot_message}}
303
- )
304
- return False
305
-
306
-
307
- print("\n")
308
- print("=================================response")
309
- print(corrupted_response)
310
-
311
- # Add latency/wait time for bot responses
312
- delay = get_response_delay(named_response);
313
- print(delay)
314
- time.sleep(delay)
315
-
316
- # Do not store/send messages if the chat has ended
317
- room_doc = rooms_collection.find_one({"_id": room_id})
318
- if not room_doc or room_doc.get("ended", False):
319
- return False
320
-
321
- # Store the response in the database
322
- bot_message = {
323
- "sender": bot_display_name,
324
- "message": named_response, #save fruits in db so page reload shows proper names
325
- "timestamp": datetime.utcnow()
326
- }
327
- rooms_collection.update_one(
328
- {"_id": room_id},
329
- {"$push": {"messages": bot_message}}
330
- )
331
-
332
- # Send the bot's response to the client
333
- socketio.emit("message", {"sender": bot_display_name, "message": named_response}, to=room_id)
334
- return False
335
-
336
- def ask_bot_round(room_id):
337
- while True:
338
- room_doc = rooms_collection.find_one({"_id": room_id})
339
- if not room_doc or room_doc.get("ended", False):
340
- return
341
-
342
- with concurrent.futures.ThreadPoolExecutor() as exec:
343
- futures = [
344
- exec.submit(ask_bot, room_id, frobot, room_doc["FroBot_name"], FROBOT_PROMPT),
345
- exec.submit(ask_bot, room_id, hotbot, room_doc["HotBot_name"], HOTBOT_PROMPT),
346
- exec.submit(ask_bot, room_id, coolbot, room_doc["CoolBot_name"], COOLBOT_PROMPT),
347
- ]
348
- results = [f.result() for f in futures]
349
-
350
- print("Raw pass check results: ", results)
351
- if not all(results):
352
- print("At least one bot responded. Not re-prompting.\n")
353
- return # at least one bot responded
354
-
355
- # All bots passed - reprompt
356
- print("All bots passed. Re-prompting for responses.\n")
357
- time.sleep(2) # prevents CPU thrashing & spamming
358
-
359
- # Build the routes
360
- #disabled landing
361
- #@app.route('/', methods=["GET"])
362
- def landing():
363
- return render_template('landing.html')
364
- #disabled waiting
365
- #@app.route('/wait', methods=["GET"])
366
- def waiting():
367
- return render_template('waiting.html')
368
- #changed /chat -> /
369
- @app.route('/', methods=["GET", "POST"])
370
- def home():
371
- #session.clear()
372
-
373
- #get PROLIFIC_PID from qualtrics
374
- #test if user_id in session
375
- prolific_pid = request.args.get("PROLIFIC_PID") or session.get('user_id') or ''
376
-
377
- if request.method == "POST":
378
- user_id = request.form.get('name')
379
- if not user_id:
380
- return render_template('home.html', error="Prolific ID is required", prolific_pid=prolific_pid)
381
- session['user_id'] = user_id
382
- return redirect(url_for('topics'))
383
- else:
384
- return render_template('home.html',prolific_pid=prolific_pid)
385
-
386
- @app.route('/topics', methods=["GET", "POST"])
387
- def topics():
388
- user_id = session.get('user_id')
389
- if not user_id:
390
- return redirect(url_for('home'))
391
-
392
- exists = db.rooms.find_one({"user_id":user_id})
393
- if exists:
394
- #set session vars for room()
395
- session['room'] = exists['_id']
396
- session['display_name'] = exists['user_name']
397
- return redirect(url_for('room'))
398
-
399
- #don't let browser cache this page
400
- resp = make_response( render_template('topics.html', topics=TOPICS_LIST) )
401
- resp.headers['Cache-Control'] = 'no-store'
402
- return resp
403
-
404
- @app.route('/choose', methods=["POST"])
405
- def choose():
406
- user_id = session.get('user_id')
407
- if not user_id:
408
- return redirect(url_for('home'))
409
- topic = request.form.get('topic')
410
- if not topic:
411
- return redirect(url_for('topics'))
412
- topic_info = next((t for t in TOPICS_LIST if t["title"] == topic), None)
413
- if topic_info is None:
414
- return redirect(url_for('topics'))
415
- # Get next room id (and add one)
416
- counter = db.counters.find_one_and_update(
417
- {"_id": "room_id"},
418
- {"$inc": {"seq": 1}}, # increment seq by 1
419
- upsert=True, # create if missing
420
- return_document=True
421
- )
422
- room_id = counter["seq"]
423
- # Pick fruit display names
424
- fruit_names = choose_names(4)
425
- user_name = fruit_names[0]
426
- frobot_name = fruit_names[1]
427
- hotbot_name = fruit_names[2]
428
- coolbot_name = fruit_names[3]
429
-
430
- # Create the new room in the database
431
- rooms_collection.insert_one({
432
- "_id": room_id,
433
- "topic": topic_info['title'],
434
- # creation date/time
435
- "created_at": datetime.utcnow(),
436
- # user identity
437
- "user_id": user_id,
438
- "user_name": user_name,
439
- # bot names
440
- "FroBot_name": frobot_name,
441
- "HotBot_name": hotbot_name,
442
- "CoolBot_name": coolbot_name,
443
- # flags needed for handling refreshes
444
- "initialPostsSent": False,
445
- "inactivity_tracker_started": False,
446
- # empty message history
447
- "messages": [],
448
- # last time user sent a message
449
- "last_activity": datetime.utcnow(),
450
- # flag for if the user aborts
451
- "aborted": False,
452
- # flag for if the chat has ended
453
- "ended": False,
454
- "ended_at": None
455
- })
456
-
457
- session['room'] = room_id
458
- session['display_name'] = user_name
459
- return redirect(url_for('room'))
460
-
461
- @app.route('/room')
462
- def room():
463
- room_id = session.get('room')
464
- display_name = session.get('display_name')
465
- if not room_id or not display_name:
466
- return redirect(url_for('home'))
467
- room_doc = rooms_collection.find_one({"_id": room_id})
468
- if not room_doc:
469
- return redirect(url_for('home'))
470
- topic = room_doc["topic"]
471
- topic_info = next((t for t in TOPICS_LIST if t["title"] == topic), None)
472
- if topic_info is None:
473
- return redirect(url_for('topics'))
474
- nonpass_messages = [
475
- m for m in room_doc["messages"]
476
- if m.get("message", "").strip() != "(pass)"
477
- ]
478
- return render_template("room.html", room=room_id, topic_info=topic_info, user=display_name, messages=nonpass_messages, FroBot_name=room_doc["FroBot_name"], HotBot_name=room_doc["HotBot_name"], CoolBot_name=room_doc["CoolBot_name"], ended=room_doc["ended"])
479
-
480
- @app.route("/abort", methods=["POST"])
481
- def abort_room():
482
- room_id = session.get("room")
483
- if not room_id:
484
- return ("Error: No room in session.", 400)
485
- rooms_collection.update_one(
486
- {"_id": room_id},
487
- {"$set": {"aborted": True}}
488
- )
489
- return ("OK", 200)
490
-
491
- @app.route("/post_survey", methods=["POST", "GET"])
492
- def post_survey():
493
- user_id = session.get('user_id')
494
- if not user_id:
495
- return render_template('home.html', error="Enter your Prolific ID.")
496
- info = db.rooms.find_one({"user_id":user_id}, {'FroBot_name':1,
497
- 'HotBot_name':1,
498
- 'CoolBot_name':1} )
499
- if not info:
500
- return render_template('home.html', error="Enter your ID.")
501
-
502
- # Store in the DB that this chat has been ended
503
- db.rooms.update_one(
504
- {"user_id":user_id},
505
- {"$set": {"ended": True, "ended_at": datetime.utcnow()}}
506
- )
507
-
508
- CName = info['CoolBot_name']
509
- FName = info['FroBot_name']
510
- HName = info['HotBot_name']
511
-
512
- SURVEY_2_LINK = f"https://umw.qualtrics.com/jfe/form/SV_eIIbPlJ2D9k4zKC?PROLIFIC_PID={user_id}&CName={CName}&FName={FName}&HName={HName}"
513
-
514
- return redirect(SURVEY_2_LINK)
515
-
516
- # Build the SocketIO event handlers
517
-
518
- @socketio.on('connect')
519
- def handle_connect():
520
- name = session.get('display_name')
521
- room = session.get('room')
522
- if not name or not room:
523
- return
524
- room_doc = rooms_collection.find_one({"_id": room})
525
- if not room_doc:
526
- return
527
- join_room(room)
528
- if (room_doc.get("initialPostsSent", False)):
529
- return
530
- # Send the message that "watermelon" has already joined the chat
531
- send({
532
- "sender": "",
533
- "message": "watermelon has entered the chat"
534
- }, to=room)
535
- # Send the message that this user has joined the chat
536
- send({
537
- "sender": "",
538
- "message": f"{name} has entered the chat"
539
- }, to=room)
540
- # Start background tasks for the bots to join after a short delay
541
- socketio.start_background_task(send_bot_joined, room, room_doc['CoolBot_name'], 3)
542
- socketio.start_background_task(send_bot_joined, room, room_doc['FroBot_name'], 7)
543
- socketio.start_background_task(send_bot_joined, room, room_doc['HotBot_name'], 13)
544
- # Start background task to send the initial watermelon post after a short delay
545
- socketio.start_background_task(send_initial_post, room, 10)
546
- rooms_collection.update_one(
547
- {"_id": room},
548
- {"$set": {"initialPostsSent": True}}
549
- )
550
- # Start user inactivity tracker
551
- if not room_doc.get("inactivity_tracker_started", False):
552
- rooms_collection.update_one(
553
- {"_id": room},
554
- {
555
- "$set": {
556
- "inactivity_tracker_started": True,
557
- "last_activity": datetime.utcnow()
558
- }
559
- }
560
- )
561
- socketio.start_background_task(user_inactivity_tracker, room)
562
-
563
- @socketio.on('message')
564
- def handle_message(payload):
565
- room = session.get('room')
566
- name = session.get('display_name')
567
- if not room or not name:
568
- return
569
-
570
- # Stop message processing if the chat has ended
571
- room_doc = rooms_collection.find_one({"_id": room})
572
- if not room_doc or room_doc.get("ended", False):
573
- return
574
-
575
- text = payload.get("message", "").strip()
576
- if not text:
577
- return # ignore empty messages
578
-
579
- # Client-visible message (no datetime)
580
- client_message = {
581
- "sender": name,
582
- "message": text
583
- }
584
- # Database-only message (with datetime)
585
- db_message = {
586
- "sender": name,
587
- "message": text,
588
- "timestamp": datetime.utcnow()
589
- }
590
- # Store the full version in the database
591
- rooms_collection.update_one(
592
- {"_id": room},
593
- {
594
- "$push": {"messages": db_message},
595
- "$set": {"last_activity": datetime.utcnow()}
596
- }
597
- )
598
- # Send only the client version (no datetime)
599
- send(client_message, to=room)
600
-
601
- # Ask each bot for a response
602
- socketio.start_background_task(ask_bot_round, room)
603
-
604
- @socketio.on('disconnect')
605
- def handle_disconnect():
606
- room = session.get("room")
607
- name = session.get("display_name")
608
-
609
- if room:
610
- send({
611
- "sender": "",
612
- "message": f"{name} has left the chat"
613
- }, to=room)
614
- leave_room(room)
615
-
616
-
617
- if __name__ == "__main__":
618
- print("Async mode:", socketio.async_mode)
619
- socketio.run(app, host='0.0.0.0', port=5000, debug=True)
620
-