Files
HeurAMS/main.py
2025-08-05 14:13:11 +08:00

291 lines
11 KiB
Python

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
# 保存原始的 open 函数
_original_open = builtins.open
# 定义新的 open 函数,默认使用 UTF-8
def _open(*args, **kwargs):
if 'encoding' not in kwargs:
kwargs['encoding'] = 'utf-8'
return _original_open(*args, **kwargs)
# 替换全局的 open
builtins.open = _open
ver = '0.3.0'
config = aux.ConfigFile("config.toml")
class MemScreen(Screen):
BINDINGS = [
("d", "toggle_dark", "改变色调"),
("q", "pop_screen", "返回主菜单"),
("v", "play_voice", "朗读"),
("0", "press('q0')", None),
("1", "press('q1')", None),
("2", "press('q2')", None),
("3", "press('q3')", None),
("4", "press('q4')", None),
("5", "press('q5')", None),
("[", "press('q5')", None),
("]", "press('q4')", None),
(";", "press('q3')", None),
("'", "press('q2')", None),
(".", "press('q1')", None),
("/", "press('q0')", None),
]
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)
##print(self.reactor.procession)
self.reactor.forward()
#self.compo:compo.Composition = compo.Placeholder(self)
self.compo = next(self.reactor.current_appar)
def compose(self) -> ComposeResult:
#print(self.compo)
yield Header(show_clock=True)
with Center():
yield Static(f"{len(self.reactor.procession) - self.reactor.index}/{len(self.reactor.procession)}")
#yield ProgressBar(total=len(self.reactor.procession) - 1, show_percentage=False, show_eta=False, id="progress")
yield from self.compo.compose()
yield Footer()
"""
def _get_progress_text(self):
return f"{len(self.reactor.procession) - self.reactor.index}/{len(self.reactor.procession)}"
"""
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)
#print(2)
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()
#self._show_finished_screen("今日目标已完成")
else:
self.reactor.set_round_templated(self.stage)
self.reactor.forward(1)
#self._update_ui()
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)
#self.call_later(lambda: self.query_one("#progress", expect_type=ProgressBar).advance(self.reactor.index))
##print(area.children)
#for child in list(area.children):
# child.remove() # 致敬传奇组件树 DOM
##print(1,list(self.compo.compose()))
#area.mount(*list(self.compo.compose()))
def report(self, quality):
assessment = self.reactor.report(self.reactor.current_atom, quality)
return assessment
"""if assessment == 1:
# 需要复习
feedback_label.update(f"评分为 {quality}, 已经加入至复习, 请重复记忆")
else:
ret = self.reactor.forward(1)
if ret == -1:
if self.reactor.round_set == 0:
if self.stage == 4:
# NOTE #
if config.get("save"):
self.reactor.save()
self._show_finished_screen("今日目标已完成")
else:
self.reactor.set_round_templated(self.stage)
self.reactor.forward(1)
self._update_ui()
self.stage += 1
return
#feedback_label.update("") # 清除反馈消息
self._update_ui()"""
#def action_press(self, btnid):
# self.on_button_pressed(btnid)
def action_play_voice(self):
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_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(), 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:
pass
if event.button.id == "start_memorizing_button":
#init_file(Path(self.atom_file).name)
newscr = MemScreen(self.nucleon_file, self.electron_file, config.get("tasked_number", 8))
self.app.push_screen(
newscr
)
#if event.button.id == "edit_metadata_button":
# init_file(Path(self.atom_file).name)
# os.system("reset;nano ./data/" + str(Path(self.atom_file).name.replace(".txt", "_atoms.json")))
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):
self.notify("无法选择此项。", severity="error")
return
selected_label = event.item.query_one(Label)
if "未找到任何 .toml 文件" in str(selected_label.renderable):
self.notify("请先在 `./atoms/` 目录中放置 .toml 文件。", severity="warning")
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.notify(f"已选择: {selected_filename}", timeout=2)
self.app.push_screen(PreparationScreen(nucleon_file, electron_file))
def action_quit_app(self) -> None:
self.app.exit()
class AppLauncher(App):
CSS_PATH = "styles.tcss"
TITLE = '潜进 - 辅助记忆程序'
BINDINGS = [("escape", "quit", "退出"), ("d", "toggle_dark", "改变色调")]
SCREENS = {
"file_selection_screen": FileSelectorScreen,
}
def on_mount(self) -> None:
#self.action_toggle_dark()
self.push_screen("file_selection_screen")
if __name__ == "__main__":
app = AppLauncher()
app.run()