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("

程序规约有效性评估——请先展开README

") 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"

(01/{selected_num:02d}) {file_names[index.value]}

") # 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"

({progress:02d}/{len(selected_index):02d}) {file_name}

", 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)