From bcdfddce10dd27d42fd80c90eb6b417a38a19b22 Mon Sep 17 00:00:00 2001 From: pluvium27 Date: Tue, 21 Apr 2026 02:06:28 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=83=A8=E5=88=86?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../interface/css/screens/preparation.tcss | 24 ++ src/heurams/interface/screens/about.py | 15 +- src/heurams/interface/screens/dashboard.py | 1 - src/heurams/interface/screens/memoqueue.py | 42 +-- src/heurams/interface/screens/precache.py | 4 +- src/heurams/interface/screens/preparation.py | 61 +++-- .../kernel/auxiliary/lict.deprecated.py | 141 ---------- .../kernel/auxiliary/refvar.deprecated.py | 242 ------------------ src/heurams/services/timer.py | 4 +- 9 files changed, 77 insertions(+), 457 deletions(-) create mode 100644 src/heurams/interface/css/screens/preparation.tcss delete mode 100644 src/heurams/kernel/auxiliary/lict.deprecated.py delete mode 100644 src/heurams/kernel/auxiliary/refvar.deprecated.py diff --git a/src/heurams/interface/css/screens/preparation.tcss b/src/heurams/interface/css/screens/preparation.tcss new file mode 100644 index 0000000..d6b03df --- /dev/null +++ b/src/heurams/interface/css/screens/preparation.tcss @@ -0,0 +1,24 @@ +#operations { + height: auto; +} + +.btn { + margin: 0 1 0 0; + padding: 0 1 0 1; +} + +#main_container { + +} + +#previewer_container { + +} + +.unit-statline { + +} + +#title { + padding: 0; +} \ No newline at end of file diff --git a/src/heurams/interface/screens/about.py b/src/heurams/interface/screens/about.py index d00b1da..1af4e7d 100644 --- a/src/heurams/interface/screens/about.py +++ b/src/heurams/interface/screens/about.py @@ -45,27 +45,23 @@ API 版本代号: `{version.codename.capitalize()}` 以 AGPL-3.0 开放源代码, 这直接意味着任何个体直接基于此代码对外或内部提供的应用和服务, 无论本地或网络, 必须向所有用户公开完整修改后的源代码, 且继续沿用 AGPL-3.0 协议. -您正使用的 TUI 用户界面是 python 版本程序库自带的基本用户界面, 作为基本的全功能前端实现与程序库测试, 如果您想去除它, 请移除程序库根目录中的 interface 文件夹. +您正使用的 TUI 用户界面是 python 版本程序库自带的基本用户界面, 以作为基本的全功能前端实现与程序库测试, 位于程序库根目录中的 interface 文件夹. 您可在项目主页 https://ams.pluv27.top 获取用户指南, 开发文档与软件更新. -如果您觉得这个软件有用, 可以在它的源代码仓库给它添加一个星标 :) +如果您觉得这个软件有用, 可以考虑参与贡献, 或在它的源代码仓库给它添加一个星标 :) -> 潜进(HeurAMS), 以及它作为一个"程序库"是自由且免费的, 但是开发工作必须投入大量精力. -> 您可以加入各种语言的翻译团队来翻译软件的界面, 您还可以制作图像、主题、音效, 或者改进软件配套的文档…… -> 不管您来自何方, 我们都欢迎您加入社区并做出贡献. -> 我们的共同目标是为人人带来高品质的辅助记忆 & 学习软件. -> 您的慷慨支持, 我们必当涌泉相报. +您的慷慨支持, 我们必当涌泉相报. 开发人员列表: - Wang Zhiyu([@pluvium27](https://github.com/pluvium27)): 发起项目与主要维护者 -特别感谢以下人士, 他们的算法与理论构成了此软件算法的基石: +特别感谢以下人士, 他们的算法与理论构成了此软件现有算法的基石: - [Piotr A. Woźniak](https://supermemo.guru/wiki/Piotr_Wozniak): SM-2 算法与 SM-15 算法理论 - [Kazuaki Tanida](https://github.com/slaypni): SM-15 算法的 CoffeeScript 实现 -- [Thoughts Memo](https://www.zhihu.com/people/L.M.Sherlock): 文献参考 +- [Thoughts Memo](https://www.zhihu.com/people/L.M.Sherlock): 中文文献参考 # 运行环境信息 @@ -86,6 +82,7 @@ Textual 框架版本: {textual_version} "返回主界面", id="back_button", variant="primary", + flat=True, classes="back-button", ) yield Footer() diff --git a/src/heurams/interface/screens/dashboard.py b/src/heurams/interface/screens/dashboard.py index e8b7525..623ae6e 100644 --- a/src/heurams/interface/screens/dashboard.py +++ b/src/heurams/interface/screens/dashboard.py @@ -93,7 +93,6 @@ class DashboardScreen(Screen): # need_review: 需要/不需要学习 # nearest_review_time: 最近下次学习时间 # progress: 进度 - # algotype: 算法类型 ## initial_time: 起始时间 # package: 包名 # prompt: 最终呈现信息 diff --git a/src/heurams/interface/screens/memoqueue.py b/src/heurams/interface/screens/memoqueue.py index 68782d7..0fbd003 100644 --- a/src/heurams/interface/screens/memoqueue.py +++ b/src/heurams/interface/screens/memoqueue.py @@ -19,15 +19,8 @@ from heurams.services.logger import get_logger from .. import shim - -class AtomState(Enum): - FAILED = auto() - NORMAL = auto() - - logger = get_logger(__name__) - class MemScreen(Screen): BINDINGS = [ ("q", "go_back", "返回"), @@ -35,18 +28,17 @@ class MemScreen(Screen): ("d", "toggle_dark", ""), ("v", "play_voice", "朗读"), ("*", "toggle_favorite", "收藏"), - ("0,1,2,3", "app.push_screen('about')", ""), ] if config_var.get()["interface"]["global"]["quick_pass"]: BINDINGS.append(("k", "quick_pass", "正确应答")) BINDINGS.append(("f", "quick_fail", "错误应答")) + rating = reactive(-1) def __init__( self, phaser: Phaser, - save_func: Callable, repo=None, name=None, id=None, @@ -54,7 +46,6 @@ class MemScreen(Screen): ) -> None: super().__init__(name, id, classes) self.phaser = phaser - self.save_func = save_func self.repo = repo self.update_state() self.fission: Fission @@ -62,8 +53,8 @@ class MemScreen(Screen): def compose(self) -> ComposeResult: yield Header(show_clock=True) with ScrollableContainer(): - yield Label(self._get_progress_text(), id="progress") - yield ScrollableContainer(id="puzzle-container") + yield Label(self._get_progress_text(), id="head_stat") + yield ScrollableContainer(id="puzzle_container") yield Footer() def update_state(self): @@ -92,33 +83,12 @@ class MemScreen(Screen): if self.repo is not None: fav_status = "已收藏" if self._is_current_atom_favorited() else "未收藏" s += f"收藏: {fav_status}\n" - """if config_var.get().get("debug_topline", 0): - try: - alia = self.fission.get_current_puzzle_inf()["alia"] # type: ignore - s += f"谜题: {alia}\n" - except: - pass - try: - stat = self.phaser.__repr__("simple", "") - s += f"{stat}\n" - except: - pass - try: - stat = self.procession.__repr__("simple", "") - s += f"{stat}\n" - except: - pass - try: - stat = self.fission.__repr__("simple", "") - s += f"{stat}\n" - except Exception as e: - s = str(e)""" s += f"进度: {self.procession.process() + 1}/{self.procession.total_length()}" return s def update_display(self): """更新进度显示""" - progress_widget = self.query_one("#progress") + progress_widget = self.query_one("#head_stat") progress_widget.update(self._get_progress_text()) # type: ignore def mount_puzzle(self): @@ -126,14 +96,14 @@ class MemScreen(Screen): if self.procession.phase == PhaserState.FINISHED: self.mount_finished_widget() return - container = self.query_one("#puzzle-container") + container = self.query_one("#puzzle_container") for i in container.children: i.remove() container.mount(self.puzzle_widget()) def mount_finished_widget(self): """挂载已完成组件""" - container = self.query_one("#puzzle-container") + container = self.query_one("#puzzle_container") for i in container.children: i.remove() from heurams.interface.widgets.finished import Finished diff --git a/src/heurams/interface/screens/precache.py b/src/heurams/interface/screens/precache.py index e775c74..14aaadd 100644 --- a/src/heurams/interface/screens/precache.py +++ b/src/heurams/interface/screens/precache.py @@ -17,7 +17,7 @@ paths = config_var.get()["global"]["paths"] cache_dir = pathlib.Path(paths.get("cache", paths["data"] + "/cache")) / "voice" -def format_size(bytes_num: int) -> str: +def human_size(bytes_num: int) -> str: """将字节数格式化为人类可读的字符串""" for unit in ["B", "KB", "MB", "GB", "TB"]: if bytes_num < 1024.0: @@ -97,7 +97,7 @@ class PrecachingScreen(Screen): self.cache_stats["total_size"] = total_size self.cache_stats["file_count"] = file_count - self.cache_stats["human_size"] = format_size(total_size) + self.cache_stats["human_size"] = human_size(total_size) self.cache_stats["cached_units"] = cached_units self.cache_stats["total_units"] = total_units self.cache_stats["cache_rate"] = cache_rate diff --git a/src/heurams/interface/screens/preparation.py b/src/heurams/interface/screens/preparation.py index dd2afe8..1f2ef6c 100644 --- a/src/heurams/interface/screens/preparation.py +++ b/src/heurams/interface/screens/preparation.py @@ -1,7 +1,7 @@ """记忆准备界面""" from textual.app import ComposeResult -from textual.containers import ScrollableContainer, Container +from textual.containers import ScrollableContainer, Container, Horizontal from textual.reactive import reactive from textual.screen import Screen from textual.widget import Widget @@ -22,6 +22,7 @@ import heurams.services.hasher as hasher from heurams.context import * from heurams.context import config_var from heurams.kernel.repolib import * +from heurams.kernel.algorithms import algorithms from heurams.services.logger import get_logger logger = get_logger(__name__) @@ -38,7 +39,7 @@ class PreparationScreen(Screen): ("0,1,2,3", "app.push_screen('about')", ""), ] - scheduled_num = reactive(config_var.get()["interface"]["global"]["scheduled_num"]) + CSS_PATH = rootdir / "interface" / "css" / "screens" / "preparation.tcss" def __init__(self, repo: Repo) -> None: super().__init__(name=None, id=None, classes=None) @@ -47,32 +48,44 @@ class PreparationScreen(Screen): def compose(self) -> ComposeResult: yield Header(show_clock=True) - with Reveal(ScrollableContainer(id="vice_container")): - yield Label(f"准备就绪: [b]{self.repo.manifest['title']}[/b]\n") - yield Label(f"[b]仓库路径: {self.repo.source}[/b]") - yield Label(f"\n单元数量: {len(self.repo)}\n") - yield Label(f"最小记忆分组: {self.scheduled_num}\n", id="schnum_label") - - yield Button( - "开始记忆", - id="start_memorizing_button", - variant="primary", - classes="start-button", + with ScrollableContainer(id="main_container"): + yield Markdown( + f"**准备就绪**: `{self.repo.manifest['title']}`\n", id="title" ) - yield Button( - "预缓存音频", - id="precache_button", - variant="success", - classes="precache-button", + yield Label(f"单元集路径: {self.repo.source}") + yield Label( + f"学习完成度: {self.repo.progress['touched']}/{len(self.repo)} [d]\\[{round(self.repo.progress['touched']/self.repo.progress['total']*100, 1)}%][/d]" + ) + yield Label( + f"调度算法: {self.repo.config["algorithm"]} {algorithms[self.repo.config["algorithm"]].desc}" + ) + yield Label( + f"学习数量: {self.repo.preview['review'] + self.scheduled_num} = {self.repo.preview['review']} [d][复习][/d] + {self.scheduled_num} [d][新识记][/d]\n", + id="schnum_label", + ) + + yield Horizontal( + Button( + "开始记忆", + id="start_memorizing_button", + variant="primary", + classes="btn", + ), + Button( + "预缓存音频", + id="precache_button", + variant="success", + classes="btn", + ), + id="operations", ) yield Static() yield Sparkline(self.spark_line_arr, summary_function=max) - yield Rule() # yield Static(str(self.spark_line_arr)) - yield Static(f"单元状态预览:\n") - for i in self.content.splitlines(): - yield Static(i, classes="full") + with Reveal(ScrollableContainer(id="previewer_container")): + for i in self.content.splitlines(): + yield Static(i, classes="unit-statline") yield Footer() # def watch_scheduled_num(self, old_scheduled_num, new_scheduled_num): @@ -84,6 +97,7 @@ class PreparationScreen(Screen): # pass def load_data(self): + self.scheduled_num = self.repo.config["scheduled_num"] content = "" spark_line_arr = [] for i in self.repo.ident_index: @@ -168,6 +182,5 @@ def launch(repo, app, scheduled_num): from .memoqueue import MemScreen pheser = rt.Phaser(atoms_to_provide) - save_func = repo.persist_to_repodir - memscreen = MemScreen(pheser, save_func, repo=repo) + memscreen = MemScreen(pheser, repo=repo) app.push_screen(memscreen) diff --git a/src/heurams/kernel/auxiliary/lict.deprecated.py b/src/heurams/kernel/auxiliary/lict.deprecated.py deleted file mode 100644 index 9d86005..0000000 --- a/src/heurams/kernel/auxiliary/lict.deprecated.py +++ /dev/null @@ -1,141 +0,0 @@ -# 已弃用 -from collections import UserList -from typing import Any, Iterator - - -class Lict(UserList): # TODO: 优化同步(惰性同步), 当前性能为 O(n) - """ "列典" 对象 - - 同时兼容字典和列表大多数 API, 两边数据同步的容器 - 列表数据是 dictobj.items() 的格式 - 支持根据字典或列表初始化 - 限制要求: - - 键名一定唯一, 且仅能为字符串 - - 值一定是引用对象 - - 不使用并发 - - 不在乎列表顺序语义(严格按键名字符序排列)和列表索引查找, 因此外部的 sort, index 等功能不可用 - - append 的元组中, 表示键名的元素不能重复, 否则会导致覆盖行为 - - """ - - def __init__( - self, - initlist: list | None = None, - initdict: dict | None = None, - ): - self.dicted_data = {} - if initdict != None: - initlist = list(initdict.items()) - super().__init__(initlist=initlist) - self._sync_based_on_list() - self.data.sort() - - def _sync_based_on_dict(self): - self.data = list(self.dicted_data.items()) - self.data.sort() - - def _sync_based_on_list(self): - self.dicted_data = {} - for i in self.data: - self.dicted_data[i[0]] = i[1] - - def __iter__(self) -> Iterator: - return self.data.__iter__() - - def __getitem__(self, i): - if isinstance(i, str): - return self.dicted_data[i] - else: - return super().__getitem__(i) - - def get_itemic_unit(self, ident): - return (ident, self.dicted_data[ident]) - - def __setitem__(self, i, item): - if isinstance(i, str): - self.dicted_data[i] = item - self._sync_based_on_dict() - else: - if item != (item[0], item[1]): - raise NotImplementedError - super().__setitem__(i, item) - self._sync_based_on_list() - - def __delitem__(self, i): - if isinstance(i, str): - del self.dicted_data[i] - self._sync_based_on_dict() - else: - super().__delitem__(i) - self._sync_based_on_list() - - def __contains__(self, item): - return item in self.data or item in self.keys() or item in self.values() - - def append(self, item: Any) -> None: - if item != (item[0], item[1]): - raise NotImplementedError - super().append(item) - self._sync_based_on_list() - self.data.sort() - - def append_new(self, item: Any): - if item != (item[0], item[1]): - raise NotImplementedError - if item[0] not in self: - super().append(item) - self._sync_based_on_list() - self.data.sort() - - def insert(self, i: int, item: Any) -> None: - if item != (item[0], item[1]): # 确保 item 是遵从限制的元组 - raise NotImplementedError - super().insert(i, item) - self._sync_based_on_list() - self.data.sort() - - def pop(self, i: int = -1) -> Any: - res = super().pop(i) - self._sync_based_on_list() - return res - - def remove(self, item: Any) -> None: - if isinstance(item, str): - item = (item, self.dicted_data[item]) - if item != (item[0], item[1]): - raise NotImplementedError - super().remove(item) - self._sync_based_on_list() - self.data.sort() - - def clear(self) -> None: - super().clear() - self._sync_based_on_list() - - def index(self): - raise NotImplementedError - - def extend(self): - raise NotImplementedError - - def sort(self): - raise NotImplementedError - - def reverse(self): - raise NotImplementedError - - def keys(self): - return self.dicted_data.keys() - - def values(self): - return self.dicted_data.values() - - def items(self): - return self.data - - def keys_equal_with(self, other): - return self.key_equality(self, other) - - @classmethod - def key_equality(cls, a, b): - return a.keys() == b.keys() diff --git a/src/heurams/kernel/auxiliary/refvar.deprecated.py b/src/heurams/kernel/auxiliary/refvar.deprecated.py deleted file mode 100644 index 71c1c5f..0000000 --- a/src/heurams/kernel/auxiliary/refvar.deprecated.py +++ /dev/null @@ -1,242 +0,0 @@ -# 已弃用 -class RefVar: - def __init__(self, initvalue) -> None: - self.data = initvalue - - def __repr__(self) -> str: - return f"RefVar({repr(self.data)})" - - def __str__(self) -> str: - return str(self.data) - - def __add__(self, other): - if isinstance(other, RefVar): - return RefVar(self.data + other.data) - return RefVar(self.data + other) - - def __radd__(self, other): - return RefVar(other + self.data) - - def __sub__(self, other): - if isinstance(other, RefVar): - return RefVar(self.data - other.data) - return RefVar(self.data - other) - - def __rsub__(self, other): - return RefVar(other - self.data) - - def __mul__(self, other): - if isinstance(other, RefVar): - return RefVar(self.data * other.data) - return RefVar(self.data * other) - - def __rmul__(self, other): - return RefVar(other * self.data) - - def __truediv__(self, other): - if isinstance(other, RefVar): - return RefVar(self.data / other.data) - return RefVar(self.data / other) - - def __rtruediv__(self, other): - return RefVar(other / self.data) - - def __floordiv__(self, other): - if isinstance(other, RefVar): - return RefVar(self.data // other.data) - return RefVar(self.data // other) - - def __rfloordiv__(self, other): - return RefVar(other // self.data) - - def __mod__(self, other): - if isinstance(other, RefVar): - return RefVar(self.data % other.data) - return RefVar(self.data % other) - - def __rmod__(self, other): - return RefVar(other % self.data) - - def __pow__(self, other): - if isinstance(other, RefVar): - return RefVar(self.data**other.data) - return RefVar(self.data**other) - - def __rpow__(self, other): - return RefVar(other**self.data) - - def __neg__(self): - return RefVar(-self.data) - - def __pos__(self): - return RefVar(+self.data) - - def __abs__(self): - return RefVar(abs(self.data)) - - def __eq__(self, other): - if isinstance(other, RefVar): - return self.data == other.data - return self.data == other - - def __ne__(self, other): - return not self.__eq__(other) - - def __lt__(self, other): - if isinstance(other, RefVar): - return self.data < other.data - return self.data < other - - def __le__(self, other): - if isinstance(other, RefVar): - return self.data <= other.data - return self.data <= other - - def __gt__(self, other): - if isinstance(other, RefVar): - return self.data > other.data - return self.data > other - - def __ge__(self, other): - if isinstance(other, RefVar): - return self.data >= other.data - return self.data >= other - - # 位运算 - def __and__(self, other): - if isinstance(other, RefVar): - return RefVar(self.data & other.data) - return RefVar(self.data & other) - - def __rand__(self, other): - return RefVar(other & self.data) - - def __or__(self, other): - if isinstance(other, RefVar): - return RefVar(self.data | other.data) - return RefVar(self.data | other) - - def __ror__(self, other): - return RefVar(other | self.data) - - def __xor__(self, other): - if isinstance(other, RefVar): - return RefVar(self.data ^ other.data) - return RefVar(self.data ^ other) - - def __rxor__(self, other): - return RefVar(other ^ self.data) - - def __lshift__(self, other): - if isinstance(other, RefVar): - return RefVar(self.data << other.data) - return RefVar(self.data << other) - - def __rlshift__(self, other): - return RefVar(other << self.data) - - def __rshift__(self, other): - if isinstance(other, RefVar): - return RefVar(self.data >> other.data) - return RefVar(self.data >> other) - - def __rrshift__(self, other): - return RefVar(other >> self.data) - - def __invert__(self): - return RefVar(~self.data) - - # 类型转换 - def __int__(self): - return int(self.data) - - def __float__(self): - return float(self.data) - - def __bool__(self): - return bool(self.data) - - def __complex__(self): - return complex(self.data) - - def __bytes__(self): - return bytes(self.data) - - def __hash__(self): - return hash(self.data) - - # 容器操作(如果底层数据支持) - def __len__(self): - return len(self.data) - - def __getitem__(self, key): - return self.data[key] - - def __setitem__(self, key, value): - self.data[key] = value - - def __delitem__(self, key): - del self.data[key] - - def __contains__(self, item): - return item in self.data - - def __iter__(self): - return iter(self.data) - - def __iadd__(self, other): - if isinstance(other, RefVar): - self.data += other.data - else: - self.data += other - return self - - def __isub__(self, other): - if isinstance(other, RefVar): - self.data -= other.data - else: - self.data -= other - return self - - def __imul__(self, other): - if isinstance(other, RefVar): - self.data *= other.data - else: - self.data *= other - return self - - def __itruediv__(self, other): - if isinstance(other, RefVar): - self.data /= other.data - else: - self.data /= other - return self - - def __ifloordiv__(self, other): - if isinstance(other, RefVar): - self.data //= other.data - else: - self.data //= other - return self - - def __imod__(self, other): - if isinstance(other, RefVar): - self.data %= other.data - else: - self.data %= other - return self - - def __ipow__(self, other): - if isinstance(other, RefVar): - self.data **= other.data - else: - self.data **= other - return self - - def __call__(self, *args, **kwargs): - if callable(self.data): - return self.data(*args, **kwargs) - raise TypeError(f"'{type(self.data).__name__}' object is not callable") - - def __getattr__(self, name): - return getattr(self.data, name) diff --git a/src/heurams/services/timer.py b/src/heurams/services/timer.py index 6081401..8eb29bf 100644 --- a/src/heurams/services/timer.py +++ b/src/heurams/services/timer.py @@ -24,7 +24,7 @@ def get_daystamp() -> int: def get_timestamp() -> float: """获取 UNIX 时间戳""" - # 搞这个类的原因是要支持可复现操作 + # 搞这个函数的原因是要支持可复现操作 time_override = config_var.get()["services"]["timer"]["timestamp_override"] if time_override != -1: logger.debug("使用覆盖的时间戳: %f", time_override) @@ -32,4 +32,4 @@ def get_timestamp() -> float: result = time.time() logger.debug("获取当前时间戳: %f", result) - return result + return result \ No newline at end of file