103 Commits

Author SHA1 Message Date
pluv 6502efd84f Merge branch 'dev'
发布 0.5.0 版本
2026-05-07 19:48:25 +08:00
pluv 048e74ad7f style: 格式化代码 2026-05-07 19:48:07 +08:00
pluv fcda88488b Merge branch 'refactor/v0.5.0' into dev
完成 0.5.0 版本重构工作
2026-05-07 18:45:12 +08:00
pluv 69f8bfb853 docs: 修改文档 2026-05-07 18:43:49 +08:00
pluv e019ffc957 docs: 修改文档 2026-05-07 18:38:03 +08:00
pluv dd2469b49a fix: 修复若干可用性问题 2026-05-07 18:17:39 +08:00
pluv 7bf78ecdff fix: 修复一个依赖问题 2026-05-07 18:02:59 +08:00
pluv cf2283446d docs: 合并前准备 2026-05-07 17:55:22 +08:00
pluv d33aa7055b fix: 总体体验改进与版本号变更 2026-05-07 17:50:30 +08:00
pluv 1975474c94 docs: 修改文档 2026-05-07 13:26:22 +08:00
pluv c2496c1bb5 docs: 更新文档 2026-05-06 21:01:51 +08:00
pluv 1caccc9114 docs: 更新文档 2026-05-06 09:46:50 +08:00
pluv cd23c2d773 docs: 更新文档 2026-05-06 09:35:58 +08:00
pluv 1cbd27279d docs: 更新文档 2026-05-05 11:53:37 +08:00
pluv 0cc19ca92f chore: 更新文档 2026-05-05 10:34:51 +08:00
pluv 03300f0b18 feat: 升级到 playsound3 以替代问题频出的 playsound 2026-05-04 14:22:45 +08:00
pluv ca86b2d8e9 feat: 使算法能被自动注册 2026-05-04 13:55:57 +08:00
pluv e2b9fb94f3 fix: 稳定性修复 2026-05-04 13:13:05 +08:00
pluv 40f4d6c6e3 docs: 更新文档 2026-05-02 02:38:33 +08:00
pluv 520c78dbc5 docs: 更新文档 2026-05-02 02:36:14 +08:00
pluv 5182572cc7 chore: 修改版本文本 2026-04-30 04:52:17 +08:00
pluv a1c1c93c57 fix(interface): 修复持久化问题 2026-04-30 04:45:05 +08:00
pluv 22b0607273 fix: 修改一处语法错误 2026-04-27 02:27:29 +08:00
pluv a95ac55f00 chore: 说明依赖 2026-04-27 02:26:32 +08:00
pluv 7b558f2438 chore: 更新依赖和文档 2026-04-27 02:04:52 +08:00
pluv dead6cb1a1 feat: 高精度时间戳支持与 SM-15M 改进 2026-04-25 02:14:55 +08:00
pluv fa2f8fa701 feat: 增加早期 FSRS 支持 2026-04-25 01:54:18 +08:00
pluv 7f2bcf45de test: 增加测试 2026-04-25 01:38:33 +08:00
pluv b235651db5 docs: 修改文档 2026-04-24 15:40:42 +08:00
pluv f0b63fdeb6 docs: 修改文档 2026-04-24 15:38:10 +08:00
pluv 5b7b4ba443 style: 代码格式化 2026-04-23 00:13:57 +08:00
pluv 1c05f42b61 docs: 更新文档 2026-04-22 23:39:20 +08:00
pluv f50c19ba82 feat(interface): 组件自动聚焦与键盘操作改进 2026-04-22 22:54:25 +08:00
pluv c2a1867c49 fix(interface): 修复 CSS_PATH 导致的样式覆写问题 2026-04-22 06:55:58 +08:00
pluv b9d88383f9 perf: 用 autoflake 删除无用依赖
没想到居然能意外地大幅提升启动速度
2026-04-22 06:44:59 +08:00
pluv 093034828b feat: 开发 unifront 前端会话模块 2026-04-21 16:52:04 +08:00
pluv fc70aa07f6 refactor: 开始翻新状态机 2026-04-21 12:52:30 +08:00
pluv e53e905e1b fix: 修复部分问题 2026-04-21 02:06:28 +08:00
pluv a610904290 style: 格式化代码 2026-04-21 00:17:03 +08:00
pluv c0483b87f8 fix: 优化 CSS 结构 2026-04-21 00:15:57 +08:00
pluv e943b1b7d7 fix: 修复 Lict 问题 2026-04-20 18:55:25 +08:00
pluv 2a47cad2d5 perf: 脏标记优化 Lict 对象操作复杂度 2026-04-20 18:43:27 +08:00
pluv 417588acea feat: 用户界面实现算法共存与切换 2026-04-20 17:50:36 +08:00
pluv 65fbdec0a9 feat: 代码格式化, 改进仪表盘, 新增多CSS支持 2026-04-20 16:30:04 +08:00
pluv 845a505ca1 feat: 试增加单元集独立设置 2026-04-20 07:39:57 +08:00
pluv d16ec03da6 feat: 增加 ZMQ 调试服务器并完善设置功能 2026-04-20 06:37:46 +08:00
pluv 41af2ada45 feat: 补全设置提示与完善设置页 2026-04-20 05:05:44 +08:00
pluv b3f95861f0 feat(interface): 图形化设置页原型 2026-04-20 03:33:05 +08:00
pluv 4a4237acbd style: 删除旧配置文件 2026-04-20 01:46:40 +08:00
pluv a38fd3d398 refactor: 对配置处理器和配置结构进行重构 2026-04-20 01:44:43 +08:00
pluv 5c43059518 fix: 变更哈希注册表以修复重复键造成的崩溃 2026-04-19 12:08:58 +08:00
pluv 053c4660d0 feat: FAST-0 特殊算法 2026-04-19 00:05:49 +08:00
pluv 215a8648c2 style: 移除设计不当的部分模块 2026-04-16 13:20:12 +08:00
pluv f7c072dd0b build: 修改依赖并引入外部库 2026-03-27 19:31:40 +08:00
pluv 230a76fe27 feat(interface): 改进界面 2026-01-28 03:35:58 +08:00
pluv 26e053f79f feat(interface): 改进仪表盘 2026-01-22 06:13:01 +08:00
pluv 76c9537ff0 fix: 修复若干问题 2026-01-17 05:45:45 +08:00
pluv 56f5dc076e fix(interface): 修复按钮标识和一个哈希问题 2026-01-15 12:49:57 +08:00
pluv 3f7bbcdc19 feat(interface): 改进状态显示 2026-01-13 23:39:10 +08:00
pluv 4cb6edf073 build: 使用 uv 包管理器 2026-01-09 00:47:34 +08:00
pluv 264af76fad fix: 改进 2026-01-08 01:00:08 +08:00
pluv c9cc8a7917 fix: 小范围修补 2026-01-08 00:56:04 +08:00
pluv 6d60f95d6a feat: 一系列新功能 2026-01-08 00:05:00 +08:00
pluv d3dadced85 feat(interface): 完成队列式记忆模块更新 2026-01-06 20:32:27 +08:00
pluv 9d9858788c fix: 修复完成屏幕问题 2026-01-06 19:58:46 +08:00
pluv 3266eda44d fix: 修正逻辑问题 2026-01-06 18:28:57 +08:00
pluv 4dd8e373b2 feat: 更新状态机 2026-01-05 05:25:14 +08:00
pluv 1d0c87b5f7 fix: 改进代码 2026-01-04 04:46:19 +08:00
pluv b8b1c5e0d7 fix(interface): 界面兼容性改进 2026-01-04 04:14:57 +08:00
pluv 4d494546b8 fix(interface): 完成主要屏幕重构 2026-01-04 03:46:45 +08:00
pluv 5a825f6eb3 fix: 部分修复重构数据格式差异 2026-01-04 02:11:07 +08:00
pluv 10f541325b feat(reactor): 状态机进一步改进 2026-01-04 00:28:44 +08:00
pluv 7f36450079 feat: 改进状态机 2026-01-03 13:08:08 +08:00
pluv 32cd44b8e7 feat(kernel): 状态机改进 2026-01-03 05:05:41 +08:00
pluv f2d91b0164 feat: 完成部分界面重构 2026-01-02 06:12:49 +08:00
pluv bd313db5f2 feat: 改进对象系统 2026-01-01 20:18:18 +08:00
pluv d25e398701 feat: 基本完成对象系统更新 2026-01-01 16:19:46 +08:00
pluv af63cceba9 feat: 改进粒子对象 2026-01-01 06:36:27 +08:00
pluv 8358e2532a feat: 进一步改进 2025-12-31 00:57:07 +08:00
pluv 8828db24b5 feat: 一些改进 2025-12-29 21:53:20 +08:00
pluv ecefc53a20 refactor: 部分更改数据结构 2025-12-28 06:16:21 +08:00
pluv b3bc5b1a09 0.4.3 版本合并 2025-12-21 23:44:28 +08:00
pluv e562e02227 style: 格式化代码 2025-12-21 23:44:13 +08:00
pluv c10a18bafd feat(interface): 增加智能单元集排序 2025-12-21 23:42:02 +08:00
pluv 6a5120920a fix: 暂时禁用实验性功能 2025-12-21 23:06:17 +08:00
pluv 56038afefe refactor(synctool): 改进同步方案 2025-12-21 21:18:31 +08:00
pluv d4dce68449 feat(synctool): 虚拟文件系统初步方案 2025-12-21 18:48:25 +08:00
pluv 8dd323a789 style: 格式化代码 2025-12-21 07:56:10 +08:00
pluv 531b0756bf feat(synctool): 增加同步功能 2025-12-21 07:49:19 +08:00
pluv 6adf1c62f6 0.4.2 版本合并 2025-12-21 06:49:38 +08:00
pluv 058cc1e4d8 feat(kernel): 添加算法切换设置 2025-12-21 06:48:30 +08:00
pluv 7e28e0cd6b style: 更新版本号和合并规则 2025-12-21 06:34:17 +08:00
pluv a3612bd3b3 fix(interface): 修复仪表盘详情 2025-12-21 06:27:00 +08:00
pluv 77441e2d40 feat(interface): 更改启动方式 2025-12-21 06:06:16 +08:00
pluv 72697f652f fix(interface): 修复显示问题 2025-12-21 05:47:22 +08:00
pluv c5fd625c87 feat: 自动音频播放与改进设计 2025-12-21 05:32:58 +08:00
pluv abd94d9a21 0.4.1 版本合并 2025-12-21 03:03:06 +08:00
pluv 91d3c86871 style(version): 更新版本号 2025-12-21 03:02:29 +08:00
pluv 66ad50c44d feat: 实验性 SM-15M 算法实现
实验性 SM-15M 逆向工程算法实现
2025-12-21 02:15:23 +08:00
pluv 2527daa923 style: isort 格式化 2025-12-19 15:13:42 +08:00
pluv 1883ca2387 style: 格式化代码 2025-12-19 15:08:26 +08:00
pluv dde6f872f0 fix(interface): 修复默认配置文件 2025-12-18 15:53:18 +08:00
pluv 16c22cf207 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 164 additions and 132 deletions
+2 -1
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,6 +81,7 @@ python3 -m heurams.interface # 启动 TUI
贡献者拥有其贡献部分的版权同意其贡献将在 AGPL-3.0 许可证(包括附加的本机 API 调用豁免条款)下发布. 贡献者拥有其贡献部分的版权同意其贡献将在 AGPL-3.0 许可证(包括附加的本机 API 调用豁免条款)下发布.
如有以下情况, 请在 PR 描述中注明: 如有以下情况, 请在 PR 描述中注明:
- 如果需要引入其他开源 vendor - 如果需要引入其他开源 vendor
- 如果需要引入其他专有的网络服务(例如当前项目中的 edgetts) - 如果需要引入其他专有的网络服务(例如当前项目中的 edgetts)
- 如果需要升级某个依赖或运行环境的版本 - 如果需要升级某个依赖或运行环境的版本
+6 -1
View File
@@ -44,6 +44,7 @@
所以可能和您的想象不同, 您事实上可以直接用鼠标点击按钮, 就像使用普通软件一样. 所以可能和您的想象不同, 您事实上可以直接用鼠标点击按钮, 就像使用普通软件一样.
### 也可以用键盘 ### 也可以用键盘
- `Tab` 键在不同区域之间切换焦点 - `Tab` 键在不同区域之间切换焦点
- `方向键` 在列表中上下移动 - `方向键` 在列表中上下移动
- `Enter` 确认选择 - `Enter` 确认选择
@@ -51,6 +52,7 @@
- 屏幕上会有按键提示, 例如 `[n] 导航器` 表示按 `n` 键打开导航器 - 屏幕上会有按键提示, 例如 `[n] 导航器` 表示按 `n` 键打开导航器
### 触屏也可以 ### 触屏也可以
在平板或手机 Termux 中, 您可以触摸或者滑动屏幕操作. 在平板或手机 Termux 中, 您可以触摸或者滑动屏幕操作.
## 我怎么启动这个软件? ## 我怎么启动这个软件?
@@ -58,6 +60,7 @@
首先需要确保系统中安装了 Python (推荐 3.12.13 版本) 并安装了 HeurAMS 的所需组件. 首先需要确保系统中安装了 Python (推荐 3.12.13 版本) 并安装了 HeurAMS 的所需组件.
### Windows ### Windows
打开"命令提示符"或"PowerShell", 输入以下命令后按回车, 或者把这玩意另存为快捷方式: 打开"命令提示符"或"PowerShell", 输入以下命令后按回车, 或者把这玩意另存为快捷方式:
``` ```
@@ -65,9 +68,11 @@ python -m heurams.interface
``` ```
### macOS ### macOS
打开"终端"应用程序, 输入以上命令. 打开"终端"应用程序, 输入以上命令.
### Linux ### Linux
打开您的终端模拟器 (一般是按 Ctrl + Alt + T), 输入以上命令. 打开您的终端模拟器 (一般是按 Ctrl + Alt + T), 输入以上命令.
如果您觉得每次输入命令太麻烦, 可以创建一个桌面快捷方式或脚本文件, 详见网上的相关教程. 如果您觉得每次输入命令太麻烦, 可以创建一个桌面快捷方式或脚本文件, 详见网上的相关教程.
@@ -324,7 +329,7 @@ Flutter 是构建跨平台图形界面的优秀框架, HeurAMS 的设计目标
当前我们优先开发了基于 Textual 的 TUI 前端和基于 Kirigami 的原生前端, 但这不排除未来出现 Flutter 或其他框架前端的可能性. 当前我们优先开发了基于 Textual 的 TUI 前端和基于 Kirigami 的原生前端, 但这不排除未来出现 Flutter 或其他框架前端的可能性.
如果您有兴趣开发 Flutter 前端, 欢迎参考[贡献指南](CONTRIBUTING.md#新的用户界面前端). 如果您有兴趣开发 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).
## 软件需要联网吗? ## 软件需要联网吗?
+16 -12
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,11 +72,14 @@
#### 稳定版本 #### 稳定版本
安装适用于用户体验的可选依赖(推荐): 安装适用于用户体验的可选依赖(推荐):
``` ```
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/
``` ```
@@ -84,12 +87,13 @@ 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/
``` ```
@@ -112,7 +116,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)
@@ -125,8 +129,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#新的用户界面前端). 详见[贡献指南](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).
## 基本用户界面前端的截图 ## 基本用户界面前端的截图
> 截图所使用的终端模拟器为 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,7 +46,6 @@
<img src="screenshots/memoqueue_recognition_2.png" width="48%"> <img src="screenshots/memoqueue_recognition_2.png" width="48%">
</div> </div>
### 设置界面 ### 设置界面
配置界面包含算法选择、音频与多种服务的提供者切换、以及界面与算法设置等选项. 配置界面包含算法选择、音频与多种服务的提供者切换、以及界面与算法设置等选项.
@@ -58,7 +57,7 @@
### 其他界面 ### 其他界面
收藏管理器可管理您手动标记的个人收藏集. 收藏管理器可管理您手动标记的个人收藏集.\
关于页面提供了程序版本号、许可协议等信息. 关于页面提供了程序版本号、许可协议等信息.
<div style="display: flex; flex-wrap: wrap; gap: 10px;"> <div style="display: flex; flex-wrap: wrap; gap: 10px;">
@@ -69,4 +68,5 @@
## KiriMemo 前端的截图 ## KiriMemo 前端的截图
截图将在 KiriMemo 前端开发趋于稳定后补充. 截图将在 KiriMemo 前端开发趋于稳定后补充.
<!-- TODO: 补充截图 --> <!-- TODO: 补充截图 -->
+1
View File
@@ -27,6 +27,7 @@ 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,6 +7,7 @@ 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()
+7 -2
View File
@@ -16,7 +16,6 @@ from textual.widgets import (
Label, Label,
ListItem, ListItem,
ListView, ListView,
Markdown,
Static, Static,
) )
@@ -117,7 +116,13 @@ class FavoriteManagerScreen(Screen):
# 创建列表项, 包含移除按钮 # 创建列表项, 包含移除按钮
container = Horizontal( container = Horizontal(
Label(display_text, classes="favorite-content"), Label(display_text, classes="favorite-content"),
Button("移除", id=f"remove-{button_key}", variant="error", flat=True, classes="favorite-item-btn"), Button(
"移除",
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)
+7 -1
View File
@@ -140,7 +140,11 @@ 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(Finished(is_saved=config_var.get()["interface"]["global"]["persist_to_file"])) container.mount(
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()
@@ -162,6 +166,7 @@ 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)
@@ -224,6 +229,7 @@ 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,6 +7,7 @@ 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 -16
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,9 +20,10 @@ from .base import BaseAlgorithm
logger = get_logger(__name__) logger = get_logger(__name__)
# 全局 Scheduler 状态文件路径 # 全局 Scheduler 状态文件路径
_SCHEDULER_STATE_FILE = pathlib.Path( _SCHEDULER_STATE_FILE = (
config_var.get()["global"]["paths"]["misc"] pathlib.Path(config_var.get()["global"]["paths"]["misc"])
) / "fsrs_scheduler_state.json" / "fsrs_scheduler_state.json"
)
def _get_global_scheduler(): def _get_global_scheduler():
@@ -78,10 +79,10 @@ class FSRSAlgorithm(BaseAlgorithm):
class AlgodataDict(TypedDict): class AlgodataDict(TypedDict):
# FSRS 特有字段 # FSRS 特有字段
fsrs_state: int # State 枚举值: 1=Learning, 2=Review, 3=Relearning fsrs_state: int # State 枚举值: 1=Learning, 2=Review, 3=Relearning
fsrs_step: int # 当前学习步进索引, -1 表示 None (Review 状态) fsrs_step: int # 当前学习步进索引, -1 表示 None (Review 状态)
fsrs_stability: float # 稳定性(秒), 0.0 表示尚未计算 fsrs_stability: float # 稳定性(秒), 0.0 表示尚未计算
fsrs_difficulty: float # 难度 [1.0, 10.0], 0.0 表示尚未计算 fsrs_difficulty: float # 难度 [1.0, 10.0], 0.0 表示尚未计算
# 标准 BaseAlgorithm 兼容字段 # 标准 BaseAlgorithm 兼容字段
real_rept: int real_rept: int
rept: int rept: int
@@ -92,7 +93,7 @@ class FSRSAlgorithm(BaseAlgorithm):
last_modify: float last_modify: float
defaults = { defaults = {
"fsrs_state": 1, # State.Learning "fsrs_state": 1, # State.Learning
"fsrs_step": 0, "fsrs_step": 0,
"fsrs_stability": 0.0, "fsrs_stability": 0.0,
"fsrs_difficulty": 0.0, "fsrs_difficulty": 0.0,
@@ -136,9 +137,7 @@ class FSRSAlgorithm(BaseAlgorithm):
# last_review # last_review
last_date = data.get("last_date", 0) last_date = data.get("last_date", 0)
card.last_review = ( card.last_review = _daystamp_to_datetime(last_date) if last_date > 0 else None
_daystamp_to_datetime(last_date) if last_date > 0 else None
)
return card return card
@@ -160,9 +159,7 @@ 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"] = ( data["next_date"] = _datetime_to_daystamp(card.due) if card.due else 0
_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
@@ -209,8 +206,7 @@ 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, " "FSRS.revisor 完成: stability=%s, difficulty=%s, state=%s, " "next_date=%d",
"next_date=%d",
card.stability, card.stability,
card.difficulty, card.difficulty,
card.state, card.state,
+19 -15
View File
@@ -4,6 +4,7 @@ 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
@@ -19,7 +20,6 @@ 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( y = math.exp(-1.0 / (10 + 1 * (a + 1)) * (i - a**0.6)) * (
-1.0 / (10 + 1 * (a + 1)) * (i - a ** 0.6) self.REMEMBERED - self.sm.requested_fi
) * (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,7 +427,9 @@ 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(1.0, (of_val - 1) * (self.interval(now) / self.optimum_interval) + 1) self.of = max(
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)
@@ -443,7 +445,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)
@@ -600,7 +602,10 @@ class SM:
# Global state management # Global state management
# ============================================================================ # ============================================================================
_GLOBAL_STATE_FILE = pathlib.Path(config_var.get()["global"]["paths"]["misc"]) / "sm15m_global_state.json" _GLOBAL_STATE_FILE = (
pathlib.Path(config_var.get()["global"]["paths"]["misc"])
/ "sm15m_global_state.json"
)
def _get_global_sm(): def _get_global_sm():
@@ -702,7 +707,8 @@ 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 else None if last_date > 0
else None
) )
next_date_ms = data.get("next_date_ms", 0) next_date_ms = data.get("next_date_ms", 0)
@@ -743,9 +749,7 @@ 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( data["interval"] = max(0, data["next_date"] - (data.get("last_date", 0) or 0))
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,6 +11,7 @@ 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(1970, 1, 1, tzinfo=datetime.timezone.utc) + datetime.timedelta( return datetime.datetime(
days=daystamp 1970, 1, 1, tzinfo=datetime.timezone.utc
) ) + datetime.timedelta(days=daystamp)
def datetime_to_daystamp(dt: datetime.datetime) -> int: def datetime_to_daystamp(dt: datetime.datetime) -> int:
+12 -3
View File
@@ -25,9 +25,18 @@ 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 = {"SM-2": {"efactor": 1.5, "rept": 3, "real_rept": 5, "interval": 10, data = {
"last_date": 100, "next_date": 200, "is_activated": 1, "SM-2": {
"last_modify": 1e9}} "efactor": 1.5,
"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
+1 -3
View File
@@ -65,8 +65,6 @@ class TestEpathModify:
def test_modify_list_index_with_parents(self): def test_modify_list_index_with_parents(self):
d = {"items": []} d = {"items": []}
result = epath( result = epath(d, "items.[3]", enable_modify=True, new_value=42, parents=True)
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]