nontGcob commited on
Commit
1728ec4
1 Parent(s): e4367ce

releasing the app to the public

Browse files
.gitattributes CHANGED
@@ -1,35 +1,28 @@
1
  *.7z filter=lfs diff=lfs merge=lfs -text
2
  *.arrow filter=lfs diff=lfs merge=lfs -text
3
  *.bin filter=lfs diff=lfs merge=lfs -text
 
4
  *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
  *.ftz filter=lfs diff=lfs merge=lfs -text
7
  *.gz filter=lfs diff=lfs merge=lfs -text
8
  *.h5 filter=lfs diff=lfs merge=lfs -text
9
  *.joblib filter=lfs diff=lfs merge=lfs -text
10
  *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
  *.model filter=lfs diff=lfs merge=lfs -text
13
  *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
  *.onnx filter=lfs diff=lfs merge=lfs -text
17
  *.ot filter=lfs diff=lfs merge=lfs -text
18
  *.parquet filter=lfs diff=lfs merge=lfs -text
19
  *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
  *.pt filter=lfs diff=lfs merge=lfs -text
23
  *.pth filter=lfs diff=lfs merge=lfs -text
24
  *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
  saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
  *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
  *.tflite filter=lfs diff=lfs merge=lfs -text
30
  *.tgz filter=lfs diff=lfs merge=lfs -text
31
  *.wasm filter=lfs diff=lfs merge=lfs -text
32
  *.xz filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
  *.7z filter=lfs diff=lfs merge=lfs -text
2
  *.arrow filter=lfs diff=lfs merge=lfs -text
3
  *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bin.* filter=lfs diff=lfs merge=lfs -text
5
  *.bz2 filter=lfs diff=lfs merge=lfs -text
 
6
  *.ftz filter=lfs diff=lfs merge=lfs -text
7
  *.gz filter=lfs diff=lfs merge=lfs -text
8
  *.h5 filter=lfs diff=lfs merge=lfs -text
9
  *.joblib filter=lfs diff=lfs merge=lfs -text
10
  *.lfs.* filter=lfs diff=lfs merge=lfs -text
 
11
  *.model filter=lfs diff=lfs merge=lfs -text
12
  *.msgpack filter=lfs diff=lfs merge=lfs -text
 
 
13
  *.onnx filter=lfs diff=lfs merge=lfs -text
14
  *.ot filter=lfs diff=lfs merge=lfs -text
15
  *.parquet filter=lfs diff=lfs merge=lfs -text
16
  *.pb filter=lfs diff=lfs merge=lfs -text
 
 
17
  *.pt filter=lfs diff=lfs merge=lfs -text
18
  *.pth filter=lfs diff=lfs merge=lfs -text
19
  *.rar filter=lfs diff=lfs merge=lfs -text
 
20
  saved_model/**/* filter=lfs diff=lfs merge=lfs -text
21
  *.tar.* filter=lfs diff=lfs merge=lfs -text
 
22
  *.tflite filter=lfs diff=lfs merge=lfs -text
23
  *.tgz filter=lfs diff=lfs merge=lfs -text
24
  *.wasm filter=lfs diff=lfs merge=lfs -text
25
  *.xz filter=lfs diff=lfs merge=lfs -text
26
  *.zip filter=lfs diff=lfs merge=lfs -text
27
+ *.zstandard filter=lfs diff=lfs merge=lfs -text
28
  *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ modules/__pycache__/
3
+ 192.168.184.49.txt
4
+ 127.0.0.1.txt
5
+ .venv/
6
+ venv/
7
+ env/
Dockerfile ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10
2
+
3
+ WORKDIR /code
4
+
5
+ COPY ./requirements.txt /code/requirements.txt
6
+
7
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
8
+
9
+ COPY . .
10
+
11
+ CMD ["waitress-serve", "--host", "0.0.0.0", "--port", "7860", "app:app"]
README.md CHANGED
@@ -1,11 +1,66 @@
1
  ---
2
- title: Vocabulary Exam Generator
3
- emoji: 💻
4
- colorFrom: pink
5
- colorTo: green
6
- sdk: docker
7
- pinned: false
 
 
 
 
 
 
 
8
  license: mit
 
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: T2E Vocabulary Exam Generator
3
+ emoji: ✏️
4
+ colorFrom: blue
5
+ colorTo: blue
6
+ sdk: gradio
7
+ sdk_version: 4.40.0
8
+ python_version: 3.10.4
9
+ app_file: app.py
10
+ models:
11
+ - osanseviero/BigGAN-deep-128
12
+ - t5-small
13
+ datasets:
14
+ - emotion
15
  license: mit
16
+ pinned: true
17
  ---
18
 
19
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces#reference
20
+
21
+ # T2E Vocabulary Exam Generator
22
+ T2E Vocabulary Exam Generator as the name suggest, is an open souce program that allows user, in this case being teachers, to generate English vocabulary exam right from their browser. We aim to help reduce the pain point of teacher having to spend his/her/their precious time and effort to make the entire vocabulary exam or quiz. To try it out, click the Demo link in the Quick Links section below.
23
+
24
+ This readme.md file is an explanation of this folder that it's in.
25
+
26
+ ## Quick Links
27
+ - Demo: https://huggingface.co/spaces/nontGcob/T2E_Vocabulary_Exam_Generator
28
+ - Documentation: https://mario-world.medium.com/text-to-exam-generator-nlp-using-machine-learning-71da8dd93a4a
29
+
30
+ ## Important File Explanation
31
+ - Exploratory_Data_Analysis.ipynb
32
+ - This code runs through the entire dataset that we have and analyse the part of speech of all the lexical vocabulary, the max/min/average vocabulary length, and the number of times a word is presented
33
+ - Test_&_Fine_tune_&_Retest.ipynb
34
+ - This code tests the accuracy of the pre-trained model which is our baseline. It fine-tunes the model. And then, it tests the fine-tuned model again to check whether the accuracy has increased or not.
35
+ - Error_Analysis.ipynb
36
+ - This code analyses the prediction by analysing the type number of correct and wrong vocabulary predictions, the types of the part of speech of the correct and wrong vocab predictions, and the average vocabulary length of the correct and wrong vocab predictions.
37
+
38
+ ## Note
39
+ Please keep in mind that in order to run the .ipynb file in this folder, you must also download the "simple_trained_wsd_pipeline" and "semcor_samples_4-samples.json" as well which are not included in the folder due to the file size limitation of the code that can be uploaded to GitHub. For more information and the explanation of the entire process of this machine learning project, visit the Documentation link that is attached above in the Quick Links section.
40
+
41
+ ## Contact me
42
+ - Name: Nutnornont Chamadol
43
+ - Email: nontc49@gmail.com
44
+ - Visit my personal website https://nontgcob.com/ to learn more about me.
45
+
46
+ ## The MIT License (MIT)
47
+
48
+ Copyright (c) 2023 Nutnornont Chamadol
49
+
50
+ Permission is hereby granted, free of charge, to any person obtaining a copy
51
+ of this software and associated documentation files (the "Software"), to deal
52
+ in the Software without restriction, including without limitation the rights
53
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
54
+ copies of the Software, and to permit persons to whom the Software is
55
+ furnished to do so, subject to the following conditions:
56
+
57
+ The above copyright notice and this permission notice shall be included in all
58
+ copies or substantial portions of the Software.
59
+
60
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
61
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
62
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
63
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
64
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
65
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
66
+ SOFTWARE.
app.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, send_file
2
+ from urllib.parse import quote as url_quote
3
+ from model import model
4
+
5
+ app = Flask(__name__)
6
+
7
+ @app.route('/process', methods=['POST'])
8
+ def process():
9
+ T2E_exam = str(request.remote_addr) + ".txt"
10
+ text = request.form['text']
11
+ cefr_level = request.form['cefr_level']
12
+
13
+ # Call your Python function here to process the data
14
+ output = model(text, cefr_level)
15
+
16
+ user_data = {cefr_level: text}
17
+ with open("user_data_log.txt", "a") as file:
18
+ file.write(str(user_data) + "\n\n")
19
+
20
+ # Save the output to a file
21
+ count = 0
22
+ max_choice = 4
23
+
24
+ with open(T2E_exam, "w") as file:
25
+ file.write("__________ T2E Vocabulary Exam Generator __________\n")
26
+ file.write("| Welcome to T2E Vocabulary Exam Generator! |\n")
27
+ file.write("| We are glad that our service is useful to you. |\n")
28
+ file.write("| |\n")
29
+ file.write("| Copyrights 2023, Nutnornont Chamadol |\n")
30
+ file.write("| Email: nontc49@gmail.com |\n")
31
+ file.write("| Visit https://nontgcob.com to learn more |\n")
32
+ file.write("| |\n")
33
+ file.write("| Your exam is generated below. |\n")
34
+ file.write("| - Happy using T2E Vocabulary Exam Generator! - |\n")
35
+ file.write("|__________________________________________________|\n")
36
+ file.write("\n")
37
+ file.write("If you don't see any text on the Result page, try changing ")
38
+ file.write("the CEFR difficulty level selected or choose ALL CEFR level ")
39
+ file.write("to make sure you get all the questions that the AI can generate. ")
40
+ file.write("Another possible reason why nothing comes out of the program is ")
41
+ file.write("because there is no word that can be turned into an exam, try ")
42
+ file.write("putting a longer text passage as an input into the textbox instead.\n")
43
+ file.write("Visit https://scribehow.com/shared/How_to_use_T2E_Vocabulary_Exam_Generator__vyYu396JT_qZ0jKATVUqeQ#89cd5f52 for more information.\n")
44
+ file.write("\n")
45
+ file.write("Note: The first choice of each question is the correct answer, the rest are trick choices!\n")
46
+ file.write("\n")
47
+ file.write("\n")
48
+
49
+ for key, value in output.items():
50
+ vvocab, sentence = key.split(" = ")
51
+ # print(f'What does the word "{vvocab}" means in this sentence "{sentence}"?')
52
+ with open(T2E_exam, "a") as file:
53
+ file.write(f'What is the meaning of the word "{vvocab}" in this sentence "{sentence}"?\n')
54
+
55
+ for choice in value:
56
+ # print(f"- {choice}")
57
+ with open(T2E_exam, "a") as file:
58
+ file.write(f"- {choice}\n")
59
+ count += 1
60
+ # if count > (max_choice + 1):
61
+ # break
62
+ with open(T2E_exam, "a") as file:
63
+ file.write("\n")
64
+
65
+ # print("output:", output)
66
+ # print(type(output))
67
+
68
+ return render_template('result.html', output=output, file_path="T2E_exam.txt")
69
+
70
+ @app.route('/')
71
+ def index():
72
+ return render_template('index.html')
73
+
74
+ @app.route('/send')
75
+ def get_file():
76
+ T2E_exam = str(request.remote_addr) + ".txt"
77
+ return send_file(
78
+ str(request.remote_addr) + ".txt",
79
+ # download_name = "T2E_exam.txt"
80
+ )
81
+
82
+ if __name__ == "__main__":
83
+ app.run(host='0.0.0.0', port=7860)
cefr-vocab.csv ADDED
The diff for this file is too large to render. See raw diff
 
model.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Importing libraries
2
+ from nltk.corpus import wordnet
3
+ import nltk
4
+ import transformers
5
+ import pandas as pd
6
+ import json
7
+ import random
8
+ import torch
9
+
10
+ device='cpu'
11
+
12
+ # Declare the (trained) model that will be used
13
+ classifier = transformers.pipeline("zero-shot-classification", model="simple_trained_wsd_pipeline", device=device)
14
+
15
+ import spacy
16
+ # Part Of Speech tagging (POS tagging)
17
+ nlp = spacy.load("en_core_web_sm")
18
+
19
+ print('successfully download model')
20
+
21
+
22
+ def model(passage, level):
23
+ # pip install spacy
24
+ # pip install transformers
25
+ # pip install torch
26
+ # pip install en_core_web_sm
27
+ # python -m spacy download en_core_web_sm
28
+ # pip install spacy-download
29
+ # pip install nltk
30
+
31
+ nltk.download('wordnet')
32
+ nltk.download('omw-1.4')
33
+
34
+ # Passing file directories into variables
35
+ # text_input = "./text_input.txt"
36
+ cefr_vocab = "cefr-vocab.csv"
37
+
38
+ # Create and open the text file
39
+ # with open(text_input, "a") as file:
40
+ # file.write(".") # Add a full stop at the end to make sure there is a full stop at the end of the text for the model to understand where to stop the sentence
41
+
42
+
43
+ # Ask the user for the CEFR level
44
+ # while True:
45
+ # cefr_level = input("Which CEFR level you want to test?: ").upper()
46
+ # if "A1" in cefr_level or "A2" in cefr_level or "B1" in cefr_level or "B2" in cefr_level or "C1" in cefr_level or "C2" in cefr_level:
47
+ # break
48
+ # else:
49
+ # continue
50
+ cefr_level = level
51
+
52
+ # Read from the input file
53
+ # with open(text_input, "r") as file:
54
+ # txt = str(file.readlines()).replace("[", "").replace("'", "").replace("]", "")
55
+ txt = passage + "."
56
+
57
+ if "." in txt:
58
+ txt = (txt.split("."))
59
+ else:
60
+ txt = txt
61
+
62
+ text_dict = {}
63
+ for n in txt:
64
+ n = n.strip()
65
+ ex1 = nlp(n)
66
+
67
+ for word in ex1:
68
+ sentence_question_tag = n.replace(word.text, f"[{word.text}]")
69
+ text_dict[f"{word.lemma_} = {sentence_question_tag}"] = word.pos_
70
+
71
+ # Collect the tagging results (filter in just NOUN, PROPN, VERB, ADJ, or ADV only)
72
+ collector = {}
73
+ for key, value in text_dict.items():
74
+ if "NOUN" in value or "VERB" in value or "ADJ" in value or "ADV" in value:
75
+ collector[key] = value
76
+
77
+ # Collect the CEFR level of the words collected before
78
+ reference = pd.read_csv(cefr_vocab)
79
+
80
+ matching = {}
81
+ for row_idx in range(reference.shape[0]):
82
+ row = reference.iloc[row_idx]
83
+ key = f"{row.headword}, {row.pos}"
84
+ matching[key] = row.CEFR
85
+
86
+ # Convert pos of the word into all lowercase to match another data set with CEFR level
87
+ for key1, value1 in collector.items():
88
+ if value1 == "NOUN":
89
+ collector[key1] = "noun"
90
+ if value1 == "VERB":
91
+ collector[key1] = "verb"
92
+ if value1 == "ADJ":
93
+ collector[key1] = "adjective"
94
+ if value1 == "ADV":
95
+ collector[key1] = "adverb"
96
+
97
+ # Matching 2 datasets together by the word and the pos
98
+ ready2filter = {}
99
+ for key, value in matching.items():
100
+ first_key, second_key = key.split(", ")
101
+ for key2, value2 in collector.items():
102
+ key2 = key2.split(" = ")
103
+ if first_key == key2[0].lower():
104
+ if second_key == value2:
105
+ ready2filter[f"{key} = {key2[1]}"] = value
106
+
107
+ # Filter in just the vocab that has the selected CEFR level that the user provided at the beginning
108
+ filtered0 = {}
109
+ for key, value in ready2filter.items():
110
+ if cefr_level == "ALL":
111
+ filtered0[key] = value
112
+ else:
113
+ if value == cefr_level:
114
+ filtered0[key] = value
115
+
116
+ # Rearrange the Python dictionary structure
117
+ filtered = {}
118
+ for key, value in filtered0.items():
119
+ key_parts = key.split(', ')
120
+ new_key = key_parts[0]
121
+ new_value = key_parts[1]
122
+ filtered[new_key] = new_value
123
+
124
+ # Grab the definition of each vocab from the NLTK wordnet English dictionary
125
+ def_filtered = {}
126
+ for key3, value3 in filtered.items():
127
+ syns = wordnet.synsets(key3)
128
+ partofspeech, context = value3.split(" = ")
129
+ def_filtered[f"{key3} = {context}"] = []
130
+
131
+ # pos conversion
132
+ if partofspeech == "noun":
133
+ partofspeech = "n"
134
+ if partofspeech == "verb":
135
+ partofspeech = "v"
136
+ if partofspeech == "adjective":
137
+ partofspeech = "s"
138
+ if partofspeech == "adverb":
139
+ partofspeech = "r"
140
+
141
+ # print("def_filtered 0:", def_filtered)
142
+
143
+ # Adding the definitions into the Python dictionary, def_filtered (syns variable does the job of finding the relevant word aka synonyms)
144
+ for s in syns:
145
+ # print('s:', s)
146
+ # print("syns:", syns)
147
+ def_filtered[f"{key3} = {context}"].append(s.definition())
148
+ # print("def_filtered 1:", def_filtered)
149
+
150
+ # Use Nvidia CUDA core if available
151
+ # if torch.cuda.is_available():
152
+ # device=0
153
+ # else:
154
+
155
+
156
+ # Process Python dictionary, def_filtereddic
157
+ correct_def = {}
158
+ for key4, value4 in def_filtered.items():
159
+ vocab, context = key4.split(" = ")
160
+ sequence_to_classify = context
161
+ candidate_labels = value4
162
+ # correct_def[key4] = []
163
+ correct_def_list = []
164
+ temp_def = []
165
+ hypothesis_template = 'The meaning of [' + vocab + '] is {}.'
166
+
167
+ output = classifier(sequence_to_classify, candidate_labels, hypothesis_template=hypothesis_template)
168
+
169
+ # Process the score of each definition and add it to the Python dictionary, correct_def
170
+ for label, score in zip(output['labels'], output['scores']):
171
+ temp_def.append(label)
172
+ # print(temp_def)
173
+ for first in range(len(temp_def)):
174
+ if first == 0:
175
+ val = f">> {temp_def[first]}"
176
+ else:
177
+ val = f"{temp_def[first]}"
178
+
179
+ correct_def_list.append(val)
180
+
181
+ print(type(key4), type(correct_def_list))
182
+ correct_def[key4] = correct_def_list
183
+
184
+ # correct_def[key4].append(f"{label}")
185
+
186
+ return correct_def
187
+
188
+ # with open(T2E_exam, "r") as file:
189
+ # exam = file.readlines()
190
+ # print(exam)
191
+ # return(exam)
192
+
193
+
194
+ # passage = "Computer is good"
195
+ # level = "A1"
196
+ # print(model(passage, level))
modules/dataset.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datasets import load_dataset
2
+
3
+ dataset = load_dataset("go_emotions", split="train")
4
+
5
+ emotions = dataset.info.features['labels'].feature.names
6
+
7
+ def query_emotion(start, end):
8
+ rows = dataset[start:end]
9
+ texts, labels = [rows[k] for k in rows.keys()]
10
+
11
+ observations = []
12
+
13
+ for i, text in enumerate(texts):
14
+ observations.append({
15
+ "text": text,
16
+ "emotion": emotions[labels[i]],
17
+ })
18
+
19
+ return observations
modules/inference.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import T5Tokenizer, T5ForConditionalGeneration
2
+
3
+ tokenizer = T5Tokenizer.from_pretrained("t5-small")
4
+ model = T5ForConditionalGeneration.from_pretrained("t5-small")
5
+
6
+
7
+ def infer_t5(input):
8
+ input_ids = tokenizer(input, return_tensors="pt").input_ids
9
+ outputs = model.generate(input_ids)
10
+
11
+ return tokenizer.decode(outputs[0], skip_special_tokens=True)
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ datasets==2.*
2
+ flask==2.2.3
3
+ requests==2.27.*
4
+ sentencepiece==0.1.*
5
+ torch==2.*
6
+ transformers==4.*
7
+ gunicorn==20.1.*
8
+ waitress==2.0.*
9
+ nltk==3.8.1
10
+ spacy==3.5.3
11
+ numpy<2.0
12
+ Werkzeug==2.2.2
13
+ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.5.0/en_core_web_sm-3.5.0-py3-none-any.whl
simple_trained_wsd_pipeline/config.json ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_name_or_path": "facebook/bart-large-mnli",
3
+ "_num_labels": 3,
4
+ "activation_dropout": 0.0,
5
+ "activation_function": "gelu",
6
+ "add_final_layer_norm": false,
7
+ "architectures": [
8
+ "BartForSequenceClassification"
9
+ ],
10
+ "attention_dropout": 0.0,
11
+ "bos_token_id": 0,
12
+ "classif_dropout": 0.0,
13
+ "classifier_dropout": 0.0,
14
+ "d_model": 1024,
15
+ "decoder_attention_heads": 16,
16
+ "decoder_ffn_dim": 4096,
17
+ "decoder_layerdrop": 0.0,
18
+ "decoder_layers": 12,
19
+ "decoder_start_token_id": 2,
20
+ "dropout": 0.1,
21
+ "encoder_attention_heads": 16,
22
+ "encoder_ffn_dim": 4096,
23
+ "encoder_layerdrop": 0.0,
24
+ "encoder_layers": 12,
25
+ "eos_token_id": 2,
26
+ "forced_eos_token_id": 2,
27
+ "gradient_checkpointing": false,
28
+ "id2label": {
29
+ "0": "contradiction",
30
+ "1": "neutral",
31
+ "2": "entailment"
32
+ },
33
+ "init_std": 0.02,
34
+ "is_encoder_decoder": true,
35
+ "label2id": {
36
+ "contradiction": 0,
37
+ "entailment": 2,
38
+ "neutral": 1
39
+ },
40
+ "max_position_embeddings": 1024,
41
+ "model_type": "bart",
42
+ "normalize_before": false,
43
+ "num_hidden_layers": 12,
44
+ "output_past": false,
45
+ "pad_token_id": 1,
46
+ "problem_type": "single_label_classification",
47
+ "scale_embedding": false,
48
+ "torch_dtype": "float32",
49
+ "transformers_version": "4.29.1",
50
+ "use_cache": true,
51
+ "vocab_size": 50265
52
+ }
simple_trained_wsd_pipeline/merges.txt ADDED
The diff for this file is too large to render. See raw diff
 
simple_trained_wsd_pipeline/pytorch_model.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ebe92cb602c9187d2ac775c5f0d98827ab9291293307c1a5090efae8ed94f251
3
+ size 1629551961
simple_trained_wsd_pipeline/special_tokens_map.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token": "<s>",
3
+ "cls_token": "<s>",
4
+ "eos_token": "</s>",
5
+ "mask_token": {
6
+ "content": "<mask>",
7
+ "lstrip": true,
8
+ "normalized": false,
9
+ "rstrip": false,
10
+ "single_word": false
11
+ },
12
+ "pad_token": "<pad>",
13
+ "sep_token": "</s>",
14
+ "unk_token": "<unk>"
15
+ }
simple_trained_wsd_pipeline/tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
simple_trained_wsd_pipeline/tokenizer_config.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "bos_token": "<s>",
4
+ "clean_up_tokenization_spaces": true,
5
+ "cls_token": "<s>",
6
+ "eos_token": "</s>",
7
+ "errors": "replace",
8
+ "mask_token": "<mask>",
9
+ "model_max_length": 1024,
10
+ "pad_token": "<pad>",
11
+ "sep_token": "</s>",
12
+ "tokenizer_class": "BartTokenizer",
13
+ "trim_offsets": true,
14
+ "unk_token": "<unk>"
15
+ }
simple_trained_wsd_pipeline/vocab.json ADDED
The diff for this file is too large to render. See raw diff
 
static/index.js ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ if (document.location.search.includes('dark-theme=true')) {
2
+ document.body.classList.add('dark-theme');
3
+ }
4
+
5
+ let cursor = 0;
6
+ const RANGE = 5;
7
+ const LIMIT = 16_000;
8
+
9
+ const textToImage = async (text) => {
10
+ const inferenceResponse = await fetch(`infer_biggan?input=${text}`);
11
+ const inferenceBlob = await inferenceResponse.blob();
12
+
13
+ return URL.createObjectURL(inferenceBlob);
14
+ };
15
+
16
+ const translateText = async (text) => {
17
+ const inferResponse = await fetch(`infer_t5?input=${text}`);
18
+ const inferJson = await inferResponse.json();
19
+
20
+ return inferJson.output;
21
+ };
22
+
23
+ const queryDataset = async (start, end) => {
24
+ const queryResponse = await fetch(`query_emotion?start=${start}&end=${end}`);
25
+ const queryJson = await queryResponse.json();
26
+
27
+ return queryJson.output;
28
+ };
29
+
30
+ const updateTable = async (cursor, range = RANGE) => {
31
+ const table = document.querySelector('.dataset-output');
32
+
33
+ const fragment = new DocumentFragment();
34
+
35
+ const observations = await queryDataset(cursor, cursor + range);
36
+
37
+ for (const observation of observations) {
38
+ let row = document.createElement('tr');
39
+ let text = document.createElement('td');
40
+ let emotion = document.createElement('td');
41
+
42
+ text.textContent = observation.text;
43
+ emotion.textContent = observation.emotion;
44
+
45
+ row.appendChild(text);
46
+ row.appendChild(emotion);
47
+ fragment.appendChild(row);
48
+ }
49
+
50
+ table.innerHTML = '';
51
+
52
+ table.appendChild(fragment);
53
+
54
+ table.insertAdjacentHTML(
55
+ 'afterbegin',
56
+ `<thead>
57
+ <tr>
58
+ <td>text</td>
59
+ <td>emotion</td>
60
+ </tr>
61
+ </thead>`
62
+ );
63
+ };
64
+
65
+ const imageGenSelect = document.getElementById('image-gen-input');
66
+ const imageGenImage = document.querySelector('.image-gen-output');
67
+ const textGenForm = document.querySelector('.text-gen-form');
68
+ const tableButtonPrev = document.querySelector('.table-previous');
69
+ const tableButtonNext = document.querySelector('.table-next');
70
+
71
+ imageGenSelect.addEventListener('change', async (event) => {
72
+ const value = event.target.value;
73
+
74
+ try {
75
+ imageGenImage.src = await textToImage(value);
76
+ imageGenImage.alt = value + ' generated from BigGAN AI model';
77
+ } catch (err) {
78
+ console.error(err);
79
+ }
80
+ });
81
+
82
+ textGenForm.addEventListener('submit', async (event) => {
83
+ event.preventDefault();
84
+
85
+ const textGenInput = document.getElementById('text-gen-input');
86
+ const textGenParagraph = document.querySelector('.text-gen-output');
87
+
88
+ try {
89
+ textGenParagraph.textContent = await translateText(textGenInput.value);
90
+ } catch (err) {
91
+ console.error(err);
92
+ }
93
+ });
94
+
95
+ tableButtonPrev.addEventListener('click', () => {
96
+ cursor = cursor > RANGE ? cursor - RANGE : 0;
97
+
98
+ if (cursor < RANGE) {
99
+ tableButtonPrev.classList.add('hidden');
100
+ }
101
+ if (cursor < LIMIT - RANGE) {
102
+ tableButtonNext.classList.remove('hidden');
103
+ }
104
+
105
+ updateTable(cursor);
106
+ });
107
+
108
+ tableButtonNext.addEventListener('click', () => {
109
+ cursor = cursor < LIMIT - RANGE ? cursor + RANGE : cursor;
110
+
111
+ if (cursor >= RANGE) {
112
+ tableButtonPrev.classList.remove('hidden');
113
+ }
114
+ if (cursor >= LIMIT - RANGE) {
115
+ tableButtonNext.classList.add('hidden');
116
+ }
117
+
118
+ updateTable(cursor);
119
+ });
120
+
121
+ textToImage(imageGenSelect.value)
122
+ .then((image) => (imageGenImage.src = image))
123
+ .catch(console.error);
124
+
125
+ updateTable(cursor)
126
+ .catch(console.error);
static/style.css ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ --text: hsl(0 0% 15%);
3
+ padding: 2.5rem;
4
+ font-family: sans-serif;
5
+ color: var(--text);
6
+ }
7
+ body.dark-theme {
8
+ --text: hsl(0 0% 90%);
9
+ background-color: hsl(223 39% 7%);
10
+ }
11
+
12
+ main {
13
+ max-width: 80rem;
14
+ text-align: center;
15
+ }
16
+
17
+ section {
18
+ display: flex;
19
+ flex-direction: column;
20
+ align-items: center;
21
+ }
22
+
23
+ a {
24
+ color: var(--text);
25
+ }
26
+
27
+ select, input, button, .text-gen-output {
28
+ padding: 0.5rem 1rem;
29
+ }
30
+
31
+ select, img, input {
32
+ margin: 0.5rem auto 1rem;
33
+ }
34
+
35
+ form {
36
+ width: 25rem;
37
+ margin: 0 auto;
38
+ }
39
+
40
+ input {
41
+ width: 70%;
42
+ }
43
+
44
+ button {
45
+ cursor: pointer;
46
+ }
47
+
48
+ .text-gen-output {
49
+ min-height: 1.2rem;
50
+ margin: 1rem;
51
+ border: 0.5px solid grey;
52
+ }
53
+
54
+ #dataset button {
55
+ width: 6rem;
56
+ margin: 0.5rem;
57
+ }
58
+
59
+ #dataset button.hidden {
60
+ visibility: hidden;
61
+ }
62
+
63
+ table {
64
+ max-width: 40rem;
65
+ text-align: left;
66
+ border-collapse: collapse;
67
+ }
68
+
69
+ thead {
70
+ font-weight: bold;
71
+ }
72
+
73
+ td {
74
+ padding: 0.5rem;
75
+ }
76
+
77
+ td:not(thead td) {
78
+ border: 0.5px solid grey;
79
+ }
templates/index.html ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Input Form | Text to Exam (Vocabulary Exam Generator)</title>
5
+ <link rel="preconnect" href="https://fonts.googleapis.com">
6
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
7
+ <link href="https://fonts.googleapis.com/css2?family=Rubik:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
8
+ <style>
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ font-family: 'Rubik', sans-serif;
13
+ background-color: #1B1B1B;
14
+ color: #FFF7D0;
15
+ font-size: 62.5%;
16
+ box-sizing: border-box;
17
+ }
18
+
19
+ .container {
20
+ padding: 3rem 3.4rem;
21
+ background-color: #212529;
22
+ border-radius: 24px;
23
+ height: 50rem;
24
+ width: 38rem;
25
+ margin: 0 auto;
26
+ margin-top: 4%;
27
+ }
28
+
29
+ h1 {
30
+ font-size: 3.2rem;
31
+ text-align: center;
32
+ background-color: transparent;
33
+ margin-bottom: .6rem;
34
+ color: #FFE66C;
35
+ }
36
+
37
+ /* Custom Scrollbar */
38
+ /* width */
39
+ ::-webkit-scrollbar {
40
+ width: .8rem;
41
+ }
42
+ /* Track */
43
+ ::-webkit-scrollbar-track {
44
+ border-radius: 10px;
45
+ /* background: #2f3439; */
46
+ }
47
+ /* Scroller */
48
+ ::-webkit-scrollbar-thumb {
49
+ background: #FFF7D0;
50
+ border-radius: 10px;
51
+ }
52
+
53
+ .subtitle {
54
+ background-color: transparent;
55
+ font-size: 1.6rem;
56
+ text-align: center;
57
+ margin-bottom: 1.5rem;
58
+ }
59
+
60
+ .docs-tutorial {
61
+ padding: .6rem 32%;
62
+ font-size: 1.6rem;
63
+ text-decoration: none;
64
+ border-radius: 10px;
65
+ color: #FFE66C;
66
+ background-color: #33363A;
67
+ transition: .3s ease-out;
68
+ }
69
+
70
+ .docs-tutorial:hover {
71
+ background-color: #FFE66C;
72
+ color: #212529;
73
+ }
74
+
75
+ .form {
76
+ margin-top: .8rem;
77
+ background-color: transparent;
78
+ width: 100%;
79
+ }
80
+
81
+ .textbox {
82
+ border: 2px solid #FFF7D0;
83
+ width: 100%;
84
+ height: 20rem;
85
+ background-color: transparent;
86
+ padding: 1.1rem;
87
+ font-size: 1.6rem;
88
+ border-radius: 13px;
89
+ margin: 1rem 0;
90
+ }
91
+
92
+ .level {
93
+ font-size: 1.6rem;
94
+ margin-right: .6rem;
95
+ background-color: transparent;
96
+ }
97
+
98
+ .cefr {
99
+ font-size: 1.6rem;
100
+ border: 2px solid #FFF7D0;
101
+ border-radius: 8px;
102
+ padding: .7rem;
103
+ width: 53.6%;
104
+ background-color: transparent;
105
+ }
106
+
107
+ .generate {
108
+ padding: .5rem;
109
+ font-size: 2rem;
110
+ border-radius: 20px;
111
+ background-color: #FFF7D0;
112
+ color: #212529;
113
+ font-weight: 600;
114
+ width: 100%;
115
+ margin-top: 1rem;
116
+ transition: .2s ease-out;
117
+ }
118
+
119
+ .generate:hover {
120
+ background-color: #FFE66C;
121
+ }
122
+
123
+ .dev {
124
+ background-color: transparent;
125
+ padding: 1rem 3rem;
126
+ width: 38rem;
127
+ margin: 0 auto;
128
+ margin-top: 10px;
129
+ margin-bottom: 1.2%;
130
+ border-radius: 40px;
131
+ border: 1px solid #33363A;
132
+ display: flex;
133
+ flex-direction: row;
134
+ font-size: 2.6rem;
135
+ display: flex;
136
+ justify-content: center;
137
+ background-color: #212529;
138
+ text-decoration: none;
139
+ transition: .2s;
140
+ }
141
+
142
+ .dev:hover {
143
+ /* background-color: #33363A; */
144
+ border: 1px solid #FFF7D0;
145
+ }
146
+
147
+ .credit {
148
+ width: fit-content;
149
+ margin-right: 3px;
150
+ background-color: transparent;
151
+ }
152
+ </style>
153
+ </head>
154
+ <body>
155
+ <div class="container">
156
+ <h1>T2E Vocabulary Exam Generator</h1>
157
+ <p class="subtitle">Generate Vocabulary Exam from context.</p>
158
+ <a class="docs-tutorial" target="_blank" href="https://scribehow.com/shared/How_to_use_T2E_Vocabulary_Exam_Generator__vyYu396JT_qZ0jKATVUqeQ">Docs & Tutorial</a>
159
+ <form action="/process" method="POST" class="form">
160
+ <textarea name="text" id="text" cols="90" rows="30" class="textbox" placeholder="Enter text here (i.e. Computer is good.)"></textarea>
161
+ <br>
162
+ <label for="cefr_level" class="level">Select CEFR level :</label>
163
+ <select name="cefr_level" id="cefr_level" class="cefr">
164
+ <option value="ALL">ALL</option>
165
+ <option value="A1">A1</option>
166
+ <option value="A2">A2</option>
167
+ <option value="B1">B1</option>
168
+ <option value="B2">B2</option>
169
+ <option value="C1">C1</option>
170
+ <option value="C2">C2</option>
171
+ </select>
172
+ <br>
173
+ <button id="changeText" class="generate">Generate</button>
174
+ </form>
175
+ </div>
176
+ <a href="https://nontgcob.com/" target="_blank" class="dev">
177
+ <p class="credit">Developed by Nutnornont Chamadol</p>
178
+ </a>
179
+
180
+ <script>
181
+ const btn = document.getElementById('changeText');
182
+
183
+ // Change button text on click
184
+ btn.addEventListener('click', function() {
185
+ btn.textContent = 'Generating exam...';
186
+ });
187
+ </script>
188
+ </body>
189
+ </html>
templates/result.html ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Result | Text to Exam (Vocabulary Exam Generator)</title>
5
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
6
+ <link rel="preconnect" href="https://fonts.googleapis.com">
7
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
8
+ <link href="https://fonts.googleapis.com/css2?family=Rubik:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
9
+ <style>
10
+ * {
11
+ margin: 0;
12
+ padding: 0;
13
+ font-family: 'Rubik', sans-serif;
14
+ background-color: #1B1B1B;
15
+ color: #FFF7D0;
16
+ font-size: 62.5%;
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ .container {
21
+ padding: 3rem 3.4rem;
22
+ background-color: #212529;
23
+ border-radius: 24px;
24
+ height: fit-content;
25
+ max-height: 53rem;
26
+ width: 80%;
27
+ margin: 0 auto;
28
+ margin-top: 5%;
29
+ }
30
+
31
+ .topbar {
32
+ display: flex;
33
+ flex-direction: row;
34
+ justify-content: space-between;
35
+ background-color: transparent;
36
+ }
37
+
38
+ h1 {
39
+ font-size: 3.5rem;
40
+ text-align: center;
41
+ background-color: transparent;
42
+ color: #FFE66C;
43
+ }
44
+
45
+ .exam {
46
+ background-color: transparent;
47
+ height: fit-content;
48
+ max-height: 34rem;
49
+ overflow-y: scroll;
50
+ margin-top: 2rem;
51
+ }
52
+
53
+ .generate_another {
54
+ background-color: #ffffff27;
55
+ width: fit-content;
56
+ height: fit-content;
57
+ padding: 1rem;
58
+ font-size: 1.8rem;
59
+ border-radius: 10px;
60
+ position: absolute;
61
+ right: 0;
62
+ margin-top: -7rem;
63
+ margin-right: 12%;
64
+ text-decoration: none;
65
+ border: 2px solid #FFE76C8D;
66
+ transition: .3s ease-out;
67
+ color: #FFE66C;
68
+ }
69
+
70
+ .generate_another:hover {
71
+ border: 2px solid #FFE66C;
72
+ background-color: #ffe76c22;
73
+ }
74
+
75
+ /* Custom Scrollbar */
76
+ /* width */
77
+ ::-webkit-scrollbar {
78
+ width: .8rem;
79
+ }
80
+ /* Track */
81
+ ::-webkit-scrollbar-track {
82
+ border-radius: 10px;
83
+ /* background: #2f3439; */
84
+ }
85
+ /* Scroller */
86
+ ::-webkit-scrollbar-thumb {
87
+ background: #FFF7D0;
88
+ border-radius: 10px;
89
+ }
90
+
91
+ .note {
92
+ background-color: transparent;
93
+ margin: 1.5rem 0;
94
+ border: 1px solid #FF0000;
95
+ border-radius: 10px;
96
+ padding: 1rem;
97
+ display: flex;
98
+ flex-direction: row;
99
+ width: 100%;
100
+ background-color: #ff000010;
101
+ }
102
+
103
+ i {
104
+ margin-right: 1rem;
105
+ }
106
+
107
+ .alert {
108
+ font-size: 1.6rem;
109
+ color: #FF0000;
110
+ background-color: transparent;
111
+ }
112
+
113
+ .question {
114
+ font-size: 1.8rem;
115
+ font-weight: 400;
116
+ background-color: transparent;
117
+ }
118
+
119
+ ul {
120
+ background-color: transparent;
121
+ }
122
+
123
+ .choice {
124
+ font-size: 1.8rem;
125
+ margin-left: 2rem;
126
+ background-color: transparent;
127
+ }
128
+
129
+ .question_divider {
130
+ height: 2rem;
131
+ background-color: transparent;
132
+ }
133
+
134
+ .download {
135
+ font-size: 2rem;
136
+ border: 2px solid #FFF7D0;
137
+ border-radius: 8px;
138
+ background-color: #FFF7D0;
139
+ color: #212529;
140
+ font-weight: 500;
141
+ padding: .5rem 1.2rem;
142
+ width: fit-content;
143
+ height: fit-content;
144
+ text-decoration: none;
145
+ }
146
+
147
+ .dev {
148
+ background-color: transparent;
149
+ padding: 1rem 3rem;
150
+ width: 38rem;
151
+ margin: 0 auto;
152
+ margin-top: 10px;
153
+ margin-bottom: 1.2%;
154
+ border-radius: 40px;
155
+ border: 1px solid #33363A;
156
+ display: flex;
157
+ flex-direction: row;
158
+ font-size: 2.6rem;
159
+ display: flex;
160
+ justify-content: center;
161
+ background-color: #212529;
162
+ text-decoration: none;
163
+ transition: .2s;
164
+ }
165
+
166
+ .dev:hover {
167
+ /* background-color: #33363A; */
168
+ border: 1px solid #FFF7D0;
169
+ }
170
+
171
+ .credit {
172
+ width: fit-content;
173
+ margin-right: 3px;
174
+ background-color: transparent;
175
+ }
176
+ </style>
177
+ </head>
178
+ <body>
179
+ <div class="container">
180
+ <div class="topbar">
181
+ <h1>Results</h1>
182
+ <a href="/send" class="download" download>Download</a>
183
+ </div>
184
+
185
+ <div class="exam">
186
+ {% if not output %}
187
+ <div class="note">
188
+ <i class="alert fa-solid fa-triangle-exclamation"></i>
189
+ <p class="alert">Oops! Something went wrong. Try changing the CEFR difficulty level or choose ALL CEFR level. You might as well try putting a longer text passage next time.
190
+ <a class="alert" target="_blank" href="https://scribehow.com/shared/How_to_use_T2E_Vocabulary_Exam_Generator__vyYu396JT_qZ0jKATVUqeQ#89cd5f52">Read more</a>
191
+ </p>
192
+ </div>
193
+ {% endif %}
194
+ {% for key, values in output.items() %}
195
+ <h2 class="question">What is the meaning of the word "{{ key.split(' = ')[0] }}" in this sentence "{{ key.split(' = ')[1] }}"?</h2>
196
+ <ul>
197
+ {% for value in values %}
198
+ <li class="choice">{{ value }}</li>
199
+ {% endfor %}
200
+ </ul>
201
+ <div class="question_divider"></div>
202
+ {% endfor %}
203
+ </div>
204
+ </div>
205
+ <a class="generate_another" href="/">Generate another exam</a>
206
+ <a href="https://nontgcob.com/" target="_blank" class="dev">
207
+ <p class="credit">Developed by Nutnornont Chamadol</p>
208
+ </a>
209
+ </body>
210
+ </html>