File size: 11,976 Bytes
01ca7d9
 
 
 
6630243
1c06f52
818842b
01ca7d9
 
 
 
 
 
 
 
 
 
1c06f52
01ca7d9
 
6630243
 
 
 
 
01ca7d9
 
6630243
01ca7d9
 
 
 
 
 
 
1c06f52
01ca7d9
 
 
1c06f52
 
 
01ca7d9
 
 
04504e0
01ca7d9
 
 
1c06f52
01ca7d9
 
 
3715d21
6630243
 
 
 
 
1c06f52
6630243
 
818842b
01ca7d9
 
 
3715d21
d279329
01ca7d9
1c06f52
818842b
3715d21
 
1c06f52
 
3715d21
 
 
 
 
 
 
 
 
1c06f52
 
3715d21
 
 
727bffd
 
 
 
 
01ca7d9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
00a5e53
01ca7d9
 
 
1c06f52
6630243
 
 
 
 
 
 
 
01ca7d9
 
 
 
 
1c06f52
818842b
1c06f52
 
 
 
 
 
 
 
 
 
01ca7d9
 
 
1c06f52
 
6630243
 
1c06f52
6630243
 
 
01ca7d9
 
 
 
 
1c06f52
 
 
 
 
 
 
01ca7d9
 
 
 
 
 
 
 
 
 
1c06f52
01ca7d9
 
1c06f52
ce06c11
01ca7d9
 
 
 
 
 
 
6630243
01ca7d9
 
 
 
ce06c11
01ca7d9
 
 
 
 
 
 
 
 
 
 
ce06c11
01ca7d9
 
1c06f52
01ca7d9
1c06f52
01ca7d9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1c06f52
 
 
01ca7d9
 
6630243
01ca7d9
 
 
6630243
1c06f52
 
 
 
 
 
 
 
 
 
 
 
818842b
 
 
 
 
01ca7d9
6630243
01ca7d9
 
 
 
 
 
 
 
1c06f52
ce06c11
1c06f52
01ca7d9
 
 
 
818842b
6630243
 
 
01ca7d9
1c06f52
 
01ca7d9
5029eb2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
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)