emvecchi commited on
Commit
2d3e83d
·
verified ·
1 Parent(s): e1b945b

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +555 -0
app.py ADDED
@@ -0,0 +1,555 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from dataclasses import dataclass, field
4
+ from typing import List, Optional, Dict
5
+ from PIL import Image
6
+
7
+ import numpy as np
8
+ import pandas as pd
9
+ import streamlit as st
10
+ from fsspec.implementations.local import LocalFileSystem
11
+ from huggingface_hub import HfFileSystem
12
+
13
+ import streamlit.components.v1 as components
14
+
15
+ @dataclass
16
+ class Field:
17
+ type: str
18
+ title: str
19
+ name: str = None
20
+ mandatory: bool = True
21
+ # if value of field is in the list of those values, makes following siblings mandatory
22
+ following_mandatory_values: list = False
23
+ skip_mandatory: bool = False
24
+ help: Optional[str] = None
25
+ children: Optional[List['Field']] = None
26
+ other_params: Optional[Dict[str, object]] = field(default_factory=lambda: {})
27
+
28
+ # Function to get user ID from URL
29
+ def get_param_from_url(param):
30
+ user_id = st.query_params.get(param, "")
31
+ return user_id
32
+
33
+ ########################################################################################
34
+ # CHANGE THE FOLLOWING VARIABLES ACCORDING TO YOUR NEEDS
35
+
36
+ # 'local' or 'hf'. hf is for Hugging Face file system but has limits on the number of access per hour
37
+ filesystem = 'hf'
38
+ # path to repo or local file system TODO rename
39
+ input_repo_path = 'datasets/emvecchi/therapy_annotation'
40
+ output_repo_path = 'datasets/emvecchi/therapy_annotation/pilot'
41
+
42
+
43
+ to_annotate_file_name = 'to_annotate.csv' # CSV file to annotate
44
+ COLS_TO_SAVE = ['dialogue_id','dialogue_name','generatedPatient']
45
+
46
+ agreement_labels = ['strongly disagree', 'disagree', 'neither agree no disagree', 'agree', 'strongly agree']
47
+ quality_labels = ['very poor', 'poor', 'acceptable', 'good', 'very good']
48
+ priority_labels = ['not a priority', 'low priority', 'neutral', 'moderate priority', 'high priority']
49
+ yes_no_labels = ['no','yes']
50
+ yes_no_other_labels = ['no','yes','other']
51
+ default_labels = agreement_labels
52
+
53
+ function_choices = ['Broadening Discussion',
54
+ 'Improving Comment Quality',
55
+ 'Content Correction',
56
+ 'Keeping Discussion on Topic',
57
+ 'Organizing Discussion',
58
+ 'Policing',
59
+ 'Resolving Site Use Issues',
60
+ 'Social Functions',
61
+ 'None',
62
+ 'Other']
63
+
64
+ default_choices = function_choices
65
+
66
+ guidelines_text = 'Please read <a href="https://tinyurl.com/tjy8swn2">the guidelines</a>'
67
+ study_code = os.environ.get("STUDY_CODE")
68
+ failed_sanity_check_code = os.environ.get("FAILED_CODE")
69
+ redirect_url = f'https://app.prolific.com/submissions/complete?cfc={study_code}'
70
+ failed_redirect_url = f'https://app.prolific.com/submissions/complete?cfc={failed_sanity_check_code}'
71
+
72
+ annotation_guidelines_fields: List[Field] = [
73
+ Field(name="annotation_guidelines", type="radio", title="Did you read the guidelines?", mandatory=True,
74
+ other_params={'labels': ['Yes, in detail, and I understand the study',
75
+ 'Yes, in detail, but still confused',
76
+ 'Yes, I skimmed it',
77
+ 'I will read it later',
78
+ 'No, not interested in reading them',
79
+ 'I can not open the link',
80
+ ],
81
+ 'accepted_values': [0]}),
82
+ ]
83
+
84
+ intro_fields: List[Field] = [
85
+ Field(type="container", title="**Participant Profession**", children=[
86
+ Field(name="intro_profession", type="radio", title="**What is your (most recent) job or profession?**",
87
+ other_params={'labels': ['Software Developer / Engineer',
88
+ 'Data Scientist / Analyst',
89
+ 'Teacher / Educator',
90
+ 'Student',
91
+ 'Research Scientist / Academic Researcher',
92
+ 'Marketing Sector',
93
+ 'Journalist / Writer',
94
+ 'Financial Analyst',
95
+ 'Business Manager / Consultant',
96
+ 'Healthcare Professional (e.g., Nurse, Doctor)',
97
+ 'Customer Service Representative',
98
+ 'Graphic Designer',
99
+ 'Sales Representative',
100
+ 'Entrepreneur / Business Owner',
101
+ 'Other'],
102
+ }, mandatory=True, following_mandatory_values=['Other']),
103
+ Field(name="intro_profession_other", type="text", title="*If Other, please specify:*", mandatory=False),
104
+ ]),
105
+ Field(type="container", title="**Experience with Moderation**", children=[
106
+ Field(name="intro_mod_experience", type="y_n_radio",
107
+ title="**Have you ever worked as a moderator in an online community?**", mandatory=True, following_mandatory_values=[1]),
108
+ Field(name="intro_mod_experience_info", type="text",
109
+ title="*If yes, where did you moderate, and for how long?*", mandatory=False),
110
+ Field(name="intro_interaction_experience", type="likert_radio",
111
+ title="**How would you quantify your experience with online moderation or interacting with moderators?** ", other_params={'labels': ['No Experience', 'Little', 'Average', 'Decent', 'Extensive Experience']},
112
+ mandatory=True),
113
+ Field(name="intro_interaction_frequency", type="likert_radio",
114
+ title="**How often do you encounter or engage with moderators in the online spaces you frequent?**", other_params={'labels': ['Never', 'Rarely', 'Sometimes', 'Often', 'Always']},
115
+ mandatory=True),
116
+ Field(name="intro_mod_confidence", type="likert_radio",
117
+ title="**Before starting this study, based on your background and the tutorial, how confident do you feel about your ability to take on the role of a moderator in an online discussion forum?**", other_params={'labels': ['Not Confident', 'Slightly Confident', 'Moderately Confident', 'Very Confident', 'Extremely Confident']},
118
+ mandatory=True),
119
+ Field(name="intro_active_passive", type="y_n_radio",
120
+ title="**Would you describe your role in online discussions as more *active* (writing, replying to comments) or more *passive* (reading, liking)?**", mandatory=True, other_params={'labels': ['active', 'passive']}),
121
+ ]),
122
+ Field(type="container", title="**If you were a moderator...**", children=[
123
+ Field(name="intro_moderation_goals", type="textarea", title="**As a moderator, what would be your goals/objectives for a comment section?**"),
124
+ Field(name="intro_experience", type="textarea", title="**What would you feel contributes to a good experience for the users/discussion?**"),
125
+ Field(name="intro_valuable_comment", type="textarea", title="**What makes a comment or contribution valuable?**"),
126
+ Field(name="intro_bad_comment", type="textarea", title="**What makes a comment or contribution of poor quality, unconstructive or detrimental to the discussion?**"),
127
+ Field(name="intro_mod_ai_hum", type='textarea', title="**What do you think is the most challenging aspect of moderation that only humans can do well or better than AI?**", mandatory=True),
128
+ ]),
129
+ ]
130
+
131
+ concluding_fields: List[Field] = [
132
+ Field(type="container", title="**Concluding Questions**", children=[
133
+ Field(name="conc_general_ease", type="likert_radio", title="**Determining when an instance would indeed benefit from moderator intervention was straightforward (easy to annotate).**"),
134
+ Field(name="conc_guess_mod_prediction", type="likert_radio", title="**I had a pretty clear idea of which instances were predicted to need moderation by your tool, and which weren't.**"),
135
+ Field(name="conc_overall_usefullness", type="likert_radio", title="**If I were a moderator, having a tool that accurately predicts and flags comments needing moderation would significantly aid in my tasks.**"),
136
+ Field(name="conc_decision_making", type="textaread", title="**If I were a moderator, would a tool with accurate predictions assist you in making *more informed* moderation decisions? How so?**"),
137
+ Field(name="conc_bottleneck", type="textarea", title="**What do you feel is the largest bottleneck (or obstacle) moderators face in online discussion moderation?**"),
138
+ Field(name="conc_needs", type="textarea", title="**Beyond the goals of this research and annotation task, what assistance do you feel computational tools (like AI) could provide to your task?**"),
139
+ ]),
140
+ ]
141
+
142
+ fields: List[Field] = [
143
+ Field(name="patient", type="input_col", title="**Patient::**"),
144
+ Field(type="expander", title="**Preceeding Comment:** *(expand)*", children=[
145
+ Field(name="dialogue_name", type="input_col", title=""),
146
+ ]),
147
+
148
+ Field(type="container", title="**Session-/Patient-Specific Properties**", children=[
149
+ Field(name="to_moderate", type="y_n_radio",
150
+ title="Explicit mention of clinical diagnosis?", mandatory=True),
151
+ Field(name="priority_level", type="likert_radio",
152
+ title="Emotional State", other_params={'labels': priority_labels}, mandatory=True),
153
+ ]),
154
+
155
+ Field(type="container", title="**Rupture-Specific Properties**", children=[
156
+ Field(name="mod_function", type="multiselect",
157
+ title="What type of rupture markers are found? *(Multiple selection possible)*",
158
+ mandatory=False, following_mandatory_values=['Other (please specify)']),
159
+ Field(name="mod_function_other", type="text", title="*If Other, please specify:*", mandatory=False),
160
+ Field(name="rupture_line", type="text", title="What lines demonstrate the rupture markers you notice?", mandatory=False),
161
+ ]),
162
+
163
+ Field(type="container", title="**True-To-Patient-Prompt Properties**", children=[
164
+ Field(name="helpful", type="y_n_radio",
165
+ title="Did the patient remain true to the provided patient prompt?", mandatory=True,
166
+ following_mandatory_values=[1]),
167
+ ]),
168
+
169
+ Field(type="container", title="**Other**", children=[
170
+ Field(name="other_comments", type="text", title="Please provide any additional details or information: *(optional)*", mandatory=False),
171
+ ]),
172
+ ]
173
+ url_conditional_fields = [
174
+ Field(name="skip", type="skip_checkbox",
175
+ title="*I am uncomfortable annotating this text and voluntarily skip this instance*", mandatory=False)
176
+ ]
177
+ INPUT_FIELD_DEFAULT_VALUES = {'slider': 0,
178
+ 'text': '',
179
+ 'textarea': '',
180
+ 'checkbox': False,
181
+ 'radio': None,
182
+ 'select_slider': 0,
183
+ 'multiselect': [],
184
+ 'likert_radio': None,
185
+ 'y_n_radio': None}
186
+ SHOW_HELP_ICON = False
187
+ SHOW_VALIDATION_ERROR_MESSAGE = True
188
+
189
+ ########################################################################################
190
+ if filesystem == 'hf':
191
+ HF_TOKEN = os.environ.get("HF_TOKEN_WRITE")
192
+ print("is none?", HF_TOKEN is None)
193
+ hf_fs = HfFileSystem(token=HF_TOKEN)
194
+ else:
195
+ hf_fs = LocalFileSystem()
196
+
197
+ def get_start_index():
198
+ if hf_fs.exists(output_repo_path + '/' + get_base_path()):
199
+ files = hf_fs.ls(output_repo_path + '/' + get_base_path())
200
+ return len(files) -2
201
+ else:
202
+ return -1
203
+
204
+ def read_data():
205
+ assert st.session_state.batch, "Batch not provided"
206
+ with hf_fs.open(input_repo_path + '/' + to_annotate_file_name.format(batch=st.session_state.batch)) as f:
207
+ return pd.read_csv(f)
208
+
209
+ def read_saved_data():
210
+ _path = get_path()
211
+ if hf_fs.exists(output_repo_path + '/' + _path):
212
+ with hf_fs.open(output_repo_path + '/' + _path) as f:
213
+ try:
214
+ return json.load(f)
215
+ except json.JSONDecodeError as e:
216
+ print(e)
217
+ return None
218
+
219
+ # Write a remote file
220
+ def save_data(data):
221
+ if not hf_fs.exists(f"{output_repo_path}/{get_base_path()}"):
222
+ hf_fs.mkdir(f"{output_repo_path}/{get_base_path()}")
223
+ with hf_fs.open(f"{output_repo_path}/{get_path()}", "w") as f:
224
+ f.write(json.dumps(data))
225
+
226
+ def get_base_path():
227
+ return f"{st.session_state.batch}/{st.session_state.user_id}"
228
+
229
+ def get_path():
230
+ return f"{get_base_path()}/{st.session_state.current_index}.json"
231
+
232
+ def display_dialogue(dialogue_path):
233
+ dialogue_text = load_text(dialogue_path)
234
+ st.markdown(
235
+ f"<details><summary><b>Annotation Guidelines</b></summary><div>{dialogue_text}</div></details><br>",
236
+ unsafe_allow_html=True)
237
+
238
+ def display_image(image_path):
239
+ with hf_fs.open(image_path) as f:
240
+ img = Image.open(f)
241
+ st.image(img, caption='8 most contributing properties', use_column_width=True)
242
+
243
+ #################################### Streamlit App ####################################
244
+
245
+ # Function to navigate rows
246
+ def navigate(index_change):
247
+ st.session_state.current_index += index_change
248
+ # only works consistently if done before rerun
249
+ js = '''
250
+ <script>
251
+ setTimeout(function() {
252
+ var titleElement = window.parent.document.querySelector("h1");
253
+ if (titleElement) {
254
+ titleElement.scrollIntoView({behavior: "smooth", block: "start"});
255
+ }
256
+ }, 100);
257
+ </script>
258
+ '''
259
+ st.components.v1.html(js, height=0)
260
+ # https://discuss.streamlit.io/t/click-twice-on-button-for-changing-state/45633/2
261
+
262
+ # disable text input enter to submit
263
+ # https://discuss.streamlit.io/t/text-input-how-to-disable-press-enter-to-apply/14457/6
264
+ components.html(
265
+ """
266
+ <script>
267
+ const inputs = window.parent.document.querySelectorAll('input');
268
+ inputs.forEach(input => {
269
+ input.addEventListener('keydown', function(event) {
270
+ if (event.key === 'Enter') {
271
+ event.preventDefault();
272
+ }
273
+ });
274
+ });
275
+ </script>
276
+ """,
277
+ height=0
278
+ )
279
+ st.rerun()
280
+
281
+ def show_field(f: Field, index: int, data_collected):
282
+ if f.type not in INPUT_FIELD_DEFAULT_VALUES.keys():
283
+ st.session_state.following_mandatory = False
284
+ match f.type:
285
+ case 'input_col':
286
+ value = st.session_state.data.iloc[index][f.name]
287
+ if value and value is not np.nan:
288
+ st.write(f.title)
289
+ if f.name == 'image_name':
290
+ display_image(os.path.join(input_repo_path, 'images', value))
291
+ elif f.name == 'dialogue_name':
292
+ #display_dialogue(os.path.join(input_repo_path, 'dialogues', value))
293
+ display_dialogue(os.path.join(input_repo_path, value))
294
+ else:
295
+ st.write(value)
296
+ case 'markdown':
297
+ st.markdown(f.title)
298
+ case 'expander' | 'container':
299
+ with (st.expander(f.title) if f.type == 'expander' else st.container(border=True)):
300
+ if f.type == 'container':
301
+ st.markdown(f.title)
302
+ for child in f.children:
303
+ show_field(child, index, data_collected)
304
+ case 'skip_checkbox':
305
+ st.checkbox(f.title, key=f.name, value=False)
306
+ else:
307
+ key = f.name + str(index)
308
+ st.session_state.data_inputs_keys.append(f.name)
309
+ value = st.session_state[key] if key in st.session_state else \
310
+ (data_collected[f.name] if data_collected else INPUT_FIELD_DEFAULT_VALUES[f.type])
311
+ if not SHOW_HELP_ICON:
312
+ f.title = f'**{f.title}**\n\n{f.help}' if f.help else f.title
313
+
314
+ validation_error = False
315
+
316
+ # form is not displayed for first time
317
+ if st.session_state.form_displayed == st.session_state.current_index:
318
+ if st.session_state.following_mandatory and f.skip_mandatory:
319
+ st.session_state.following_mandatory = False
320
+ if f.following_mandatory_values and st.session_state[key] in f.following_mandatory_values:
321
+ st.session_state.following_mandatory = True
322
+ if f.mandatory or st.session_state.following_mandatory:
323
+ if st.session_state[key] == INPUT_FIELD_DEFAULT_VALUES[f.type]:
324
+ st.session_state.valid = False
325
+ validation_error = True
326
+ elif f.following_mandatory_values and st.session_state[key] in f.following_mandatory_values:
327
+ st.session_state.following_mandatory = True
328
+
329
+ # check for any unaccepted values
330
+ if (
331
+ (f.other_params.get('accepted_values') and
332
+ value not in f.other_params.get('accepted_values')) or
333
+ (f.other_params.get('accepted_values_per_sample') and
334
+ index in f.other_params.get('accepted_values_per_sample') and
335
+ value not in f.other_params.get('accepted_values_per_sample').get(index))
336
+ ):
337
+ st.session_state.unacceptable_response = True
338
+
339
+ if f.mandatory or st.session_state.following_mandatory:
340
+ f.title += " :red[* required!]" if (validation_error and not SHOW_VALIDATION_ERROR_MESSAGE) else' :red[*]'
341
+ f.help = None
342
+
343
+ match f.type:
344
+ case 'checkbox':
345
+ st.checkbox(f.title,
346
+ key=key,
347
+ value=value, help=f.help)
348
+ case 'radio':
349
+ labels = default_labels if not f.other_params.get('labels') else f.other_params.get('labels')
350
+ st.radio(f.title,
351
+ options=range(len(labels)),
352
+ format_func=lambda x: labels[x],
353
+ key=key,
354
+ index=value, help=f.help, horizontal=False)
355
+ case 'slider':
356
+ st.slider(f.title,
357
+ min_value=0, max_value=6, step=1,
358
+ key=key,
359
+ value=value, help=f.help)
360
+ case 'select_slider':
361
+ labels = default_labels if not f.other_params.get('labels') else f.other_params.get('labels')
362
+ st.select_slider(f.title,
363
+ options=[0, 20, 40, 60, 80, 100],
364
+ format_func=lambda x: labels[x // 20],
365
+ key=key,
366
+ value=value, help=f.help)
367
+ case 'multiselect':
368
+ choices = default_choices if not f.other_params.get('choices') else f.other_params.get('choices')
369
+ st.multiselect(f.title,
370
+ options = choices,
371
+ format_func=lambda x: x,
372
+ key=key, max_selections=3,
373
+ default=value, help=f.help)
374
+ case 'likert_radio':
375
+ labels = default_labels if not f.other_params.get('labels') else f.other_params.get('labels')
376
+ st.radio(f.title,
377
+ options=[0, 1, 2, 3, 4],
378
+ format_func=lambda x: labels[x],
379
+ key=key,
380
+ index=value, help=f.help, horizontal=True)
381
+ case 'y_n_radio':
382
+ labels = yes_no_labels if not f.other_params.get('labels') else f.other_params.get('labels')
383
+ st.radio(f.title,
384
+ options=[0, 1],
385
+ format_func=lambda x: labels[x],
386
+ key=key,
387
+ index=value, help=f.help, horizontal=True)
388
+ case 'text':
389
+ st.text_input(f.title, key=key, value=value, max_chars=None)
390
+ case 'textarea':
391
+ st.text_area(f.title, key=key, value=value, max_chars=None)
392
+
393
+ if validation_error:
394
+ st.session_state.unacceptable_response = False
395
+ st.error(f"Mandatory field")
396
+
397
+ def show_fields(fields: List[Field]):
398
+ st.session_state.valid = True
399
+ index = st.session_state.current_index
400
+ data_collected = read_saved_data()
401
+ st.session_state.data_inputs_keys = []
402
+ st.session_state.following_mandatory = False
403
+
404
+ for field in fields:
405
+ show_field(field, index, data_collected)
406
+
407
+ submitted = st.form_submit_button("Submit")
408
+ if submitted:
409
+ if 'unacceptable_response' in st.session_state and st.session_state.unacceptable_response:
410
+ prep_and_save_data(index, ('skip' in st.session_state and st.session_state['skip']))
411
+ st.rerun()
412
+ skip_sample = ('skip' in st.session_state and st.session_state['skip'])
413
+ if not skip_sample and not st.session_state.valid:
414
+ st.error("Please fill in all mandatory fields")
415
+ # st.rerun() # filed-out values are not shown otherwise
416
+ else:
417
+ with st.spinner(text="saving"):
418
+ prep_and_save_data(index, skip_sample)
419
+ st.success("Feedback submitted successfully!")
420
+ navigate(1)
421
+
422
+ st.session_state.form_displayed = st.session_state.current_index
423
+
424
+ def prep_and_save_data(index, skip_sample):
425
+ save_data({
426
+ 'user_id': st.session_state.user_id,
427
+ 'index': st.session_state.current_index,
428
+ 'batch': st.session_state.batch,
429
+ **(st.session_state.data.iloc[index][COLS_TO_SAVE].to_dict() if 0 <= index < len(st.session_state.data) else {}),
430
+ **{k: st.session_state[k + str(index)] for k in st.session_state.data_inputs_keys},
431
+ 'skip': skip_sample
432
+ })
433
+
434
+ # st.set_page_config(layout='wide')
435
+ # Title of the app
436
+ st.title("Should We Moderate this Comment?")
437
+
438
+ st.markdown(
439
+ """<style>
440
+ div[data-testid="stMarkdownContainer"] > p {
441
+ font-size: 1rem;
442
+ }
443
+ section.main > div {max-width:60rem}
444
+ </style>
445
+ """, unsafe_allow_html=True)
446
+
447
+ def add_annotation_guidelines():
448
+ guidelines_text = load_text("texts/guidelines.md")
449
+ st.write(f"username is {st.session_state.user_id}")
450
+ st.markdown(
451
+ f"<details><summary><b>Annotation Guidelines</b></summary><div>{guidelines_text}</div></details><br>",
452
+ unsafe_allow_html=True)
453
+
454
+ if 'unacceptable_response' in st.session_state and st.session_state.unacceptable_response:
455
+ #error_message = "You are not eligible for this study.<br><br>"
456
+ error_message = "<br>"
457
+ if st.session_state.current_index >= -5:
458
+ error_message += (
459
+ "Thank you for your time! You will receive a small compensation for your contribution up to now. <br><br>"
460
+ f'Please return to the study and copy/paste this code: <b>{failed_sanity_check_code}</b>, or '
461
+ f'<a href="https://app.prolific.com/submissions/complete?cfc={failed_sanity_check_code}">Click Here</a>'
462
+ )
463
+ # Display the error message using custom HTML
464
+ st.markdown(
465
+ f"""
466
+ <div style="background-color: #f8d7da; color: #721c24; padding: 10px; border-radius: 5px; border: 1px solid #f5c6cb;">
467
+ <h4>Error: You are not eligible for this study.</h4> {error_message}
468
+ </div>
469
+ """,
470
+ unsafe_allow_html=True
471
+ )
472
+ st.stop()
473
+
474
+ # batch-specific data subset
475
+ st.session_state.batch = get_param_from_url("batch")
476
+
477
+ # Load the data to annotate
478
+ if 'data' not in st.session_state:
479
+ st.session_state.data = read_data()
480
+
481
+ # user id
482
+ user_id_from_url = get_param_from_url("user_id")
483
+ if user_id_from_url:
484
+ st.session_state.user_id = user_id_from_url
485
+
486
+ # current index
487
+ if 'current_index' not in st.session_state:
488
+ start_index = get_start_index()
489
+ if start_index < len(st.session_state.data)-1:
490
+ st.session_state.current_index = start_index
491
+ else:
492
+ st.session_state.current_index = start_index+1
493
+ st.session_state.form_displayed = -1
494
+
495
+ if get_param_from_url('show_extra_fields'):
496
+ fields += url_conditional_fields
497
+ else:
498
+ fields += url_conditional_fields
499
+
500
+ def add_validated_submit(fields, message):
501
+ st.session_state.form_displayed = st.session_state.current_index
502
+ if st.form_submit_button("Submit"):
503
+ if all(not x for x in fields):
504
+ st.error(message)
505
+ else:
506
+ navigate(1)
507
+
508
+ def add_checked_submit():
509
+ check = st.checkbox('I agree', key='consent')
510
+ add_validated_submit([check], "Please agree to give your consent to proceed")
511
+
512
+ def load_text(file):
513
+ with open(file, "r") as file:
514
+ consent_text = file.read()
515
+ return consent_text
516
+
517
+ if st.session_state.current_index == -6:
518
+ with st.form("data_form"):
519
+ st.markdown(load_text("texts/consent_text.md"))
520
+ add_checked_submit()
521
+
522
+ elif st.session_state.current_index == -1:
523
+ if st.session_state.get('user_id'):
524
+ navigate(1)
525
+ else:
526
+ with st.form("data_form"):
527
+ st.session_state.user_id = st.text_input('User ID', value=user_id_from_url)
528
+ add_validated_submit([st.session_state.user_id], "Please enter a valid user ID")
529
+
530
+ elif st.session_state.current_index < len(st.session_state.data):
531
+ add_annotation_guidelines()
532
+ with st.form("data_form"+str(st.session_state.current_index)):
533
+ show_fields(fields)
534
+
535
+ elif st.session_state.current_index == len(st.session_state.data):
536
+ st.write(f"**Thank you for taking part in this study!** \n ")
537
+ if st.button("Previous"):
538
+ navigate(-1)
539
+
540
+ # Navigation buttons
541
+ if -4 < st.session_state.current_index < len(st.session_state.data):
542
+ if st.button("Previous"):
543
+ navigate(-1)
544
+
545
+ if 0 <= st.session_state.current_index < len(st.session_state.data):
546
+ st.write(f"Page {st.session_state.current_index + 1} out of {len(st.session_state.data)}")
547
+
548
+ st.markdown(
549
+ """<style>
550
+ div[data-testid="InputInstructions"] {
551
+ visibility: hidden;
552
+ }
553
+ </style>""", unsafe_allow_html=True
554
+ )
555
+