Spaces:
Runtime error
Runtime error
Commit ·
1017384
1
Parent(s): 0198630
Rename app_researcher.py to app.py
Browse files- app.py +4 -0
- app_researcher.py +0 -671
app.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import os
|
| 3 |
+
interview=gr.load("qualz/interview", src="spaces",hf_token=os.environ["hf_token"])
|
| 4 |
+
interview.launch()
|
app_researcher.py
DELETED
|
@@ -1,671 +0,0 @@
|
|
| 1 |
-
import gradio as gr
|
| 2 |
-
import openai
|
| 3 |
-
from dotenv import find_dotenv
|
| 4 |
-
import json
|
| 5 |
-
import os
|
| 6 |
-
from pathlib import Path
|
| 7 |
-
from utils.app_common import * # common functions for both apps
|
| 8 |
-
import hashlib
|
| 9 |
-
from interviewer_utils.interviewer_persona import AI_PERSONALITIES
|
| 10 |
-
from utils.database_helper import DatabaseIO
|
| 11 |
-
import bcrypt
|
| 12 |
-
import pymongo
|
| 13 |
-
import pymongo.errors
|
| 14 |
-
|
| 15 |
-
load_dotenv()
|
| 16 |
-
|
| 17 |
-
USER_ID = os.environ["DEV_USER_ID"]
|
| 18 |
-
TO_FILTER_STUDIES_BY_USERID = False
|
| 19 |
-
project_dir = Path(os.environ['PROJECT_DIR'])
|
| 20 |
-
assert project_dir.exists()
|
| 21 |
-
|
| 22 |
-
with open(project_dir / 'study_prompts.json') as sp:
|
| 23 |
-
DEFINITION_OBJECTIVE_MAP = json.load(sp)
|
| 24 |
-
|
| 25 |
-
# todo refactor: move this to study processor
|
| 26 |
-
def_objective_keys = DEFINITION_OBJECTIVE_MAP.keys() # iterate it in whatever order
|
| 27 |
-
DEFINITION_OBJECTIVE_CHOICES_DICT = {k: DEFINITION_OBJECTIVE_MAP[k]['descriptive_name'] for k in def_objective_keys}
|
| 28 |
-
DEFINITION_CHOICES_OBJECTIVE_DICT = {v: k for k, v in DEFINITION_OBJECTIVE_CHOICES_DICT.items()}
|
| 29 |
-
|
| 30 |
-
FRAMEWORK_CHOICES = {
|
| 31 |
-
"Ethnographic": {
|
| 32 |
-
"Description": "An in-depth exploration of a particular cultural or social group. "
|
| 33 |
-
"Best for: Studies where immersion in a community or culture is essential "
|
| 34 |
-
"to understand its norms, rituals, and day-to-day practices. "
|
| 35 |
-
"Useful when you want to capture the intricacies of daily life and societal roles."
|
| 36 |
-
},
|
| 37 |
-
"Narrative": {
|
| 38 |
-
"Description": "Focuses on collecting individual stories to shed light on personal"
|
| 39 |
-
" experiences and the meanings people attach to them. "
|
| 40 |
-
"Best for: Research aimed at understanding personal histories, "
|
| 41 |
-
"changes over time, and the richness of individual life journeys."
|
| 42 |
-
},
|
| 43 |
-
"Grounded Theory": {
|
| 44 |
-
"Description": "A systematic approach aimed at generating new theories or concepts "
|
| 45 |
-
"based on patterns found in qualitative data. "
|
| 46 |
-
"Best for: Studies that start without a predetermined theory, "
|
| 47 |
-
"allowing the data itself to drive the development of a new theory or framework."
|
| 48 |
-
},
|
| 49 |
-
"Case Study": {
|
| 50 |
-
"Description": "An in-depth examination of a specific instance, event,"
|
| 51 |
-
" or case to gain a comprehensive understanding of its complexities."
|
| 52 |
-
" Best for: Research that delves deep into a particular situation or problem, "
|
| 53 |
-
"often to understand the 'why' and 'how' of an observed phenomenon."
|
| 54 |
-
},
|
| 55 |
-
"Phenomenological": {
|
| 56 |
-
"Description": "Seeks to understand the essence of a certain phenomenon by exploring "
|
| 57 |
-
"individual lived experiences related to it. Best for: Studies focusing"
|
| 58 |
-
" on deeply personal and subjective experiences, like the essence of grief "
|
| 59 |
-
"or the experience of being a caregiver."
|
| 60 |
-
},
|
| 61 |
-
"Exploratory": {
|
| 62 |
-
"Description": "An approach used to dive into areas of study where little is known, "
|
| 63 |
-
"aiming to discover fresh insights or formulate hypotheses for future research. "
|
| 64 |
-
"Best for: Preliminary research phases where the aim is to identify patterns, "
|
| 65 |
-
"ideas, or hypotheses rather than confirm preset notions."
|
| 66 |
-
}
|
| 67 |
-
}
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
def check_credentials(username, password):
|
| 71 |
-
|
| 72 |
-
try:
|
| 73 |
-
with DatabaseIO(collection_name=INTERVIEW_USERS_COLLECTION) as db_io:
|
| 74 |
-
user_collection = db_io.collection
|
| 75 |
-
user = user_collection.find_one({"username": username})
|
| 76 |
-
if user is not None:
|
| 77 |
-
if type(user) == list:
|
| 78 |
-
user = user[0]
|
| 79 |
-
if user and bcrypt.checkpw(password.encode('utf-8'), user['password']):
|
| 80 |
-
return True
|
| 81 |
-
else:
|
| 82 |
-
return False
|
| 83 |
-
except pymongo.errors.ConnectionFailure as e:
|
| 84 |
-
print(f"Could not connect to MongoDB: {e}")
|
| 85 |
-
return False
|
| 86 |
-
except Exception as e:
|
| 87 |
-
print(f"Error occurred: {e}")
|
| 88 |
-
return False
|
| 89 |
-
|
| 90 |
-
def create_link_for_real_interviews(study_id):
|
| 91 |
-
hash_code = hashlib.sha256(os.urandom(32)).hexdigest()[:32]
|
| 92 |
-
return f"https://qualz.net/studyId={study_id}&code={hash_code}"
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
def delete_study(this_study_obj, this_study_repository):
|
| 96 |
-
this_study_repository.delete_study(study_id=this_study_obj._id)
|
| 97 |
-
this_study_obj.delete_study()
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
def save_synthetic_panelists(this_study_obj,
|
| 101 |
-
this_study_repository):
|
| 102 |
-
if not this_study_obj:
|
| 103 |
-
return "Please select a study"
|
| 104 |
-
return this_study_repository.update_study(study_id=this_study_obj._id, updated_data=this_study_obj.to_dict()),
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
def update_study(this_study_obj,
|
| 108 |
-
this_study_repository,
|
| 109 |
-
this_study_descriptive_name,
|
| 110 |
-
this_study_topic,
|
| 111 |
-
this_study_objective,
|
| 112 |
-
this_study_framework,
|
| 113 |
-
this_study_research_questions,
|
| 114 |
-
this_interview_questions):
|
| 115 |
-
if not len(this_study_descriptive_name) or not len(this_study_topic) or not len(this_study_objective) or not len(
|
| 116 |
-
this_study_framework) or not len(this_study_research_questions) or not len(
|
| 117 |
-
this_study_research_questions) or not len(this_interview_questions):
|
| 118 |
-
return "Study Creation failed, all fields above are mandatory"
|
| 119 |
-
updated_items = {
|
| 120 |
-
'study_descriptive_name': this_study_descriptive_name,
|
| 121 |
-
'study_topic': this_study_topic,
|
| 122 |
-
'study_objective': this_study_objective,
|
| 123 |
-
'study_framework': this_study_framework,
|
| 124 |
-
'research_questions': this_study_research_questions,
|
| 125 |
-
'interview_guidelines': this_interview_questions
|
| 126 |
-
|
| 127 |
-
}
|
| 128 |
-
|
| 129 |
-
return this_study_repository.update_study(study_id=this_study_obj._id, updated_data=updated_items)
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
def show_interview_link_markdown(
|
| 133 |
-
this_study_obj,
|
| 134 |
-
):
|
| 135 |
-
if hasattr(this_study_obj, "interview_url") and len(this_study_obj.interview_url) >= 5:
|
| 136 |
-
study_url = this_study_obj.interview_url
|
| 137 |
-
else:
|
| 138 |
-
study_url = create_link_for_real_interviews(str(this_study_obj._id))
|
| 139 |
-
|
| 140 |
-
study_markdown = f"""
|
| 141 |
-
|
| 142 |
-
Please ask your participants to do the following:
|
| 143 |
-
|
| 144 |
-
1.Go to https://www.qualz.net
|
| 145 |
-
|
| 146 |
-
2.Scroll down to Demos and Interview section
|
| 147 |
-
|
| 148 |
-
3. Enter their email address in the 'username'
|
| 149 |
-
|
| 150 |
-
4. Enter this code in the password section: {str(this_study_obj._id)}
|
| 151 |
-
|
| 152 |
-
As people complete interviews, it will appear here."""
|
| 153 |
-
return study_markdown, ""
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
def add_framework_details(dropdown_choice):
|
| 157 |
-
return FRAMEWORK_CHOICES.get(dropdown_choice)['Description']
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
def guess_study_objective(study_access_obj,
|
| 161 |
-
this_study_descriptive_name,
|
| 162 |
-
this_topic,
|
| 163 |
-
this_objective):
|
| 164 |
-
print(this_study_descriptive_name, this_topic, this_objective)
|
| 165 |
-
|
| 166 |
-
if not len(this_study_descriptive_name) or not len(this_topic) or not len(this_objective):
|
| 167 |
-
return study_access_obj, "", "To get suggestions on objective, please provide ALL of study name" \
|
| 168 |
-
"topic and jot down your initial thoughts for study objective",
|
| 169 |
-
|
| 170 |
-
else:
|
| 171 |
-
study_objective, study_reasoning = study_access_obj.chat_handler.guess_study_objective(
|
| 172 |
-
research_name=this_study_descriptive_name,
|
| 173 |
-
research_topic=this_topic,
|
| 174 |
-
study_objective_draft_or_instructions=this_objective)
|
| 175 |
-
# some magic buttons are available in the factory method as well as the study object
|
| 176 |
-
# if study object is the caller, update the in memory version of the study
|
| 177 |
-
# todo write this back to the study! by a direct access pattern
|
| 178 |
-
# if type(study_access_obj) == Study:
|
| 179 |
-
# study_access_obj.study_objective = study_objective
|
| 180 |
-
return study_access_obj, study_objective, study_reasoning
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
def start_synthetic_interviews(this_study_obj):
|
| 186 |
-
if not this_study_obj:
|
| 187 |
-
return this_study_obj, "Please select a study"
|
| 188 |
-
if not this_study_obj.synthetic_panel_pending_interviews or not len(this_study_obj.synthetic_panel_pending_interviews):
|
| 189 |
-
return this_study_obj, "Please create a panel before starting the interview process"
|
| 190 |
-
|
| 191 |
-
this_status_msg = f"Started conducting interviews with {len(this_study_obj.synthetic_panel_pending_interviews)} " \
|
| 192 |
-
f"panelists. Please check back on this study after 15-20 minutes" \
|
| 193 |
-
f"If registered for email updates, you will receive an email update when synthetic interviews" \
|
| 194 |
-
f"are complete."
|
| 195 |
-
return this_status_msg
|
| 196 |
-
|
| 197 |
-
def guess_best_study_framework(
|
| 198 |
-
study_access_obj,
|
| 199 |
-
this_study_descriptive_name,
|
| 200 |
-
this_topic,
|
| 201 |
-
this_objective):
|
| 202 |
-
if not len(this_study_descriptive_name) or not len(this_topic) or not len(this_objective):
|
| 203 |
-
return study_access_obj, "", "To get suggestions on theoretical framework, please provide ALL of study " \
|
| 204 |
-
"name, " \
|
| 205 |
-
"topic and jot down your initial thoughts for study objective. " \
|
| 206 |
-
"The more details you have the better",
|
| 207 |
-
|
| 208 |
-
else:
|
| 209 |
-
study_framework_suggestion, framework_suggestion_reasoning = \
|
| 210 |
-
study_access_obj.chat_handler.guess_theoretical_framework(
|
| 211 |
-
research_name=this_study_descriptive_name,
|
| 212 |
-
research_topic=this_topic,
|
| 213 |
-
research_objective=this_objective)
|
| 214 |
-
# if type(study_access_obj) == Study:
|
| 215 |
-
# study_access_obj.study_objective = study_framework_suggestion
|
| 216 |
-
return study_access_obj, study_framework_suggestion, framework_suggestion_reasoning
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
def guess_interview_guidelines(
|
| 220 |
-
study_access_obj,
|
| 221 |
-
this_study_topic,
|
| 222 |
-
this_study_objective,
|
| 223 |
-
this_study_framework,
|
| 224 |
-
this_study_research_questions,
|
| 225 |
-
this_interview_guidelines_draft
|
| 226 |
-
|
| 227 |
-
):
|
| 228 |
-
if not len(this_study_topic) or not len(this_study_objective) or not len(this_study_framework) or not len(
|
| 229 |
-
this_study_research_questions) or not len(this_interview_guidelines_draft):
|
| 230 |
-
return "", "To get suggestions on interview script, please provide ALL of study " \
|
| 231 |
-
"please provide a few lines of initial draft or instructions AND ensure ALL of " \
|
| 232 |
-
"the other fields above are written out well",
|
| 233 |
-
else:
|
| 234 |
-
this_interview_script, this_interview_script_details = \
|
| 235 |
-
study_access_obj.chat_handler.guess_interview_guidelines(
|
| 236 |
-
research_topic=this_study_topic,
|
| 237 |
-
research_objective=this_study_objective,
|
| 238 |
-
research_framework=this_study_framework,
|
| 239 |
-
research_questions=this_study_research_questions,
|
| 240 |
-
interview_guidelines_draft_or_instructions=this_interview_guidelines_draft
|
| 241 |
-
)
|
| 242 |
-
# if type(study_access_obj) == Study:
|
| 243 |
-
# study_access_obj.study_objective = study_framework_suggestion
|
| 244 |
-
return this_interview_script, this_interview_script_details
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
def get_generic_markdown(data, indent=0) -> str:
|
| 248 |
-
markdown_text = ""
|
| 249 |
-
if isinstance(data, list):
|
| 250 |
-
for item in data:
|
| 251 |
-
markdown_text += " " * indent + "- " + get_generic_markdown(item, indent + 1) + "\n"
|
| 252 |
-
elif isinstance(data, dict):
|
| 253 |
-
for key, value in data.items():
|
| 254 |
-
markdown_text += " " * indent + f"**{key}:** " + get_generic_markdown(value, indent + 1) + "\n"
|
| 255 |
-
else:
|
| 256 |
-
markdown_text += str(data)
|
| 257 |
-
return markdown_text
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
def create_modify_synthetic_panel(study_access_obj, m_panel_creation_instructions, m_num_panelists):
|
| 261 |
-
if not study_access_obj:
|
| 262 |
-
return study_access_obj, "", "Please select a study"
|
| 263 |
-
|
| 264 |
-
m_synthetic_panelists, rationale, discussion = study_access_obj.chat_handler.create_synthetic_panel(
|
| 265 |
-
research_topic=study_access_obj.study_topic,
|
| 266 |
-
research_objective=study_access_obj.study_objective,
|
| 267 |
-
research_framework=study_access_obj.study_framework,
|
| 268 |
-
instructions=m_panel_creation_instructions,
|
| 269 |
-
num_panelists=int(m_num_panelists))
|
| 270 |
-
study_access_obj.synthetic_panel_pending_interviews = m_synthetic_panelists
|
| 271 |
-
markdown_string = get_generic_markdown([m_synthetic_panelists, rationale, discussion])
|
| 272 |
-
return study_access_obj, markdown_string, "Created new synthetic panel, you can save it for later or start interviews"
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
def guess_some_research_questions(study_access_obj,
|
| 276 |
-
this_study_descriptive_name,
|
| 277 |
-
this_topic,
|
| 278 |
-
this_objective,
|
| 279 |
-
this_framework,
|
| 280 |
-
this_research_questions):
|
| 281 |
-
# if not study_access_obj:
|
| 282 |
-
# return study_access_obj, "", "",
|
| 283 |
-
if not len(this_study_descriptive_name) or not len(this_topic) or not len(this_objective) or not len(
|
| 284 |
-
this_framework) or not len(this_research_questions):
|
| 285 |
-
return study_access_obj, "", "To get suggestions on research questions ALL of study name, " \
|
| 286 |
-
"topic, objectives, and theoretical framework" \
|
| 287 |
-
" and jot down your initial thoughts for research questions objective. " \
|
| 288 |
-
"The more details you have the better",
|
| 289 |
-
|
| 290 |
-
else:
|
| 291 |
-
research_questions, reasoning = study_access_obj.chat_handler.guess_research_questions(
|
| 292 |
-
research_name=this_study_descriptive_name,
|
| 293 |
-
research_topic=this_topic,
|
| 294 |
-
research_objective=this_objective,
|
| 295 |
-
research_framework=this_framework,
|
| 296 |
-
research_questions_draft=this_research_questions)
|
| 297 |
-
return study_access_obj, research_questions, reasoning
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
def create_new_study_fn(
|
| 301 |
-
study_repository_obj,
|
| 302 |
-
this_study_descriptive_name,
|
| 303 |
-
this_topic,
|
| 304 |
-
this_objective,
|
| 305 |
-
this_framework,
|
| 306 |
-
this_research_questions
|
| 307 |
-
):
|
| 308 |
-
if not len(this_study_descriptive_name) or not len(this_topic) or not len(this_objective) or not len(
|
| 309 |
-
this_framework) or not len(this_research_questions):
|
| 310 |
-
return "Study Creation failed, all fields above are mandatory", study_repository_obj
|
| 311 |
-
else:
|
| 312 |
-
|
| 313 |
-
study_id = study_repository_obj.create_study({"study_descriptive_name": this_study_descriptive_name,
|
| 314 |
-
"study_topic": this_topic,
|
| 315 |
-
"study_objective": this_objective,
|
| 316 |
-
"study_framework": this_framework,
|
| 317 |
-
"research_questions": this_research_questions,
|
| 318 |
-
})
|
| 319 |
-
return f"New Study with id: {str(study_id)} created. You can now access it in existing studies tab on top", \
|
| 320 |
-
study_repository_obj
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
def show_existing_study_fields(this_study_obj):
|
| 324 |
-
this_synthetic_panel_markdown = get_generic_markdown(
|
| 325 |
-
{'Panelists': this_study_obj.synthetic_panel_pending_interviews,
|
| 326 |
-
'Reason': "",
|
| 327 |
-
'Discussion': ""})
|
| 328 |
-
|
| 329 |
-
return this_study_obj, this_study_obj._id, this_study_obj.study_descriptive_name, this_study_obj.study_topic,\
|
| 330 |
-
this_study_obj.study_objective, \
|
| 331 |
-
this_study_obj.study_framework, this_study_obj.research_questions, this_study_obj.interview_guidelines, \
|
| 332 |
-
this_synthetic_panel_markdown, ""
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
def get_dropdown_choices(dropdown_choices_study_repository_obj=None):
|
| 336 |
-
accessed_during_page_load = False
|
| 337 |
-
if not dropdown_choices_study_repository_obj:
|
| 338 |
-
accessed_during_page_load = True
|
| 339 |
-
dropdown_choices_study_repository_obj = StudyRepository()
|
| 340 |
-
# creating a new repository object as state might not be initialized
|
| 341 |
-
existing_studies = dropdown_choices_study_repository_obj.get_studies(user_id=USER_ID,
|
| 342 |
-
to_filter_by_user_id=TO_FILTER_STUDIES_BY_USERID)
|
| 343 |
-
_ids = [st['_id'] for st in existing_studies]
|
| 344 |
-
_descriptions = [st['study_descriptive_name'] for st in existing_studies]
|
| 345 |
-
this_choices = [f"{str(_id)}:{desc}" for _id, desc in zip(_ids, _descriptions)]
|
| 346 |
-
|
| 347 |
-
# workaround for first page load the repository object may not be initialized
|
| 348 |
-
if accessed_during_page_load:
|
| 349 |
-
return this_choices
|
| 350 |
-
else:
|
| 351 |
-
return gr.Dropdown.update(choices=this_choices)
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 355 |
-
gr.Markdown(f"Welcome to QualZ. I am your friendly A.I. Assistant for research. "
|
| 356 |
-
f"Go through the various tabs to select a topic to start researching!")
|
| 357 |
-
view_study_repository = gr.State(value=StudyRepository())
|
| 358 |
-
# existing_studies_dropdown_choices = get_dropdown_choices()
|
| 359 |
-
view_active_study = gr.State(value=None)
|
| 360 |
-
with gr.Tab("+ New Study"):
|
| 361 |
-
with gr.Row():
|
| 362 |
-
with gr.Column():
|
| 363 |
-
new_study_descriptive_name = gr.Textbox(label="New Research Name",
|
| 364 |
-
placeholder="Give your study a memorable name to refer it by")
|
| 365 |
-
|
| 366 |
-
new_study_topic = gr.Textbox(label="Study Topic",
|
| 367 |
-
placeholder="Give your study a topic")
|
| 368 |
-
|
| 369 |
-
new_study_objective_details = gr.Markdown("Jot down your thoughts and let Qualz suggestions")
|
| 370 |
-
with gr.Row():
|
| 371 |
-
with gr.Column(scale=25):
|
| 372 |
-
new_study_objective = gr.Textbox(label="Study objective",
|
| 373 |
-
placeholder="Briefly describe the main aim of this study...")
|
| 374 |
-
with gr.Column(scale=1):
|
| 375 |
-
new_study_objective_magic = gr.Button(value="💫")
|
| 376 |
-
|
| 377 |
-
new_framework_details = gr.Markdown(
|
| 378 |
-
value="These are broad categories and can serve as a starting point. "
|
| 379 |
-
"Depending on the specific nature and goals of your research, "
|
| 380 |
-
"you might choose to narrow down or further specify these choices.")
|
| 381 |
-
with gr.Row():
|
| 382 |
-
with gr.Column(scale=25):
|
| 383 |
-
new_study_framework = gr.Dropdown(choices=[
|
| 384 |
-
"Ethnographic", "Narrative", "Grounded Theory", "Case Study", "Phenomenological",
|
| 385 |
-
"Exploratory"
|
| 386 |
-
], label="Choose a theoretical framework")
|
| 387 |
-
with gr.Column(scale=1):
|
| 388 |
-
new_framework_selection_magic = gr.Button(value="💫")
|
| 389 |
-
|
| 390 |
-
new_research_question_details = gr.Markdown(
|
| 391 |
-
value="Jot down your thoughts and click to see suggestions",
|
| 392 |
-
label="About Study Framework Choice")
|
| 393 |
-
with gr.Row():
|
| 394 |
-
with gr.Column(scale=25):
|
| 395 |
-
new_study_research_questions = gr.TextArea(
|
| 396 |
-
value="What are some research questions you have ? It is best to start with "
|
| 397 |
-
"a few and expand on it later as new information becomes available",
|
| 398 |
-
label="Research Questions"
|
| 399 |
-
)
|
| 400 |
-
with gr.Column(scale=1):
|
| 401 |
-
new_research_questions_magic = gr.Button(value="💫")
|
| 402 |
-
|
| 403 |
-
create_new_study = gr.Button(value="Create New Study", variant="primary")
|
| 404 |
-
new_status_update = gr.Textbox(label="Status")
|
| 405 |
-
new_study_framework.select(add_framework_details,
|
| 406 |
-
inputs=[new_study_framework],
|
| 407 |
-
outputs=[new_framework_details])
|
| 408 |
-
|
| 409 |
-
new_study_objective_magic.click(guess_study_objective,
|
| 410 |
-
inputs=[
|
| 411 |
-
view_study_repository,
|
| 412 |
-
new_study_descriptive_name,
|
| 413 |
-
new_study_topic,
|
| 414 |
-
new_study_objective],
|
| 415 |
-
outputs=[view_study_repository,
|
| 416 |
-
new_study_objective,
|
| 417 |
-
new_study_objective_details])
|
| 418 |
-
|
| 419 |
-
new_framework_selection_magic.click(guess_best_study_framework,
|
| 420 |
-
inputs=[view_study_repository,
|
| 421 |
-
new_study_descriptive_name, new_study_topic,
|
| 422 |
-
new_study_objective],
|
| 423 |
-
outputs=[view_study_repository,
|
| 424 |
-
new_study_framework, new_framework_details])
|
| 425 |
-
|
| 426 |
-
new_research_questions_magic.click(guess_some_research_questions,
|
| 427 |
-
inputs=[view_study_repository,
|
| 428 |
-
new_study_descriptive_name, new_study_topic,
|
| 429 |
-
new_study_objective, new_study_framework,
|
| 430 |
-
new_study_research_questions],
|
| 431 |
-
outputs=[view_study_repository,
|
| 432 |
-
new_study_research_questions, new_research_question_details]
|
| 433 |
-
)
|
| 434 |
-
create_new_study.click(create_new_study_fn,
|
| 435 |
-
inputs=[view_study_repository,
|
| 436 |
-
new_study_descriptive_name, new_study_topic,
|
| 437 |
-
new_study_objective, new_study_framework,
|
| 438 |
-
new_study_research_questions
|
| 439 |
-
],
|
| 440 |
-
outputs=[
|
| 441 |
-
new_status_update, view_study_repository
|
| 442 |
-
])
|
| 443 |
-
with gr.Tab(" Existing Study"):
|
| 444 |
-
with gr.Row():
|
| 445 |
-
with gr.Column():
|
| 446 |
-
with gr.Row():
|
| 447 |
-
with gr.Column(scale=5):
|
| 448 |
-
gr.Markdown("Choose existing study")
|
| 449 |
-
with gr.Column(scale=2):
|
| 450 |
-
refresh_studies = gr.Button("Refresh")
|
| 451 |
-
study_choice_dropdown = gr.Dropdown(
|
| 452 |
-
choices=get_dropdown_choices(None),
|
| 453 |
-
label="Choose a study", interactive=True)
|
| 454 |
-
|
| 455 |
-
with gr.Tab("+ Add Details"):
|
| 456 |
-
existing_study_id = gr.Textbox(label="Study ID")
|
| 457 |
-
existing_study_descriptive_name = gr.Textbox(label="Descriptive Name", interactive=True)
|
| 458 |
-
existing_study_topic = gr.Textbox(label="Study Topic", interactive=True)
|
| 459 |
-
|
| 460 |
-
with gr.Accordion('Modify Existing Details:', open=False):
|
| 461 |
-
existing_study_objective_details = gr.Markdown("Jot down your thoughts and let Qualz give suggestions")
|
| 462 |
-
with gr.Row():
|
| 463 |
-
with gr.Column(scale=25):
|
| 464 |
-
existing_study_objective = gr.Textbox(label="Study objective",
|
| 465 |
-
placeholder="Briefly describe the main aim of this study...")
|
| 466 |
-
with gr.Column(scale=1):
|
| 467 |
-
existing_study_objective_magic = gr.Button(value="💫")
|
| 468 |
-
|
| 469 |
-
existing_framework_details = gr.Markdown(
|
| 470 |
-
value="These are broad categories and can serve as a starting point. "
|
| 471 |
-
"Depending on the specific nature and goals of your research, "
|
| 472 |
-
"you might choose to narrow down or further specify these choices.")
|
| 473 |
-
with gr.Row():
|
| 474 |
-
with gr.Column(scale=25):
|
| 475 |
-
existing_study_framework = gr.Dropdown(choices=[
|
| 476 |
-
"Ethnographic", "Narrative", "Grounded Theory", "Case Study", "Phenomenological",
|
| 477 |
-
"Exploratory"
|
| 478 |
-
], label="Choose a theoretical framework")
|
| 479 |
-
with gr.Column(scale=1):
|
| 480 |
-
existing_framework_selection_magic = gr.Button(value="💫")
|
| 481 |
-
|
| 482 |
-
existing_research_question_details = gr.Markdown(
|
| 483 |
-
value="Jot down your thoughts and click to see suggestions",
|
| 484 |
-
label="About Study Framework Choice")
|
| 485 |
-
with gr.Row():
|
| 486 |
-
with gr.Column(scale=25):
|
| 487 |
-
existing_study_research_questions = gr.TextArea(
|
| 488 |
-
value="What are some research questions you have ? It is best to start with "
|
| 489 |
-
"a few and expand on it later as new information becomes available",
|
| 490 |
-
label="Research Questions"
|
| 491 |
-
)
|
| 492 |
-
with gr.Column(scale=1):
|
| 493 |
-
existing_research_questions_magic = gr.Button(value="💫")
|
| 494 |
-
|
| 495 |
-
interview_script_details = gr.Markdown(
|
| 496 |
-
value="Create a numbered outline of questions to ask, or give instructions about this and "
|
| 497 |
-
"click the magic button to create guideline questions according to all study details",
|
| 498 |
-
label=" Interview Guidelines")
|
| 499 |
-
with gr.Row():
|
| 500 |
-
with gr.Column(scale=25):
|
| 501 |
-
interview_script_guideline = gr.TextArea(
|
| 502 |
-
value="Start with some questions you would like to ask, or enter instructions"
|
| 503 |
-
"for creating the guiding questions. ",
|
| 504 |
-
label="Interview Guideline"
|
| 505 |
-
)
|
| 506 |
-
with gr.Column(scale=1):
|
| 507 |
-
interview_script_guideline_magic = gr.Button(value="💫")
|
| 508 |
-
existing_update_study_btn = gr.Button(value="Update study Details", variant="primary")
|
| 509 |
-
existing_delete_study_btn = gr.Button(value="Delete Study", variant="secondary")
|
| 510 |
-
with gr.Tab("Interviews"):
|
| 511 |
-
with gr.Tab("Human Interviews"):
|
| 512 |
-
ai_personality = gr.Dropdown(value="Rachel", choices=list(AI_PERSONALITIES.keys()),
|
| 513 |
-
label="Select AI Personality to conduct interviews. "
|
| 514 |
-
"Selection will determine voice that you can preview below",
|
| 515 |
-
interactive=True)
|
| 516 |
-
interview_duration = gr.Slider(minimum=10, maximum=120, value=30, step=10,
|
| 517 |
-
label="Intended duration of interview ")
|
| 518 |
-
|
| 519 |
-
existing_interview_link_markdown = gr.Markdown(f"""
|
| 520 |
-
Your Study link will appear here after an interview script is created!
|
| 521 |
-
""")
|
| 522 |
-
with gr.Tab("Synthetic Interviews"):
|
| 523 |
-
with gr.Row():
|
| 524 |
-
with gr.Column():
|
| 525 |
-
number_of_synth_panelists = gr.Number(label="Number of Panelists", value=5)
|
| 526 |
-
panel_creation_instructions = gr.Text(label="Panel Creation Instructions",
|
| 527 |
-
placeholder=
|
| 528 |
-
"Enter helpful instructions for panel creation")
|
| 529 |
-
create_synthetic_interviewers_button = gr.Button(value="Create/Update a panel",
|
| 530 |
-
variant="primary")
|
| 531 |
-
cost_estimate = gr.Label("Cost Estimate: $5")
|
| 532 |
-
|
| 533 |
-
with gr.Column():
|
| 534 |
-
# todo refactor name
|
| 535 |
-
synthetic_panelists_markdown = gr.Markdown("Suggested panelists will appear here")
|
| 536 |
-
|
| 537 |
-
# synthetic_panelist_1 = gr.TextArea("ID: 123123; Name: Francis Age: 55, Gender: Male,"
|
| 538 |
-
# "Sector: Large Enterprise, "
|
| 539 |
-
# "Bio: Francis is a market director drawn to this field out of a "
|
| 540 |
-
# "fascination with consumer behavior and a desire to influence"
|
| 541 |
-
#
|
| 542 |
-
# "business strategy", interactive=False, label="1", lines=3)
|
| 543 |
-
# synthetic_panelists_desc_text_areas = []
|
| 544 |
-
# for i in range(15):
|
| 545 |
-
# a = gr.TextArea("ID: 123123; Name: Francis Age: 55, Gender: Male,"
|
| 546 |
-
# "Sector: Large Enterprise, "
|
| 547 |
-
# "Bio: Francis is a market director drawn to this field out of a "
|
| 548 |
-
# "fascination with consumer behavior and a desire to influence"
|
| 549 |
-
# "business strategy", interactive=False, label=str(i), lines=3)
|
| 550 |
-
# synthetic_panelists_desc_text_areas.append(a)
|
| 551 |
-
# synthetic_panelists_desc_text_areas.append(view_active_study)
|
| 552 |
-
|
| 553 |
-
with gr.Row():
|
| 554 |
-
save_panelists_to_db_button = gr.Button(value="Save current panelists",
|
| 555 |
-
variant="secondary")
|
| 556 |
-
start_synthetic_interviews_button = gr.Button(value="Start Synthetic Interviews (Auto saves)",
|
| 557 |
-
variant="primary")
|
| 558 |
-
|
| 559 |
-
# with gr.Row():
|
| 560 |
-
# synthetic_interview_status = gr.Textbox(label="Status")
|
| 561 |
-
|
| 562 |
-
with gr.Tab("2. Interview Analysis"):
|
| 563 |
-
pass
|
| 564 |
-
with gr.Tab("3. Interpretation"):
|
| 565 |
-
pass
|
| 566 |
-
with gr.Row():
|
| 567 |
-
existing_status_update = gr.Textbox(label="Status")
|
| 568 |
-
|
| 569 |
-
start_synthetic_interviews_button.click(start_synthetic_interviews,
|
| 570 |
-
inputs=[view_active_study],
|
| 571 |
-
outputs=[view_active_study, existing_status_update])
|
| 572 |
-
save_panelists_to_db_button.click(save_synthetic_panelists,
|
| 573 |
-
inputs=[view_active_study, view_study_repository],
|
| 574 |
-
outputs=[existing_status_update])
|
| 575 |
-
study_choice_dropdown.select(set_active_study,
|
| 576 |
-
inputs=[view_study_repository,
|
| 577 |
-
study_choice_dropdown],
|
| 578 |
-
outputs=[view_active_study, existing_status_update], queue=False).then(
|
| 579 |
-
show_existing_study_fields,
|
| 580 |
-
inputs=[view_active_study],
|
| 581 |
-
outputs=[view_active_study,
|
| 582 |
-
existing_study_id,
|
| 583 |
-
existing_study_descriptive_name,
|
| 584 |
-
existing_study_topic,
|
| 585 |
-
existing_study_objective,
|
| 586 |
-
existing_study_framework,
|
| 587 |
-
existing_study_research_questions,
|
| 588 |
-
interview_script_guideline,
|
| 589 |
-
synthetic_panelists_markdown,
|
| 590 |
-
existing_status_update
|
| 591 |
-
]
|
| 592 |
-
).then(
|
| 593 |
-
show_interview_link_markdown,
|
| 594 |
-
inputs=[view_active_study],
|
| 595 |
-
outputs=[existing_interview_link_markdown, existing_status_update]
|
| 596 |
-
)
|
| 597 |
-
refresh_studies.click(get_dropdown_choices, inputs=[view_study_repository], outputs=[study_choice_dropdown])
|
| 598 |
-
existing_study_objective_magic.click(guess_study_objective,
|
| 599 |
-
inputs=[
|
| 600 |
-
view_active_study,
|
| 601 |
-
existing_study_descriptive_name,
|
| 602 |
-
existing_study_topic,
|
| 603 |
-
existing_study_objective],
|
| 604 |
-
outputs=[view_active_study,
|
| 605 |
-
existing_study_objective,
|
| 606 |
-
existing_study_objective_details])
|
| 607 |
-
|
| 608 |
-
existing_framework_selection_magic.click(guess_best_study_framework,
|
| 609 |
-
inputs=[view_active_study,
|
| 610 |
-
existing_study_descriptive_name, existing_study_topic,
|
| 611 |
-
existing_study_objective],
|
| 612 |
-
outputs=[view_active_study,
|
| 613 |
-
existing_study_framework, existing_framework_details])
|
| 614 |
-
|
| 615 |
-
existing_research_questions_magic.click(guess_some_research_questions,
|
| 616 |
-
inputs=[view_active_study,
|
| 617 |
-
existing_study_descriptive_name, existing_study_topic,
|
| 618 |
-
existing_study_objective, existing_study_framework,
|
| 619 |
-
existing_study_research_questions],
|
| 620 |
-
outputs=[view_active_study,
|
| 621 |
-
existing_study_research_questions,
|
| 622 |
-
existing_research_question_details]
|
| 623 |
-
)
|
| 624 |
-
create_synthetic_interviewers_button.click(create_modify_synthetic_panel,
|
| 625 |
-
inputs=[
|
| 626 |
-
view_active_study,
|
| 627 |
-
panel_creation_instructions,
|
| 628 |
-
number_of_synth_panelists,
|
| 629 |
-
],
|
| 630 |
-
outputs=[
|
| 631 |
-
view_active_study,
|
| 632 |
-
synthetic_panelists_markdown,
|
| 633 |
-
existing_status_update
|
| 634 |
-
])
|
| 635 |
-
interview_script_guideline_magic.click(
|
| 636 |
-
guess_interview_guidelines,
|
| 637 |
-
inputs=[
|
| 638 |
-
view_active_study,
|
| 639 |
-
existing_study_topic,
|
| 640 |
-
existing_study_objective,
|
| 641 |
-
existing_study_framework,
|
| 642 |
-
existing_study_research_questions,
|
| 643 |
-
interview_script_guideline
|
| 644 |
-
],
|
| 645 |
-
outputs=[
|
| 646 |
-
interview_script_guideline,
|
| 647 |
-
interview_script_details
|
| 648 |
-
]
|
| 649 |
-
)
|
| 650 |
-
existing_update_study_btn.click(update_study,
|
| 651 |
-
inputs=[view_active_study,
|
| 652 |
-
view_study_repository,
|
| 653 |
-
existing_study_descriptive_name, existing_study_topic,
|
| 654 |
-
existing_study_objective, existing_study_framework,
|
| 655 |
-
existing_study_research_questions,
|
| 656 |
-
interview_script_guideline
|
| 657 |
-
],
|
| 658 |
-
outputs=[
|
| 659 |
-
existing_status_update
|
| 660 |
-
])
|
| 661 |
-
existing_delete_study_btn.click(delete_study,
|
| 662 |
-
inputs=[view_active_study,
|
| 663 |
-
view_study_repository
|
| 664 |
-
],
|
| 665 |
-
outputs=[
|
| 666 |
-
existing_status_update
|
| 667 |
-
])
|
| 668 |
-
if __name__ == "__main__":
|
| 669 |
-
_ = load_dotenv(find_dotenv())
|
| 670 |
-
openai.api_key = os.getenv('OPENAI_API_KEY')
|
| 671 |
-
demo.queue(concurrency_count=10).launch(server_port=7860, share=True, auth=check_credentials)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|