fix: 修复部分问题

This commit is contained in:
2026-04-21 02:06:28 +08:00
parent 6f183e4d9d
commit bcdfddce10
9 changed files with 77 additions and 457 deletions

View File

@@ -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;
}

View File

@@ -45,27 +45,23 @@ API 版本代号: `{version.codename.capitalize()}`
以 AGPL-3.0 开放源代码, 这直接意味着任何个体直接基于此代码对外或内部提供的应用和服务, 无论本地或网络, 必须向所有用户公开完整修改后的源代码, 且继续沿用 AGPL-3.0 协议. 以 AGPL-3.0 开放源代码, 这直接意味着任何个体直接基于此代码对外或内部提供的应用和服务, 无论本地或网络, 必须向所有用户公开完整修改后的源代码, 且继续沿用 AGPL-3.0 协议.
您正使用的 TUI 用户界面是 python 版本程序库自带的基本用户界面, 作为基本的全功能前端实现与程序库测试, 如果您想去除它, 请移除程序库根目录中的 interface 文件夹. 您正使用的 TUI 用户界面是 python 版本程序库自带的基本用户界面, 作为基本的全功能前端实现与程序库测试, 位于程序库根目录中的 interface 文件夹.
您可在项目主页 https://ams.pluv27.top 获取用户指南, 开发文档与软件更新. 您可在项目主页 https://ams.pluv27.top 获取用户指南, 开发文档与软件更新.
如果您觉得这个软件有用, 可以在它的源代码仓库给它添加一个星标 :) 如果您觉得这个软件有用, 可以考虑参与贡献, 或在它的源代码仓库给它添加一个星标 :)
> 潜进(HeurAMS), 以及它作为一个"程序库"是自由且免费的, 但是开发工作必须投入大量精力. 您的慷慨支持, 我们必当涌泉相报.
> 您可以加入各种语言的翻译团队来翻译软件的界面, 您还可以制作图像、主题、音效, 或者改进软件配套的文档……
> 不管您来自何方, 我们都欢迎您加入社区并做出贡献.
> 我们的共同目标是为人人带来高品质的辅助记忆 & 学习软件.
> 您的慷慨支持, 我们必当涌泉相报.
开发人员列表: 开发人员列表:
- Wang Zhiyu([@pluvium27](https://github.com/pluvium27)): 发起项目与主要维护者 - Wang Zhiyu([@pluvium27](https://github.com/pluvium27)): 发起项目与主要维护者
特别感谢以下人士, 他们的算法与理论构成了此软件算法的基石: 特别感谢以下人士, 他们的算法与理论构成了此软件现有算法的基石:
- [Piotr A. Woźniak](https://supermemo.guru/wiki/Piotr_Wozniak): SM-2 算法与 SM-15 算法理论 - [Piotr A. Woźniak](https://supermemo.guru/wiki/Piotr_Wozniak): SM-2 算法与 SM-15 算法理论
- [Kazuaki Tanida](https://github.com/slaypni): SM-15 算法的 CoffeeScript 实现 - [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", id="back_button",
variant="primary", variant="primary",
flat=True,
classes="back-button", classes="back-button",
) )
yield Footer() yield Footer()

View File

@@ -93,7 +93,6 @@ class DashboardScreen(Screen):
# need_review: 需要/不需要学习 # need_review: 需要/不需要学习
# nearest_review_time: 最近下次学习时间 # nearest_review_time: 最近下次学习时间
# progress: 进度 # progress: 进度
# algotype: 算法类型
## initial_time: 起始时间 ## initial_time: 起始时间
# package: 包名 # package: 包名
# prompt: 最终呈现信息 # prompt: 最终呈现信息

View File

@@ -19,15 +19,8 @@ from heurams.services.logger import get_logger
from .. import shim from .. import shim
class AtomState(Enum):
FAILED = auto()
NORMAL = auto()
logger = get_logger(__name__) logger = get_logger(__name__)
class MemScreen(Screen): class MemScreen(Screen):
BINDINGS = [ BINDINGS = [
("q", "go_back", "返回"), ("q", "go_back", "返回"),
@@ -35,18 +28,17 @@ class MemScreen(Screen):
("d", "toggle_dark", ""), ("d", "toggle_dark", ""),
("v", "play_voice", "朗读"), ("v", "play_voice", "朗读"),
("*", "toggle_favorite", "收藏"), ("*", "toggle_favorite", "收藏"),
("0,1,2,3", "app.push_screen('about')", ""),
] ]
if config_var.get()["interface"]["global"]["quick_pass"]: if config_var.get()["interface"]["global"]["quick_pass"]:
BINDINGS.append(("k", "quick_pass", "正确应答")) BINDINGS.append(("k", "quick_pass", "正确应答"))
BINDINGS.append(("f", "quick_fail", "错误应答")) BINDINGS.append(("f", "quick_fail", "错误应答"))
rating = reactive(-1) rating = reactive(-1)
def __init__( def __init__(
self, self,
phaser: Phaser, phaser: Phaser,
save_func: Callable,
repo=None, repo=None,
name=None, name=None,
id=None, id=None,
@@ -54,7 +46,6 @@ class MemScreen(Screen):
) -> None: ) -> None:
super().__init__(name, id, classes) super().__init__(name, id, classes)
self.phaser = phaser self.phaser = phaser
self.save_func = save_func
self.repo = repo self.repo = repo
self.update_state() self.update_state()
self.fission: Fission self.fission: Fission
@@ -62,8 +53,8 @@ class MemScreen(Screen):
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Header(show_clock=True) yield Header(show_clock=True)
with ScrollableContainer(): with ScrollableContainer():
yield Label(self._get_progress_text(), id="progress") yield Label(self._get_progress_text(), id="head_stat")
yield ScrollableContainer(id="puzzle-container") yield ScrollableContainer(id="puzzle_container")
yield Footer() yield Footer()
def update_state(self): def update_state(self):
@@ -92,33 +83,12 @@ class MemScreen(Screen):
if self.repo is not None: if self.repo is not None:
fav_status = "已收藏" if self._is_current_atom_favorited() else "未收藏" fav_status = "已收藏" if self._is_current_atom_favorited() else "未收藏"
s += f"收藏: {fav_status}\n" 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()}" s += f"进度: {self.procession.process() + 1}/{self.procession.total_length()}"
return s return s
def update_display(self): 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 progress_widget.update(self._get_progress_text()) # type: ignore
def mount_puzzle(self): def mount_puzzle(self):
@@ -126,14 +96,14 @@ class MemScreen(Screen):
if self.procession.phase == PhaserState.FINISHED: if self.procession.phase == PhaserState.FINISHED:
self.mount_finished_widget() self.mount_finished_widget()
return return
container = self.query_one("#puzzle-container") container = self.query_one("#puzzle_container")
for i in container.children: for i in container.children:
i.remove() i.remove()
container.mount(self.puzzle_widget()) container.mount(self.puzzle_widget())
def mount_finished_widget(self): def mount_finished_widget(self):
"""挂载已完成组件""" """挂载已完成组件"""
container = self.query_one("#puzzle-container") container = self.query_one("#puzzle_container")
for i in container.children: for i in container.children:
i.remove() i.remove()
from heurams.interface.widgets.finished import Finished from heurams.interface.widgets.finished import Finished

View File

@@ -17,7 +17,7 @@ paths = config_var.get()["global"]["paths"]
cache_dir = pathlib.Path(paths.get("cache", paths["data"] + "/cache")) / "voice" 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"]: for unit in ["B", "KB", "MB", "GB", "TB"]:
if bytes_num < 1024.0: if bytes_num < 1024.0:
@@ -97,7 +97,7 @@ class PrecachingScreen(Screen):
self.cache_stats["total_size"] = total_size self.cache_stats["total_size"] = total_size
self.cache_stats["file_count"] = file_count 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["cached_units"] = cached_units
self.cache_stats["total_units"] = total_units self.cache_stats["total_units"] = total_units
self.cache_stats["cache_rate"] = cache_rate self.cache_stats["cache_rate"] = cache_rate

View File

@@ -1,7 +1,7 @@
"""记忆准备界面""" """记忆准备界面"""
from textual.app import ComposeResult 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.reactive import reactive
from textual.screen import Screen from textual.screen import Screen
from textual.widget import Widget from textual.widget import Widget
@@ -22,6 +22,7 @@ import heurams.services.hasher as hasher
from heurams.context import * from heurams.context import *
from heurams.context import config_var from heurams.context import config_var
from heurams.kernel.repolib import * from heurams.kernel.repolib import *
from heurams.kernel.algorithms import algorithms
from heurams.services.logger import get_logger from heurams.services.logger import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -38,7 +39,7 @@ class PreparationScreen(Screen):
("0,1,2,3", "app.push_screen('about')", ""), ("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: def __init__(self, repo: Repo) -> None:
super().__init__(name=None, id=None, classes=None) super().__init__(name=None, id=None, classes=None)
@@ -47,32 +48,44 @@ class PreparationScreen(Screen):
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Header(show_clock=True) yield Header(show_clock=True)
with Reveal(ScrollableContainer(id="vice_container")): with ScrollableContainer(id="main_container"):
yield Label(f"准备就绪: [b]{self.repo.manifest['title']}[/b]\n") yield Markdown(
yield Label(f"[b]仓库路径: {self.repo.source}[/b]") f"**准备就绪**: `{self.repo.manifest['title']}`\n", id="title"
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",
) )
yield Button( yield Label(f"单元集路径: {self.repo.source}")
"预缓存音频", yield Label(
id="precache_button", f"学习完成度: {self.repo.progress['touched']}/{len(self.repo)} [d]\\[{round(self.repo.progress['touched']/self.repo.progress['total']*100, 1)}%][/d]"
variant="success", )
classes="precache-button", 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 Static()
yield Sparkline(self.spark_line_arr, summary_function=max) yield Sparkline(self.spark_line_arr, summary_function=max)
yield Rule()
# yield Static(str(self.spark_line_arr)) # yield Static(str(self.spark_line_arr))
yield Static(f"单元状态预览:\n") with Reveal(ScrollableContainer(id="previewer_container")):
for i in self.content.splitlines(): for i in self.content.splitlines():
yield Static(i, classes="full") yield Static(i, classes="unit-statline")
yield Footer() yield Footer()
# def watch_scheduled_num(self, old_scheduled_num, new_scheduled_num): # def watch_scheduled_num(self, old_scheduled_num, new_scheduled_num):
@@ -84,6 +97,7 @@ class PreparationScreen(Screen):
# pass # pass
def load_data(self): def load_data(self):
self.scheduled_num = self.repo.config["scheduled_num"]
content = "" content = ""
spark_line_arr = [] spark_line_arr = []
for i in self.repo.ident_index: for i in self.repo.ident_index:
@@ -168,6 +182,5 @@ def launch(repo, app, scheduled_num):
from .memoqueue import MemScreen from .memoqueue import MemScreen
pheser = rt.Phaser(atoms_to_provide) pheser = rt.Phaser(atoms_to_provide)
save_func = repo.persist_to_repodir memscreen = MemScreen(pheser, repo=repo)
memscreen = MemScreen(pheser, save_func, repo=repo)
app.push_screen(memscreen) app.push_screen(memscreen)

View File

@@ -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()

View File

@@ -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)

View File

@@ -24,7 +24,7 @@ def get_daystamp() -> int:
def get_timestamp() -> float: def get_timestamp() -> float:
"""获取 UNIX 时间戳""" """获取 UNIX 时间戳"""
# 搞这个的原因是要支持可复现操作 # 搞这个函数的原因是要支持可复现操作
time_override = config_var.get()["services"]["timer"]["timestamp_override"] time_override = config_var.get()["services"]["timer"]["timestamp_override"]
if time_override != -1: if time_override != -1:
logger.debug("使用覆盖的时间戳: %f", time_override) logger.debug("使用覆盖的时间戳: %f", time_override)
@@ -32,4 +32,4 @@ def get_timestamp() -> float:
result = time.time() result = time.time()
logger.debug("获取当前时间戳: %f", result) logger.debug("获取当前时间戳: %f", result)
return result return result