98 Commits

Author SHA1 Message Date
pluv 0068e1b792 Merge branch 'refactor/v0.5.0' into dev
完成 0.5.0 版本重构工作
2026-05-07 18:45:12 +08:00
pluv abb272df3a docs: 修改文档 2026-05-07 18:43:49 +08:00
pluv add368cd2e docs: 修改文档 2026-05-07 18:38:03 +08:00
pluv 2324c9959b fix: 修复若干可用性问题 2026-05-07 18:17:39 +08:00
pluv 93bcdd63fe fix: 修复一个依赖问题 2026-05-07 18:02:59 +08:00
pluv 0a40642303 docs: 合并前准备 2026-05-07 17:55:22 +08:00
pluv 4ca9b0bd0d fix: 总体体验改进与版本号变更 2026-05-07 17:50:30 +08:00
pluv 7a1c1c6093 docs: 修改文档 2026-05-07 13:26:22 +08:00
pluv cd6bac3d00 docs: 更新文档 2026-05-06 21:01:51 +08:00
pluv f5a457f7c5 docs: 更新文档 2026-05-06 09:46:50 +08:00
pluv 4d54208848 docs: 更新文档 2026-05-06 09:35:58 +08:00
pluv b3155e18e5 docs: 更新文档 2026-05-05 11:53:37 +08:00
pluv d7da5890aa chore: 更新文档 2026-05-05 10:34:51 +08:00
pluv 629dcec03a feat: 升级到 playsound3 以替代问题频出的 playsound 2026-05-04 14:22:45 +08:00
pluv 190699b447 feat: 使算法能被自动注册 2026-05-04 13:55:57 +08:00
pluv 37a05874b2 fix: 稳定性修复 2026-05-04 13:13:05 +08:00
pluv 2ee7ff6b0e docs: 更新文档 2026-05-02 02:38:33 +08:00
pluv d982e11bf6 docs: 更新文档 2026-05-02 02:36:14 +08:00
pluv 8b691d9db8 chore: 修改版本文本 2026-04-30 04:52:17 +08:00
pluv 2625632e9c fix(interface): 修复持久化问题 2026-04-30 04:45:05 +08:00
pluv face433660 fix: 修改一处语法错误 2026-04-27 02:27:29 +08:00
pluv a0180b8cc0 chore: 说明依赖 2026-04-27 02:26:32 +08:00
pluv b818a1a0a4 chore: 更新依赖和文档 2026-04-27 02:04:52 +08:00
pluv 0aa9803b76 feat: 高精度时间戳支持与 SM-15M 改进 2026-04-25 02:14:55 +08:00
pluv f3049fde90 feat: 增加早期 FSRS 支持 2026-04-25 01:54:18 +08:00
pluv 2c9e854507 test: 增加测试 2026-04-25 01:38:33 +08:00
pluv 6ed1455111 docs: 修改文档 2026-04-24 15:40:42 +08:00
pluv df00d49e0b docs: 修改文档 2026-04-24 15:38:10 +08:00
pluv 613b4ba045 style: 代码格式化 2026-04-23 00:13:57 +08:00
pluv aaee4d513e docs: 更新文档 2026-04-22 23:39:20 +08:00
pluv 39ea047367 feat(interface): 组件自动聚焦与键盘操作改进 2026-04-22 22:54:25 +08:00
pluv 2c583d97d7 fix(interface): 修复 CSS_PATH 导致的样式覆写问题 2026-04-22 06:55:58 +08:00
pluv 7278cba258 perf: 用 autoflake 删除无用依赖
没想到居然能意外地大幅提升启动速度
2026-04-22 06:44:59 +08:00
pluv e65db69fc6 feat: 开发 unifront 前端会话模块 2026-04-21 16:52:04 +08:00
pluv 9dd6733063 refactor: 开始翻新状态机 2026-04-21 12:52:30 +08:00
pluv bcdfddce10 fix: 修复部分问题 2026-04-21 02:06:28 +08:00
pluv 6f183e4d9d style: 格式化代码 2026-04-21 00:17:03 +08:00
pluv 4246061c29 fix: 优化 CSS 结构 2026-04-21 00:15:57 +08:00
pluv cf2f29cbd7 fix: 修复 Lict 问题 2026-04-20 18:55:25 +08:00
pluv a960d06d0a perf: 脏标记优化 Lict 对象操作复杂度 2026-04-20 18:43:27 +08:00
pluv d01dbe2648 feat: 用户界面实现算法共存与切换 2026-04-20 17:50:36 +08:00
pluv 4ca9c65bea feat: 代码格式化, 改进仪表盘, 新增多CSS支持 2026-04-20 16:30:04 +08:00
pluv 8677e828c7 feat: 试增加单元集独立设置 2026-04-20 07:39:57 +08:00
pluv 709e15663b feat: 增加 ZMQ 调试服务器并完善设置功能 2026-04-20 06:37:46 +08:00
pluv 85925b9d44 feat: 补全设置提示与完善设置页 2026-04-20 05:05:44 +08:00
pluv 82fd8ab199 feat(interface): 图形化设置页原型 2026-04-20 03:33:05 +08:00
pluv 334d04444d style: 删除旧配置文件 2026-04-20 01:46:40 +08:00
pluv d1a1fa193f refactor: 对配置处理器和配置结构进行重构 2026-04-20 01:44:43 +08:00
pluv a8cd774123 fix: 变更哈希注册表以修复重复键造成的崩溃 2026-04-19 12:08:58 +08:00
pluv 0572848803 feat: FAST-0 特殊算法 2026-04-19 00:05:49 +08:00
pluv 279a78f7ce style: 移除设计不当的部分模块 2026-04-16 13:20:12 +08:00
pluv 2a388642e0 build: 修改依赖并引入外部库 2026-03-27 19:31:40 +08:00
pluv 92b4772eda feat(interface): 改进界面 2026-01-28 03:35:58 +08:00
pluv b38eaf4ef6 feat(interface): 改进仪表盘 2026-01-22 06:13:01 +08:00
pluv dde6df9bf0 fix: 修复若干问题 2026-01-17 05:45:45 +08:00
pluv 1077fcab39 fix(interface): 修复按钮标识和一个哈希问题 2026-01-15 12:49:57 +08:00
pluv bcb198f4d4 feat(interface): 改进状态显示 2026-01-13 23:39:10 +08:00
pluv ed361f88e2 build: 使用 uv 包管理器 2026-01-09 00:47:34 +08:00
pluv dd50dc7511 fix: 改进 2026-01-08 01:00:08 +08:00
pluv 67809463f1 fix: 小范围修补 2026-01-08 00:56:04 +08:00
pluv c051f0ffdd feat: 一系列新功能 2026-01-08 00:05:00 +08:00
pluv 1398368e18 feat(interface): 完成队列式记忆模块更新 2026-01-06 20:32:27 +08:00
pluv 85f9e84da4 fix: 修复完成屏幕问题 2026-01-06 19:58:46 +08:00
pluv 1f97a0c5ed fix: 修正逻辑问题 2026-01-06 18:28:57 +08:00
pluv 7d0f4e1466 feat: 更新状态机 2026-01-05 05:25:14 +08:00
pluv 1c297d3949 fix: 改进代码 2026-01-04 04:46:19 +08:00
pluv 5f64ae7250 fix(interface): 界面兼容性改进 2026-01-04 04:14:57 +08:00
pluv d9bf28cf75 fix(interface): 完成主要屏幕重构 2026-01-04 03:46:45 +08:00
pluv f4fd966373 fix: 部分修复重构数据格式差异 2026-01-04 02:11:07 +08:00
pluv 8cd6a8ccc6 feat(reactor): 状态机进一步改进 2026-01-04 00:28:44 +08:00
pluv 64223ce948 feat: 改进状态机 2026-01-03 13:08:08 +08:00
pluv e7e26e3e45 feat(kernel): 状态机改进 2026-01-03 05:05:41 +08:00
pluv 64c9a5c1a7 feat: 完成部分界面重构 2026-01-02 06:12:49 +08:00
pluv 238013ac46 feat: 改进对象系统 2026-01-01 20:18:18 +08:00
pluv 52ef6c65d4 feat: 基本完成对象系统更新 2026-01-01 16:19:46 +08:00
pluv 3ce102b358 feat: 改进粒子对象 2026-01-01 06:36:27 +08:00
pluv e830989368 feat: 进一步改进 2025-12-31 00:57:07 +08:00
pluv f056aaf00d feat: 一些改进 2025-12-29 21:53:20 +08:00
pluv 82560ef4e7 refactor: 部分更改数据结构 2025-12-28 06:16:21 +08:00
pluv ddcebb6dcd style: 格式化代码 2025-12-21 23:44:13 +08:00
pluv ce4860b930 feat(interface): 增加智能单元集排序 2025-12-21 23:42:02 +08:00
pluv 44dfd63212 fix: 暂时禁用实验性功能 2025-12-21 23:06:17 +08:00
pluv f6572cf5dd refactor(synctool): 改进同步方案 2025-12-21 21:18:31 +08:00
pluv 5c35e14d0f feat(synctool): 虚拟文件系统初步方案 2025-12-21 18:48:25 +08:00
pluv 490b87c639 style: 格式化代码 2025-12-21 07:56:10 +08:00
pluv 22eea3d668 feat(synctool): 增加同步功能 2025-12-21 07:49:19 +08:00
pluv 29b5978f8a feat(kernel): 添加算法切换设置 2025-12-21 06:48:30 +08:00
pluv 94f788d8f4 style: 更新版本号和合并规则 2025-12-21 06:34:17 +08:00
pluv ebaaac81db fix(interface): 修复仪表盘详情 2025-12-21 06:27:00 +08:00
pluv b8cfc03c2e feat(interface): 更改启动方式 2025-12-21 06:06:16 +08:00
pluv 9b7b633ec0 fix(interface): 修复显示问题 2025-12-21 05:47:22 +08:00
pluv fd0c197e98 feat: 自动音频播放与改进设计 2025-12-21 05:32:58 +08:00
pluv 4070246041 style(version): 更新版本号 2025-12-21 03:02:29 +08:00
pluv 083f813651 feat: 实验性 SM-15M 算法实现
实验性 SM-15M 逆向工程算法实现
2025-12-21 02:15:23 +08:00
pluv 2600dffee3 style: isort 格式化 2025-12-19 15:13:42 +08:00
pluv b09e67b3eb style: 格式化代码 2025-12-19 15:08:26 +08:00
pluv 218dba21f2 fix(interface): 修复默认配置文件 2025-12-18 15:53:18 +08:00
pluv 3866b9620c refactor: 完成 0.4.0 版本更新
完成 0.4.0 版本更新, 为了消除此前提交消息风格不一致与错误提交超大文件的问题, 维持代码统计数据的准确性和提交消息风格的一致性, 重新初始化仓库; 旧的提交历史在 HeurAMS-legacy 仓库(https://gitea.imwangzhiyu.xyz/ajax/HeurAMS-legacy)
2025-12-17 22:31:38 +08:00
19 changed files with 132 additions and 164 deletions
+1 -2
View File
@@ -3,7 +3,7 @@
欢迎为此项目做出贡献! 欢迎为此项目做出贡献!
> [!NOTE] > [!NOTE]
> 我们已经开始着手于基于 KDE 用户界面框架 `Kirigami` 的现代跨平台前端开发, 称作 "KiriMemo", 包名是 "org.kde.kirimemo", 但其并非 KDE 项目\ > 我们已经开始着手于基于 KDE 用户界面框架 `Kirigami` 的现代跨平台前端开发, 称作 "KiriMemo", 包名是 "org.kde.kirimemo", 但其并非 KDE 项目
> 它通过 `PyOtherSide` 直接复用 python 内核, 为 Windows, Linux, macOS, Android, iOS 和 Plasma Mobile 提供现代用户界面 > 它通过 `PyOtherSide` 直接复用 python 内核, 为 Windows, Linux, macOS, Android, iOS 和 Plasma Mobile 提供现代用户界面
> 如果您善于开发 C++, QML, Qt 与 KDE 框架, 欢迎加入到 KiriMemo 项目的开发 > 如果您善于开发 C++, QML, Qt 与 KDE 框架, 欢迎加入到 KiriMemo 项目的开发
@@ -81,7 +81,6 @@ python3 -m heurams.interface # 启动 TUI
贡献者拥有其贡献部分的版权同意其贡献将在 AGPL-3.0 许可证(包括附加的本机 API 调用豁免条款)下发布. 贡献者拥有其贡献部分的版权同意其贡献将在 AGPL-3.0 许可证(包括附加的本机 API 调用豁免条款)下发布.
如有以下情况, 请在 PR 描述中注明: 如有以下情况, 请在 PR 描述中注明:
- 如果需要引入其他开源 vendor - 如果需要引入其他开源 vendor
- 如果需要引入其他专有的网络服务(例如当前项目中的 edgetts) - 如果需要引入其他专有的网络服务(例如当前项目中的 edgetts)
- 如果需要升级某个依赖或运行环境的版本 - 如果需要升级某个依赖或运行环境的版本
+1 -6
View File
@@ -44,7 +44,6 @@
所以可能和您的想象不同, 您事实上可以直接用鼠标点击按钮, 就像使用普通软件一样. 所以可能和您的想象不同, 您事实上可以直接用鼠标点击按钮, 就像使用普通软件一样.
### 也可以用键盘 ### 也可以用键盘
- `Tab` 键在不同区域之间切换焦点 - `Tab` 键在不同区域之间切换焦点
- `方向键` 在列表中上下移动 - `方向键` 在列表中上下移动
- `Enter` 确认选择 - `Enter` 确认选择
@@ -52,7 +51,6 @@
- 屏幕上会有按键提示, 例如 `[n] 导航器` 表示按 `n` 键打开导航器 - 屏幕上会有按键提示, 例如 `[n] 导航器` 表示按 `n` 键打开导航器
### 触屏也可以 ### 触屏也可以
在平板或手机 Termux 中, 您可以触摸或者滑动屏幕操作. 在平板或手机 Termux 中, 您可以触摸或者滑动屏幕操作.
## 我怎么启动这个软件? ## 我怎么启动这个软件?
@@ -60,7 +58,6 @@
首先需要确保系统中安装了 Python (推荐 3.12.13 版本) 并安装了 HeurAMS 的所需组件. 首先需要确保系统中安装了 Python (推荐 3.12.13 版本) 并安装了 HeurAMS 的所需组件.
### Windows ### Windows
打开"命令提示符"或"PowerShell", 输入以下命令后按回车, 或者把这玩意另存为快捷方式: 打开"命令提示符"或"PowerShell", 输入以下命令后按回车, 或者把这玩意另存为快捷方式:
``` ```
@@ -68,11 +65,9 @@ python -m heurams.interface
``` ```
### macOS ### macOS
打开"终端"应用程序, 输入以上命令. 打开"终端"应用程序, 输入以上命令.
### Linux ### Linux
打开您的终端模拟器 (一般是按 Ctrl + Alt + T), 输入以上命令. 打开您的终端模拟器 (一般是按 Ctrl + Alt + T), 输入以上命令.
如果您觉得每次输入命令太麻烦, 可以创建一个桌面快捷方式或脚本文件, 详见网上的相关教程. 如果您觉得每次输入命令太麻烦, 可以创建一个桌面快捷方式或脚本文件, 详见网上的相关教程.
@@ -329,7 +324,7 @@ Flutter 是构建跨平台图形界面的优秀框架, HeurAMS 的设计目标
当前我们优先开发了基于 Textual 的 TUI 前端和基于 Kirigami 的原生前端, 但这不排除未来出现 Flutter 或其他框架前端的可能性. 当前我们优先开发了基于 Textual 的 TUI 前端和基于 Kirigami 的原生前端, 但这不排除未来出现 Flutter 或其他框架前端的可能性.
如果您有兴趣开发 Flutter 前端, 欢迎参考[贡献指南](CONTRIBUTING.md#%E6%96%B0%E7%9A%84%E7%94%A8%E6%88%B7%E7%95%8C%E9%9D%A2%E5%89%8D%E7%AB%AF). 如果您有兴趣开发 Flutter 前端, 欢迎参考[贡献指南](CONTRIBUTING.md#新的用户界面前端).
## 软件需要联网吗? ## 软件需要联网吗?
+12 -16
View File
@@ -2,18 +2,18 @@
## 概述 ## 概述
"潜进" (HeurAMS: Heuristic Auxiliary Memorizing Scheduler, 启发式记忆辅助调度器) 是一种基于启发式算法与认知科学理论的辅助记忆调度器, 旨在帮助用户更高效地进行记忆工作与学习规划,\ "潜进" (HeurAMS: Heuristic Auxiliary Memorizing Scheduler, 启发式记忆辅助调度器) 是一种基于启发式算法与认知科学理论的辅助记忆调度器, 旨在帮助用户更高效地进行记忆工作与学习规划,
也是一种开放, 优雅, 易于扩展的间隔重复调度器实验平台, 旨在帮助研究者更高效地进行前沿记忆算法的研究. 也是一种开放, 优雅, 易于扩展的间隔重复调度器实验平台, 旨在帮助研究者更高效地进行前沿记忆算法的研究.
## 关于此仓库 ## 关于此仓库
此仓库为 "潜进" 的核心程序库在 python 语言下的实现\ 此仓库为 "潜进" 的核心程序库在 python 语言下的实现
包含数据模型与框架, 并内置了基于 textual 框架的前端实现 (interface 子模块)\ 包含数据模型与框架, 并内置了基于 textual 框架的前端实现 (interface 子模块)
除了通过内置前端进行学习外, 开发者也能在 python 环境中导入 `heurams` 库或使用 `RPC``heurams` 程序库实例通讯, 使用框架构建其他辅助记忆功能前端或其他应用程序 除了通过内置前端进行学习外, 开发者也能在 python 环境中导入 `heurams` 库或使用 `RPC``heurams` 程序库实例通讯, 使用框架构建其他辅助记忆功能前端或其他应用程序
> [!NOTE] > [!NOTE]
> 我们已经着手于基于 KDE 用户界面框架 `Kirigami` 的现代跨平台前端开发, 称作 "KiriMemo", 包名是 "org.kde.kirimemo", 但其并非 KDE 项目\ > 我们已经着手于基于 KDE 用户界面框架 `Kirigami` 的现代跨平台前端开发, 称作 "KiriMemo", 包名是 "org.kde.kirimemo", 但其并非 KDE 项目
> 它通过 `PyOtherSide` 直接复用 python 内核, 为 Windows, Linux, macOS, Android, iOS 和 Plasma Mobile 提供现代用户界面\ > 它通过 `PyOtherSide` 直接复用 python 内核, 为 Windows, Linux, macOS, Android, iOS 和 Plasma Mobile 提供现代用户界面
> 如果您善于开发 C++, QML, Qt 与 KDE 框架, 欢迎加入到 KiriMemo 项目的开发 > 如果您善于开发 C++, QML, Qt 与 KDE 框架, 欢迎加入到 KiriMemo 项目的开发
## 特性 ## 特性
@@ -72,14 +72,11 @@
#### 稳定版本 #### 稳定版本
安装适用于用户体验的可选依赖(推荐): 安装适用于用户体验的可选依赖(推荐):
``` ```
python -m pip install heurams[basic] -i https://pypi.pluv27.top/root/stable/+simple/ python -m pip install heurams[basic] -i https://pypi.pluv27.top/root/stable/+simple/
``` ```
安装适用于一般计算机的通用音频模块(基于 playsound3):
安装适用于一般计算机的通用音频模块(基于 playsound3):\
(此项不适用于 termux 环境, termux 的音频支持是内建的) (此项不适用于 termux 环境, termux 的音频支持是内建的)
``` ```
python -m pip install heurams[audio-playsound] -i https://pypi.pluv27.top/root/stable/+simple/ python -m pip install heurams[audio-playsound] -i https://pypi.pluv27.top/root/stable/+simple/
``` ```
@@ -87,13 +84,12 @@ python -m pip install heurams[audio-playsound] -i https://pypi.pluv27.top/root/s
#### 开发版本 #### 开发版本
> [!CAUTION] > [!CAUTION]
> 对于部分 Linux 发行版和 Android Termux 用户:\ > 对于部分 Linux 发行版和 Android Termux 用户:
> 您需要先行安装 `cmake` 和 `libzmq` 才能正确安装项目的 `zmq` 依赖.\ > 您需要先行安装 `cmake` 和 `libzmq` 才能正确安装项目的 `zmq` 依赖.
> 例如在 termux 上先运行 `pkg install cmake clang libzmq`.\ > 例如在 termux 上先运行 `pkg install cmake clang libzmq`.
> 项目功能本身不依赖它, 但需要该依赖用于启动可选的调试服务器. > 项目功能本身不依赖它, 但需要该依赖用于启动可选的调试服务器.
安装全部可选依赖(推荐): 安装全部可选依赖(推荐):
``` ```
python -m pip install heurams[all] -i https://pypi.pluv27.top/root/dev/+simple/ python -m pip install heurams[all] -i https://pypi.pluv27.top/root/dev/+simple/
``` ```
@@ -116,7 +112,7 @@ python -m pip install heurams[all] -i https://pypi.pluv27.top/root/dev/+simple/
### 从源码安装 ### 从源码安装
我们提供原生 python 和 uv 两种安装方式.\ 我们提供原生 python 和 uv 两种安装方式.
详见[贡献指南](CONTRIBUTING.md). 详见[贡献指南](CONTRIBUTING.md).
## 常见问题 (FAQ) ## 常见问题 (FAQ)
@@ -129,8 +125,8 @@ python -m pip install heurams[all] -i https://pypi.pluv27.top/root/dev/+simple/
## 参与项目 ## 参与项目
欢迎参与到项目协作中!\ 欢迎参与到项目协作中!
详见[贡献指南](CONTRIBUTING.md).\ 详见[贡献指南](CONTRIBUTING.md).
关于 AI 辅助开发的说明, 请参阅 [AGENTS.md](AGENTS.md). 关于 AI 辅助开发的说明, 请参阅 [AGENTS.md](AGENTS.md).
## 项目标识 ## 项目标识
+9 -9
View File
@@ -5,18 +5,18 @@
- Textual 基本用户界面 (heurams.interface): 基于 Python Textual 框架构建的程序库内置跨平台 TUI 界面, 支持触屏、鼠标、键盘多操作模式, 是当前开箱即用的默认前端. - Textual 基本用户界面 (heurams.interface): 基于 Python Textual 框架构建的程序库内置跨平台 TUI 界面, 支持触屏、鼠标、键盘多操作模式, 是当前开箱即用的默认前端.
- KiriMemo (org.kde.kirimemo): 基于 KDE Kirigami 框架的现代跨平台前端, 使用 C++ 和 QML 构建, 通过 `PyOtherSide` 直接复用 Python 内核, 为多种平台提供原生体验 (尚未稳定). - KiriMemo (org.kde.kirimemo): 基于 KDE Kirigami 框架的现代跨平台前端, 使用 C++ 和 QML 构建, 通过 `PyOtherSide` 直接复用 Python 内核, 为多种平台提供原生体验 (尚未稳定).
欢迎为现有前端贡献代码, 或开发您自己的前端.\ 欢迎为现有前端贡献代码, 或开发您自己的前端.
详见[贡献指南](CONTRIBUTING.md#%E6%96%B0%E7%9A%84%E7%94%A8%E6%88%B7%E7%95%8C%E9%9D%A2%E5%89%8D%E7%AB%AF). 详见[贡献指南](CONTRIBUTING.md#新的用户界面前端).
## 基本用户界面前端的截图 ## 基本用户界面前端的截图
> 截图所使用的终端模拟器为 KDE Konsole\ > 截图所使用的终端模拟器为 KDE Konsole
> 字体为 Cascadia Code 和 Noto Sans SC\ > 字体为 Cascadia Code 和 Noto Sans SC
> 终端尺寸设置为 80x25 (软件也支持更大的终端尺寸) > 终端尺寸设置为 80x25 (软件也支持更大的终端尺寸)
### 仪表盘与导航器 ### 仪表盘与导航器
仪表盘包含学习面板的总体视图, 包括不同功能区域的操作入口, 统计信息, 以及单元集概览.\ 仪表盘包含学习面板的总体视图, 包括不同功能区域的操作入口, 统计信息, 以及单元集概览.
导航器是一个实用的模态窗口, 能带您在多种功能间自如切换, 按 `n` 键或单击下方按钮可在任意界面迅速打开/关闭导航器. 导航器是一个实用的模态窗口, 能带您在多种功能间自如切换, 按 `n` 键或单击下方按钮可在任意界面迅速打开/关闭导航器.
<div style="display: flex; flex-wrap: wrap; gap: 10px;"> <div style="display: flex; flex-wrap: wrap; gap: 10px;">
@@ -27,7 +27,7 @@
### 准备界面与预缓存工具 ### 准备界面与预缓存工具
学习准备界面展示了单元集基本信息和每个单元的学习状态, 并提供了学习和预缓存的入口.\ 学习准备界面展示了单元集基本信息和每个单元的学习状态, 并提供了学习和预缓存的入口.
预缓存工具使您能提前预缓存文本转语音资源以确保复习流程的顺畅体验和离线复习能力, 但即使您不预先缓存, 资源也会在复习播放时被自动加载. 预缓存工具使您能提前预缓存文本转语音资源以确保复习流程的顺畅体验和离线复习能力, 但即使您不预先缓存, 资源也会在复习播放时被自动加载.
<div style="display: flex; flex-wrap: wrap; gap: 10px;"> <div style="display: flex; flex-wrap: wrap; gap: 10px;">
@@ -37,7 +37,7 @@
### 记忆队列界面 ### 记忆队列界面
队列式学习记忆的主要界面.\ 队列式学习记忆的主要界面.
同一知识点可产生多种谜题类型的评估方式, 软件内置完形填空与识别题等多种测试类型, 您可在复习流程中按顺序完成不同测试. 同一知识点可产生多种谜题类型的评估方式, 软件内置完形填空与识别题等多种测试类型, 您可在复习流程中按顺序完成不同测试.
<div style="display: flex; flex-wrap: wrap; gap: 10px;"> <div style="display: flex; flex-wrap: wrap; gap: 10px;">
@@ -46,6 +46,7 @@
<img src="screenshots/memoqueue_recognition_2.png" width="48%"> <img src="screenshots/memoqueue_recognition_2.png" width="48%">
</div> </div>
### 设置界面 ### 设置界面
配置界面包含算法选择、音频与多种服务的提供者切换、以及界面与算法设置等选项. 配置界面包含算法选择、音频与多种服务的提供者切换、以及界面与算法设置等选项.
@@ -57,7 +58,7 @@
### 其他界面 ### 其他界面
收藏管理器可管理您手动标记的个人收藏集.\ 收藏管理器可管理您手动标记的个人收藏集.
关于页面提供了程序版本号、许可协议等信息. 关于页面提供了程序版本号、许可协议等信息.
<div style="display: flex; flex-wrap: wrap; gap: 10px;"> <div style="display: flex; flex-wrap: wrap; gap: 10px;">
@@ -68,5 +69,4 @@
## KiriMemo 前端的截图 ## KiriMemo 前端的截图
截图将在 KiriMemo 前端开发趋于稳定后补充. 截图将在 KiriMemo 前端开发趋于稳定后补充.
<!-- TODO: 补充截图 --> <!-- TODO: 补充截图 -->
-1
View File
@@ -27,7 +27,6 @@ user_data = workdir / "data"
if not user_data.exists(): if not user_data.exists():
logger.info("初始化数据目录: %s", user_data) logger.info("初始化数据目录: %s", user_data)
import shutil import shutil
shutil.copytree(default_data, user_data) shutil.copytree(default_data, user_data)
else: else:
(workdir / "data" / "config").mkdir(parents=True, exist_ok=True) (workdir / "data" / "config").mkdir(parents=True, exist_ok=True)
-1
View File
@@ -7,7 +7,6 @@ import pickle
logger = get_logger(__name__) logger = get_logger(__name__)
def start_debug_server(app): def start_debug_server(app):
logger = get_logger("zmq_debug") logger = get_logger("zmq_debug")
context = zmq.Context() context = zmq.Context()
+2 -7
View File
@@ -16,6 +16,7 @@ from textual.widgets import (
Label, Label,
ListItem, ListItem,
ListView, ListView,
Markdown,
Static, Static,
) )
@@ -116,13 +117,7 @@ class FavoriteManagerScreen(Screen):
# 创建列表项, 包含移除按钮 # 创建列表项, 包含移除按钮
container = Horizontal( container = Horizontal(
Label(display_text, classes="favorite-content"), Label(display_text, classes="favorite-content"),
Button( Button("移除", id=f"remove-{button_key}", variant="error", flat=True, classes="favorite-item-btn"),
"移除",
id=f"remove-{button_key}",
variant="error",
flat=True,
classes="favorite-item-btn",
),
classes="favorite-item", classes="favorite-item",
) )
return ListItem(container) return ListItem(container)
+1 -7
View File
@@ -140,11 +140,7 @@ class MemScreen(Screen):
if config_var.get()["interface"]["global"]["persist_to_file"]: if config_var.get()["interface"]["global"]["persist_to_file"]:
self.repo.persist_to_repodir() self.repo.persist_to_repodir()
container.mount( container.mount(Finished(is_saved=config_var.get()["interface"]["global"]["persist_to_file"]))
Finished(
is_saved=config_var.get()["interface"]["global"]["persist_to_file"]
)
)
def on_button_pressed(self, event): def on_button_pressed(self, event):
event.stop() event.stop()
@@ -166,7 +162,6 @@ class MemScreen(Screen):
play_by_path(path) play_by_path(path)
else: else:
from heurams.services.tts_service import convertor from heurams.services.tts_service import convertor
convertor(self.atom.registry["nucleon"]["tts_text"], path) convertor(self.atom.registry["nucleon"]["tts_text"], path)
play_by_path(path) play_by_path(path)
@@ -229,7 +224,6 @@ class MemScreen(Screen):
def action_go_back_notif(self): def action_go_back_notif(self):
self.notify("确定吗? 按下大写 Q 以返回") self.notify("确定吗? 按下大写 Q 以返回")
def action_go_back(self): def action_go_back(self):
self.app.pop_screen() self.app.pop_screen()
@@ -81,10 +81,10 @@ class ClozePuzzle(BasePuzzleWidget):
btnid = f"sel000-{h}" btnid = f"sel000-{h}"
logger.debug(f"建立按钮 {btnid}") logger.debug(f"建立按钮 {btnid}")
self.btn_shortcuts[f"{c}"] = btnid self.btn_shortcuts[f"{c}"] = btnid
btns.append(Button(f"{i}", id=f"{btnid}", classes="cloze-option-btn")) btns.append(Button(f"{i}", id=f"{btnid}", classes='cloze-option-btn'))
for i in range((len(btns) + 1) // 2): for i in range((len(btns)+1)//2):
if 2 * i + 1 + 1 <= len(btns): if 2 * i + 1 + 1 <= len(btns):
yield Horizontal(btns[i], btns[len(btns) - 1 - i], classes="hori") yield Horizontal(btns[i], btns[len(btns) - 1 - i], classes='hori')
else: else:
yield btns[i] yield btns[i]
s.focus() s.focus()
@@ -136,7 +136,7 @@ class ClozePuzzle(BasePuzzleWidget):
self.atom.minimize(rating) self.atom.minimize(rating)
def on_key(self, event: Key) -> None: def on_key(self, event: Key) -> None:
# self.notify(event.key) #self.notify(event.key)
if event.key in self.btn_shortcuts: if event.key in self.btn_shortcuts:
btn_id = self.btn_shortcuts.get(event.key) btn_id = self.btn_shortcuts.get(event.key)
btn_id = "#" + btn_id btn_id = "#" + btn_id
-1
View File
@@ -7,7 +7,6 @@ logger = get_logger(__name__)
_registry: dict[str, type["BaseAlgorithm"]] = {} _registry: dict[str, type["BaseAlgorithm"]] = {}
class BaseAlgorithm: class BaseAlgorithm:
algo_name = "BaseAlgorithm" algo_name = "BaseAlgorithm"
desc = "算法基类" desc = "算法基类"
+12 -8
View File
@@ -3,7 +3,7 @@ FSRS 算法模块 — 基于 py-fsrs 的现代间隔重复调度器
基于: https://github.com/open-spaced-repetition/py-fsrs 基于: https://github.com/open-spaced-repetition/py-fsrs
""" """
import json
import os import os
import pathlib import pathlib
from datetime import datetime, timezone, timedelta from datetime import datetime, timezone, timedelta
@@ -20,10 +20,9 @@ from .base import BaseAlgorithm
logger = get_logger(__name__) logger = get_logger(__name__)
# 全局 Scheduler 状态文件路径 # 全局 Scheduler 状态文件路径
_SCHEDULER_STATE_FILE = ( _SCHEDULER_STATE_FILE = pathlib.Path(
pathlib.Path(config_var.get()["global"]["paths"]["misc"]) config_var.get()["global"]["paths"]["misc"]
/ "fsrs_scheduler_state.json" ) / "fsrs_scheduler_state.json"
)
def _get_global_scheduler(): def _get_global_scheduler():
@@ -137,7 +136,9 @@ class FSRSAlgorithm(BaseAlgorithm):
# last_review # last_review
last_date = data.get("last_date", 0) last_date = data.get("last_date", 0)
card.last_review = _daystamp_to_datetime(last_date) if last_date > 0 else None card.last_review = (
_daystamp_to_datetime(last_date) if last_date > 0 else None
)
return card return card
@@ -159,7 +160,9 @@ class FSRSAlgorithm(BaseAlgorithm):
if card.last_review if card.last_review
else data.get("last_date", 0) else data.get("last_date", 0)
) )
data["next_date"] = _datetime_to_daystamp(card.due) if card.due else 0 data["next_date"] = (
_datetime_to_daystamp(card.due) if card.due else 0
)
data["interval"] = max(0, data["next_date"] - data["last_date"]) data["interval"] = max(0, data["next_date"] - data["last_date"])
data["last_modify"] = get_timestamp() data["last_modify"] = get_timestamp()
return algodata return algodata
@@ -206,7 +209,8 @@ class FSRSAlgorithm(BaseAlgorithm):
algodata[cls.algo_name]["rept"] += 1 algodata[cls.algo_name]["rept"] += 1
logger.debug( logger.debug(
"FSRS.revisor 完成: stability=%s, difficulty=%s, state=%s, " "next_date=%d", "FSRS.revisor 完成: stability=%s, difficulty=%s, state=%s, "
"next_date=%d",
card.stability, card.stability,
card.difficulty, card.difficulty,
card.state, card.state,
+15 -19
View File
@@ -4,7 +4,6 @@ SM-15M — 基于 sm.js 的间隔重复算法
基于: https://github.com/slaypni/sm.js 基于: https://github.com/slaypni/sm.js
原始 CoffeeScript (c) 2014 Kazuaki Tanida, MIT 许可证 原始 CoffeeScript (c) 2014 Kazuaki Tanida, MIT 许可证
""" """
import datetime import datetime
import json import json
import math import math
@@ -20,6 +19,7 @@ from heurams.services.timer import (
get_timestamp_ms, get_timestamp_ms,
daystamp_to_datetime, daystamp_to_datetime,
datetime_to_daystamp, datetime_to_daystamp,
get_now_datetime,
) )
from .base import BaseAlgorithm from .base import BaseAlgorithm
@@ -111,7 +111,7 @@ def power_law_model(a, b):
"""y = a * x^b""" """y = a * x^b"""
def y_func(x): def y_func(x):
return a * (x**b) return a * (x ** b)
def x_func(y): def x_func(y):
if a == 0 or b == 0: if a == 0 or b == 0:
@@ -134,7 +134,7 @@ def fixed_point_power_law_regression(points, fixed_point):
sum_sqX = sum(x * x for x in X) sum_sqX = sum(x * x for x in X)
b = sumXY / sum_sqX if sum_sqX else 0 b = sumXY / sum_sqX if sum_sqX else 0
return power_law_model(q / (p**b), b) return power_law_model(q / (p ** b), b)
# ============================================================================ # ============================================================================
@@ -161,7 +161,7 @@ class FI_G:
def _register_point(self, fi, grade): def _register_point(self, fi, grade):
self.points.append([fi, grade + self.GRADE_OFFSET]) self.points.append([fi, grade + self.GRADE_OFFSET])
if len(self.points) > self.MAX_POINTS_COUNT: if len(self.points) > self.MAX_POINTS_COUNT:
self.points = self.points[-self.MAX_POINTS_COUNT :] self.points = self.points[-self.MAX_POINTS_COUNT:]
self._graph = None self._graph = None
def update(self, grade, item, now): def update(self, grade, item, now):
@@ -203,7 +203,7 @@ class ForgettingCurve:
is_remembered = grade >= THRESHOLD_RECALL is_remembered = grade >= THRESHOLD_RECALL
self.points.append([uf, self.REMEMBERED if is_remembered else self.FORGOTTEN]) self.points.append([uf, self.REMEMBERED if is_remembered else self.FORGOTTEN])
if len(self.points) > self.MAX_POINTS_COUNT: if len(self.points) > self.MAX_POINTS_COUNT:
self.points = self.points[-self.MAX_POINTS_COUNT :] self.points = self.points[-self.MAX_POINTS_COUNT:]
self._curve = None self._curve = None
def retention(self, uf): def retention(self, uf):
@@ -253,9 +253,9 @@ class ForgettingCurves:
pts = [] pts = []
for i in range(21): for i in range(21):
v = MIN_AF + NOTCH_AF * i v = MIN_AF + NOTCH_AF * i
y = math.exp(-1.0 / (10 + 1 * (a + 1)) * (i - a**0.6)) * ( y = math.exp(
self.REMEMBERED - self.sm.requested_fi -1.0 / (10 + 1 * (a + 1)) * (i - a ** 0.6)
) ) * (self.REMEMBERED - self.sm.requested_fi)
pts.append([v, min(self.REMEMBERED, y)]) pts.append([v, min(self.REMEMBERED, y)])
partial = [[0, self.REMEMBERED]] + pts partial = [[0, self.REMEMBERED]] + pts
row.append(ForgettingCurve(partial)) row.append(ForgettingCurve(partial))
@@ -427,9 +427,7 @@ class Item:
now = datetime.datetime.now() now = datetime.datetime.now()
af_idx = self.lapse if self.repetition == 0 else self.af_index() af_idx = self.lapse if self.repetition == 0 else self.af_index()
of_val = self.sm.ofm.of(self.repetition, af_idx) of_val = self.sm.ofm.of(self.repetition, af_idx)
self.of = max( self.of = max(1.0, (of_val - 1) * (self.interval(now) / self.optimum_interval) + 1)
1.0, (of_val - 1) * (self.interval(now) / self.optimum_interval) + 1
)
self.optimum_interval = round(self.optimum_interval * self.of) self.optimum_interval = round(self.optimum_interval * self.of)
self.previous_date = now self.previous_date = now
self.due_date = now + datetime.timedelta(milliseconds=self.optimum_interval) self.due_date = now + datetime.timedelta(milliseconds=self.optimum_interval)
@@ -445,7 +443,7 @@ class Item:
estimated_af = max(MIN_AF, min(MAX_AF, corrected_uf)) estimated_af = max(MIN_AF, min(MAX_AF, corrected_uf))
self._afs.append(estimated_af) self._afs.append(estimated_af)
if len(self._afs) > self.MAX_AFS_COUNT: if len(self._afs) > self.MAX_AFS_COUNT:
self._afs = self._afs[-self.MAX_AFS_COUNT :] self._afs = self._afs[-self.MAX_AFS_COUNT:]
wsum = sum(af * (i + 1) for i, af in enumerate(self._afs)) wsum = sum(af * (i + 1) for i, af in enumerate(self._afs))
wtotal = sum(range(1, len(self._afs) + 1)) wtotal = sum(range(1, len(self._afs) + 1))
self.af(wsum / wtotal if wtotal else estimated_af) self.af(wsum / wtotal if wtotal else estimated_af)
@@ -602,10 +600,7 @@ class SM:
# Global state management # Global state management
# ============================================================================ # ============================================================================
_GLOBAL_STATE_FILE = ( _GLOBAL_STATE_FILE = pathlib.Path(config_var.get()["global"]["paths"]["misc"]) / "sm15m_global_state.json"
pathlib.Path(config_var.get()["global"]["paths"]["misc"])
/ "sm15m_global_state.json"
)
def _get_global_sm(): def _get_global_sm():
@@ -707,8 +702,7 @@ class SM15MAlgorithm(BaseAlgorithm):
last_date = data.get("last_date", 0) last_date = data.get("last_date", 0)
item.previous_date = ( item.previous_date = (
daystamp_to_datetime(last_date).replace(tzinfo=None) daystamp_to_datetime(last_date).replace(tzinfo=None)
if last_date > 0 if last_date > 0 else None
else None
) )
next_date_ms = data.get("next_date_ms", 0) next_date_ms = data.get("next_date_ms", 0)
@@ -749,7 +743,9 @@ class SM15MAlgorithm(BaseAlgorithm):
data["last_date"] = datetime_to_daystamp(item.previous_date) data["last_date"] = datetime_to_daystamp(item.previous_date)
data["next_date_ms"] = int(item.due_date.timestamp() * 1000) data["next_date_ms"] = int(item.due_date.timestamp() * 1000)
data["next_date"] = datetime_to_daystamp(item.due_date) data["next_date"] = datetime_to_daystamp(item.due_date)
data["interval"] = max(0, data["next_date"] - (data.get("last_date", 0) or 0)) data["interval"] = max(
0, data["next_date"] - (data.get("last_date", 0) or 0)
)
data["last_modify"] = get_timestamp() data["last_modify"] = get_timestamp()
return algodata return algodata
@@ -11,7 +11,6 @@ from heurams.services.logger import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
def play_by_path(path: pathlib.Path): def play_by_path(path: pathlib.Path):
logger.debug("playsound_audio.play_by_path: 开始播放 %s", path) logger.debug("playsound_audio.play_by_path: 开始播放 %s", path)
try: try:
+3 -3
View File
@@ -43,9 +43,9 @@ def get_timestamp_ms() -> int:
def daystamp_to_datetime(daystamp: int) -> datetime.datetime: def daystamp_to_datetime(daystamp: int) -> datetime.datetime:
"""将日戳转换为 UTC datetime (当日午夜)""" """将日戳转换为 UTC datetime (当日午夜)"""
return datetime.datetime( return datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc) + datetime.timedelta(
1970, 1, 1, tzinfo=datetime.timezone.utc days=daystamp
) + datetime.timedelta(days=daystamp) )
def datetime_to_daystamp(dt: datetime.datetime) -> int: def datetime_to_daystamp(dt: datetime.datetime) -> int:
+3 -12
View File
@@ -25,18 +25,9 @@ class TestElectronInit:
assert e.algodata["SM-2"]["efactor"] == 2.5 assert e.algodata["SM-2"]["efactor"] == 2.5
def test_existing_data_preserved(self, timer_context): def test_existing_data_preserved(self, timer_context):
data = { data = {"SM-2": {"efactor": 1.5, "rept": 3, "real_rept": 5, "interval": 10,
"SM-2": { "last_date": 100, "next_date": 200, "is_activated": 1,
"efactor": 1.5, "last_modify": 1e9}}
"rept": 3,
"real_rept": 5,
"interval": 10,
"last_date": 100,
"next_date": 200,
"is_activated": 1,
"last_modify": 1e9,
}
}
e = Electron("test-id", data) e = Electron("test-id", data)
assert e.algodata["SM-2"]["efactor"] == 1.5 assert e.algodata["SM-2"]["efactor"] == 1.5
assert e.algodata["SM-2"]["rept"] == 3 assert e.algodata["SM-2"]["rept"] == 3
+3 -1
View File
@@ -65,6 +65,8 @@ class TestEpathModify:
def test_modify_list_index_with_parents(self): def test_modify_list_index_with_parents(self):
d = {"items": []} d = {"items": []}
result = epath(d, "items.[3]", enable_modify=True, new_value=42, parents=True) result = epath(
d, "items.[3]", enable_modify=True, new_value=42, parents=True
)
assert result == 42 assert result == 42
assert d["items"] == [None, None, None, 42] assert d["items"] == [None, None, None, 42]