diff --git a/README.md b/README.md index b87f3fd..b8feab1 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,7 @@ - 关于云端文档源实现, 请参阅 "潜进-R" (HeurRepo) 仓库 ## 概述 - -"潜进" (HeurAMS) 是为习题册, 古诗词, 及其他问答/记忆/理解型知识设计的辅助记忆软件, 提供动态规划的优化记忆方案 +"潜进" (HeurAMS: Heuristic Auxiliary Memorizing Scheduler, 启发式记忆辅助调度器) 是为习题册, 古诗词, 及其他问答/记忆/理解型知识设计的辅助记忆软件, 提供动态规划的优化记忆方案 ## 技术集成与特性 @@ -35,7 +34,7 @@ - 支持触屏/鼠标/键盘多操作模式 - 简洁直观的复习流程设计 -## 屏幕截图 +## 屏幕截图 (基本用户界面) > 单击图片以放大 diff --git a/main.py b/main.py index 670805c..dbe0e7c 100644 --- a/main.py +++ b/main.py @@ -1,247 +1,8 @@ -from textual.app import App, ComposeResult -from textual.widgets import ( - Header, - Footer, - ListView, - ProgressBar, - DirectoryTree, - ListItem, - Label, - Static, - Button, -) -from textual.containers import Container, Horizontal, Center -from textual.screen import Screen -import pathlib -import threading -import edge_tts as tts -from playsound import playsound -import particles as pt -from reactor import Reactor, Apparatus -import auxiliary as aux -import compositions as compo -import builtins +from textual.app import App +from screens import FileSelectorScreen ver = "0.3.2" -config = aux.ConfigFile("config.toml") - - -class MemScreen(Screen): - BINDINGS = [ - ("d", "toggle_dark", "改变色调"), - ("q", "pop_screen", "返回主菜单"), - ("v", "play_voice", "朗读"), - ] - if config.get("quick_pass"): - BINDINGS.append(("k", "quick_pass", "快速通过[调试]")) - btn = dict() - - def __init__( - self, - nucleon_file: pt.NucleonUnion, - electron_file: pt.ElectronUnion, - tasked_num, - ): - super().__init__(name=None, id=None, classes=None) - self.reactor = Reactor(nucleon_file, electron_file, self, tasked_num) - self.stage = 1 - self.stage += self.reactor.set_round_templated(self.stage) - self.reactor.forward() - self.compo = next(self.reactor.current_appar) - - def compose(self) -> ComposeResult: - if type(self.compo).__name__ == "Recognition": - self.action_play_voice() - yield Header(show_clock=True) - with Center(): - yield Static( - f"{len(self.reactor.procession) - self.reactor.index}/{len(self.reactor.procession)}" - ) - yield from self.compo.compose() - yield Footer() - - def on_mount(self): - pass - - def on_button_pressed(self, event): - ret = self.compo.handler(event, "button") - self._forward_judge(ret) - - def _forward_judge(self, ret): - if ret == -1: - return - if ret == 0: - try: - self.compo = next(self.reactor.current_appar) - self.refresh_ui() - except StopIteration: - nxt = self.reactor.forward(1) - try: - self.compo = next(self.reactor.current_appar) - except: - pass - if nxt == -1: - if self.reactor.round_set == 0: - if self.stage == 4: - if config.get("save"): - self.reactor.save() - self.compo = compo.Finished( - self, None, pt.Atom.placeholder() - ) - self.refresh_ui() - else: - self.reactor.set_round_templated(self.stage) - self.reactor.forward(1) - self.stage += 1 - self.compo = next(self.reactor.current_appar) - self.refresh_ui() - return - return - else: - self.refresh_ui() - return - if ret == 1: - self.refresh_ui() - return - - def refresh_ui(self): - self.call_later(self.recompose) - print(type(self.compo).__name__) - - def action_play_voice(self): - print("VOICE") - def play(): - cache_dir = pathlib.Path(f"./cache/voice/") - cache_dir.mkdir(parents=True, exist_ok=True) - cache = cache_dir / f"{self.reactor.current_atom[1].content.replace('/','')}.wav" - if not cache.exists(): - communicate = tts.Communicate( - self.reactor.current_atom[1].content.replace("/", ""), - "zh-CN-YunjianNeural", - ) - communicate.save_sync( - f"./cache/voice/{self.reactor.current_atom[1].content.replace('/','')}.wav" - ) - playsound(str(cache)) - - threading.Thread(target=play).start() - - def action_quick_pass(self): - self.reactor.report(self.reactor.current_atom, 5) - self._forward_judge(0) - def action_toggle_dark(self): - self.app.action_toggle_dark() - - def action_pop_screen(self): - self.app.pop_screen() - - -class PreparationScreen(Screen): - BINDINGS = [("q", "go_back", "返回"), ("escape", "quit_app", "退出")] - - def __init__( - self, nucleon_file: pt.NucleonUnion, electron_file: pt.ElectronUnion - ) -> None: - super().__init__(name=None, id=None, classes=None) - self.nucleon_file = nucleon_file - self.electron_file = electron_file - - def compose(self) -> ComposeResult: - yield Header(show_clock=True) - with Container(id="learning_screen_container"): - yield Label(f"记忆项目: [b]{self.nucleon_file.name}[/b]\n") - yield Label( - f"核子文件对象: ./nucleon/[b]{self.nucleon_file.name}[/b].toml" - ) - yield Label( - f"电子文件对象: ./electron/[b]{self.electron_file.name}[/b].toml" - ) - yield Label(f"核子数量:{len(self.nucleon_file)}") - yield Button( - "开始记忆", - id="start_memorizing_button", - variant="primary", - classes="start-button", - ) - yield Static(f"\n全文如下:\n") - yield Static(self._get_full_content().replace("/", ""), classes="full") - yield Footer() - - def _get_full_content(self): - content = "" - for i in self.nucleon_file.nucleons: - content += i["content"] - return content - - def action_go_back(self): - self.app.pop_screen() - - def action_quit_app(self): - self.app.exit() - - def on_button_pressed(self, event: Button.Pressed) -> None: - if event.button.id == "start_memorizing_button": - newscr = MemScreen( - self.nucleon_file, self.electron_file, config.get("tasked_number", 6) - ) - self.app.push_screen(newscr) - - -class FileSelectorScreen(Screen): - global ver - - def compose(self) -> ComposeResult: - yield Header(show_clock=True) - yield Container( - Label(f'欢迎使用 "潜进" 辅助记忆软件, 版本 {ver}', classes="title-label"), - Label("选择要学习的文件:", classes="title-label"), - ListView(id="file-list", classes="file-list-view"), - ) - yield Footer() - - def on_mount(self) -> None: - file_list_widget = self.query_one("#file-list", ListView) - nucleon_path = pathlib.Path("./nucleon") - nucleon_files = sorted( - [f.name for f in nucleon_path.iterdir() if f.suffix == ".toml"] - ) - - if nucleon_files: - for filename in nucleon_files: - file_list_widget.append(ListItem(Label(filename))) - else: - file_list_widget.append( - ListItem(Static("在 ./nucleon/ 中未找到任何核子文件. 请放置文件后重启应用.")) - ) - file_list_widget.disabled = True - - def on_list_view_selected(self, event: ListView.Selected) -> None: - if not isinstance(event.item, ListItem): - return - - selected_label = event.item.query_one(Label) - if "未找到任何 .toml 文件" in str(selected_label.renderable): - return - - selected_filename = str(selected_label.renderable) - nucleon_file = pt.NucleonUnion( - pathlib.Path("./nucleon") / selected_filename - ) - electron_file_path = pathlib.Path("./electron") / selected_filename - if electron_file_path.exists(): - pass - else: - electron_file_path.touch() - electron_file = pt.ElectronUnion( - pathlib.Path("./electron") / selected_filename - ) - self.app.push_screen(PreparationScreen(nucleon_file, electron_file)) - - def action_quit_app(self) -> None: - self.app.exit() - - class AppLauncher(App): CSS_PATH = "styles.css" TITLE = "潜进 - 辅助记忆程序" diff --git a/reactor.py b/reactor.py index 3d59ce6..d3ef6b6 100644 --- a/reactor.py +++ b/reactor.py @@ -20,6 +20,9 @@ class Apparatus(): for i in self.positron["testdata"].keys(): if i == "additional_inf": continue + if i == "fill_blank_test": # 加深 + self.procession.append(comps.registry[i](screen, reactor, atom)) + self.procession.append(comps.registry[i](screen, reactor, atom)) self.procession.append(comps.registry[i](screen, reactor, atom)) # self.procession.reverse() random.shuffle(self.procession) @@ -139,6 +142,7 @@ class Reactor(): e.revisor(5, True) continue e.revisor(q) + def report(self, atom, quality): "向反应器和最低质量记录汇报" if atom in self.atoms_new: diff --git a/screens.py b/screens.py new file mode 100644 index 0000000..d76697e --- /dev/null +++ b/screens.py @@ -0,0 +1,240 @@ +from textual.app import App, ComposeResult +from textual.widgets import ( + Header, + Footer, + ListView, + ProgressBar, + DirectoryTree, + ListItem, + Label, + Static, + Button, +) +from textual.containers import Container, Horizontal, Center +from textual.screen import Screen +import pathlib +import threading +import edge_tts as tts +from playsound import playsound +import particles as pt +from reactor import Reactor, Apparatus +import auxiliary as aux +import compositions as compo +import builtins +import main + +config = aux.ConfigFile("config.toml") + +class MemScreen(Screen): + BINDINGS = [ + ("d", "toggle_dark", "改变色调"), + ("q", "pop_screen", "返回主菜单"), + ("v", "play_voice", "朗读"), + ] + if config.get("quick_pass"): + BINDINGS.append(("k", "quick_pass", "快速通过[调试]")) + btn = dict() + + def __init__( + self, + nucleon_file: pt.NucleonUnion, + electron_file: pt.ElectronUnion, + tasked_num, + ): + super().__init__(name=None, id=None, classes=None) + self.reactor = Reactor(nucleon_file, electron_file, self, tasked_num) + self.stage = 1 + self.stage += self.reactor.set_round_templated(self.stage) + self.reactor.forward() + self.compo = next(self.reactor.current_appar) + + def compose(self) -> ComposeResult: + if type(self.compo).__name__ == "Recognition": + self.action_play_voice() + yield Header(show_clock=True) + with Center(): + yield Static( + f"{len(self.reactor.procession) - self.reactor.index}/{len(self.reactor.procession)}" + ) + yield from self.compo.compose() + yield Footer() + + def on_mount(self): + pass + + def on_button_pressed(self, event): + ret = self.compo.handler(event, "button") + self._forward_judge(ret) + + def _forward_judge(self, ret): + if ret == -1: + return + if ret == 0: + try: + self.compo = next(self.reactor.current_appar) + self.refresh_ui() + except StopIteration: + nxt = self.reactor.forward(1) + try: + self.compo = next(self.reactor.current_appar) + except: + pass + if nxt == -1: + if self.reactor.round_set == 0: + if self.stage == 4: + if config.get("save"): + self.reactor.save() + self.compo = compo.Finished( + self, None, pt.Atom.placeholder() + ) + self.refresh_ui() + else: + self.reactor.set_round_templated(self.stage) + self.reactor.forward(1) + self.stage += 1 + self.compo = next(self.reactor.current_appar) + self.refresh_ui() + return + return + else: + self.refresh_ui() + return + if ret == 1: + self.refresh_ui() + return + + def refresh_ui(self): + self.call_later(self.recompose) + print(type(self.compo).__name__) + + def action_play_voice(self): + print("VOICE") + def play(): + cache_dir = pathlib.Path(f"./cache/voice/") + cache_dir.mkdir(parents=True, exist_ok=True) + cache = cache_dir / f"{self.reactor.current_atom[1].content.replace('/','')}.wav" + if not cache.exists(): + communicate = tts.Communicate( + self.reactor.current_atom[1].content.replace("/", ""), + "zh-CN-YunjianNeural", + ) + communicate.save_sync( + f"./cache/voice/{self.reactor.current_atom[1].content.replace('/','')}.wav" + ) + playsound(str(cache)) + + threading.Thread(target=play).start() + + def action_quick_pass(self): + self.reactor.report(self.reactor.current_atom, 5) + self._forward_judge(0) + def action_toggle_dark(self): + self.app.action_toggle_dark() + + def action_pop_screen(self): + self.app.pop_screen() + + +class PreparationScreen(Screen): + BINDINGS = [("q", "go_back", "返回"), ("escape", "quit_app", "退出")] + + def __init__( + self, nucleon_file: pt.NucleonUnion, electron_file: pt.ElectronUnion + ) -> None: + super().__init__(name=None, id=None, classes=None) + self.nucleon_file = nucleon_file + self.electron_file = electron_file + + def compose(self) -> ComposeResult: + yield Header(show_clock=True) + with Container(id="learning_screen_container"): + yield Label(f"记忆项目: [b]{self.nucleon_file.name}[/b]\n") + yield Label( + f"核子文件对象: ./nucleon/[b]{self.nucleon_file.name}[/b].toml" + ) + yield Label( + f"电子文件对象: ./electron/[b]{self.electron_file.name}[/b].toml" + ) + yield Label(f"核子数量:{len(self.nucleon_file)}") + yield Button( + "开始记忆", + id="start_memorizing_button", + variant="primary", + classes="start-button", + ) + yield Static(f"\n全文如下:\n") + yield Static(self._get_full_content().replace("/", ""), classes="full") + yield Footer() + + def _get_full_content(self): + content = "" + for i in self.nucleon_file.nucleons: + content += i["content"] + return content + + def action_go_back(self): + self.app.pop_screen() + + def action_quit_app(self): + self.app.exit() + + def on_button_pressed(self, event: Button.Pressed) -> None: + if event.button.id == "start_memorizing_button": + newscr = MemScreen( + self.nucleon_file, self.electron_file, config.get("tasked_number", 6) + ) + self.app.push_screen(newscr) + + +class FileSelectorScreen(Screen): + + def compose(self) -> ComposeResult: + yield Header(show_clock=True) + yield Container( + Label(f'欢迎使用 "潜进" 辅助记忆软件, 版本 {main.ver}', classes="title-label"), + Label("选择要学习的文件:", classes="title-label"), + ListView(id="file-list", classes="file-list-view"), + ) + yield Footer() + + def on_mount(self) -> None: + file_list_widget = self.query_one("#file-list", ListView) + nucleon_path = pathlib.Path("./nucleon") + nucleon_files = sorted( + [f.name for f in nucleon_path.iterdir() if f.suffix == ".toml"] + ) + + if nucleon_files: + for filename in nucleon_files: + file_list_widget.append(ListItem(Label(filename))) + else: + file_list_widget.append( + ListItem(Static("在 ./nucleon/ 中未找到任何核子文件. 请放置文件后重启应用.")) + ) + file_list_widget.disabled = True + + def on_list_view_selected(self, event: ListView.Selected) -> None: + if not isinstance(event.item, ListItem): + return + + selected_label = event.item.query_one(Label) + if "未找到任何 .toml 文件" in str(selected_label.renderable): + return + + selected_filename = str(selected_label.renderable) + nucleon_file = pt.NucleonUnion( + pathlib.Path("./nucleon") / selected_filename + ) + electron_file_path = pathlib.Path("./electron") / selected_filename + if electron_file_path.exists(): + pass + else: + electron_file_path.touch() + electron_file = pt.ElectronUnion( + pathlib.Path("./electron") / selected_filename + ) + self.app.push_screen(PreparationScreen(nucleon_file, electron_file)) + + def action_quit_app(self) -> None: + self.app.exit() +