LHospital13's picture
Typo fixed
00a5e53
from __future__ import annotations
import json
import logging
import random
import hashlib
import uuid
from functools import partial
from pathlib import Path
import gradio as gr
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logging.basicConfig(format="%(asctime)s [%(levelname)s] - %(message)s")
dump_score = "scores.json"
dump_info = "userinfo.json"
# load code samples
# samples_num = 0
# for path in Path("samples").glob("*.java"):
# samples_num = samples_num + 1
# selected_num = int(samples_num / 2)
# selected_index = random.sample(range(samples_num), selected_num)
samples = []
file_names = []
for i, path in enumerate(Path("samples").glob("*.java")):
samples.append(path.read_text(encoding="utf-8"))
file_names.append(path.name)
file_names_with_id = [f"{i:02d} - {name}" for i, name in enumerate(file_names)]
logger.info(f"Loaded {len(samples)} samples")
# map from user name to {file name: score}
global_score: dict[str, dict[str, int]] = dict()
global_info: dict[str, list[str, str, str]] = dict()
if Path(dump_score).exists():
with open(dump_score, encoding="utf-8") as f:
global_score = json.load(f)
if Path(dump_info).exists():
with open(dump_info, encoding="utf-8") as f:
global_info = json.load(f)
score_desc = [
"",
"根本没有注释所表达的规约",
"有规约,但其意义过于trivial,对于任何类似pattern的代码都有效",
"规约有一定意义,但对于理解程序实际所做的事没有什么帮助",
"规约能够充分描述程序中某些部分应满足的性质,但尚不足以完全描述整个程序所做的事",
"规约能够充分描述程序整体与各个部分的行为",
]
help_markdown = Path("README.md").read_text(encoding="utf-8")
with gr.Blocks(title="程序规约有效性评估——用户调研") as demo:
samples_num = len(samples)
selected_num = int(samples_num / 2)
selected_index = gr.State([])
def select_index(selected_index):
array = [0]
array.extend(random.sample(range(1, samples_num), selected_num - 1))
array.sort()
return array
index = gr.State(0)
notify_same_name = gr.State(True)
title = gr.HTML("<h1><center>程序规约有效性评估——请先展开README</center></h1>")
with gr.Accordion(label="Readme", open=False):
help = gr.Markdown(help_markdown)
with gr.Accordion(label="User Info", open=True) as info_accordion:
user_name = gr.State("")
user_gender = gr.Radio(
["Male", "Female", "Other", "Prefer Undisclosed"],
value="Unknown",
interactive=True,
label="Gender"
)
user_age = gr.Textbox(label="Age",
placeholder="Enter your age (a single arabic number)",
interactive=True,
autofocus=True
)
user_major = gr.Dropdown(
label="Major",
choices=["Architecture", "Astronomy", "Biology", "Chemistry", "Computer Science", "Earth Science", "Economics and Finance", "Electrical Science", "Environmental Science", "Materials Science", "Mathematics", "Physics", "Others"],
value="Unknown",
multiselect=False,
interactive=True
)
user_willing = gr.Textbox(label="Further Evaluation Willingness",
placeholder="Would you like to participate in futher evaluation? If so, please leave your contact information, e.g. your email",
interactive=True,
autofocus=True
)
with gr.Row(equal_height=False):
button_prev = gr.Button(value="<")
with gr.Column(scale=12):
with gr.Row():
score_description = gr.Textbox(value=score_desc[1],
label="Description",
scale=3)
score_slider = gr.Slider(
minimum=1,
maximum=5,
step=1,
value=1,
label="Score",
)
score_slider.release(lambda value: gr.Text(
value=score_desc[value], label="Description"),
inputs=[score_slider],
outputs=[score_description])
submit = gr.Button(value="Submit this question", variant="primary")
with gr.Row():
code_title = gr.HTML(
f"<h2>(01/{selected_num:02d}) {file_names[index.value]}</h2>")
# code_select = gr.Dropdown(
# choices=file_names_with_id,
# value=file_names_with_id[index.value],
# interactive=True,
# show_label=False,
# multiselect=False,
# )
code_select = gr.State(0)
code_block = gr.Code(
samples[index.value],
language="javascript",
)
pswd_box = gr.Textbox(value="Thanks to MuYang for his tremendous contributions to this website!", show_label=False, interactive=True)
with gr.Row():
res_block1 = gr.Code(
language="javascript",
visible=False
)
res_block2 = gr.Code(
language="javascript",
visible=False
)
button_next = gr.Button(value=">")
def update_index(file_index: int, selected_index: list, username: str, delta: int):
progress = 0
for i, index in enumerate(selected_index):
if index == file_index:
progress = (i + delta) % len(selected_index) + 1
file_index = selected_index[(i + delta) % len(selected_index)]
break
# file_index = (file_index + delta + len(samples)) % len(samples)
file_name = file_names[file_index]
file_name_id = file_names_with_id[file_index]
scores = global_score.get(username, dict())
# set score if exists
if file_name not in scores:
slider = gr.Slider(
minimum=1,
maximum=5,
step=1,
value=1,
label="Score",
)
desc = score_desc[1]
else:
slider = gr.Slider(
minimum=1,
maximum=5,
step=1,
value=scores[file_name],
label="Score",
)
desc = score_desc[scores[file_name]]
return (file_index, f"<h2>({progress:02d}/{len(selected_index):02d}) {file_name}</h2>",
samples[file_index], slider, desc, file_name_id)
def submit_score(file_index: int, selected_index: list,
username: str, usergender:str, userage:str, usermajor:str, userwilling:str,
value: int,
notify_name: bool = True):
# first check if user name is set
if not username:
# prompt user to set name
gr.Warning("Please set your name first!")
logging.info("User name not set.")
return *update_index(file_index, selected_index, username,
delta=0), notify_name
filename = file_names[file_index]
scores = global_score.setdefault(username, dict())
userinfo = global_info.setdefault(username, ["Unknown", "Unknown", "Unknown", "Unknown"])
# check if user name duplicated
if notify_name and filename in scores:
gr.Warning("Existing user name detected.")
logging.info(f"User name {username} duplicated.")
notify_name = False
logger.info(f"User {username} scored {filename} with {value}")
num_scored_prev = len(scores)
# update the score to global score
global_score[username][filename] = value
global_info[username] = [usergender, userage, usermajor, userwilling]
# check if all files scored
num_scored = len(global_score[username])
if num_scored == selected_num and num_scored_prev < selected_num:
gr.Info(
"Congratulations! All tasks done! Thank you for your contributions!")
logging.info(f"User {username} scored all files.")
# get all scores of i
scores_i = [
user_scores[filename] for user_scores in global_score.values()
if filename in user_scores
]
if len(scores_i) > 0:
avg = sum(scores_i) / len(scores_i)
plural = "s" if len(scores_i) > 1 else ""
file_show = f"{file_index:02d} - {file_names[file_index]}"
gr.Info(f"{len(scores_i)} guy{plural} scored "
f"[{file_show}] with {avg:.2f}/5")
# dump global score
with open(dump_score, "w", encoding="utf-8") as f:
json.dump(global_score, f, ensure_ascii=False, indent=4)
# dump global info
with open(dump_info, "w", encoding="utf-8") as f:
json.dump(global_info, f, ensure_ascii=False, indent=4)
# update index by 1
return *update_index(file_index, selected_index, username, delta=1), notify_name
def select_code(filename_id: str, username: str):
file_index = file_names_with_id.index(filename_id)
return update_index(file_index, selected_index, username, delta=0)
def process_pswd(pswd:str):
m = hashlib.md5()
m.update(pswd.encode("utf-8"))
if m.hexdigest() == "1dde58fa912bb474be7e9bea10cdb6a0":
return (gr.Code(language="javascript",visible=True), gr.Code(language="javascript",visible=True))
return (gr.Code(language="javascript",visible=False), gr.Code(language="javascript",visible=False))
def load_result():
res1 = json.dumps(global_score, ensure_ascii=False, indent=4)
res2 = json.dumps(global_info, ensure_ascii=False, indent=4)
return (res1, res2)
def gen_uuid():
return uuid.uuid4().hex
demo.load(select_index, inputs=[selected_index], outputs=[selected_index]).then(gen_uuid, inputs=None, outputs=user_name)
inputs = [index, selected_index, user_name]
outputs = [
index, code_title, code_block, score_slider, score_description,
code_select
]
button_prev.click(partial(update_index, delta=-1), inputs, outputs)
button_next.click(partial(update_index, delta=+1), inputs, outputs)
submit.click(submit_score,
inputs=[index, selected_index,
user_name, user_gender, user_age, user_major, user_willing,
score_slider, notify_same_name],
outputs=[
index, code_title, code_block, score_slider,
score_description, code_select, notify_same_name
])
# user_name.input(partial(update_index, delta=0), inputs, outputs)
# code_select.select(partial(select_code),
# inputs=[code_select, user_name],
# outputs=outputs)
pswd_box.submit(process_pswd, inputs=[pswd_box], outputs=[res_block1, res_block2]).then(load_result, inputs=None, outputs=[res_block1, res_block2])
if __name__ == "__main__":
demo.queue().launch(debug=True, share=False)