80 Commits

Author SHA1 Message Date
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
102 changed files with 7383 additions and 1576 deletions
+157 -29
View File
@@ -1,19 +1,34 @@
# HeurAMS specific rules
*.log.*
*.log
*.pkl
/data
# Editor
# Project specific additions
.devflag
.vscode/
.directory
__pycache__/
.idea/
cache/
data/repo/cngk
data/repo/eotgk
data/repo/evtgk
data/misc
data/cache
data/session
*.egg-info/
build/
dist/
old/
AGENT.md
AGENTS.md
*.log.*
*.pkl
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
@@ -32,6 +47,16 @@ share/python-wheels/
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
@@ -46,10 +71,73 @@ coverage.xml
.hypothesis/
.pytest_cache/
cover/
.claude/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly used for packaging.
#poetry.lock
#poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
@@ -59,32 +147,72 @@ ENV/
env.bak/
venv.bak/
# Linux
*~
.fuse_hidden*
.directory
.Trash-*
.nfs*
## KDE Dolphin
.directory
# Spyder project settings
.spyderproject
.spyproject
# macOS
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
.idea/
# Audio cache and temporary files
*.mp3
*.wav
*.ogg
*.tmp
# LLM cache files
*.cache
*.jsonl
# Log files
*.log
logs/
# OS generated files
.DS_Store
.AppleDouble
.LSOverride
.DS_Store?
._*
.AppleDB
.AppleDesktop
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Windows
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
[Dd]esktop.ini
$RECYCLE.BIN/
*.lnk
Desktop.ini
# Other trash
AGENT.md
# It's not AGENTS.md
# Linux
*~
# VSCode
.vscode/
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Temporary files
*.tmp
*.temp
-50
View File
@@ -1,50 +0,0 @@
# AI 编程工具说明
本文档为 AI 工具以及在使用 AI 辅助向 HeurAMS 项目贡献代码的开发者提供指导, 一般而言此文件会被自动读入多种 AI 工具的上下文.
AI 工具应当完整阅读此 `/AGENTS.md` 文件.
## 查阅开发文档
在帮助进行 HeurAMS 开发时,AI 工具应遵循标准的开发规范与流程, 应当自动查看或是在用户发出"初始化/init"指令后查看:
- [贡献指南](/CONTRIBUTING.md)
- [自述文件](/README.md)
- [项目架构](/ARCHITECTURE.md)
## 明确禁止行为
1. 禁止 AI 自动生成 PR 或 patch 文件
2. 禁止 AI 在未经人工确认的情况下修改现有代码
3. 禁止 AI 不使用格式化工具而生成格式化文件的行为
4. 禁止 AI 修复任何"bug", 而不经人工确认
5. 禁止一切不遵循项目设计原则, 另造独立库的 "糊屎" 行为
6. 禁止 AI 直接操作 pip, uv, apt 等工具修改外部依赖或工具, 而应让人类开发者自己操作依赖
7. 禁止使用不同于任何现有文件的现有注释语言的其他语言写新注释
8. 禁止不读文件就直接覆写
9. 绝对禁止修改此 `/AGENTS.md` 文件
## 许可证与法律要求
所有贡献必须符合许可要求, 所有代码必须与 AGPL-3.0-or-later 许可以及项目附加豁免条款(位于 LICENSE 文件尾部 237 至 245 行)兼容.
## Signed-off-by 与 DCO
AI 代理**严禁添加** Signed-off-by 标签.
只有人类能够合法地认证 DCO.
人类提交者负责:
- 审阅所有 AI 生成的代码
- 确保符合许可要求
- 添加自己的 Signed-off-by 标签以认证 DCO
- 对贡献负责任
AI 助手负责:
- 了解运行环境, 例如操作系统或具体发行版
- 遵循此文档所述规则
- 主动提醒使用 AI 工具的开发者
本文档参考自 <a href="https://docs.kernel.org/process/coding-assistants.html" target="_blank" rel="noopener noreferrer">AI Coding Assistants — The Linux Kernel documentation</a>
+4 -4
View File
@@ -68,7 +68,7 @@ graph TB
项目以物理粒子隐喻为核心, 将记忆单元拆解为三个模型:
### Nucleon (核子) - 内容层
### Nucleon (核子) 内容层
```
Nucleon(ident, payload, common)
@@ -79,7 +79,7 @@ Nucleon(ident, payload, common)
-`repo.payload``repo.typedef["common"]` 配对创建.
- 一旦创建, 内容不可修改 (`__setitem__` 抛出 `AttributeError`).
### Electron (电子) - 状态层
### Electron (电子) 状态层
```
Electron(ident, algodata, algo_name)
@@ -89,7 +89,7 @@ Electron(ident, algodata, algo_name)
- `algodata` 是到仓库 `algodata.lict` 中对应字典的**引用**, 修改即持久化.
- 核心方法:`activate()` (标记激活)、`revisor()` (评分迭代)、`is_due()` (到期判断).
### Orbital (轨道) - 策略层
### Orbital (轨道) 策略层
```
orbital = {
@@ -104,7 +104,7 @@ orbital = {
- 定义复习阶段流程和各阶段内谜题选择策略的纯字典.
- 每个阶段对应一组 `(谜题类型, 概率系数)` 元组列表, 概率系数 >1 的部分表示强制重复次数.
### Atom (原子) - 运行时组装
### Atom (原子) 运行时组装
```
Atom(nucleon, electron, orbital)
+33 -35
View File
@@ -1,24 +1,15 @@
# 贡献指南与二次开发
欢迎支持此项目!
目前, 项目仓库主服务器为<a href="https://git.pluv27.top/pluv/HeurAMS" target="_blank" rel="noopener noreferrer">作者的 Gitea 实例</a>, 它负责管理同步, 保证可用性并同时接受来自多个社区的协作, 并在 <a href="https://github.com/pluvium27/HeurAMS" target="_blank" rel="noopener noreferrer">GitHub</a>, <a href="https://invent.kde.org/pluv/HeurAMS" target="_blank" rel="noopener noreferrer">KDE Invent</a> 和 <a href="https://gitee.com/pluv/HeurAMS" target="_blank" rel="noopener noreferrer">Gitee</a> 设置了镜像同步.
这丝毫不影响项目接受来自 <a href="https://github.com/pluvium27/HeurAMS" target="_blank" rel="noopener noreferrer">GitHub</a>, <a href="https://invent.kde.org/pluv/HeurAMS" target="_blank" rel="noopener noreferrer">KDE Invent</a> 和 <a href="https://gitee.com/pluv/HeurAMS" target="_blank" rel="noopener noreferrer">Gitee</a> 的 PR, 在 GitHub, KDE Invent 和 Gitee 所接受的 PR 会保留贡献者标识并按原样同步回所有平台, 欢迎在任意平台为项目做出贡献.
> [!NOTE]
> 我们已经开始着手于基于 KDE 用户界面框架 `Kirigami` 的现代跨平台前端开发, 称作 "KiriMemo", 包名是 "org.kde.kirimemo", 但其并非 KDE 项目\
> 它通过 `PyOtherSide` 直接复用 python 内核, 为 Windows, Linux, macOS, Android, iOS 和 Plasma Mobile 提供现代用户界面
> 如果您善于开发 C++, QML, Qt 与 KDE 框架, 欢迎加入到 KiriMemo 项目的开发
欢迎此项目做出贡献!
## 开发规范
分支划分:
- `dev` 分支(仓库默认分支): 主线开发分支, 自身仅用于非重构的问题修复和整合功能分支, 拉取请求在该分支合并
- `master` 分支: 主线稳定版本, 仅当稳定版本释出或修补版本时将 `dev` 合并到 `master`
- 功能与重构分支: 从 `dev` 分支创建, 命名格式为 `feature/描述``fix/描述``refactor/描述``next/版本号`
- 功能与重构分支先合并至 `dev`, 再合并`master`
- `main` 分支: 稳定版本, 仅当稳定版本释出或修补版本时将 `dev` 合并到 `main`
- `dev` 分支: 主线开发版本, 自身仅用于非重构的问题修复和整合功能分支
- 功能与重构分支: 从 `dev` 分支创建, 命名格式为 `feature/描述``fix/描述``refactor/v版本号`
- 不要将功能与重构分支先应被合并至 `dev` 后在 `dev` 完成文档开发后再释出`main`
代码格式化:
@@ -42,7 +33,7 @@
mdformat --number .
```
- 对于 Textual CSS, 可以使用 `prettier` 格式化
- 格式化不是必需的, 可以整合入一次 `style` 提交, 但 `master` 和 `dev` 分支上的代码应尽量整洁, 以便合并时审查
- 格式化不是必需的, 可以整合入一次 `style` 提交, 但 `main` 和 `dev` 分支上的代码应尽量整洁, 以便合并时审查
提交消息:
@@ -54,65 +45,72 @@
- 为了一致性和可追溯性, 项目自 v0.4.0 重构后重新初始化仓库起就禁止使用 Fast-forward 合并
- 可以设置 `git config merge.ff false`
提交署名方式:
由于 KDE Invent 设施的奇怪 git hook, commit 的 Author 字段需要看起来像个真名(例如 Wang Zhiyu 不能写为 wangzhiyu, 否则 KDE Invent 的 hook 会拒绝 push),
所以请确保您的 git 配置使用了类似正式姓名的格式 (例如 git config user.name "Li Hua", 也即中间需要空格), 不一定要真实姓名, 邮箱无要求, 也可以将单名重复使用两次 (例如 Thura Thura) 以通过检测.
## 设置开发环境
```bash
git clone https://git.pluv27.top/pluv/HeurAMS # 默认分支为 dev, 所以不必切换分支
# 克隆仓库
git clone https://git.pluv27.top/pluv/HeurAMS
cd HeurAMS
# 可能需要切换到 dev 分支
git checkout dev
# 如果决定使用 uv (推荐)
## 首先要安装uv, 例如通过 pip 或者其他包管理器
python3 -m pip install uv
uv sync --all-extras # 同步开发运行环境
uv run heurams
uv run heurams # 验证包安装
# 如果决定使用原生 python 环境 (不推荐, 但我们保留了这种方式以便在不便支持 uv 与硬链接的环境和文件系统(例如 termux)上运行)
uv run tui # 启动 TUI
python3 -m pip install -e .[all] # 安装依赖并将 HeurAMS 安装为本地包
# 如果决定使用原生 python 环境 (不推荐, 但我们保留了这种方式以便在不便支持 uv 与硬链接的环境和文件系统(例如 termux)运行 HeurAMS)
## 安装依赖并将 HeurAMS 安装为本地包
python3 -m pip install -r requirements.txt
python3 -m pip install -e .
python3 -m heurams # 验证安装
python3 -m heurams.interface # 启动 TUI
python3 -m heurams.__interface__ # 启动 TUI
```
## 许可证与外部引用
贡献者拥有其贡献部分的版权同意其贡献将在 AGPL-3.0 许可证(包括附加的本机 API 调用豁免条款)下发布.
贡献者拥有其贡献部分的版权同意其贡献将在 AGPL-3.0 许可证下发布.
有以下情况, 请在 PR 描述中注明:
果您认为有必要引入其他开源的 vendor, 请在 PR 中注明或手动联系以便我们审查 vendor 许可证并更改此处和网站上的关于与版权声明
- 如果需要引入其他开源 vendor
- 如果需要引入其他专有的网络服务(例如当前项目中的 edgetts)
- 如果需要升级某个依赖或运行环境的版本
如果您认为有必要引入其他专有的网络服务(就像现在项目中的 edgetts), 请也在 PR 中注明
## 新的用户界面前端
如果您认为有必要升级某个依赖或运行环境的版本, 请也在 PR 中注明
## 新的用户界面前端与其他语言移植
HeurAMS 被设计为一个可独立于前端的程序库, 这意味着:
- 我们的内置 Textual TUI 前端不是唯一可用的前端
- 如果您有一个自己开发的且可用的 HeurAMS 前端 (例如未实现的 Flutter 前端), 并且以 AGPL-3.0/GPL-3.0 开放源代码, 可以联系我们将它转移到 HeurAMS 的官方仓库中以便共同维护, 您将保留您的版权并可主导该仓库下的开发工作 :)
- 您可以在自己的项目中以独立进程/服务调用 HeurAMS (但不能在代码中链接), 而免于受 AGPL-3.0 "污染". 为了这点, 我们正在完善可选择启用的跨进程 RPC 模块, 这将成为潜进内核的跨平台标准件.
- 您还可以在自己的项目中以独立进程/服务调用 HeurAMS, 根据 AGPL-3.0 及本项目的附加许可条款, 如果调用发生在同一主机上且不涉及外部网络转发, 则可豁免许可证规定的特定义务而免于受 AGPL-3.0 "污染". 为了这点, 我们正在完善可选择启用的跨进程 RPC 模块, 这将成为潜进内核的跨平台标准件.
- 如果您有一个自己开发的且可用的 HeurAMS 前端 (例如我们暂未实现的 flutter 前端), 并且以 AGPL-3.0/GPL-3.0 开放源代码, 可以联系我们将它转移到 HeurAMS 的官方仓库中以便共同维护, 您将保留您的版权并可主导该仓库下的开发工作 :)
- 如果您通过独立进程/服务调用方式开发了另外的软件, 开源但不愿使用 AGPL-3.0/GPL-3.0 许可证, 也可以联系我们, 我们乐于将您的项目链接添加到友链中
- 如果您想创建程序库的其他语言 (例如 dart 或 rust) 版本以协助此语言下的方便集成, 并且同样以 AGPL-3.0/GPL-3.0 开放源代码, 也可以联系我们将它转移到 HeurAMS 的官方仓库中以便共同维护, 您将保留您的版权并可主导该仓库下的开发工作 :)
## 软件开发之外的贡献
即使您不是软件开发人员, 我们也欢迎您加入贡献!
您可以:
- 协助创建或核对各种语言的翻译来翻译软件的界面和文档
- 制作开放的记忆单元集(包括但不限于文字、图像、音效)给其他用户使用
- 协助创建各种语言的翻译来翻译软件的界面 (但我们目前还没有 i18n 平台, 所以如果您想贡献翻译, 可能需要手动联系我们)
- 制作图像、主题、音效乃至制作开放的记忆单元集给其他用户使用
- 改进软件配套的文档
- 维护软件的开发/交流群组
- 给其他用户答疑解惑或分享自己的经验
- 在讨论区提出新想法或反馈问题
-416
View File
@@ -1,416 +0,0 @@
# 常见问题
## 什么是终端模拟器?
终端模拟器是在图形桌面环境中模拟并使用终端的应用程序, 例如 KDE Konsole, GNOME Terminal, Windows Terminal, iTerm2 等.
较旧 Windows 的那个很寒酸的小黑窗口也是终端模拟器(conhost.exe), 但它对此软件基本用户界面(以及一切现代终端应用)支持不佳, 建议在 Windows 平台使用 WezTerm (支持 sixel) 或 Windows Terminal (不支持 sixel).
## 软件支持移动设备吗?
基本用户界面 (Textual TUI) 可在 Android Termux 中良好运行.
此外, 正在开发的 KiriMemo 前端基于 KDE Kirigami 框架, 将原生支持 Android 和 iOS.
## HeurAMS 和 Anki 有什么区别?
大体地说:
| 方面 | HeurAMS | Anki |
|------|---------|------|
| 数据格式 | 文本文件 (TOML/JSON), 人类可读 | SQLite 和资源文件组成的专有压缩格式 (.apkg) |
| 复习模式 | 多阶段流程 + 多种谜题类型 | 单面/双面闪卡 |
| 算法系统 | 模块化, 可插拔, 多种算法可选 | 内置 SM-2 / FSRS |
| 插件生态 | 较少, 体现于类似微内核架构的"能力扩展", 例如新算法或新服务 | 多, 但为不受限的"猴子补丁" |
| 用户基数 | 少 | 多 |
| 现有资源丰富度 | 少 | 多 |
| AI 辅助产生单元集/牌组 | 原生支持 | 困难 |
| 协议 | AGPL-3.0, 有一个附加豁免条款 | AGPL-3.0 |
## 软件是免费的吗?
是的, 完全免费, 且开源. 您无需支付任何费用即可使用全部功能.
## 黑乎乎的这个界面我怎么用?
首先, 如果您只是想要一个亮色模式, 可以直接按下 `d` 键或点击 "d 主题" 按钮, 这会让您的界面变得白乎乎的(
得益于微软几十年对用户进行的"命令行即落后"教育, 以及 `conhost.exe``cmd.exe` 的糟糕体验, 您对终端用户界面感到不适应是完全正常的.
但实际上, 虽然看起来像老式电脑屏幕, Textual 和终端标准其实比您想象得要现代一些.
### 可以用鼠标
现代终端模拟器 (如 Windows Terminal、Konsole、iTerm2、WezTerm 等) 支持一个叫做 "鼠标跟踪" (Mouse Tracking) 的功能, 而 Textual 启动时会发送特殊指令给终端让它报告鼠标事件.
所以可能和您的想象不同, 您事实上可以直接用鼠标点击按钮, 就像使用普通软件一样.
### 也可以用键盘
- `Tab` 键在不同区域之间切换焦点
- `方向键` 在列表中上下移动
- `Enter` 确认选择
- `q` 返回
- 屏幕上会有按键提示, 例如 `[n] 导航器` 表示按 `n` 键打开导航器
### 触屏也可以
在平板或手机 Termux 中, 您可以触摸或者滑动屏幕操作.
## 我怎么启动这个软件?
首先需要确保系统中安装了 Python (推荐 3.12.13 版本) 并安装了 HeurAMS 的所需组件.
### Windows
打开"命令提示符"或"PowerShell", 输入以下命令后按回车, 或者把这玩意另存为快捷方式:
```
python -m heurams.interface
```
### macOS
打开"终端"应用程序, 输入以上命令.
### Linux
打开您的终端模拟器 (一般是按 Ctrl + Alt + T), 输入以上命令.
如果您觉得每次输入命令太麻烦, 可以创建一个桌面快捷方式或脚本文件, 详见网上的相关教程.
## 我怎么退出软件?
按键盘上的 `q` 键返回主界面后退出.
您的学习进度会自动保存, 不会丢失.
## 图片像素很大, 模糊得像马赛克一样怎么办?
这说明图像以 Halfcell 兼容模式显示
终端模拟器需要支持 sixel 图像协议才能高清地显示图片, 对于不支持的图片, 软件只能以低清的兼容模式显示.
- WezTerm (适用于几乎所有操作系统): 支持
- KDE Konsole: 支持
- GNOME Terminal: 不支持
- iTerm2 (macOS): 支持
- Windows Terminal: 不支持
- mintty (Windows): 支持
如果您的终端不支持图片, 软件的其他功能不受影响, 只是记忆内容中的图片无法显示.
## 中文显示成乱码或方框怎么办?
这说明您没有正确设置终端中文字体. 请检查:
1. 您没有使用 getty 和 xterm 这种明确不支持非 ASCII 字体的终端
2. 终端设置中的字体选项, 选择一款支持中文的字体, 例如 "Noto Sans SC", "微软雅黑", "Source Han Sans"
3. 确保终端的字符编码设置为 UTF-8 (通常是默认值)
## 我的数据存哪里了? 会不会丢?
数据存储在软件安装目录下的 `data/` 文件夹中:
- 您可以用记事本直接打开查看和修改
- 复制粘贴 `data/` 文件夹到 U 盘或网盘即可完成备份 (推荐定期备份)
- 即使软件卸载了, 只要保留 `data/` 文件夹, 重新安装后复制回去, 所有学习记录都在
## 怎么跟朋友分享我的单元集?
找到 `data/repo/` 下的对应文件夹, 复制整个文件夹发给朋友即可. 对方把它放到自己的 `data/repo/` 目录下就能用.
您也可以导出为单一文本文件或压缩包, 通过微信、QQ、邮件等方式分享.
## 我复制粘贴不了内容?
一般来说, 在终端中:
- 复制: Ctrl+Shift+C
- 粘贴: Ctrl+Shift+V
这和普通软件的操作习惯不太一样(因为 Ctrl + C 在终端中的语义是 "中断进程"), 但稍加适应即可.
## 字体太小/太大怎么办?
在您的终端模拟器设置中找到"字体大小"选项进行调整.
软件会跟随终端的字体设置.
## 为什么我的界面和截图不一样?
截图使用的是 KDE Plasma 桌面上的 Konsole, 80x25 字符尺寸, Cascadia Code 和 Noto Sans SC 字体.
如果您的终端尺寸更大, 界面会更宽裕; 如果使用不同字体或者不同操作系统, 视觉效果会略有差异.
功能上完全一致.
## 评分 (1-5) 是什么意思? 我该怎么打分?
需要说明的是, 我们非常不鼓励这种类似 Anki, 让用户自己直接给自己评分的单元集设计(在我们的程序中, 这种方式的实现被称为 `basic_puzzle`, 基本只用于算法测试).
因为我们认为这种方式非常主观, 而且还需要您思考"我是多少分""我是不是乐观了""我是不是分低了""我要是把分评错了怎么办"这一系列打断记忆进程且令人焦虑的问题, 这本质上是把责任推给用户, 并且违背了认知科学原理.
并且这种方式于学术研究与实验不利, 用户自评分产生的数据是不可靠的.
因此 HeurAMS 的前端内建了基于用户行为分析的自动评分系统, 也就是"谜题".
它会根据题目本身难度和您的答题行为(包括但不限于正确性, 操作回退次数, 有效答题时间)并自动为您评分.
但如果您或者某个单元集选择使用 `basic_puzzle`, 或者打算自己实现自动评分系统, 分数含义如下:
| 分数 | 含义 | 说明 |
|------|------|------|
| 1 | 完全忘了 | 一点都没想起来, 跟没学过一样 |
| 2 | 模糊 | 好像见过, 但答不上来 |
| 3 | 有点印象 | 想了一会儿才答对, 不太确定 |
| 4 | 比较顺利 | 能答对, 但稍微犹豫了一下 |
| 5 | 非常轻松 | 立刻反应过来, 毫不费力 |
-**1-2 分**: 软件会认为您还没掌握, 短期内会再次安排复习
-**3 分**: 正常掌握, 按计划间隔复习
-**4-5 分**: 掌握得很好, 下次复习间隔会拉长
我们建议您不要纠结, 凭第一感觉打分就好, 软件会根据您的评分自动调整复习节奏.
当然我们还是建议尽量避免这种方式并尽量使用其他谜题评测.
您可能认为那种方式可以让用户直接"干预"算法, 类似百词斩的"斩"功能用于跳过已经熟悉的内容, 实际上您并不需要那样做: 我们内建了"快速通过/正确应答"功能, 等同于直接选择"5".
## 我每天都要打开软件吗? 不学会怎样?
理论上不需要每天打开. 软件会自动记录每个知识点下次该复习的时间.
但建议您每天打开软件看下状态.
即使隔了几天甚至几周没学:
- 已经记住的知识点不会消失, 只是下次复习时会多复习几次
- 学习记录完好保存在 `data/` 文件夹里, 不会丢失
建议尽量按软件的提醒复习, 效果最好; 但忙的时候跳过几天也没关系.
## 能同时学多个科目吗?
可以.
每个科目或课程可以做成独立的"单元集".
## 我换电脑了, 怎么迁移数据?
1. 在旧电脑上复制整个 `data/` 文件夹到 U 盘或网盘
2. 在新电脑上安装好 HeurAMS
3. 用 U 盘里的 `data/` 文件夹覆盖新电脑上的 `data/` 文件夹
所有学习记录、配置、单元集全部迁移完毕.
## 一些术语听不懂
| 术语 | 意思 |
|------|--------|
| **单元集** | 相当于"一本书"或"一门课", 包含一系列知识点 |
| **谜题** | 测试您的题目类型 (选择题、填空题等) |
| **算法** | 决定什么时候该复习哪个知识点的"智能排课表" |
| **复习队列** | 今天需要复习的知识点列表 |
| **激活** | 第一次开始学习某个知识点 |
| **到期** | 到了该复习这个知识点的时间了 |
## 软件卡住了/没反应怎么办?
1. 建议先等片刻
2. 如果不行, 直接关闭终端窗口
3. 重新打开软件
4. 如果您有时间的话, 欢迎报告问题, 我们为此深表歉意
## 同时用 Anki 和 HeurAMS 会冲突吗?
不会.
两者是独立的软件, 数据互不影响. 您可以逐步将内容迁移到 HeurAMS, 也可以两个一起用.
## 我需要安装 Python 吗?
需要的, HeurAMS 是基于 Python 的软件.
- Windows/macOS: 从 python.org 下载安装即可
- Linux: 系统通常已自带 Python
- Android: 安装 Termux 应用, 然后在 Termux 中安装 Python (运行 `pkg in python`)
如果看到"python 不是内部或外部命令"的提示, 说明 Python 没有正确安装或添加到系统路径, 搜索"Python 安装教程"按步骤操作即可.
HeurAMS 建议的 Python 版本是 3.12.13.
## 软件安全吗? 会不会有病毒?
HeurAMS 是开源软件, 所有代码公开可查阅, 不会有病毒或后门.
它只读写自己的 `data/` 文件夹, 不会动您电脑上的其他文件.
## 软件报错, 出现一堆我看不懂的英文怎么办?
1. 把错误信息复制下来
2. 先找一下这个页面有没有收录您遇到的问题
3. 如果没有, 可以和软件日志一起上传到 issues, 我们会尽快处理
## 复习到一半可以暂停吗? 下次会从头开始吗?
暂时没有保存中间状态的功能, 但我们很快会添加.
## 怎么看我学了多少? 有统计吗?
仪表盘界面会显示统计信息.
您可以通过导航器随时回到仪表盘查看.
## 我觉得复习太快/太慢了, 能调吗?
可以. 您可以通过切换算法或调整算法参数或改变记忆单元数来改变复习节奏.
在设置界面可以找到相关设置.
## 能换界面颜色/主题吗?
能的, Textual 框架提供了多种主题.
## 我不小心删了东西, 能恢复吗?
这取决于您系统是否启用回收站, 软件本身没有自动备份功能.
## 能把软件放 U 盘随身带吗?
可以. 将整个 HeurAMS 文件夹复制到 U 盘, 在任意电脑上安装 Python 后运行即可. `data/` 目录下的学习数据也会一同携带.
## 怎么关掉语音朗读?
在设置界面中找到 TTS 相关选项, 将其关闭即可.
## 哪里可以下载别人做好的单元集?
目前项目还没有官方的单元集市场.
但随着社区发展, 未来可能会有用户分享的单元集, 您也可以和朋友互相分享.
## 我能把学习内容导出打印吗?
可以.
软件本身支持将单元集导出为单一文本文件, 您可以用任何文本编辑器打开并打印. 也可以直接复制内容到 Word 等软件.
## 我想从头重新学, 怎么重置?
删除 `data/repo/` 下对应单元集文件夹中的 `algodata.json` 文件即可重置所有学习进度.
## 如何创建自己的单元集?
`data/repo/` 目录下创建一个新文件夹, 包含以下文件即可:
```
data/repo/my_pack/
├── manifest.toml # 元信息: title, author 等
├── typedef.toml # 通用字段定义和谜题配置
├── payload.toml # 记忆条目内容
├── algodata.json # 算法状态数据 (可留空)
└── schedule.toml # 复习策略配置
```
您也可以使用我们提供的工具从 CSV 等格式转换, 或利用 AI 工具生成.
## 如何切换算法?
设置界面中有详细的说明.
## 如何从 Anki 导入?
暂时没有迁移工具, 因为两个软件的设计思路不同.
但欢迎关注 HeurStudio 项目, 它能从根本上解决内容创建与迁移问题 :)
## 为什么不用 Flutter?
Flutter 是构建跨平台图形界面的优秀框架, HeurAMS 的设计目标之一是保持核心程序库独立于特定前端.
但 Flutter 在 "集成 python" 方面不如 PyOtherSide, 只能通过 RPC 标准件和程序库通讯, 并且 Flutter 的桌面多窗口一直以来没有被官方稳定支持, 所以我们暂时放弃了 Flutter 而选择了 Kirigami.
当前我们优先开发了基于 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).
## 软件需要联网吗?
核心复习功能完全离线可用. 以下功能需要联网:
- 文本转语音 (TTS) 的 Edge TTS 提供者
- LLM 提供者 (如 OpenAI 兼容 API)
- 从远程仓库下载单元集
## 许可证中的"本机 API 调用豁免"是什么?
简言之, 如果您在自己的程序中通过本地进程间 API 方式的通信 (如同一主机上的 RPC 调用) 使用 HeurAMS, 而无需通过网络转发, 则您的程序不受 AGPL-3.0 许可证的约束.
这项附加条款旨在鼓励第三方前端和工具的开发.
所以 HeurAMS 的许可证实质上是比原始的 AGPL-3.0 松一点的.
## HeurAMS 和百词斩有什么区别?
大体地说:
| 方面 | HeurAMS | 百词斩 |
|------|---------|--------|
| 使用场景 | 电脑/终端 | 手机 App |
| 学习内容 | 任何知识, 不限语言和科目 | 英语单词 |
| 记忆策略 | 多算法可选, 可自定义复习流程 | 固定算法, 不可调 |
| 测验方式 | 选择题/填空题/识别题等多种谜题 | 看图选词/听音选义等多种谜题 |
| 内容创建 | 自己创建或导入, 完全自由 | 仅官方提供词书 |
| 费用 | 完全免费, 无内购 | 免费记忆功能 + 付费课程 |
| 数据所有权 | 数据在您自己手上, 文本文件 | 数据不可被提取 |
| 离线使用 | 核心功能完全离线 | 部分功能需联网 |
| 学习统计 | 基础统计 | 打卡/排行榜/社交 |
## 百词斩有图片联想记忆, HeurAMS 也有吗?
支持.
如果您的终端支持图片显示 (如 Konsole 或者 WezTerm), 单元集中可以包含图片, 复习时会直接展示.
但图片需要您自己放入单元集.
## 百词斩有打卡和排行榜, HeurAMS 有吗?
目前没有.
HeurAMS 不设打卡、排行榜或社交功能, 也不向任何人收集您的学习数据.
## 百词斩有现成的词书, HeurAMS 去哪找内容?
百词斩的课程是官方制作好的, HeurAMS 的内容需要您自己创建或从社区获取.
详见"如何创建自己的单元集?".
## 百词斩在手机上用很方便, HeurAMS 能在手机用吗?
可以, 但现阶段需要您"折腾"一下.
Android 手机安装 Termux 后可运行 HeurAMS 的基本用户界面.
此外, 正在开发的 KiriMemo 前端将原生支持 Android 和 iOS, 这就不需要用户去折腾了.
## 百词斩能背单词, HeurAMS 还能学什么?
任何需要记忆的知识都可以: 外语单词、医学名词、法律条文、历史年代、化学方程式、编程语法、乐谱符号...
单元集的内容完全由您自己定义.
## 百词斩学英语效果很好, 换成 HeurAMS 会不会效果差?
考虑到百词斩的算法和词库是事实上闭源的, 我们无从得知算法来源.
但 HeurAMS 的架构设计可保证单元集制成后效果不比百词斩差, 甚至优于百词斩.
HeurAMS 的间隔重复算法基于相同的认知科学原理, 且算法透明可调, 您可以自由选择最适合自己的调度策略.
## 如何参与项目?
详见[贡献指南](CONTRIBUTING.md).
即使不是开发者, 您也可以通过编写文档、制作记忆单元集、翻译界面、答疑等方式参与.
-84
View File
@@ -1,84 +0,0 @@
## 特性
### 间隔重复调度器
> 许多出版物都广泛讨论了不同重复间隔对学习效果的影响. 特别是, 间隔效应被认为是一种普遍现象. 间隔效应是指, 如果重复的间隔是分散/稀疏的, 而不是集中重复, 那么学习任务的表现会更好. 因此, 有观点提出, 学习中使用的最佳重复间隔是**最长的, 但不会导致遗忘的间隔**.
- 软件开箱即用, 无需多加配置即可使用默认的 `SM-2` 算法进行学习
- 算法模块是 "潜进" 内核 (heurams.kernel) 中的一等公民, 内核天然支持插拔各型算法
- 无需安装繁杂的插件即可分单元集完成算法快速切换与调优, 研究者可以方便地修改算法模块以便捷地进行研究与测试
- 默认使用 `SM-2` 简单间隔重复算法, 此算法亦用作 `Anki` 闪卡记忆软件的默认闪卡调度器
- 还内置了 `NSP-0` 筛选用非间隔重复算法以便快速筛选记忆内容, `FSRS` 先进间隔重复算法作为效率更高的调度器, 与 `SM-15M (移植自 sm.js 项目)` 复杂间隔重复算法(逆向工程)
- 算法模块可以标记记忆项目, 也可以动态规划每个记忆单元的记忆间隔时间表, 动态跟踪记忆反馈数据, 以优化长期记忆保留率与稳定性
- 得益于项目的模块化架构与单元集结构设计, 同一个单元集可以与任意种算法共存并互通, 这对研究者及想探索/实验高效率方法的用户极其友好
### 多模态学习进程
与 Anki 的 SQLite `.apkg` 包不同, 我们坚持使用人类可读的文件夹组织单元集, 这带来了若干好处, 包括:
- 人类可读: 您可以用任意工具, 乃至一个记事本自由修改记忆载荷数据而无需打开软件
- 元数据配置: 配置自由度极高, 可以任意组合, 重造, 乃至创造新内容
- 测验, 算法与知识互相隔离: 一条知识不再是单一的闪卡, 不仅可以用若干不同的算法规划, 还可以用多种并行的谜题类型测验, 极大地提升学习效果和丰富度. 作为学习者, 您无需担忧概念复杂--仅需从云端下载单元集即可开箱即用上述特性!
- 多模态学习
- 软件自身集成了文本转语音 (TTS) , 音频与语言模型 (LLM) 模块, 这些功能乃至功能本身都是可插拔, 可扩展, 可切换驱动的, 这为内容创建了极大的丰富度
- 软件内置多种谜题类型, 包括选择题 (MCQ), 填空题 (Cloze) 与识别题 (Recognition), 您可在同一单元应用多种, 或是选择性启用
- 软件天然支持动态内容生成, 支持宏驱动的模板系统, 根据上下文乃至语言模型动态生成知识点的解析
- 在间隔重复研究尚被 SuperMemo 系列独占的时代, Wozniak 就早已表示 "如果不能理解知识, 就无需记忆它". 今天, 我们依然相信理解是记忆的基石
- 云同步与分享优化:
- 由于记忆数据和单元集文件都是文本文件, 故可进行快速的增量同步而无需完整地上传所有文件, 并且设计天然支持版本控制
- 如果您想分享单文件, 软件也支持导出为压缩包或合并成单文本文件以通过纯文本文件形式在 pastebin 等平台分享
- 性能提升: 得益于现代且支持分块的文件组织结构, 潜进能在保持高自由度的同时仅使用 python 就能达到敏捷且低占用的用户体验
- AI 辅助友好: 想象您有一些 `.apkg` 牌组或一大段教材内容, 您可以方便且高效率地使用 AI 工具以创建可在 HeurAMS 使用的单元集
### 内置实用用户界面
尽管不是唯一前端, 但响应式 Textual 框架构建的内置终端用户界面在多种场景下仍具有独特优势:
- 跨平台, 并支持触屏/鼠标/键盘多操作模式
- 与几乎所有现代终端模拟器相容
- 对于<a href="https://www.arewesixelyet.com/" target="_blank" rel="noopener noreferrer">支持 sixel 协议的终端模拟器</a>, 可高清显示图像内容
- 对于不支持 sixel 协议的终端模拟器, 也支持图片低清的兼容显示模式
- 可通过 textual-web 作为服务部署, 并在任意浏览器使用
- 简洁直观, 键盘友好, 全功能且高效率的用户界面设计
- 易于嵌入: 可在 getty/kmscon 中运行而无需任何桌面图形服务
- 资源占用小, 运行流畅, 不拖泥带水
- 便于测试与调试程序库
查看[屏幕截图](SCREENSHOTS.md).
## 包依赖组说明
由于部分依赖只被少数功能需要, 所以我们把可选依赖分得比较细, 前面提供的命令会安装部分可选依赖, 以下是依赖组列表:
| 依赖组 | 包含模块 | 说明 |
|--------|----------|------|
| 构建系统 | hatchling | 构建时安装 |
| 最小化安装 | tabulate, toml, transitions, click | 核心驱动程序库, 始终必需 |
| interface | textual | 基本用户界面依赖 |
| algo-fsrs | fsrs | FSRS 算法模块 |
| tts-edgetts | edge-tts | 微软文本转语音 |
| llm | llms-py | API 调用 |
| audio-playsound | playsound3 | 通用音频模块 |
| dev | zmq, pytest, pytest-cov | 开发调试与测试工具 |
| basic | [tts-edgetts], [llm], [algo-fsrs] | 适用于用户体验的较轻依赖组(推荐) |
| all | 以上所有依赖 | 完整安装组 |
## 关于此仓库
此仓库为 "潜进" 的核心程序库在 python 语言下的实现\
包含数据模型与框架, 并内置了基于 textual 框架的前端实现 (interface 子模块)\
除了通过内置前端进行学习外, 开发者也能在 python 环境中导入 `heurams` 库或使用 `RPC``heurams` 程序库实例通讯, 使用框架构建其他辅助记忆功能前端或其他应用程序
潜进项目的所有仓库如下:
| 项目名称 | 状态 | 说明 | 包名 | 技术栈 | 目标平台 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| HeurAMS | 开发中<br/>原型可用 | 提供通用核心程序库与基本用户界面 | `heurams` | Python | 标准 Python 环境 |
| KiriMemo | 开发中<br/>原型可用 | 基于 KDE 技术的现代跨平台前端 | `org.kde.kirimemo` | C++, Qt6, Kirigami, PyOtherSide | 桌面与移动设备 |
| ArkMemo | 开发中 | 基于 ArkUI 的现代跨平台前端 | `top.pluv27.arkmemo` | ArkTS, ArkUI | 移动设备 |
| HeurStudio | 计划中 | AI 辅助的单体单元集高级创建与编辑工具 | `top.pluv27.heurstudio` | C++, Qt6, Kirigami, PyOtherSide | 桌面 |
| HeurSync | 开发中 | 用户数据同步服务器<br/>集成 Web 前端与排行榜 | `heursync` | Go, SQL | 网页与服务器 |
| HeurRepo | 开发中 | 单元集文档源服务器<br/>与单元集分享平台 | `heurrepo` | Go, SQL | 网页与服务器 |
尽管现在后三样有点画大饼的意思, 但是我们的路线是明了的
+524 -108
View File
@@ -1,27 +1,60 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software.
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and modification follow.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
@@ -29,217 +62,600 @@ The precise terms and conditions for copying, distribution and modification foll
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based on the Program.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work.
A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that same work.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices".
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
"Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version".
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
HeurAMS
Copyright (C) 2025 Wang Zhiyu (pluvium27)
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see <http://www.gnu.org/licenses/>.
---
Additional Permission under AGPL Section 7
Local API exemption: When the program is accessed solely via API (including RPC, REST API, GraphQL, or any machine-to-machine interface) from a caller on the same host machine, the source disclosure requirement of AGPL Section 13 is waived.
Any API call from a different machine (including LAN, private network, or the internet) remains fully subject to AGPL Section 13.
This exemption does NOT apply to human-facing web interfaces.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
+92 -64
View File
@@ -3,110 +3,138 @@
## 概述
"潜进" (HeurAMS: Heuristic Auxiliary Memorizing Scheduler, 启发式记忆辅助调度器) 是一种基于启发式算法与认知科学理论的辅助记忆调度器, 旨在帮助用户更高效地进行记忆工作与学习规划,
也是一种开放, 优雅, 易于扩展的间隔重复调度器实验平台, 旨在帮助研究者更高效地进行前沿记忆算法的调查实验与研究.
也是一种开放, 优雅, 易于扩展的间隔重复调度器实验平台, 旨在帮助研究者更高效地进行前沿记忆算法的研究.
[详细介绍](INTRODUCTION.md) [屏幕截图](SCREENSHOTS.md)
## 项目结构
<p align="left">
<a href="https://github.com/pluvium27/HeurAMS" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/GitHub-fafafa?style=for-the-badge&logo=github&logoColor=181717" alt="GitHub" /></a>
<a href="https://invent.kde.org/pluv/HeurAMS" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/KDE_Invent-1D99F3?style=for-the-badge&logo=kde&logoColor=white" alt="KDE Invent" /></a>
<a href="https://gitee.com/pluv/HeurAMS" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/Gitee-C71D23?style=for-the-badge&logo=gitee&logoColor=white" alt="Gitee" /></a>
<a href="https://git.pluv27.top/pluv/HeurAMS" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/git.pluv27.top-609926?style=for-the-badge&logo=gitea&logoColor=white" alt="git.pluv27.top" /></a>
</p>
这个仓库是 "潜进" 的核心程序库在 python 语言下的实现\
包含数据模型与框架, 并内置了基于 textual 框架的前端实现 (interface 子模块)\
除了通过内置前端进行学习外, 开发者也能在 python 环境中导入 `heurams` 库或使用 `RPC``heurams` 程序库实例通讯, 使用框架构建其他辅助记忆功能前端或其他应用程序
## 特性
### 间隔重复调度器
> 许多出版物都广泛讨论了不同重复间隔对学习效果的影响. 特别是, 间隔效应被认为是一种普遍现象. 间隔效应是指, 如果重复的间隔是分散/稀疏的, 而不是集中重复, 那么学习任务的表现会更好. 因此, 有观点提出, 学习中使用的最佳重复间隔是**最长的, 但不会导致遗忘的间隔**.
- 软件开箱即用, 无需多加配置即可使用默认的 `SM-2` 算法进行学习
- 此外, 算法模块是 "潜进" 内核 (heurams.kernel) 中的一等公民, 内核天然支持插拔各型算法
- 无需安装繁杂的插件即可分单元集完成算法快速切换与调优, 研究者可以方便地修改算法模块以便捷地进行研究与测试
- 默认使用 `SM-2` 简单间隔重复算法, 此算法亦用作 `Anki` 闪卡记忆软件的默认闪卡调度器
- 还内置 `NSP-0` 筛选用非间隔重复算法以便快速筛选记忆内容, `FSRS` 先进间隔重复算法作为效率更高的调度器, 与 `SM-15M (移植自 sm.js 项目)` 复杂间隔重复算法(逆向工程)
- 算法模块可以标记记忆项目, 也可以动态规划每个记忆单元的记忆间隔时间表, 动态跟踪记忆反馈数据, 以优化长期记忆保留率与稳定性
- 得益于项目的模块化架构与单元集结构设计, 一个项目甚至可以与任意种算法共存并互通, 这对研究者及想探索/实验高效率方法的用户极其友好
### 多模态学习进程
与 Anki 的 SQLite `.apkg` 包不同, 我们坚持使用人类可读的文件夹组织单元集, 这带来了若干好处, 包括:
- 人类可读: 您可以用任意工具, 乃至一个记事本自由修改记忆载荷数据而无需打开软件
- 元数据配置: 配置自由度极高, 可以任意组合, 重造, 乃至创造新内容
- 测验, 算法与知识互相隔离: 一条知识不再是单一的闪卡, 不仅可以用若干不同的算法规划, 还可以用多种并行的谜题类型测验, 极大地提升您的学习效果和丰富度. 作为学习者, 您无需担忧概念复杂--仅需从云端下载单元集即可开箱即用上述特性!
- 多模态学习
- 软件自身集成了文本转语音 (TTS) , 音频与语言模型 (LLM) 模块, 这些功能乃至功能本身都是可插拔, 可扩展, 可切换驱动的, 这为内容创建了极大的丰富度
- 软件内置多种谜题类型, 包括选择题 (MCQ), 填空题 (Cloze) 与识别题 (Recognition), 您可在同一单元应用多种, 或是选择启用
- 软件天然支持动态内容生成, 支持宏驱动的模板系统, 根据上下文乃至语言模型动态生成知识点的解析
- 在间隔重复研究尚被 SuperMemo 系列独占的时代, Wozniak 就早已表示 "如果不能理解知识, 就无需记忆它". 今天, 我们依然相信理解是记忆的基石
- 云同步与分享优化: 由于我们的记忆数据和单元集文件都是文本文件, 故可进行快速的增量同步而无需完整地上传所有文件, 并且设计天然支持分享内容的版本控制, 如果您想分享单文件, 我们也支持导出为压缩包或合并单文本文件以通过纯文本文件形式在 pastebin 等平台分享
- 性能提升: 得益于现代且支持分块的文件组织结构, 潜进能在保持高自由度的同时仅使用 python 就能达到敏捷且低占用的用户体验
### 实用用户界面
- 响应式 Textual 框架构建的跨平台 TUI 界面
- 支持触屏/鼠标/键盘多操作模式
- 简洁直观的复习流程设计
## 快速开始
### 从包管理器安装
潜进 (包名是 `heurams`) 处于早期开发考虑, 尚未上架 PyPI, 但您可以用 pip 支持的 git 协议安装稳定版和开发版本, 这要求您的电脑上安装了 python 环境 (建议 3.12.13 及之后版本).
#### 面向用户的安装
从稳定的 `master` 分支安装, 并安装适用于用户体验的可选依赖(推荐):
```
pip install --upgrade 'heurams[basic] @ https://git.pluv27.top/pluv/HeurAMS/archive/master.zip'
```
从较前沿, 大致稳定的 `dev` 分支安装, 并安装适用于用户体验的可选依赖(如果您追求较前沿的改进):
```
pip install --force-reinstall --no-deps 'heurams[basic] @ https://git.pluv27.top/pluv/HeurAMS/archive/dev.zip'
```
安装适用于一般计算机的通用音频模块(基于 playsound3):\
(此项不适用于 termux 环境, termux 的音频支持是内建的)
```
pip install --upgrade 'heurams[audio-playsound] @ https://git.pluv27.top/pluv/HeurAMS/archive/master.zip'
```
#### 面向开发者的安装
潜进(heurams) 处于早期开发考虑, 尚未上架 PyPI, 但您可以用我们的基础设施安装稳定版和开发版本.
> [!CAUTION]
> 对于部分 Linux 发行版和 Android Termux 用户:\
> 您需要先行安装 `cmake` 和 `libzmq` 才能正确安装项目的 `zmq` 依赖.\
> 例如在 termux 上先运行 `pkg install cmake clang libzmq`.\
> 项目功能本身不依赖它, 但需要该依赖用于启动可选的调试服务器.
> 您需要先行安装 `cmake` 和 `libzmq` 才能正确安装项目的 `zmq` 依赖\
> 例如在 termux 上先运行 `pkg install cmake clang libzmq`\
> 项目功能本身不依赖它, 但需要该依赖用于启动可选的调试服务器
`dev` 分支进行基于 git 的可编辑安装, 并安装全部可选依赖(推荐):
#### 稳定版本
```
pip install --force-reinstall --no-deps 'heurams[all] @ https://git.pluv27.top/pluv/HeurAMS/archive/dev.zip'
python -m pip install heurams[all] -i https://pypi.pluv27.top/root/stable/+simple/ # 安装全部可选依赖(推荐)
```
> 您也可以从 `refactor/...` 等特定分支安装
#### 开发版本
[依赖组说明](INTRODUCTION.md#包依赖组说明)
```
python -m pip install heurams[all] -i https://pypi.pluv27.top/root/dev/+simple/ # 安装全部可选依赖(推荐)
```
#### 依赖组说明
由于部分依赖只被少数功能需要, 所以我们把可选依赖分得比较细, 前面提供的命令会安装所有可选依赖, 以下是依赖组列表
- 基础依赖: (只能驱动程序库)
- tabulate: 终端表格生成
- toml: TOML 文件加载
- transitions: 状态机依赖
- `interface` 依赖组: (基本用户界面依赖)
- textual: 终端用户界面
- psutil: 获取系统信息
- `algo-fsrs` 依赖组:
- py-fsrs: FSRS 算法模块需要
- `tts-edgetts` 依赖组:
- edge-tts:微软文本转语音
- `misc-jieba` 依赖组:
- jieba: 中文智能分词所需
- `llm-openai` 依赖组:
- openai: OpenAI API 所需
- `audio-playsound` 依赖组:
- playsound: 通用音频播放
- pygobject: playsound 依赖
- `dev` 依赖组:
- zmq: 远程调试服务器所需
- pytest: 测试所需
- pytest-cov: 测试所需
- `all` 依赖组:
- 包含以上所有依赖
### 从源码安装
我们提供原生 python 和 uv 两种安装方式.\
详见[贡献指南](CONTRIBUTING.md).
## 常见问题 (FAQ)
详见[常见问题](FAQ.md).
## 项目架构
## 项目结构
详见[架构说明](ARCHITECTURE.md).
## 参与项目
欢迎参与到项目协作中!\
详见[贡献指南](CONTRIBUTING.md).\
关于 AI 辅助开发的说明, 请参阅 [AGENTS.md](AGENTS.md).
## 项目标识
HeurAMS 项目标识如下, 文件(位图和矢量图)位于 `./src/heurams/assets/art/` 目录.
<img src="src/heurams/assets/art/banner128-light.png" height="96px" title="位图横幅(不透明)">
<div style="display: flex; flex-wrap: wrap; gap: 5px;">
<img src="src/heurams/assets/art/logo.svg" height="96px" title="矢量图标">
<img src="src/heurams/assets/art/logo-mono-light.svg" height="96px" title="单色明亮矢量图标">
<img src="src/heurams/assets/art/logo-mono-dark.svg" height="96px" title="单色暗色矢量图标">
</div>
颜色分别是: `#1660A5 (海蓝色)` `#545F70 (蓝灰色)` `#FFFFFF (单色明亮图标白色)` `#1A1A1A (单色暗色图标深黑色)` `#2f2f35 (文字颜色)`.
欢迎参与到项目协作中! 请参阅 [CONTRIBUTING.md](CONTRIBUTING.md) 了解贡献指南.
## 许可证
### 项目本身
本项目基于 AGPL-3.0 许可证开放源代码, 并有一个豁免本机 API 调用的附加条款, 较标准 AGPL-3.0 更松.
本项目基于 AGPL-3.0 许可证开放源代码.
详见根目录下 [LICENSE](LICENSE) 文件.
### 第三方代码
项目在 `src/heurams/vendor/` 目录下嵌入或在其他位置间接使用了以下第三方代码(可能有修改):
#### py-fsrs (open-spaced-repetition)
- 上游版本: 6.3.1
- 引用方式: vendor
- 位置: `src/heurams/vendor/pyfsrs/`
- 原项目: [py-fsrs](https://github.com/open-spaced-repetition/py-fsrs)
- 原版权: Copyright (c) 2026 Open Spaced Repetition Contributors
- 原许可证: MIT License
#### SM.js (slaypni)
- 上游版本: commit `6e3bb4a` (2015年2月4日上游已停止维护)
- 上游版本: commit `6e3bb4afaf484426deb4a9fa3bcffe42ac066b45` (2015年2月4日上游已停止维护)
- 引用方式: 将 coffeescript 重写为 python 并间接引用, 数学原理一致; 并对重写后代码进行逻辑, 性能与标准化 API 改进
- 位置: `src/heurams/kernel/algorithms/sm15m*.py`
- 原项目: [SM.js](https://github.com/slaypni/SM-15)
-72
View File
@@ -1,72 +0,0 @@
# 用户界面屏幕截图
潜进 (HeurAMS) 项目目前有两个前端实现, 此文档用于呈现它们的截图 (尽量与最新版本同步):
- Textual 基本用户界面 (heurams.interface): 基于 Python Textual 框架构建的程序库内置跨平台 TUI 界面, 支持触屏、鼠标、键盘多操作模式, 是当前开箱即用的默认前端.
- 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).
## 基本用户界面前端的截图
> 截图所使用的终端模拟器为 KDE Konsole\
> 字体为 Cascadia Code 和 Noto Sans SC\
> 终端尺寸设置为 80x25 (软件也支持更大的终端尺寸)
### 仪表盘与导航器
仪表盘包含学习面板的总体视图, 包括不同功能区域的操作入口, 统计信息, 以及单元集概览.\
导航器是一个实用的模态窗口, 能带您在多种功能间自如切换, 按 `n` 键或单击下方按钮可在任意界面迅速打开/关闭导航器.
<div style="display: flex; flex-wrap: wrap; gap: 10px;">
<img src="screenshots/dashboard_1.png" width="48%">
<img src="screenshots/dashboard_2.png" width="48%">
<img src="screenshots/navigator_1.png" width="48%">
</div>
### 准备界面与预缓存工具
学习准备界面展示了单元集基本信息和每个单元的学习状态, 并提供了学习和预缓存的入口.\
预缓存工具使您能提前预缓存文本转语音资源以确保复习流程的顺畅体验和离线复习能力, 但即使您不预先缓存, 资源也会在复习播放时被自动加载.
<div style="display: flex; flex-wrap: wrap; gap: 10px;">
<img src="screenshots/preparation.png" width="48%">
<img src="screenshots/precache_1.png" width="48%">
</div>
### 记忆队列界面
队列式学习记忆的主要界面.\
同一知识点可产生多种谜题类型的评估方式, 软件内置完形填空与识别题等多种测试类型, 您可在复习流程中按顺序完成不同测试.
<div style="display: flex; flex-wrap: wrap; gap: 10px;">
<img src="screenshots/memoqueue_cloze_1.png" width="48%">
<img src="screenshots/memoqueue_recognition_1.png" width="48%">
<img src="screenshots/memoqueue_recognition_2.png" width="48%">
</div>
### 设置界面
配置界面包含算法选择、音频与多种服务的提供者切换、以及界面与算法设置等选项.
<div style="display: flex; flex-wrap: wrap; gap: 10px;">
<img src="screenshots/setting_1.png" width="48%">
<img src="screenshots/setting_2.png" width="48%">
</div>
### 其他界面
收藏管理器可管理您手动标记的个人收藏集.\
关于页面提供了程序版本号、许可协议等信息.
<div style="display: flex; flex-wrap: wrap; gap: 10px;">
<img src="screenshots/about_1.png" width="48%">
<img src="screenshots/favmanager_1.png" width="48%">
</div>
## KiriMemo 前端的截图
截图将在 KiriMemo 前端开发趋于稳定后补充.
<!-- TODO: 补充截图 -->
@@ -10,7 +10,7 @@ quick_pass = true
_quick_pass_desc = "[调试] 启用快速应答功能(跳过测验)"
auto_pass = false
_auto_pass_desc = "[调试] 自动通过测试模式"
scheduled_num = 35
scheduled_num = 420
_scheduled_num_desc = "默认记忆单元数量\n可被单元集设置覆盖"
refresh_on_resume = true
_refresh_on_resume_desc = "[调试] 每当 Screen 激活后都刷新状态"
@@ -1,2 +1,2 @@
min_denominator = " 1"
min_denominator = "2"
_min_denominator_desc = "设空比例系数的倒数"
+11
View File
@@ -0,0 +1,11 @@
algorithm = "NSP-0"
_algorithm_desc = "记忆调度算法"
scheduled_num = 420
_scheduled_num_desc = "单次记忆单元数量"
[_algorithm_candidate]
NSP-0 = "筛选用非间隔重复调度器"
none = "不设置默认调度器"
SM-2 = "第二代 SuperMemo 简单间隔重复调度器"
SM-15M = "第15代 SuperMemo 复杂间隔重复调度器 (不稳定且逆向工程)"
FSRS = "先进开放间隔重复调度器"
+11
View File
@@ -0,0 +1,11 @@
algorithm = "SM-2"
_algorithm_desc = "记忆调度算法"
scheduled_num = 20
_scheduled_num_desc = "单次记忆单元数量"
[_algorithm_candidate]
NSP-0 = "筛选用非间隔重复调度器"
none = "不设置默认调度器"
SM-2 = "第二代 SuperMemo 简单间隔重复调度器"
SM-15M = "第15代 SuperMemo 复杂间隔重复调度器 (不稳定且逆向工程)"
FSRS = "先进开放间隔重复调度器"
@@ -2,7 +2,7 @@ provider = "playsound"
_provider_desc = "音频驱动类型"
[_provider_candidate]
playsound = "Python 桌面跨平台音频系统, 使用了 playsound3 替代了老旧的 playsound"
playsound = "python 跨平台音频系统"
termux = "Android Termux 音频系统"
mpg123 = "通用音频系统, 依赖系统 mpg123"
pulseaudio = "高级音频路由系统"
@@ -1,4 +1,4 @@
provider = "none"
provider = "openai"
_provider_desc = "模型接口类型"
[_provider_candidate]
@@ -1,4 +1,4 @@
provider = "none"
provider = "webdav"
_provider_desc = "同步服务驱动类型"
[_provider_candidate]
+1
View File
@@ -0,0 +1 @@
[]
+14
View File
@@ -0,0 +1,14 @@
{
"临安春雨初霁": {
"SM-2": {
"efactor": 2.5,
"real_rept": 2,
"rept": 0,
"interval": 1,
"last_date": 20573,
"next_date": 20574,
"is_activated": 1,
"last_modify": 1777521623.7446923
}
}
}
+22
View File
@@ -0,0 +1,22 @@
使用仪器分析分子:
质谱仪 -> 质量特征, 测分子质量(最大质荷比)
红外光谱 -> 化学键和官能团
核磁共振氢谱 -> 氢原子的种类和数目
X射线衍射 -> 复杂分子, 分析键长键角
元素分析仪 -> 确定元素种类及比例, 写出实验式
元素转换:
C -> CO2
S -> SO2
H -> H2O
N -> N2 (注意)
Cl -> HCl
烷烃:
可以燃烧, 火焰较明亮
可以在光照下和卤素单质发生取代(卤素单质光照下生成自由基 发生自由基取代反应)
可以高温分解为小的烷烃和烯烃
不和酸性高锰酸钾, 酸碱反应, 不和Br2(CCl4)反应
除 CH3Br 为气体外,多溴代物为液体("油状液滴"), CBr4是固体
光照用紫外灯或强日光(太阳光直射),不能用普通白炽灯 uv含量低
+4
View File
@@ -0,0 +1,4 @@
title = "高考化学"
package = "chmgk"
author = "__heurams__"
desc = "高考古诗文 60 篇"
+5
View File
@@ -0,0 +1,5 @@
["临安春雨初霁"]
note = ['陆游〔宋代〕']
content = "临安/春雨/初霁/"
translation = "临安春雨初霁"
keyword_note = { "临安" = "南宋都城, 今杭州", "霁" = "雨后或雪后转晴" }
+11
View File
@@ -0,0 +1,11 @@
schedule = ["quick_review", "recognition", "final_review"]
[routes]
quick_review = [["SelectMeaning", "1.0"], ["Recognition", "1.0"]]
recognition = [["Recognition", "1.0"]]
final_review = [["FillBlank", "1.0"], ["Recognition", "1.0"]]
[annotation]
"quick_review" = "复习旧知"
"recognition" = "新知识"
"final_review" = "总复习"
+17
View File
@@ -0,0 +1,17 @@
[annotation]
note = "笔记"
keyword_note = "关键词翻译"
translation = "语句翻译"
delimiter = "分隔符"
content = "内容"
tts_text = "文本转语音文本"
[common]
delimiter = "/"
tts_text = "eval:payload['content'].replace('/', '')"
[common.puzzles] # 谜题定义
# 我们称 "Recognition" 为 recognition 谜题的 alia
"Recognition" = { __origin__ = "recognition", __hint__ = "", primary = "eval:payload['content']", secondary = ["eval:payload['keyword_note']", "eval:payload['note']"], top_dim = ["eval:payload['translation']"] }
"SelectMeaning" = { __origin__ = "mcq", __hint__ = "eval:payload['content']", primary = "eval:payload['content']", mapping = "eval:payload['keyword_note']", jammer = "eval:list(payload['keyword_note'].values())", max_riddles_num = "eval:default['mcq']['max_riddles_num']", prefix = "选择正确项: " }
"FillBlank" = { __origin__ = "cloze", __hint__ = "", text = "eval:payload['content']", delimiter = "eval:nucleon['delimiter']", min_denominator = "eval:default['cloze']['min_denominator']"}
+1
View File
@@ -0,0 +1 @@
{}
+4
View File
@@ -0,0 +1,4 @@
title = "高考必背古诗文-筛选"
package = "cngk-t"
author = "__heurams__"
desc = "高考古诗文 60 篇"
File diff suppressed because it is too large Load Diff
+11
View File
@@ -0,0 +1,11 @@
schedule = ["quick_review", "recognition", "final_review"]
[routes]
quick_review = [["FillBlank", "1.0"], ["Recognition", "1.0"]]
recognition = [["FillBlank", "1.0"]]
final_review = [["FillBlank", "1.0"], ["Recognition", "1.0"]]
[annotation]
"quick_review" = "复习旧知"
"recognition" = "新知识"
"final_review" = "总复习"
+17
View File
@@ -0,0 +1,17 @@
[annotation]
note = "笔记"
keyword_note = "关键词翻译"
translation = "语句翻译"
delimiter = "分隔符"
content = "内容"
tts_text = "文本转语音文本"
[common]
delimiter = "/"
tts_text = "eval:payload['content'].replace('/', '')"
[common.puzzles] # 谜题定义
# 我们称 "Recognition" 为 recognition 谜题的 alia
"Recognition" = { __origin__ = "recognition", __hint__ = "", primary = "eval:payload['content']", secondary = ["eval:payload['keyword_note']", "eval:payload['note']"], top_dim = ["eval:payload['translation']"] }
"SelectMeaning" = { __origin__ = "mcq", __hint__ = "eval:payload['content']", primary = "eval:payload['content']", mapping = "eval:payload['keyword_note']", jammer = "eval:list(payload['keyword_note'].values())", max_riddles_num = "eval:default['mcq']['max_riddles_num']", prefix = "选择正确项: " }
"FillBlank" = { __origin__ = "cloze", __hint__ = "", text = "eval:payload['content']", delimiter = "eval:nucleon['delimiter']", min_denominator = "eval:default['cloze']['min_denominator']"}
+21 -21
View File
@@ -1,7 +1,7 @@
[project]
name = "heurams"
version = "0.5.1"
authors = [{ name = "Wang Zhiyu", email = "pluvium27@outlook.com" }]
version = "0.5.0"
authors = [{ name = "pluvium27", email = "pluvium27@outlook.com" }]
description = "Heuristic Auxiliary Memory Scheduler"
readme = "README.md"
requires-python = ">=3.12"
@@ -13,7 +13,6 @@ license = "AGPL-3.0-or-later"
license-files = ["LICENSE"]
dependencies = [ # 这些依赖只能驱动 kernel 程序库
"click>=8.3.3",
"tabulate>=0.10.0",
"toml>=0.10.2",
"transitions>=0.9.3",
@@ -21,17 +20,25 @@ dependencies = [ # 这些依赖只能驱动 kernel 程序库
[project.optional-dependencies]
interface = [ # 基本用户界面依赖
"textual>=8.2.5",
"textual>=8.2.3",
"psutil>=7.2.2",
]
algo-fsrs = [
"fsrs>=6.3.1", # FSRS 算法底层依赖
]
tts-edgetts = [
"edge-tts>=7.2.8", # 微软 TTS
]
llm = ["llms-py>=3.0.0"]
audio-playsound = ["playsound3>=3.3.1"]
misc-jieba = [
"jieba>=0.42.1", # 用于中文智能分词
]
llm-openai = [
"openai>=2.32.0",
]
audio-playsound = [
"playsound==1.2.2",
"pygobject>=3.56.2", # playsound 依赖它
]
dev = [ # 调试所需依赖
"zmq>=0.0.0", # 用于 ZMQ 远程调试服务器, 在 linux 上建议先安装 libzmq
"pytest>=8.0.0", # 用于普通测试
@@ -40,27 +47,20 @@ dev = [ # 调试所需依赖
all = [
"heurams[algo-fsrs]",
"heurams[tts-edgetts]",
"heurams[llm]",
"heurams[misc-jieba]",
"heurams[llm-openai]",
"heurams[audio-playsound]",
"heurams[dev]",
]
basic = [
"heurams[algo-fsrs]",
"heurams[interface]",
"heurams[tts-edgetts]",
"heurams[llm]",
]
[project.urls]
Homepage = "https://ams.pluv27.top"
Homepage = "https://ams.pluv.top"
Issues = "https://github.com/heurams/heurams/issues"
[[tool.uv.index]]
url = "https://mirrors.ustc.edu.cn/pypi/simple"
default = true
[project.scripts]
heurams = "heurams.__main__:main"
tui = "heurams.interface.__main__:main"
heurams-tui = "heurams.interface.__main__:main"
[tool.pytest.ini_options]
testpaths = ["tests"]
@@ -72,5 +72,5 @@ markers = [
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
requires = ["uv_build>=0.7.19"]
build-backend = "uv_build"
+8
View File
@@ -0,0 +1,8 @@
edge-tts==7.0.2
jieba==0.42.1
openai==1.0.0
playsound==1.2.2
tabulate>=0.9.0
textual==7.0.0
toml==0.10.2
transitions==0.9.3
Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

+1 -1
View File
@@ -1,4 +1,4 @@
#print("欢迎使用 HeurAMS 及其组件!")
print("欢迎使用 HeurAMS 及其组件!")
# 补充日志记录
from heurams.services.logger import get_logger
+16 -57
View File
@@ -1,63 +1,22 @@
import platform
import click
from heurams.services.version import ver, stage, codename, codename_cn
@click.group(
invoke_without_command=True,
help=(
f"HeurAMS {ver} - 启发式辅助记忆调度器"
),
context_settings={"help_option_names": ["-h", "--help"]},
)
@click.version_option(
ver, "-v", "--version",
prog_name="HeurAMS",
message=f"%(prog)s %(version)s {stage} ({codename}/{codename_cn}), {platform.system()}",
)
@click.pass_context
def cli(ctx):
if ctx.invoked_subcommand is None:
click.echo(cli.get_help(ctx))
ctx.exit(0)
@cli.command()
def tui():
"""启动内置基本用户界面 (TUI)"""
import heurams.interface.__main__ as tui_module
tui_module.main()
def _print_version():
click.echo(
f"HeurAMS {ver} ({codename}/{codename_cn}), 阶段: {stage}"
)
@cli.command()
def version():
"""输出版本信息"""
_print_version()
@cli.command(name="ver", hidden=True)
def ver_cmd():
"""输出版本信息"""
_print_version()
@cli.command(name="help")
@click.pass_context
def help_cmd(ctx):
"""显示此帮助信息"""
click.echo(cli.get_help(ctx.parent))
import heurams.services.version as ver
# __main__.py
def main():
cli()
prompt = f"""HeurAMS {ver.ver} 已经被成功地安装在系统中.
HeurAMS 被设计为一个带有辅助记忆调度器功能的软件包, 无法直接被执行, 但可被其他 Python 程序调用.
若您想启动内置的基本用户界面:
请运行 python -m heurams.interface,
或者 python -m heurams.interface.__main__
python 代指您使用的解释器, 在某些发行版中可能是 python3, 而 python 命令被指向了 python2.
尽管项目保留了 requirements.txt, 我们仍不推荐使用系统 python 和原始 venv 进行开发.
项目的推荐开发环境工具是 uv.
如果你的环境已经安装了 uv:
先运行 uv sync 同步环境, 此命令只需要执行一遍, uv 会自动处理依赖.
然后通过运行 uv run tui 启动内置基本用户界面.
此时您的解释器在项目目录里的 .venv/bin 中, 使用 IDE 开发前, 务必切换解释器!
注意: 一个常见的误区是, 执行 interface 下的 __main__.py 运行基本用户界面, 这会导致 Python 上下文环境异常, 请不要这样做."""
print(prompt)
if __name__ == "__main__":
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

-25
View File
@@ -1,25 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="0 0 554.4 554.4" version="1.1">
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<title>HeurAMS Monochrome Logo - Dark</title>
<desc>Dark monochrome logo of the HeurAMS Project</desc>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 554.4 L 554.4 554.4 L 554.4 0 L 0 0 L 0 554.4 z" style="fill: none"/>
</g>
<g id="axes_1">
<g id="patch_2">
<path d="M 66.528 487.872 L 199.584 487.872 L 199.584 354.816 L 66.528 354.816 z" clip-path="url(#p4da876c7a0)" style="fill: #1A1A1A; stroke: #1A1A1A; stroke-linejoin: miter"/>
</g>
<g id="patch_3">
<path d="M 199.584 354.816 L 487.872 354.816 L 487.872 66.528 L 199.584 66.528 z" clip-path="url(#p4da876c7a0)" style="fill: #1A1A1A; stroke: #1A1A1A; stroke-linejoin: miter"/>
</g>
</g>
</g>
<defs>
<clipPath id="p4da876c7a0">
<rect x="0" y="0" width="554.4" height="554.4"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1,25 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="0 0 554.4 554.4" version="1.1">
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<title>HeurAMS Monochrome Logo - Light</title>
<desc>Light monochrome logo of the HeurAMS Project</desc>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 554.4 L 554.4 554.4 L 554.4 0 L 0 0 L 0 554.4 z" style="fill: none"/>
</g>
<g id="axes_1">
<g id="patch_2">
<path d="M 66.528 487.872 L 199.584 487.872 L 199.584 354.816 L 66.528 354.816 z" clip-path="url(#p4da876c7a0)" style="fill: #ffffff; stroke: #ffffff; stroke-linejoin: miter"/>
</g>
<g id="patch_3">
<path d="M 199.584 354.816 L 487.872 354.816 L 487.872 66.528 L 199.584 66.528 z" clip-path="url(#p4da876c7a0)" style="fill: #ffffff; stroke: #ffffff; stroke-linejoin: miter"/>
</g>
</g>
</g>
<defs>
<clipPath id="p4da876c7a0">
<rect x="0" y="0" width="554.4" height="554.4"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

-25
View File
@@ -1,25 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="0 0 554.4 554.4" version="1.1">
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<title>HeurAMS Logo</title>
<desc>The logo of the HeurAMS Project</desc>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 554.4 L 554.4 554.4 L 554.4 0 L 0 0 L 0 554.4 z" style="fill: none"/>
</g>
<g id="axes_1">
<g id="patch_2">
<path d="M 66.528 487.872 L 199.584 487.872 L 199.584 354.816 L 66.528 354.816 z" clip-path="url(#p4da876c7a0)" style="fill: #1660a5; stroke: #1660a5; stroke-linejoin: miter"/>
</g>
<g id="patch_3">
<path d="M 199.584 354.816 L 487.872 354.816 L 487.872 66.528 L 199.584 66.528 z" clip-path="url(#p4da876c7a0)" style="fill: #545f70; stroke: #545f70; stroke-linejoin: miter"/>
</g>
</g>
</g>
<defs>
<clipPath id="p4da876c7a0">
<rect x="0" y="0" width="554.4" height="554.4"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File
View File
View File
+1 -9
View File
@@ -22,17 +22,9 @@ logger = get_logger(__name__)
logger.debug(f"包目录: {rootdir}")
logger.debug(f"工作目录: {workdir}")
default_data = rootdir / "assets" / "data"
user_data = workdir / "data"
if not user_data.exists():
logger.info("初始化数据目录: %s", user_data)
import shutil
shutil.copytree(default_data, user_data)
else:
(workdir / "data" / "config").mkdir(parents=True, exist_ok=True)
config_var: ContextVar[ConfigDict] = ContextVar(
config_var: ContextVar[ConfigDict].get = ContextVar(
"config_var",
default=ConfigDict(workdir / "data" / "config"),
)
+3 -3
View File
@@ -72,8 +72,9 @@ class HeurAMSApp(App):
) -> None: # 用来给没使用/禁用的快捷键占位, 因为 Binding 删除不了
pass
'''
# 移除烦人的 "rich traceback", 但可能导致未定义行为出现, 所以注释掉
# 移除烦人的 "rich traceback"
# Textual 官方不会管这破事, 写 Rich 写入脑了导致的
# 不知道哪来的自信改标准库的 traceback
# https://github.com/Textualize/textual/discussions/6255
# NOTE: 进行 textual 版本升级时, 确保查看过上游代码, 尤其是 App 的 _exception
# 如果行为变了就把下面的删了 (虽然有 fallback)
@@ -88,4 +89,3 @@ class HeurAMSApp(App):
self._close_messages_no_wait()
raise self._exception
super().panic(*args) # ditto
'''
+19 -1
View File
@@ -2,13 +2,30 @@ from heurams.interface import *
from heurams.context import config_var
from heurams.services.logger import get_logger
import threading
import zmq
import pickle
logger = get_logger(__name__)
def environment_check():
from pathlib import Path
logger.debug("检查环境路径")
subdir = ["cache/voice", "repo", "global", "config"]
for i in subdir:
i = Path(config_var.get()["global"]["paths"]["data"]) / i
if not i.exists():
logger.info("创建目录: %s", i)
print(f"创建 {i}")
i.mkdir(exist_ok=True, parents=True)
else:
logger.debug("目录已存在: %s", i)
print(f"找到 {i}")
logger.debug("环境检查完成")
def start_debug_server(app):
import zmq
logger = get_logger("zmq_debug")
context = zmq.Context()
socket = context.socket(zmq.REP)
@@ -39,6 +56,7 @@ def start_debug_server(app):
def main():
environment_check()
app = HeurAMSApp()
-21
View File
@@ -13,24 +13,3 @@ NavigatorScreen {
border: thick $background 80%;
background: $surface;
}
.favorite-item {
height: auto;
}
.favorite-item-btn {
dock: right;
width: 8;
padding: 0;
}
.memoqueue-container {
padding: 1 2;
width: auto;
}
.memoqueue-container > * {
border: heavy $secondary;
width: 1fr;
padding: 0 1 0 1;
}
@@ -2,7 +2,3 @@
height: auto;
width: auto;
}
.hori {
height: auto;
}
+35 -13
View File
@@ -4,12 +4,14 @@ from textual.app import ComposeResult
from textual.containers import ScrollableContainer
from textual.screen import Screen
from textual.widgets import Button, Footer, Header, Label, Markdown
from textual import events, on
import heurams.services.version as version
from heurams.context import *
import platform
import shutil
import psutil
import os
import sys
@@ -38,39 +40,46 @@ class AboutScreen(Screen):
)
with ScrollableContainer(id="about_container"):
yield Label("[b]关于与版本信息[/b]")
# 获取系统信息
textual_version = self._get_textual_version()
terminal_info = self._get_terminal_info()
python_version = self._get_python_version()
os_version = self._get_os_version()
disk_usage = self._get_disk_usage()
memory_info = self._get_memory_info()
about_text = f"""
# 关于 HeurAMS "潜进"
# 关于 "潜进"
主程序库版本: `{version.ver}-python`
用户界面分支: `Textual TUI (基本用户界面)`
用户界面版本: `{version.ver}`
API 版本代号: `{version.codename.capitalize()}`
> 一个基于启发式算法与认知科学理论的辅助记忆调度器, 旨在帮助用户更高效地进行记忆工作与学习规划.
> 一个开放, 优雅, 易于扩展的间隔重复调度器实验平台, 旨在帮助研究者更高效地进行前沿记忆算法的研究.
一个基于启发式算法与认知科学理论的辅助记忆调度器, 旨在帮助用户更高效地进行记忆工作与学习规划.
一个开放, 优雅, 易于扩展的间隔重复调度器实验平台, 旨在帮助研究者更高效地进行前沿记忆算法的研究.
您可在项目主页 https://ams.pluv27.top 获取用户指南, 开发文档与软件更新, 并参与到软件的开发与改进工作.
以 AGPL-3.0 开放源代码, 这直接意味着任何个体直接基于此代码对外或内部提供的应用和服务, 无论本地或网络, 必须向所有用户公开完整修改后的源代码, 且继续沿用 AGPL-3.0 协议.
以 GNU Affero 通用公共许可证 (第3版) 开放源代码, 并有一条豁免本机 API 调用的附加条款, 用于其他前端到程序库的接口调用.
您正使用的 TUI 用户界面是 python 版本程序库自带的基本用户界面, 以作为第一个全功能前端实现与程序库测试套件, 位于程序库根目录中的 interface 文件夹.
正使用程序库内置的终端用户界面, 它是第一个全功能前端实现与程序库测试套件, 位于程序库的 interface 子目录.
可在项目主页 https://ams.pluv27.top 获取用户指南, 开发文档与软件更新.
如果您觉得这个软件有用, 可以考虑参与贡献, 或在它的源代码仓库给它添加一个星标 :)
您的慷慨支持, 我们必当涌泉相报.
开发人员列表:
- 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 算法理论
- [Kazuaki Tanida](https://github.com/slaypni): SM-15 算法的 CoffeeScript 逆向实现
- [Thoughts Memo](https://www.zhihu.com/people/L.M.Sherlock): 间隔重复文献参考
- [Open Spaced Repetition](https://github.com/open-spaced-repetition): FSRS 算法底层实现
- [Kazuaki Tanida](https://github.com/slaypni): SM-15 算法的 CoffeeScript 实现
- [Thoughts Memo](https://www.zhihu.com/people/L.M.Sherlock): 中文文献参考
# 运行环境信息
@@ -80,10 +89,12 @@ Textual 框架版本: {textual_version}
终端模拟器: {terminal_info}
操作系统版本: {os_version}
存储余量: {disk_usage}
内存大小: {memory_info}
报告问题时, 请复制这些信息到问题描述, 并上传软件日志 `heurams.log` 作为附件, 以协助开发者定位错误
"""
yield Markdown(about_text, classes="about-markdown")
yield Button(
"返回主界面",
id="back_button",
@@ -152,9 +163,20 @@ Textual 框架版本: {textual_version}
def _get_disk_usage(self) -> str:
"""获取磁盘使用情况"""
usage = shutil.disk_usage("/")
try:
usage = psutil.disk_usage("/")
free_gb = usage.free / (1024**3)
total_gb = usage.total / (1024**3)
percent_free = (free_gb / total_gb) * 100
#print(f"{free_gb:.1f} GB ({percent_free:.1f}%)")
return f"{free_gb:.1f} GB ({percent_free:.1f}%)"
except Exception:
return "未知"
def _get_memory_info(self) -> str:
"""获取内存信息"""
try:
memory = psutil.virtual_memory()
total_gb = memory.total / (1024**3)
return f"{total_gb:.1f} GB"
except Exception:
return "未知"
+4 -4
View File
@@ -66,10 +66,10 @@ class DashboardScreen(Screen):
Vertical(
Label(f"已加载 {len(self.repos)} 个单元集"),
Label(
f"共计 {reduce(lambda x, y: x + y, map(lambda x: x.progress['total'], self.repos)) if self.repos else 0} 个单元"
f"共计 {reduce(lambda x, y: x + y, map(lambda x: x.progress['total'], self.repos))} 个单元"
),
Label(
f"已激活 {reduce(lambda x, y: x + y, map(lambda x: x.progress['touched'], self.repos)) if self.repos else 0} 个单元"
f"已激活 {reduce(lambda x, y: x + y, map(lambda x: x.progress['touched'], self.repos))} 个单元"
),
Label(f""),
classes="right",
@@ -160,8 +160,8 @@ class DashboardScreen(Screen):
repo_list_widget.append(
ListItem(
Static(
f"{config_var.get()['global']['paths']['repo']} 中未找到任何单元集仓库目录.\n"
"请导入单元集后重启应用, 或者新建单元集."
f"{config_var.get()['global']['paths']['repo']} 中未找到任何仓库.\n"
"请导入仓库后重启应用, 或者新建空的仓库."
),
id="not-found",
)
+13 -15
View File
@@ -7,7 +7,7 @@ from typing import List, Optional
from textual import events, on
from textual.app import ComposeResult
from textual.containers import ScrollableContainer, Horizontal
from textual.containers import ScrollableContainer
from textual.screen import Screen
from textual.widgets import (
Button,
@@ -16,6 +16,7 @@ from textual.widgets import (
Label,
ListItem,
ListView,
Markdown,
Static,
)
@@ -84,7 +85,7 @@ class FavoriteManagerScreen(Screen):
def _encode_favorite_key(self, repo_path: str, ident: str) -> str:
"""编码仓库路径和标识符为安全的按钮 ID 部分"""
# 使用 \x00 分隔两部分, 然后进行 base64 编码
# 使用 \x00 分隔两部分然后进行 base64 编码
combined = f"{repo_path}\x00{ident}"
encoded = base64.urlsafe_b64encode(combined.encode()).decode()
# 去掉填充的等号
@@ -103,26 +104,23 @@ class FavoriteManagerScreen(Screen):
# 尝试获取仓库信息
repo_info = self._get_repo_info(fav.repo_path, fav)
title = repo_info.get("title", fav.repo_path) if repo_info else fav.repo_path
content_preview = repo_info.get("content_preview", "") if repo_info else ""
added_time = self._format_time(fav.added)
# 构建显示文本
display_text = f"{fav.ident}\n"
display_text += f" [d]添加于: {added_time}\n 来自 {title}[/d]"
display_text = f"[b]{title}[/b] ({fav.ident})\n"
if content_preview:
display_text += f"{content_preview}\n"
display_text += f"添加于: {added_time}"
if fav.tags:
display_text += f"{', '.join(fav.tags)}"
display_text += f" 标签: {', '.join(fav.tags)}"
# 创建安全的按钮 ID
button_key = self._encode_favorite_key(fav.repo_path, fav.ident)
# 创建列表项, 包含移除按钮
container = Horizontal(
Label(display_text, classes="favorite-content"),
Button(
"移除",
id=f"remove-{button_key}",
variant="error",
flat=True,
classes="favorite-item-btn",
),
# 创建列表项包含移除按钮
container = ScrollableContainer(
Markdown(display_text, classes="favorite-content"),
Button("移除", id=f"remove-{button_key}", variant="error"),
classes="favorite-item",
)
return ListItem(container)
+9 -19
View File
@@ -24,13 +24,12 @@ logger = get_logger(__name__)
class MemScreen(Screen):
BINDINGS = [
("q", "go_back_notif", "返回"),
("q", "go_back", "返回"),
("p", "prev", "查看上一个"),
("d", "toggle_dark", ""),
("v", "play_voice", "朗读"),
("*", "toggle_favorite", "收藏"),
("r", "resume_mark"),
("Q", "go_back"),
("n", "block_prompt"),
("s", "block_prompt"),
("z", "block_prompt"),
@@ -74,7 +73,7 @@ class MemScreen(Screen):
yield Header(
show_clock=config_var.get()["interface"]["global"]["clock_on_header"]
)
with ScrollableContainer(classes="memoqueue-container"):
with ScrollableContainer():
yield Label(self._get_progress_text(), id="head_stat")
yield ScrollableContainer(id="puzzle_container")
yield Footer()
@@ -105,13 +104,12 @@ class MemScreen(Screen):
return Static(f"无法生成谜题 {e}")
def _get_progress_text(self):
s = ""
s = f"阶段: {self.procession.route.name}\n"
# 收藏状态
if self.repo is not None:
fav_status = "已收藏" if self._is_current_atom_favorited() else "未收藏"
s += f"[{fav_status}] "
s += f"[{self.procession.process() + 1}/{self.procession.total_length()}] \[{self.procession.route.name}]\n"
if self.procession.cursor - 1 >= 0:
s += f"上一个: [d]{self.procession.atoms[self.procession.cursor - 1]['ident']}[/d]"
s += f"收藏: {fav_status}\n"
s += f"进度: {self.procession.process() + 1}/{self.procession.total_length()}"
return s
def update_display(self):
@@ -140,11 +138,7 @@ class MemScreen(Screen):
if config_var.get()["interface"]["global"]["persist_to_file"]:
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):
event.stop()
@@ -161,7 +155,6 @@ class MemScreen(Screen):
path = Path(config_var.get()["global"]["paths"]["data"]) / "cache" / "voice"
path = path / f"{get_md5(self.atom.registry['nucleon']["tts_text"])}.wav"
logger.debug(str(path))
if path.exists():
play_by_path(path)
else:
@@ -227,9 +220,6 @@ class MemScreen(Screen):
self.update_state() # 刷新状态
self.expander = self.procession.get_expander()
def action_go_back_notif(self):
self.notify("确定吗? 按下大写 Q 以返回")
def action_go_back(self):
self.app.pop_screen()
@@ -243,14 +233,14 @@ class MemScreen(Screen):
"""获取仓库相对路径(相对于 data/repo)"""
if self.repo is None:
return ""
# self.repo.source 是 Path 对象, 指向仓库目录
# self.repo.source 是 Path 对象指向仓库目录
repo_full_path = self.repo.source
data_repo_path = Path(config_var.get()["global"]["paths"]["data"]) / "repo"
try:
rel_path = repo_full_path.relative_to(data_repo_path)
return str(rel_path)
except ValueError:
# 如果不在 data/repo 下, 则返回完整路径(字符串形式)
# 如果不在 data/repo 下则返回完整路径(字符串形式)
return str(repo_full_path)
def _is_current_atom_favorited(self) -> bool:
+1 -1
View File
@@ -34,7 +34,7 @@ class NavigatorScreen(ModalScreen):
OTHERS = [
("退出程序", "self.app.exit()"),
("项目主页", "webbrowser.open('https://ams.pluv27.top')"),
("项目主页", "webbrowser.open('https://ams.imwangzhiyu.xyz')"),
]
def compose(self) -> ComposeResult:
+2 -2
View File
@@ -14,7 +14,7 @@ import heurams.kernel.particles as pt
import heurams.services.hasher as hasher
from heurams.context import *
# 兼容性缓存路径:优先使用 paths.cache, 否则使用 data/cache
# 兼容性缓存路径:优先使用 paths.cache否则使用 data/cache
paths = config_var.get()["global"]["paths"]
cache_dir = pathlib.Path(paths.get("cache", paths["data"] + "/cache")) / "voice"
@@ -55,7 +55,7 @@ class PrecachingScreen(Screen):
self.precache_worker = None
self.cancel_flag = 0
self.desc = desc
# 不再需要缓存配置, 保留配置读取以兼容
# 不再需要缓存配置保留配置读取以兼容
self.cache_stats = {
"total_size": 0,
"file_count": 0,
+3 -3
View File
@@ -130,7 +130,7 @@ class SyncScreen(Screen):
log_widget = self.query_one("#log_output")
log_widget.update("\n".join(self.log_messages)) # type: ignore
except Exception:
pass # 如果组件未就绪, 忽略错误
pass # 如果组件未就绪忽略错误
def on_button_pressed(self, event: Button.Pressed) -> None:
"""处理按钮点击事件"""
@@ -150,7 +150,7 @@ class SyncScreen(Screen):
def test_connection(self):
"""测试 WebDAV 服务器连接"""
if not self.sync_service:
self.log_message("同步服务未初始化, 请检查配置", is_error=True)
self.log_message("同步服务未初始化请检查配置", is_error=True)
self.update_status("❌ 同步服务未初始化")
return
@@ -172,7 +172,7 @@ class SyncScreen(Screen):
def start_sync(self):
"""开始同步"""
if not self.sync_service:
self.log_message("同步服务未初始化, 无法开始同步", is_error=True)
self.log_message("同步服务未初始化无法开始同步", is_error=True)
return
if self.is_syncing:
@@ -2,7 +2,7 @@ import copy
import random
from typing import TypedDict
from textual.containers import ScrollableContainer, Horizontal
from textual.containers import ScrollableContainer
from textual.widget import Widget
from textual.widgets import Button, Label, Markdown
from textual.events import Key
@@ -71,7 +71,6 @@ class ClozePuzzle(BasePuzzleWidget):
# 渲染当前问题的选项
with ScrollableContainer(id="btn-container") as s:
c = 0
btns = []
for i in self.ans:
h = str(hash(i))
if hash(i) in self.hashmap.keys():
@@ -81,12 +80,7 @@ class ClozePuzzle(BasePuzzleWidget):
btnid = f"sel000-{h}"
logger.debug(f"建立按钮 {btnid}")
self.btn_shortcuts[f"{c}"] = btnid
btns.append(Button(f"{i}", id=f"{btnid}", classes="cloze-option-btn"))
for i in range((len(btns) + 1) // 2):
if 2 * i + 1 + 1 <= len(btns):
yield Horizontal(btns[i], btns[len(btns) - 1 - i], classes="hori")
else:
yield btns[i]
yield Button(f"[{c}] {i}", id=f"{btnid}")
s.focus()
yield Button("退格", id="delete")
@@ -136,7 +130,7 @@ class ClozePuzzle(BasePuzzleWidget):
self.atom.minimize(rating)
def on_key(self, event: Key) -> None:
# self.notify(event.key)
self.notify(event.key)
if event.key in self.btn_shortcuts:
btn_id = self.btn_shortcuts.get(event.key)
btn_id = "#" + btn_id
+1 -1
View File
@@ -1,3 +1,3 @@
# Kernel - HeurAMS 核心
包括记忆规划相关状态机, 算法, 仓库系统, 辅助库与数据结构, 可脱离业务层
记忆规划相关算法与数据结构, 可脱离业务层
+18 -11
View File
@@ -1,14 +1,21 @@
import importlib
import pkgutil
from pathlib import Path
from .base import BaseAlgorithm
from .sm2 import SM2Algorithm
from .sm15m import SM15MAlgorithm
from .nsp0 import NSP0Algorithm
from .fsrs import FSRSAlgorithm
__path__ = [str(Path(__file__).parent)]
__all__ = [
"SM2Algorithm",
"BaseAlgorithm",
"SM15MAlgorithm",
"NSP0Algorithm",
"FSRSAlgorithm",
]
for _finder, _name, _ispkg in pkgutil.iter_modules(__path__):
if _name == "base":
continue
importlib.import_module(f".{_name}", __package__)
algorithms = BaseAlgorithm.get_registry()
algorithms = {
"SM-2": SM2Algorithm,
"NSP-0": NSP0Algorithm,
"SM-15M": SM15MAlgorithm,
"FSRS": FSRSAlgorithm,
"Base": BaseAlgorithm,
}
-10
View File
@@ -5,21 +5,11 @@ from heurams.services.logger import get_logger
logger = get_logger(__name__)
_registry: dict[str, type["BaseAlgorithm"]] = {}
class BaseAlgorithm:
algo_name = "BaseAlgorithm"
desc = "算法基类"
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
_registry[cls.algo_name] = cls
@classmethod
def get_registry(cls) -> dict[str, type["BaseAlgorithm"]]:
return dict(_registry)
class AlgodataDict(TypedDict):
real_rept: int
rept: int
+16 -12
View File
@@ -3,7 +3,7 @@ FSRS 算法模块 — 基于 py-fsrs 的现代间隔重复调度器
基于: https://github.com/open-spaced-repetition/py-fsrs
"""
import json
import os
import pathlib
from datetime import datetime, timezone, timedelta
@@ -20,20 +20,19 @@ from .base import BaseAlgorithm
logger = get_logger(__name__)
# 全局 Scheduler 状态文件路径
_SCHEDULER_STATE_FILE = (
pathlib.Path(config_var.get()["global"]["paths"]["misc"])
/ "fsrs_scheduler_state.json"
)
_SCHEDULER_STATE_FILE = pathlib.Path(
config_var.get()["global"]["paths"]["data"]
) / "global" / "fsrs_scheduler_state.json"
def _get_global_scheduler():
"""获取全局 FSRS Scheduler 实例, 从文件加载或创建新的"""
"""获取全局 FSRS Scheduler 实例从文件加载或创建新的"""
if os.path.exists(_SCHEDULER_STATE_FILE):
try:
with open(_SCHEDULER_STATE_FILE, "r", encoding="utf-8") as f:
return Scheduler.from_json(f.read())
except Exception:
logger.warning("FSRS Scheduler 状态文件加载失败, 创建新实例")
logger.warning("FSRS Scheduler 状态文件加载失败创建新实例")
return Scheduler()
@@ -81,7 +80,7 @@ class FSRSAlgorithm(BaseAlgorithm):
# FSRS 特有字段
fsrs_state: int # State 枚举值: 1=Learning, 2=Review, 3=Relearning
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 表示尚未计算
# 标准 BaseAlgorithm 兼容字段
real_rept: int
@@ -137,7 +136,9 @@ class FSRSAlgorithm(BaseAlgorithm):
# last_review
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
@@ -159,7 +160,9 @@ class FSRSAlgorithm(BaseAlgorithm):
if card.last_review
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["last_modify"] = get_timestamp()
return algodata
@@ -191,7 +194,7 @@ class FSRSAlgorithm(BaseAlgorithm):
if is_new_activation:
card = Card()
logger.debug("新激活, 创建新 Card")
logger.debug("新激活创建新 Card")
else:
card = cls._algodata_to_card(algodata)
@@ -206,7 +209,8 @@ class FSRSAlgorithm(BaseAlgorithm):
algodata[cls.algo_name]["rept"] += 1
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.difficulty,
card.state,
+1 -2
View File
@@ -10,7 +10,7 @@ logger = get_logger(__name__)
class NSP0Algorithm(BaseAlgorithm):
algo_name = "NSP-0"
desc = "快速筛选用非间隔重复调度器"
desc = "快速筛选用特殊调度器"
class AlgodataDict(TypedDict):
real_rept: int
@@ -54,7 +54,6 @@ class NSP0Algorithm(BaseAlgorithm):
logger.debug("feedback 为 -1, 跳过更新")
return
algodata[cls.algo_name]["interval"] = 1 if feedback <= 3 else float("inf")
if not algodata[cls.algo_name]["important"]:
algodata[cls.algo_name]["important"] = (
1 if feedback <= 3 else algodata[cls.algo_name]["important"]
)
+15 -17
View File
@@ -4,7 +4,6 @@ SM-15M — 基于 sm.js 的间隔重复算法
基于: https://github.com/slaypni/sm.js
原始 CoffeeScript (c) 2014 Kazuaki Tanida, MIT 许可证
"""
import datetime
import json
import math
@@ -20,6 +19,7 @@ from heurams.services.timer import (
get_timestamp_ms,
daystamp_to_datetime,
datetime_to_daystamp,
get_now_datetime,
)
from .base import BaseAlgorithm
@@ -253,9 +253,9 @@ class ForgettingCurves:
pts = []
for i in range(21):
v = MIN_AF + NOTCH_AF * i
y = math.exp(-1.0 / (10 + 1 * (a + 1)) * (i - a**0.6)) * (
self.REMEMBERED - self.sm.requested_fi
)
y = math.exp(
-1.0 / (10 + 1 * (a + 1)) * (i - a ** 0.6)
) * (self.REMEMBERED - self.sm.requested_fi)
pts.append([v, min(self.REMEMBERED, y)])
partial = [[0, self.REMEMBERED]] + pts
row.append(ForgettingCurve(partial))
@@ -427,9 +427,7 @@ class Item:
now = datetime.datetime.now()
af_idx = self.lapse if self.repetition == 0 else self.af_index()
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.previous_date = now
self.due_date = now + datetime.timedelta(milliseconds=self.optimum_interval)
@@ -602,10 +600,9 @@ class SM:
# 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"]["data"]
) / "global" / "sm15m_global_state.json"
def _get_global_sm():
@@ -614,7 +611,7 @@ def _get_global_sm():
with open(_GLOBAL_STATE_FILE, "r", encoding="utf-8") as f:
return SM.load(json.load(f))
except Exception:
logger.warning("SM-15M 全局状态文件加载失败, 创建新实例")
logger.warning("SM-15M 全局状态文件加载失败创建新实例")
sm = SM()
_save_global_sm(sm)
return sm
@@ -649,7 +646,7 @@ class SM15MAlgorithm(BaseAlgorithm):
# 毫秒精度(子日排程)
last_date_ms: int
next_date_ms: int
# BaseAlgorithm 兼容(天精度, 向后兼容)
# BaseAlgorithm 兼容(天精度向后兼容)
real_rept: int
rept: int
interval: int
@@ -697,7 +694,7 @@ class SM15MAlgorithm(BaseAlgorithm):
opt_days * 24 * 60 * 60 * 1000 if opt_days > 0 else sm.interval_base
)
# 毫秒精度优先, 退化至天精度
# 毫秒精度优先退化至天精度
last_date_ms = data.get("last_date_ms", 0)
if last_date_ms:
item.previous_date = datetime.datetime(1970, 1, 1) + datetime.timedelta(
@@ -707,8 +704,7 @@ class SM15MAlgorithm(BaseAlgorithm):
last_date = data.get("last_date", 0)
item.previous_date = (
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)
@@ -749,7 +745,9 @@ class SM15MAlgorithm(BaseAlgorithm):
data["last_date"] = datetime_to_daystamp(item.previous_date)
data["next_date_ms"] = int(item.due_date.timestamp() * 1000)
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()
return algodata
+1 -1
View File
@@ -10,7 +10,7 @@ logger = get_logger(__name__)
class SM2Algorithm(BaseAlgorithm):
algo_name = "SM-2"
desc = "SuperMemo2 (1987) 简单间隔重复调度器"
desc = "经典间隔重复算法"
class AlgodataDict(TypedDict):
efactor: float
@@ -1,10 +1,12 @@
"""通用音频适配器
基于 playsound3 的音频播放器, 在绝大多数 python 环境上提供音频服务
注意: 在 termux 不可用
基于 playsound 的音频播放器, 在绝大多数 python 环境上提供音频服务
注意: 在未配置 pulseaudio 的 termux 不可用
"""
import pathlib
import playsound
from heurams.services.logger import get_logger
logger = get_logger(__name__)
@@ -13,8 +15,8 @@ logger = get_logger(__name__)
def play_by_path(path: pathlib.Path):
logger.debug("playsound_audio.play_by_path: 开始播放 %s", path)
try:
import playsound3
playsound3.playsound(str(path))
playsound.playsound(str(path))
logger.debug("播放完成: %s", path)
except Exception as e:
logger.error("播放失败: %s, 错误: %s", path, e)
raise
+3 -1
View File
@@ -12,10 +12,12 @@ logger = get_logger(__name__)
# from .protocol import PlayFunctionProtocol
def play_by_path(path: pathlib.Path):
logger.debug("termux_audio.play_by_path: 开始播放 %s", path)
try:
os.system(f"play-audio {path.resolve()}")
os.system(f"play-audio {path}")
logger.debug("播放命令已执行: %s", path)
except Exception as e:
logger.error("播放失败: %s, 错误: %s", path, e)
raise
+4 -4
View File
@@ -26,14 +26,14 @@ class BaseLLM:
"""发送聊天消息并获取响应
Args:
messages: 消息列表, 每个消息为 {"role": "user"|"assistant"|"system", "content": "消息内容"}
**kwargs: 其他参数, 如 temperature, max_tokens 等
messages: 消息列表每个消息为 {"role": "user"|"assistant"|"system", "content": "消息内容"}
**kwargs: 其他参数如 temperature, max_tokens 等
Returns:
模型返回的文本响应
"""
logger.debug("BaseLLM.chat: messages=%d, kwargs=%s", len(messages), kwargs)
logger.warning("BaseLLM.chat 是基类方法, 未实现具体功能")
logger.warning("BaseLLM.chat 是基类方法未实现具体功能")
await asyncio.sleep(0) # 避免未使用异步的警告
return "BaseLLM 未实现具体功能"
@@ -50,6 +50,6 @@ class BaseLLM:
logger.debug(
"BaseLLM.chat_stream: messages=%d, kwargs=%s", len(messages), kwargs
)
logger.warning("BaseLLM.chat_stream 是基类方法, 未实现具体功能")
logger.warning("BaseLLM.chat_stream 是基类方法未实现具体功能")
await asyncio.sleep(0)
yield "BaseLLM 未实现流式功能"
+3 -3
View File
@@ -27,8 +27,8 @@ class OpenAILLM(BaseLLM):
try:
from openai import AsyncOpenAI
except ImportError:
logger.error("未安装 openai 库, 请运行: pip install openai")
raise ImportError("未安装 openai 库, 请运行: pip install openai")
logger.error("未安装 openai 库请运行: pip install openai")
raise ImportError("未安装 openai 库请运行: pip install openai")
self._client = AsyncOpenAI(
api_key=self.api_key if self.api_key else None,
@@ -49,7 +49,7 @@ class OpenAILLM(BaseLLM):
"max_tokens": kwargs.get("max_tokens", 1000),
}
# 合并参数, 优先使用传入的 kwargs
# 合并参数优先使用传入的 kwargs
request_kwargs = {**default_kwargs, **kwargs}
request_kwargs["messages"] = messages
+1 -1
View File
@@ -1,3 +1,3 @@
# Services - 服务
基础服务相关代码定义
基础服务相关代码
+1 -1
View File
@@ -10,7 +10,7 @@ from heurams.services.exceptions import WTFException
logger = get_logger(__name__)
class ConfigDict(UserDict):
class ConfigDict(UserDict): # 舒服了
_instances = {} # 必须使用单例模式, 不然有严重的多实例导致的配置无法持久化问题
def __new__(cls, config_path: pathlib.Path, dict=None):
+2 -2
View File
@@ -73,7 +73,7 @@ class FavoriteManager:
with open(self._file_path, "r", encoding="utf-8") as f:
data = json.load(f)
self._favorites = [FavoriteItem.from_dict(item) for item in data]
logger.debug("收藏列表加载成功, %d", len(self._favorites))
logger.debug("收藏列表加载成功%d", len(self._favorites))
except Exception as e:
logger.error("加载收藏列表失败: %s", e)
self._favorites = []
@@ -86,7 +86,7 @@ class FavoriteManager:
data = [item.to_dict() for item in self._favorites]
with open(self._file_path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
logger.debug("收藏列表保存成功, %d", len(self._favorites))
logger.debug("收藏列表保存成功%d", len(self._favorites))
except Exception as e:
logger.error("保存收藏列表失败: %s", e)
+3 -3
View File
@@ -43,9 +43,9 @@ def get_timestamp_ms() -> int:
def daystamp_to_datetime(daystamp: int) -> datetime.datetime:
"""将日戳转换为 UTC datetime (当日午夜)"""
return datetime.datetime(
1970, 1, 1, tzinfo=datetime.timezone.utc
) + datetime.timedelta(days=daystamp)
return datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc) + datetime.timedelta(
days=daystamp
)
def datetime_to_daystamp(dt: datetime.datetime) -> int:
+2 -2
View File
@@ -3,8 +3,8 @@ from heurams.services.logger import get_logger
logger = get_logger(__name__)
ver = "0.5.1"
stage = "stable"
ver = "0.5.0"
stage = "rc.1"
codename = "fulcrum"
codename_cn = "支点"
+11 -11
View File
@@ -6,9 +6,9 @@
转换规则:
1. `ident` 列用作 TOML 的 section 标题(`[ident]`)
2. 若某行的 `ident` 为空, 则自动按顺序生成标识符, 例如 `idx_1`、`idx_2` 等
2. 若某行的 `ident` 为空则自动按顺序生成标识符例如 `idx_1`、`idx_2` 等
3. 所有其他列(除 `ident` 外)都作为该 section 下的键值对
4. 所有列都是可选的, 但 `ident` 为空时会自动生成
4. 所有列都是可选的但 `ident` 为空时会自动生成
示例 CSV:
```csv
@@ -61,8 +61,8 @@ meaning = "狗发出的声音"
补充说明:
- 自动生成的标识符使用 `idx_` 前缀加数字序列
- 生成序列基于原始 CSV 中 `ident` 为空的行出现的顺序
- 所有值都保留为字符串类型, 符合 TOML 字符串格式要求
- 如果 CSV 包含更多列, 它们也会以相同方式转换为键值对
- 所有值都保留为字符串类型符合 TOML 字符串格式要求
- 如果 CSV 包含更多列它们也会以相同方式转换为键值对
- 支持 `-r` 参数指定随机种子来打乱 section 顺序
"""
@@ -79,8 +79,8 @@ def csv_to_toml(csv_path, toml_path=None, random_seed=None):
Args:
csv_path (str): 输入CSV文件路径
toml_path (str): 输出TOML文件路径, 默认为相同目录下同名文件
random_seed (int): 随机种子, 用于打乱section顺序, None表示不打乱
toml_path (str): 输出TOML文件路径默认为相同目录下同名文件
random_seed (int): 随机种子用于打乱section顺序None表示不打乱
"""
# 检查CSV文件是否存在
csv_file = Path(csv_path)
@@ -108,7 +108,7 @@ def csv_to_toml(csv_path, toml_path=None, random_seed=None):
print("错误: CSV文件为空或格式不正确")
sys.exit(1)
# 如果指定了随机种子, 设置随机种子并打乱行顺序
# 如果指定了随机种子设置随机种子并打乱行顺序
if random_seed is not None:
random.seed(random_seed)
random.shuffle(rows)
@@ -119,7 +119,7 @@ def csv_to_toml(csv_path, toml_path=None, random_seed=None):
idx_counter = 1
for row in rows:
# 处理ident列, 为空时生成自动标识符
# 处理ident列为空时生成自动标识符
ident = row.get("ident", "").strip()
if not ident:
ident = f"idx_{idx_counter}"
@@ -155,7 +155,7 @@ def csv_to_toml(csv_path, toml_path=None, random_seed=None):
def main():
"""主函数"""
parser = argparse.ArgumentParser(
description="将CSV文件转换为TOML格式, 支持随机打乱section顺序",
description="将CSV文件转换为TOML格式支持随机打乱section顺序",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
@@ -168,10 +168,10 @@ def main():
parser.add_argument("csv_path", help="输入的CSV文件路径")
parser.add_argument(
"toml_path", nargs="?", help="输出的TOML文件路径, 默认为CSV同名文件"
"toml_path", nargs="?", help="输出的TOML文件路径默认为CSV同名文件"
)
parser.add_argument(
"-r", "--random-seed", type=int, help="随机种子, 用于打乱TOML section的顺序"
"-r", "--random-seed", type=int, help="随机种子用于打乱TOML section的顺序"
)
args = parser.parse_args()
-150
View File
@@ -1,150 +0,0 @@
# HeurAMS 测试指南
## 概览
HeurAMS 使用 [pytest](https://docs.pytest.org/) 作为测试框架, 测试文件统一存放在项目根目录的 `tests/` 目录下。
- **测试框架**: pytest >= 8.0.0
- **覆盖率**: pytest-cov >= 6.0.0
- **配置**: `pyproject.toml` 中的 `[tool.pytest.ini_options]`
- **所有测试纯单元测试** — 不涉及 I/O、网络或真实文件系统依赖
## 运行测试
```bash
# 从项目根目录运行全部测试
uv run pytest
# 显示详细测试名
uv run pytest -v
# 运行单个测试文件
uv run pytest tests/test_sm2.py
# 按关键词筛选
uv run pytest -k "revisor"
# 带覆盖率报告
uv run pytest --cov=heurams
# 生成 HTML 覆盖率报告
uv run pytest --cov=heurams --cov-report=html
```
当前全部 **128 个测试** 通过 (0.2s)。
## 测试套件结构
所有 9 个测试文件平铺在 `tests/` 目录下:
| 文件 | 用例数 | 测试对象 |
|---|---|---|
| `test_base_algorithm.py` | 7 | `kernel.algorithms.base.BaseAlgorithm` |
| `test_sm2.py` | 16 | `kernel.algorithms.sm2.SM2Algorithm` |
| `test_nsp0.py` | 9 | `kernel.algorithms.nsp0.NSP0Algorithm` |
| `test_electron.py` | 18 | `kernel.particles.electron.Electron` |
| `test_lict.py` | 29 | `kernel.auxiliary.lict.Lict` |
| `test_evalizor.py` | 8 | `kernel.auxiliary.evalizor.Evalizer` |
| `test_epath.py` | 11 | `services.epath` |
| `test_hasher.py` | 5 | `services.hasher` |
| `test_textproc.py` | 7 | `services.textproc` |
## 共享 Fixtures (`conftest.py`)
`tests/conftest.py` 提供了四个全局 fixture:
- **`timer_config`** — 返回一个 `ConfigDict`, 其中计时器被覆写为确定值 (`daystamp=20000`, `timestamp=1e9`), 使算法测试不依赖系统时钟
- **`timer_context`** — 通过 `ConfigContext` 上下文管理器在测试期间应用计时器覆写
- **`sample_algodata_sm2`** — 一份预激活状态的 SM-2 `algodata` 字典 (deepcopy, 防止 fixture 污染)
- **`sample_algodata_nsp0`** — 同上, 针对 NSP-0
## 测试模式与约定
### 组织方式
- 测试全部使用 **class-based 组织** (例如 `TestSM2Revisor`, `TestElectronInit`)
- 每个测试类聚焦一个模块的一个方面 (默认值、方法、边界情况、属性等)
- 类名以 `Test` 开头, 方法名以 `test_` 开头
### 数据隔离
- 使用 `deepcopy(algodata)` 防止 fixture 突变在测试间泄漏
- 算法测试依赖 `timer_context` fixture 以获得确定性日期/时间
### 断言风格
- 仅使用标准 `assert` 语句, 无第三方断言库
- 异常测试使用 `pytest.raises()`
### 自定义标记
`pyproject.toml` 中定义了以下 pytest 标记 (当前尚未使用):
- `slow` — 慢速测试, 可用 `-m "not slow"` 跳过
- `integration` — 集成测试
## 覆盖率现状
**已有测试覆盖的模块:**
- `kernel.algorithms.base` — 完整
- `kernel.algorithms.sm2` — 完整
- `kernel.algorithms.nsp0` — 完整
- `kernel.particles.electron` — 完整
- `kernel.auxiliary.lict` — 完整
- `kernel.auxiliary.evalizor` — 完整
- `services.epath` — 完整
- `services.hasher` — 完整
- `services.textproc` — 完整
**尚无测试覆盖的模块 (欢迎贡献):**
- `kernel.particles.atom`, `nucleon`, `orbital`
- `kernel.reactor` (router, procession, expander)
- `kernel.puzzles` (所有题型)
- `kernel.algorithms.sm15m`, `kernel.algorithms.fsrs`
- `services.config`, `services.timer`, `services.logger`, `services.audio_service`, `services.tts_service`,
`services.favorite_service`, `services.attic`
- `interface/` (完整 TUI)
- `providers/` (所有提供者后端)
- `repolib.repo`
## 编写新测试
1. 在 `tests/` 下创建 `test_<模块名>.py` 文件
2. 文件头部加模块文档字符串
3. 按功能划分测试类 (每个类一个测试主题)
4. 算法相关测试使用 `timer_context` fixture 保证确定性
5. 使用 `deepcopy` 保护共享 fixture
6. 运行 `uv run pytest tests/test_<模块名>.py -v` 验证
示例结构:
```python
"""Tests for heurams.module.submodule.SomeClass"""
import pytest
from heurams.module.submodule import SomeClass
class TestSomeClassInit:
def test_defaults(self):
...
class TestSomeClassMethod:
def test_normal_case(self):
...
def test_edge_case(self):
...
```
## 开发环境设置
参考 [CONTRIBUTING.md](../CONTRIBUTING.md):
```bash
uv sync --all-extras # 安装开发依赖
uv run pytest # 运行测试
```
+3 -12
View File
@@ -25,18 +25,9 @@ class TestElectronInit:
assert e.algodata["SM-2"]["efactor"] == 2.5
def test_existing_data_preserved(self, timer_context):
data = {
"SM-2": {
"efactor": 1.5,
"rept": 3,
"real_rept": 5,
"interval": 10,
"last_date": 100,
"next_date": 200,
"is_activated": 1,
"last_modify": 1e9,
}
}
data = {"SM-2": {"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)
assert e.algodata["SM-2"]["efactor"] == 1.5
assert e.algodata["SM-2"]["rept"] == 3

Some files were not shown because too many files have changed in this diff Show More