Add timer pop-up instructions

#4
README.md CHANGED
@@ -6,6 +6,6 @@ colorTo: blue
6
  sdk: docker
7
  pinned: false
8
  license: mit
9
- short_description: A space for hosting our experiment website tehe
10
  ---
11
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
6
  sdk: docker
7
  pinned: false
8
  license: mit
9
+ short_description: A space for hosting our experiment website
10
  ---
11
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
chat_application/main.py CHANGED
@@ -1,4 +1,3 @@
1
- from google.api_core import exceptions
2
  from flask import Flask, request, render_template, redirect, url_for, session, make_response, render_template_string
3
  from flask_socketio import SocketIO, join_room, leave_room, send
4
  from pymongo import MongoClient
@@ -29,17 +28,14 @@ from duplicate_detection import duplicate_check
29
  from huggingface_hub import upload_folder
30
  from huggingface_hub import HfApi
31
  from huggingface_hub import login
32
- from datetime import datetime
33
-
34
 
35
  class datasetHandler():
36
 
37
- def __init__(self,hf_token,mongoDB_name,max_dumps = 10):
38
  login(hf_token)
39
  self.api = HfApi(token = hf_token)
40
  self.DB_name = mongoDB_name
41
- self.timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
42
- self.max_dumps = max_dumps
43
 
44
  def make_dump(self):
45
  try:
@@ -51,47 +47,21 @@ class datasetHandler():
51
 
52
  def upload_dump(self):
53
  try:
54
- upload_folder(folder_path="/tmp/mongoDBContents",path_in_repo=f"mongoDump_{self.timestamp}", repo_id="ProjectFrozone/MongoDBDumps", repo_type="dataset")
55
  return 0
56
  except Exception as e:
57
  print(e)
58
  return 1
59
 
60
- def list_dumps(self):
61
- all_files = self.api.list_repo_files(repo_id="ProjectFrozone/MongoDBDumps", repo_type="dataset")
62
- all_dirs = [f[:f.index("/")] for f in all_files if "mongoDump_" in f]
63
- dates = [date[date.index("_") + 1:] for date in all_dirs]
64
- return (all_dirs, dates)
65
-
66
- def delete_dump(self,dump_name):
67
- self.api.delete_folder(
68
- repo_id="ProjectFrozone/MongoDBDumps",
69
- path_in_repo=f"{dump_name}",
70
- repo_type="dataset",
71
- commit_message=f"Deleted {dump_name}"
72
- )
73
-
74
- def cleanup_dataset(self,dirs,dates):
75
- if len(dates) > self.max_dumps:
76
- to_remove = dirs[0]
77
- self.delete_dump(to_remove)
78
- return f"Deleted {to_remove}"
79
- return "Nothing to delete"
80
-
81
  def dump_db(self):
82
  self.make_dump()
83
  self.upload_dump()
84
-
85
- def clean(self):
86
- dirs,dates = self.list_dumps()
87
- print(self.cleanup_dataset(dirs,dates))
88
- # End database backup code
89
 
90
  #controls
91
  CHAT_CONTEXT = 20 #how many messages from chat history to append to inference prompt
92
  #minimum number of chars where we start checking for duplicate messages
93
  DUP_LEN = 25 #since short messages may reasonably be the same
94
- REMOVE_PUNC_RATE = .8 #how often to remove final punctuation
95
 
96
  # Directory alignment
97
  BASE_DIR = Path(__file__).resolve().parent
@@ -137,15 +107,12 @@ frobot = GenerativeModel(frotj.tuned_model_endpoint_name)
137
  #change to endpoints
138
  hotbot = "projects/700531062565/locations/us-central1/endpoints/6225523347153747968"
139
  coolbot = "projects/700531062565/locations/us-central1/endpoints/1700531621553242112"
140
- #frobot = "projects/700531062565/locations/us-central1/endpoints/2951406418055397376"
141
- #make frobot actually a coolbot (!4/30)
142
- frobot = coolbot
143
 
144
  # MongoDB setup
145
  client = MongoClient("mongodb://127.0.0.1:27017/")
146
  db = client["huggingFaceData"]
147
  rooms_collection = db.rooms
148
- feedback_collection = db.feedback
149
 
150
  # List of fruits to choose display names from
151
  FRUIT_NAMES = ["blueberry", "strawberry", "orange", "cherry"]
@@ -180,16 +147,12 @@ TOPICS_LIST = [
180
  }
181
  ]
182
 
183
- """
184
- (!4/30) Make frobot actually a coolbot
185
-
186
  # FroBot Main Prompt
187
  with open(PROJECT_ROOT / "data" / "prompts" / "frobot_prompt_main.txt") as f:
188
  FROBOT_PROMPT = f.read()
189
  # Instructions
190
  with open(PROJECT_ROOT / "data" / "inference_instructions" / "frobot_instructions_main.txt") as f:
191
  FROBOT_INSTRUCT = f.read()
192
- """
193
 
194
  # HotBot Prompt
195
  with open(PROJECT_ROOT / "data" / "prompts" / "hotbot_prompt_main.txt") as h:
@@ -205,10 +168,6 @@ with open(PROJECT_ROOT / "data" / "prompts" / "coolbot_prompt_main.txt") as c:
205
  with open(PROJECT_ROOT / "data" / "inference_instructions" / "coolbot_instructions_main.txt") as c:
206
  COOLBOT_INSTRUCT = c.read()
207
 
208
- # (!4/30) point frobot instructions at coolbot's
209
- FROBOT_PROMPT = COOLBOT_PROMPT
210
- FROBOT_INSTRUCT = COOLBOT_INSTRUCT
211
-
212
  # Randomly select fruits to use for display names
213
  def choose_names(n):
214
  # Return n unique random fruit names
@@ -312,12 +271,6 @@ def replace_semicolons(text, probability=0.80):
312
  modified_text.append(char)
313
  return ''.join(modified_text)
314
 
315
- def get_last_paragraph(text):
316
- text = text.strip()
317
- if "\n" not in text:
318
- return text
319
- return text.rsplit("\n", 1)[-1].strip()
320
-
321
  def get_response_delay(response):
322
  baseDelay = 5 # standard delay for thinking
323
  randFactor = np.random.uniform(0,30, len(response) // 4)
@@ -331,7 +284,7 @@ def get_response_delay(response):
331
 
332
  # Ask a bot for its response, store in DB, and send to client
333
  # Returns true if the bot passed
334
- def ask_bot(room_id, bot, bot_display_name, initial_prompt, instruct_prompt , wait_time = 1):
335
  # Prevents crashing if bot model did not load
336
  if bot is None:
337
  return False
@@ -364,35 +317,6 @@ def ask_bot(room_id, bot, bot_display_name, initial_prompt, instruct_prompt , wa
364
  ),
365
  )
366
  parsed_response = response.candidates[0].content.parts[0].text.strip()
367
-
368
- # Deal with rate limit issues
369
- except exceptions.TooManyRequests:
370
- print(f"429 Rate Limit Exceeded")
371
- socketio.sleep(wait_time)
372
- wait_time *= 2
373
-
374
- # Prevent Stack Overflow
375
- if wait_time > 32:
376
- print("Rate Limit Exceeded and Exponential Backoff Too Long")
377
- print("Treating this bot's response as a pass.")
378
- room_doc = rooms_collection.find_one({"_id": room_id})
379
- if not room_doc or room_doc.get("ended", False):
380
- return False
381
- # Store the error response in the database
382
- bot_message = {
383
- "sender": bot_display_name,
384
- "message": "ERROR in bot response - treated as a (pass)",
385
- "timestamp": datetime.utcnow()
386
- }
387
- rooms_collection.update_one(
388
- {"_id": room_id},
389
- {"$push": {"messages": bot_message}}
390
- )
391
- return True
392
-
393
- return ask_bot(room_id, bot, bot_display_name, initial_prompt, instruct_prompt , wait_time = wait_time)
394
-
395
-
396
  except Exception as e:
397
  print("Error in bot response: ", e)
398
  print("Treating this bot's response as a pass.")
@@ -423,19 +347,6 @@ def ask_bot(room_id, bot, bot_display_name, initial_prompt, instruct_prompt , wa
423
  parsed_response = re.sub(r"\b"
424
  + aliases[bot_display_name]
425
  + r"\b:\s?", '', parsed_response)
426
-
427
- # Only keep the last paragraph of frobot responses
428
- """
429
- (!4/30) commenting this cout because the CCHU experiment does not have a frobot
430
- We do not want this happening for frobot = coolbot
431
-
432
- if bot == frobot:
433
- print("=========== OG FROBOT RESPONSE")
434
- print(parsed_response)
435
- parsed_response = get_last_paragraph(parsed_response)
436
- print("=========== LAST PARAGRAPH")
437
- print(parsed_response)
438
- """
439
 
440
  # Check for if the bot passed (i.e. response = "(pass)")
441
  if ("(pass)" in parsed_response) or (parsed_response == ""):
@@ -457,26 +368,21 @@ def ask_bot(room_id, bot, bot_display_name, initial_prompt, instruct_prompt , wa
457
  print("PASSED")
458
  return True # a pass is still recorded in the database, but not sent to the client
459
 
460
- #sub letters for names, so if the bot addressed A -> Apple
461
- named_response = let_to_name(room_id, parsed_response)
462
  #remove encapsulating quotes
463
- no_quotes = remove_quotes(named_response)
464
  #humanize the response (remove obvious AI formatting styles)
465
  humanized_response = humanize(no_quotes)
466
  #replace most semicolons
467
  less_semicolons_response = replace_semicolons(humanized_response)
468
  #corrupt the response (add some typos and misspellings)
469
- corrupted_response = corrupt(less_semicolons_response, misspell_aug_p=0.01, typo_aug_p=0.005)
470
  #remove weird chars
471
  no_weird_chars = remove_weird_characters(corrupted_response)
472
- #remove trailing punctuation % of the time
473
- if random.random() < REMOVE_PUNC_RATE:
474
- no_weird_chars = re.sub(r'[^\w\s]+$', '', no_weird_chars)
475
-
476
- final_response = no_weird_chars
477
 
478
  #check that there are no reccent duplicate messages
479
- if len(final_response) > DUP_LEN and duplicate_check(final_response, context):
480
  print("****DUPLICATE MESSAGE DETECTED")
481
  print("Treating this bot's response as a pass.")
482
  # Do not store/send messages if the chat has ended
@@ -486,7 +392,7 @@ def ask_bot(room_id, bot, bot_display_name, initial_prompt, instruct_prompt , wa
486
  # Store the error response in the database
487
  bot_message = {
488
  "sender": bot_display_name,
489
- "message": f"DUPLICATE message detected - treated as a (pass) : {final_response}",
490
  "timestamp": datetime.utcnow()
491
  }
492
  rooms_collection.update_one(
@@ -501,7 +407,7 @@ def ask_bot(room_id, bot, bot_display_name, initial_prompt, instruct_prompt , wa
501
  print(corrupted_response)
502
 
503
  # Add latency/wait time for bot responses
504
- delay = get_response_delay(final_response);
505
  print(delay)
506
  time.sleep(delay)
507
 
@@ -513,7 +419,7 @@ def ask_bot(room_id, bot, bot_display_name, initial_prompt, instruct_prompt , wa
513
  # Store the response in the database
514
  bot_message = {
515
  "sender": bot_display_name,
516
- "message": final_response, #save fruits in db so page reload shows proper names
517
  "timestamp": datetime.utcnow()
518
  }
519
  rooms_collection.update_one(
@@ -522,7 +428,7 @@ def ask_bot(room_id, bot, bot_display_name, initial_prompt, instruct_prompt , wa
522
  )
523
 
524
  # Send the bot's response to the client
525
- socketio.emit("message", {"sender": bot_display_name, "message": final_response}, to=room_id)
526
  return False
527
 
528
  def ask_bot_round(room_id):
@@ -554,7 +460,6 @@ def backup_mongo(time):
554
  token = os.getenv("HF_TOKEN")
555
  handler = datasetHandler(token , 'huggingFaceData')
556
  handler.dump_db()
557
- handler.clean()
558
  socketio.sleep(time)
559
 
560
  # Build the routes
@@ -582,8 +487,7 @@ def home():
582
  session['user_id'] = user_id
583
  return redirect(url_for('topics'))
584
  else:
585
- link = f"https://umw.qualtrics.com/jfe/form/SV_08v26NssCOwZTP8?PROLIFIC_PID={prolific_pid}"
586
- return render_template('home.html',prolific_pid=prolific_pid, feedback_form_url=link)
587
 
588
  @app.route('/topics', methods=["GET", "POST"])
589
  def topics():
@@ -655,14 +559,6 @@ def choose():
655
  "ended": False,
656
  "ended_at": None
657
  })
658
- # Create the new feedback in the database
659
- feedback_collection.insert_one({
660
- "_id": room_id,
661
- # creation date/time
662
- "created_at": datetime.utcnow(),
663
- # user identity
664
- "user_id": user_id,
665
- })
666
 
667
  session['room'] = room_id
668
  session['display_name'] = user_name
@@ -685,18 +581,8 @@ def room():
685
  m for m in room_doc["messages"]
686
  if len(re.findall(r"pass",m.get("message", "").strip())) == 0
687
  ]
688
- if session.get('user_id'):
689
- link = f"https://umw.qualtrics.com/jfe/form/SV_08v26NssCOwZTP8?PROLIFIC_PID={session.get('user_id')}"
690
- return render_template("room.html", room=room_id, topic_info=topic_info, user=display_name,
691
- messages=nonpass_messages, FroBot_name=room_doc["FroBot_name"],
692
- HotBot_name=room_doc["HotBot_name"], CoolBot_name=room_doc["CoolBot_name"],
693
- ended=room_doc["ended"], feedback_form_url=link)
694
- else:
695
- return render_template("room.html", room=room_id, topic_info=topic_info, user=display_name,
696
- messages=nonpass_messages, FroBot_name=room_doc["FroBot_name"],
697
- HotBot_name=room_doc["HotBot_name"], CoolBot_name=room_doc["CoolBot_name"],
698
- ended=room_doc["ended"])
699
-
700
  @app.route("/abort", methods=["POST"])
701
  def abort_room():
702
  room_id = session.get("room")
@@ -729,9 +615,7 @@ def post_survey():
729
  FName = info['FroBot_name']
730
  HName = info['HotBot_name']
731
 
732
- #SURVEY_2_LINK = f"https://umw.qualtrics.com/jfe/form/SV_eIIbPlJ2D9k4zKC?PROLIFIC_PID={user_id}&CName={CName}&FName={FName}&HName={HName}"
733
- SURVEY_2_LINK = f"https://umw.qualtrics.com/jfe/form/SV_cTH90tot1jAbBYO?PROLIFIC_PID={user_id}&CName={CName}&DName={FName}&HName={HName}"
734
- # (!4/30) CCHU experiment survey 2 link
735
 
736
  return redirect(SURVEY_2_LINK)
737
 
@@ -833,37 +717,6 @@ def handle_message(payload):
833
  # Ask each bot for a response
834
  socketio.start_background_task(ask_bot_round, room)
835
 
836
-
837
- @socketio.on('feedback_given')
838
- def handle_message(payload):
839
- room = session.get('room')
840
- name = session.get('display_name')
841
- if not room or not name:
842
- return
843
-
844
- text = payload.get("feedback", "").strip()
845
- if not text:
846
- return # ignore empty text
847
-
848
- # Database-only message (with datetime)
849
- db_feedback = {
850
- "message": text,
851
- "timestamp": datetime.utcnow()
852
- }
853
-
854
- print(db_feedback)
855
- # Store the full version in the database
856
- result = feedback_collection.update_one(
857
- {"_id": room},
858
- {"$push": {"feedback_responses": db_feedback}}
859
- )
860
-
861
- if result:
862
- print(result)
863
- if result.modified_count > 0:
864
- return {'status':'True'}
865
- return {'ststus':'False'}
866
-
867
  @socketio.on('disconnect')
868
  def handle_disconnect():
869
  room = session.get("room")
 
 
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
 
28
  from huggingface_hub import upload_folder
29
  from huggingface_hub import HfApi
30
  from huggingface_hub import login
31
+ import os
 
32
 
33
  class datasetHandler():
34
 
35
+ def __init__(self,hf_token,mongoDB_name):
36
  login(hf_token)
37
  self.api = HfApi(token = hf_token)
38
  self.DB_name = mongoDB_name
 
 
39
 
40
  def make_dump(self):
41
  try:
 
47
 
48
  def upload_dump(self):
49
  try:
50
+ upload_folder(folder_path="/tmp/mongoDBContents",path_in_repo="mongoDump", repo_id="ProjectFrozone/MongoDBDumps", repo_type="dataset")
51
  return 0
52
  except Exception as e:
53
  print(e)
54
  return 1
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  def dump_db(self):
57
  self.make_dump()
58
  self.upload_dump()
59
+ # End backup code
 
 
 
 
60
 
61
  #controls
62
  CHAT_CONTEXT = 20 #how many messages from chat history to append to inference prompt
63
  #minimum number of chars where we start checking for duplicate messages
64
  DUP_LEN = 25 #since short messages may reasonably be the same
 
65
 
66
  # Directory alignment
67
  BASE_DIR = Path(__file__).resolve().parent
 
107
  #change to endpoints
108
  hotbot = "projects/700531062565/locations/us-central1/endpoints/6225523347153747968"
109
  coolbot = "projects/700531062565/locations/us-central1/endpoints/1700531621553242112"
110
+ frobot = "projects/700531062565/locations/us-central1/endpoints/2951406418055397376"
 
 
111
 
112
  # MongoDB setup
113
  client = MongoClient("mongodb://127.0.0.1:27017/")
114
  db = client["huggingFaceData"]
115
  rooms_collection = db.rooms
 
116
 
117
  # List of fruits to choose display names from
118
  FRUIT_NAMES = ["blueberry", "strawberry", "orange", "cherry"]
 
147
  }
148
  ]
149
 
 
 
 
150
  # FroBot Main Prompt
151
  with open(PROJECT_ROOT / "data" / "prompts" / "frobot_prompt_main.txt") as f:
152
  FROBOT_PROMPT = f.read()
153
  # Instructions
154
  with open(PROJECT_ROOT / "data" / "inference_instructions" / "frobot_instructions_main.txt") as f:
155
  FROBOT_INSTRUCT = f.read()
 
156
 
157
  # HotBot Prompt
158
  with open(PROJECT_ROOT / "data" / "prompts" / "hotbot_prompt_main.txt") as h:
 
168
  with open(PROJECT_ROOT / "data" / "inference_instructions" / "coolbot_instructions_main.txt") as c:
169
  COOLBOT_INSTRUCT = c.read()
170
 
 
 
 
 
171
  # Randomly select fruits to use for display names
172
  def choose_names(n):
173
  # Return n unique random fruit names
 
271
  modified_text.append(char)
272
  return ''.join(modified_text)
273
 
 
 
 
 
 
 
274
  def get_response_delay(response):
275
  baseDelay = 5 # standard delay for thinking
276
  randFactor = np.random.uniform(0,30, len(response) // 4)
 
284
 
285
  # Ask a bot for its response, store in DB, and send to client
286
  # Returns true if the bot passed
287
+ def ask_bot(room_id, bot, bot_display_name, initial_prompt, instruct_prompt):
288
  # Prevents crashing if bot model did not load
289
  if bot is None:
290
  return False
 
317
  ),
318
  )
319
  parsed_response = response.candidates[0].content.parts[0].text.strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  except Exception as e:
321
  print("Error in bot response: ", e)
322
  print("Treating this bot's response as a pass.")
 
347
  parsed_response = re.sub(r"\b"
348
  + aliases[bot_display_name]
349
  + r"\b:\s?", '', parsed_response)
 
 
 
 
 
 
 
 
 
 
 
 
 
350
 
351
  # Check for if the bot passed (i.e. response = "(pass)")
352
  if ("(pass)" in parsed_response) or (parsed_response == ""):
 
368
  print("PASSED")
369
  return True # a pass is still recorded in the database, but not sent to the client
370
 
 
 
371
  #remove encapsulating quotes
372
+ no_quotes = remove_quotes(parsed_response)
373
  #humanize the response (remove obvious AI formatting styles)
374
  humanized_response = humanize(no_quotes)
375
  #replace most semicolons
376
  less_semicolons_response = replace_semicolons(humanized_response)
377
  #corrupt the response (add some typos and misspellings)
378
+ corrupted_response = corrupt(less_semicolons_response)
379
  #remove weird chars
380
  no_weird_chars = remove_weird_characters(corrupted_response)
381
+ #sub letters for names, so if the bot addressed A -> Apple
382
+ named_response = let_to_name(room_id, no_weird_chars)
 
 
 
383
 
384
  #check that there are no reccent duplicate messages
385
+ if len(named_response) > DUP_LEN and duplicate_check(named_response, context):
386
  print("****DUPLICATE MESSAGE DETECTED")
387
  print("Treating this bot's response as a pass.")
388
  # Do not store/send messages if the chat has ended
 
392
  # Store the error response in the database
393
  bot_message = {
394
  "sender": bot_display_name,
395
+ "message": f"DUPLICATE message detected - treated as a (pass) : {named_response}",
396
  "timestamp": datetime.utcnow()
397
  }
398
  rooms_collection.update_one(
 
407
  print(corrupted_response)
408
 
409
  # Add latency/wait time for bot responses
410
+ delay = get_response_delay(named_response);
411
  print(delay)
412
  time.sleep(delay)
413
 
 
419
  # Store the response in the database
420
  bot_message = {
421
  "sender": bot_display_name,
422
+ "message": named_response, #save fruits in db so page reload shows proper names
423
  "timestamp": datetime.utcnow()
424
  }
425
  rooms_collection.update_one(
 
428
  )
429
 
430
  # Send the bot's response to the client
431
+ socketio.emit("message", {"sender": bot_display_name, "message": named_response}, to=room_id)
432
  return False
433
 
434
  def ask_bot_round(room_id):
 
460
  token = os.getenv("HF_TOKEN")
461
  handler = datasetHandler(token , 'huggingFaceData')
462
  handler.dump_db()
 
463
  socketio.sleep(time)
464
 
465
  # Build the routes
 
487
  session['user_id'] = user_id
488
  return redirect(url_for('topics'))
489
  else:
490
+ return render_template('home.html',prolific_pid=prolific_pid)
 
491
 
492
  @app.route('/topics', methods=["GET", "POST"])
493
  def topics():
 
559
  "ended": False,
560
  "ended_at": None
561
  })
 
 
 
 
 
 
 
 
562
 
563
  session['room'] = room_id
564
  session['display_name'] = user_name
 
581
  m for m in room_doc["messages"]
582
  if len(re.findall(r"pass",m.get("message", "").strip())) == 0
583
  ]
584
+ 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"])
585
+
 
 
 
 
 
 
 
 
 
 
586
  @app.route("/abort", methods=["POST"])
587
  def abort_room():
588
  room_id = session.get("room")
 
615
  FName = info['FroBot_name']
616
  HName = info['HotBot_name']
617
 
618
+ SURVEY_2_LINK = f"https://umw.qualtrics.com/jfe/form/SV_eIIbPlJ2D9k4zKC?PROLIFIC_PID={user_id}&CName={CName}&FName={FName}&HName={HName}"
 
 
619
 
620
  return redirect(SURVEY_2_LINK)
621
 
 
717
  # Ask each bot for a response
718
  socketio.start_background_task(ask_bot_round, room)
719
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
720
  @socketio.on('disconnect')
721
  def handle_disconnect():
722
  room = session.get("room")
chat_application/static/styles/styles.css CHANGED
@@ -462,12 +462,12 @@ hr {
462
  transition: 0.15s ease-in-out;
463
  }
464
 
465
- #abortYesBtn, #abortYesBtn-pre, #cancelFeedbackStatusBtn2 {
466
  background: #d9534f;
467
  color: white;
468
  }
469
 
470
- #abortYesBtn:hover, #abortYesBtn-pre:hover, #cancelFeedbackStatusBtn2:hover {
471
  background: #c9302c;
472
  }
473
 
@@ -498,48 +498,15 @@ hr {
498
  background: #ccc;
499
  }
500
 
501
- #welcomeOkBtn, #timerOkBtn, #submitFeedbackBtn, #cancelFeedbackStatusBtn {
502
  background: green;
503
  color: white;
504
  }
505
 
506
- #welcomeOkBtn:hover, #timerOkBtn:hover, #submitFeedbackBtn:hover, #cancelFeedbackStatusBtn:hover {
507
  background: #016601;
508
  }
509
 
510
- #feedback-btn {
511
- color: white;
512
- font-weight: 800;
513
- background-color: green;
514
- text-decoration: none;
515
- padding: 6px;
516
- border: 2px solid green;
517
- display: inline-block;
518
- margin-top: 5px;
519
- border-radius: 10px;
520
- transition: all 0.1s ease-in;
521
- }
522
-
523
- #feedback-btn:hover {
524
- color: green;
525
- background-color: white;
526
- }
527
-
528
- .feedback-col {
529
- display: flex;
530
- flex-direction: column;
531
- gap: 6px;
532
-
533
- textarea {
534
- width: 100%;
535
- min-height: 120px;
536
- padding: 10px;
537
- border: 1px solid #ccc;
538
- border-radius: 6px;
539
- resize: vertical;
540
- }
541
- }
542
-
543
  #idYesBtn {
544
  background: green;
545
  color: white;
@@ -567,12 +534,12 @@ hr {
567
  background: #016991;
568
  }
569
 
570
- #endNoBtn, #cancelFeedbackBtn {
571
  background: #e5e5e5;
572
  color: #333;
573
  }
574
 
575
- #endNoBtn:hover, #cancelFeedbackBtn:hover {
576
  background: #ccc;
577
  }
578
 
 
462
  transition: 0.15s ease-in-out;
463
  }
464
 
465
+ #abortYesBtn, #abortYesBtn-pre {
466
  background: #d9534f;
467
  color: white;
468
  }
469
 
470
+ #abortYesBtn:hover, #abortYesBtn-pre:hover {
471
  background: #c9302c;
472
  }
473
 
 
498
  background: #ccc;
499
  }
500
 
501
+ #welcomeOkBtn, #timerOkBtn {
502
  background: green;
503
  color: white;
504
  }
505
 
506
+ #welcomeOkBtn:hover, #timerOkBtn:hover {
507
  background: #016601;
508
  }
509
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
510
  #idYesBtn {
511
  background: green;
512
  color: white;
 
534
  background: #016991;
535
  }
536
 
537
+ #endNoBtn {
538
  background: #e5e5e5;
539
  color: #333;
540
  }
541
 
542
+ #endNoBtn:hover {
543
  background: #ccc;
544
  }
545
 
chat_application/templates/home.html CHANGED
@@ -11,6 +11,9 @@
11
  <button type="button" id="continue">Continue</button>
12
  </div>
13
  <hr />
 
 
 
14
  </form>
15
  </div>
16
  <div id="confirmID-modal" class="modal">
 
11
  <button type="button" id="continue">Continue</button>
12
  </div>
13
  <hr />
14
+ <div class="feedback-notice">
15
+ <p>We encourage you to message us directly through Prolific about any concerns. Additionally, you may provide feedback regarding the experiment <a class="feedback-link" href="{{ feedback_form_url | default('https://umw.qualtrics.com/jfe/form/SV_08v26NssCOwZTP8') }}">in this form</a>.</p>
16
+ </div>
17
  </form>
18
  </div>
19
  <div id="confirmID-modal" class="modal">
chat_application/templates/room.html CHANGED
@@ -30,7 +30,6 @@
30
  <h2 id="room-code-display">Topic: <span class="topic-title">{{ topic_info.title }}</span></h2>
31
  </div>
32
  <div class="topic-header-buttons">
33
- <button id="feedback-btn">Share Feedback and Report Bugs</button>
34
  <button id="end-exp-btn">End Chat Session</button>
35
  <button id="abort-exp-btn">Abort Experiment</button>
36
  </div>
@@ -49,45 +48,22 @@
49
  <div id="abort-modal" class="modal">
50
  <div class="modal-content">
51
  <h3>Are you sure you want to leave this experiment?</h3>
 
52
  <p><strong>If you finished your 20 minutes in the chatroom, do NOT exit via this button. Use the "End Chat Session" button instead.</strong></p>
53
- <p>By clicking yes, you will exit the experiment <strong>without</strong> completing the final survey.</p>
54
  <div class="modal-buttons">
55
- <button class="modal-btn" id="abortYesBtn">End early with code "C9V2XFDU"</button>
56
- <button class="modal-btn" id="abortNoBtn">Cancel</button>
57
  </div>
58
  </div>
59
  </div>
60
-
61
- <div id="feedback-modal" class="modal">
62
- <div class="modal-content">
63
- <h3>Feedback</h3>
64
- <form id="feedback-form">
65
- <div class="feedback-col">
66
- <label for="enter-feedback">Tell us if you noticed something confusing, unexpected, or not working.</label>
67
- <textarea id="enter-feedback" name="feedback"></textarea>
68
- </div>
69
- <br>
70
- <div class="modal-buttons">
71
- <button class="modal-btn" id="submitFeedbackBtn" type="submit">Submit</button>
72
- <button type="button" class="modal-btn" id="cancelFeedbackBtn">Cancel</button>
73
- </div>
74
- </form>
75
- </div>
76
- </div>
77
-
78
- <div id="feedback-status-confirm" class="modal">
79
  <div class="modal-content">
80
- <p>Feedback submitted.</p>
81
- <div class="modal-buttons">
82
- <button class="modal-btn" id="cancelFeedbackStatusBtn">Ok</button>
83
- </div>
84
- </div>
85
- </div>
86
- <div id="feedback-status-fail" class="modal">
87
- <div class="modal-content">
88
- <p>Feedback failed to submit.</p>
89
  <div class="modal-buttons">
90
- <button class="modal-btn" id="cancelFeedbackStatusBtn2">Ok</button>
 
91
  </div>
92
  </div>
93
  </div>
@@ -117,7 +93,6 @@
117
  document.getElementById("send-btn").disabled = true;
118
  document.getElementById("end-exp-btn").disabled = true;
119
  document.getElementById("abort-exp-btn").disabled = true;
120
- document.getElementById("feedback-btn").disabled = true; //since without socket io, it won't submit
121
  if (socketio) {
122
  socketio.close();
123
  }
@@ -142,7 +117,6 @@
142
  };
143
  // Creates the post-survey link (based on the bot names)
144
  const endpoint = "{{ url_for('post_survey') }}";
145
- const endpointQuitEarly = "https://app.prolific.com/submissions/complete?cc=C9V2XFDU";
146
  socketio.on("message", function (message) { createChatItem(message.message, message.sender) });
147
  function createChatItem(message, sender) {
148
  //autoscroll capabilities
@@ -212,27 +186,30 @@
212
  document.getElementById("send-btn").disabled = true;
213
  document.getElementById("end-exp-btn").disabled = true;
214
  document.getElementById("abort-exp-btn").disabled = true;
215
- document.getElementById("feedback-btn").disabled = true; //since without socket io, it won't submit
216
  if (socketio) {
217
  socketio.close();
218
  }
219
  };
220
  // Handler for the Abort Experiment confirmation pop-up
221
  let modal = document.getElementById("abort-modal");
 
222
  document.getElementById("abort-exp-btn").onclick = function () {
223
  modal.style.display = "block";
224
  };
225
- document.getElementById("abortNoBtn").onclick = function () {
 
 
 
 
226
  modal.style.display = "none";
227
  };
228
-
229
  document.getElementById("abortYesBtn").onclick = function (e) {
230
  //block browser confirmation popup
231
  e.stopPropagation();
232
  // Mark that user aborted and redirect to ending survey
233
  fetch("/abort", { method: "POST" })
234
  .then(() => {
235
- window.open(endpointQuitEarly, "_blank");
236
  });
237
  modal.style.display = "none";
238
  textarea.disabled = true;
@@ -240,53 +217,14 @@
240
  document.getElementById("send-btn").disabled = true;
241
  document.getElementById("end-exp-btn").disabled = true;
242
  document.getElementById("abort-exp-btn").disabled = true;
243
- document.getElementById("feedback-btn").disabled = true; //since without socket io, it won't submit
244
  if (socketio) {
245
  socketio.close();
246
  }
 
247
  };
248
-
249
- //handler for feedback modal popup
250
- let feedbackModal = document.getElementById("feedback-modal");
251
- let form = document.getElementById("feedback-form");
252
- let feedbackConfirm = document.getElementById("feedback-status-confirm");
253
- let feedbackFail = document.getElementById("feedback-status-fail");
254
- document.getElementById("feedback-btn").onclick = function () {
255
- feedbackModal.style.display = "block";
256
- };
257
- document.getElementById("cancelFeedbackBtn").onclick = function () {
258
- feedbackModal.style.display = "none";
259
- };
260
-
261
- document.getElementById("cancelFeedbackStatusBtn").onclick = function () {
262
- feedbackConfirm.style.display = "none";
263
- feedbackFail.style.display = "none";
264
- };
265
-
266
- document.getElementById("cancelFeedbackStatusBtn2").onclick = function () {
267
- feedbackConfirm.style.display = "none";
268
- feedbackFail.style.display = "none";
269
  };
270
-
271
- //override form function to instead emit save feedback event
272
- form.addEventListener("submit", function (e) {
273
- e.preventDefault();
274
-
275
- const data = new FormData(form);
276
- const feedback = data.get("feedback")?.trim();
277
-
278
- if (!feedback) return;
279
-
280
- socketio.emit("feedback_given", { feedback }, (response) => {
281
- if (response?.status === "True" || response?.status === true) {
282
- feedbackConfirm.style.display = "block";
283
- form.reset();
284
- document.getElementById("feedback-modal").style.display = "none";
285
- } else {
286
- feedbackFail.style.display = "block";
287
- }
288
- });
289
- });
290
 
291
  // add auto scroll
292
  function isNearBottom(container, threshold = 120) {
 
30
  <h2 id="room-code-display">Topic: <span class="topic-title">{{ topic_info.title }}</span></h2>
31
  </div>
32
  <div class="topic-header-buttons">
 
33
  <button id="end-exp-btn">End Chat Session</button>
34
  <button id="abort-exp-btn">Abort Experiment</button>
35
  </div>
 
48
  <div id="abort-modal" class="modal">
49
  <div class="modal-content">
50
  <h3>Are you sure you want to leave this experiment?</h3>
51
+ <p>This action is permanent. You will be redirected to the post-survey and will not be able to return to the chat room.</p>
52
  <p><strong>If you finished your 20 minutes in the chatroom, do NOT exit via this button. Use the "End Chat Session" button instead.</strong></p>
 
53
  <div class="modal-buttons">
54
+ <button class="modal-btn" id="abortYesBtn-pre">Yes</button>
55
+ <button class="modal-btn" id="abortNoBtn-pre">Cancel</button>
56
  </div>
57
  </div>
58
  </div>
59
+ <div id="abort-modal-confirm" class="modal">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  <div class="modal-content">
61
+ <h3>Confirmation</h3>
62
+ <p>By clicking yes, you will exit the experiment <strong>without</strong> completing the final survey.</p>
63
+ <p>We encourage you to message us about any concerns or to <a href="{{ feedback_form_url | default('https://umw.qualtrics.com/jfe/form/SV_08v26NssCOwZTP8') }}" target="_blank">provide feedback here</a>.</p>
 
 
 
 
 
 
64
  <div class="modal-buttons">
65
+ <button class="modal-btn" id="abortYesBtn">Yes</button>
66
+ <button class="modal-btn" id="abortNoBtn">Cancel</button>
67
  </div>
68
  </div>
69
  </div>
 
93
  document.getElementById("send-btn").disabled = true;
94
  document.getElementById("end-exp-btn").disabled = true;
95
  document.getElementById("abort-exp-btn").disabled = true;
 
96
  if (socketio) {
97
  socketio.close();
98
  }
 
117
  };
118
  // Creates the post-survey link (based on the bot names)
119
  const endpoint = "{{ url_for('post_survey') }}";
 
120
  socketio.on("message", function (message) { createChatItem(message.message, message.sender) });
121
  function createChatItem(message, sender) {
122
  //autoscroll capabilities
 
186
  document.getElementById("send-btn").disabled = true;
187
  document.getElementById("end-exp-btn").disabled = true;
188
  document.getElementById("abort-exp-btn").disabled = true;
 
189
  if (socketio) {
190
  socketio.close();
191
  }
192
  };
193
  // Handler for the Abort Experiment confirmation pop-up
194
  let modal = document.getElementById("abort-modal");
195
+ let abortModalConfirm = document.getElementById("abort-modal-confirm");
196
  document.getElementById("abort-exp-btn").onclick = function () {
197
  modal.style.display = "block";
198
  };
199
+ document.getElementById("abortYesBtn-pre").onclick = function () {
200
+ abortModalConfirm.style.display = "block";
201
+ modal.style.display = "none";
202
+ };
203
+ document.getElementById("abortNoBtn-pre").onclick = function () {
204
  modal.style.display = "none";
205
  };
 
206
  document.getElementById("abortYesBtn").onclick = function (e) {
207
  //block browser confirmation popup
208
  e.stopPropagation();
209
  // Mark that user aborted and redirect to ending survey
210
  fetch("/abort", { method: "POST" })
211
  .then(() => {
212
+ window.open(endpoint, "_blank");
213
  });
214
  modal.style.display = "none";
215
  textarea.disabled = true;
 
217
  document.getElementById("send-btn").disabled = true;
218
  document.getElementById("end-exp-btn").disabled = true;
219
  document.getElementById("abort-exp-btn").disabled = true;
 
220
  if (socketio) {
221
  socketio.close();
222
  }
223
+ abortModalConfirm.style.display = "none";
224
  };
225
+ document.getElementById("abortNoBtn").onclick = function () {
226
+ abortModalConfirm.style.display = "none";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
 
229
  // add auto scroll
230
  function isNearBottom(container, threshold = 120) {