JpVocab / app.py
penut85420's picture
add select last & select random
92bac5a
import json
import os
import random
from dataclasses import asdict, dataclass
import gradio as gr
import pandas as pd
def launch_app():
app = App()
app.launch()
@dataclass
class Vocab:
kana: str
kanji: str
meaning: str
def info(self):
info = (self.kanji, self.kana, self.meaning)
return " - ".join(i for i in info if i)
class App:
def __init__(self):
vocab = [[Vocab(**vv) for vv in v] for v in load_json("data/vocab.json")]
ch = [f"Ch. {i:02d}" for i, _ in enumerate(vocab, 1)]
self.vocab: dict[str, list[Vocab]] = dict(zip(ch, vocab))
self.__init_app()
def __init_app(self):
with self.__init_blocks() as self.app:
self.__init_states()
self.__init_layout()
self.__init_events()
def __init_blocks(self):
font = gr.themes.GoogleFont("Noto Sans Mono")
text_size = gr.themes.sizes.text_md
theme = gr.themes.Soft(font=font, text_size=text_size)
return gr.Blocks(title="日文單字小測驗", theme=theme)
def __init_states(self):
self.question_list = gr.State(None)
self.curr_item = gr.State(None)
self.correct = gr.State(0)
self.total = gr.State(0)
self.last_item = gr.State(None)
def __init_layout(self):
with gr.Tabs() as self.tabs:
with gr.Tab("設定", id=0):
self.__init_chapters()
with gr.Tab("測驗", id=1):
self.__init_quiz()
with gr.Tab("紀錄", id=2):
self.__init_record()
with gr.Tab("單字"):
self.__init_vocab()
def __init_chapters(self):
ch = sorted(list(self.vocab.keys()))
self.chapters = gr.CheckboxGroup(ch, label="章節")
with gr.Row():
self.select_all_btn = gr.Button("全選")
self.deselect_all_btn = gr.Button("全不選")
with gr.Row():
self.select_last_btn = gr.Button("選擇倒數章節")
self.select_rand_btn = gr.Button("選擇隨機章節")
self.select_n_ch = gr.Slider(1, len(ch), 6, step=1, label="章節數量")
self.start_btn = gr.Button("開始測驗")
def __init_quiz(self):
desc = "完成設定後按下「開始測驗」"
with gr.Row():
self.question = gr.Textbox(placeholder=desc, label="題目", interactive=False)
self.score = gr.Textbox("0/0", label="分數", submit_btn=True)
with gr.Row():
self.answer = gr.Textbox(label="作答", submit_btn=True)
self.audio = gr.Audio(
type="filepath",
label="發音",
autoplay=True,
show_download_button=False,
show_share_button=False,
editable=False,
)
def __init_record(self):
with gr.Row():
self.record = gr.TextArea(show_label=False, lines=15)
with gr.Row():
self.again_btn = gr.Button("再次測驗")
self.back_to_setting_btn = gr.Button("回到設定")
def __init_vocab(self):
for ch in self.vocab:
with gr.Tab(ch):
df = pd.DataFrame([asdict(v) for v in self.vocab[ch]])
df = df[["meaning", "kana", "kanji"]]
df = df.rename(columns=dict(meaning="意思", kana="假名", kanji="漢字"))
gr.Dataframe(df)
def __init_events(self):
init_inns = [self.chapters]
init_outs = [self.question_list, self.tabs]
init_args = gr_args(self.init_questions, init_inns, init_outs)
reset_outs = [self.score, self.correct, self.total, self.record, self.audio]
reset_args = gr_args(self.reset_score, None, reset_outs)
next_inns = [self.question_list]
next_outs = [self.curr_item, self.question, self.question_list, self.answer]
next_args = gr_args(self.next_question, next_inns, next_outs)
check_inns = [self.answer, self.curr_item, self.correct]
check_inns += [self.total, self.record, self.question_list]
check_outs = [self.score, self.correct, self.total, self.record]
check_outs += [self.tabs, self.last_item, self.audio]
check_args = gr_args(self.check, check_inns, check_outs)
back_args = gr_args(self.back_to_setting, None, self.tabs)
read_args = gr_args(self.read_vocab, self.last_item, self.audio)
select_all_args = gr_args(self.select_all, None, self.chapters)
deselect_all_args = gr_args(self.deselect_all, None, self.chapters)
select_last_six_args = gr_args(self.select_last_six, self.select_n_ch, self.chapters)
select_rand_six_args = gr_args(self.select_rand_six, self.select_n_ch, self.chapters)
self.start_btn.click(**init_args).then(**reset_args).then(**next_args)
self.again_btn.click(**init_args).then(**reset_args).then(**next_args)
self.answer.submit(**check_args).then(**next_args)
self.back_to_setting_btn.click(**back_args)
self.score.submit(**read_args)
self.select_all_btn.click(**select_all_args)
self.deselect_all_btn.click(**deselect_all_args)
self.select_last_btn.click(**select_last_six_args)
self.select_rand_btn.click(**select_rand_six_args)
def init_questions(self, chapters):
question_list = [v for ch in chapters for v in self.vocab[ch]]
random.shuffle(question_list)
selected_tab = gr.Tabs(selected=1) if question_list else gr.Tabs(selected=0)
return question_list, selected_tab
def next_question(self, question_list: list[Vocab]):
if question_list:
item = question_list.pop()
return item, item.meaning, question_list, None
return None, None, None, None
def check(self, answer: str, item: Vocab, correct, total, record, question_list):
answer = answer.strip()
total += 1
if answer == item.kana:
correct += 1
info = f"{correct}/{total} - 正確"
elif item.kanji is not None and answer == item.kanji:
correct += 1
info = f"{correct}/{total} - 正確"
else:
info = f"錯誤 {answer} => {item.info()}"
record = f"{record}{info}\n"
info = f"{correct}/{total} - {info}"
if not question_list:
record = f"{record}此輪得分 - {correct}/{total}"
tab_idx = 1 if question_list else 2
tab_idx = gr.Tabs(selected=tab_idx)
return info, correct, total, record, tab_idx, item.kana, self.read_vocab(item.kana)
def reset_score(self):
return "0/0", 0, 0, None, None
def back_to_setting(self):
return gr.Tabs(selected=0)
def read_vocab(self, text):
return os.path.join("data", "tts", f"{text}.mp3")
def select_all(self):
return list(self.vocab.keys())
def deselect_all(self):
return list()
def select_last_six(self, n):
return list(self.vocab.keys())[-n:]
def select_rand_six(self, n):
return random.sample(list(self.vocab.keys()), n)
def launch(self):
self.app.launch()
def gr_args(fn, inputs=None, outputs=None, show_progress="hidden"):
return dict(fn=fn, inputs=inputs, outputs=outputs, show_progress=show_progress)
def load_json(path):
with open(path, "rt", encoding="UTF-8") as fp:
return json.load(fp)
if __name__ == "__main__":
launch_app()