docs: 更新文档

This commit is contained in:
2026-05-06 09:35:58 +08:00
parent b3155e18e5
commit 4d54208848
37 changed files with 394 additions and 17186 deletions

View File

@@ -4,7 +4,7 @@
> [!NOTE]
> 我们已经开始着手于基于 KDE 用户界面框架 `Kirigami` 的现代跨平台前端开发, 称作 "KiriMemo", 包名是 "org.kde.kirimemo", 但其并非 KDE 项目
> 它通过 `PyOtherSide` 直接复用 python 内核, 为 Windows, Linux, macOS, Android 和 Plasma Mobile 提供现代用户界面
> 它通过 `PyOtherSide` 直接复用 python 内核, 为 Windows, Linux, macOS, Android, iOS 和 Plasma Mobile 提供现代用户界面
> 如果您善于开发 C++, QML, Qt 与 KDE 框架, 欢迎加入到 KiriMemo 项目的开发
## 开发规范
@@ -86,11 +86,10 @@ python3 -m heurams.interface # 启动 TUI
贡献者拥有其贡献部分的版权同意其贡献将在 AGPL-3.0 许可证(包括附加的本机 API 调用豁免条款)下发布.
果您认为有必要引入其他开源的 vendor, 请在 PR 中注明或手动联系以便我们审查 vendor 许可证并更改此处和网站上的关于与版权声明
如果您认为有必要引入其他专有的网络服务(就像现在项目中的 edgetts), 请也在 PR 中注明
如果您认为有必要升级某个依赖或运行环境的版本, 请也在 PR 中注明
有以下情况, 请在 PR 描述中注明:
- 如果需要引入其他开源 vendor
- 如果需要引入其他专有的网络服务(例如当前项目中的 edgetts)
- 如果需要升级某个依赖或运行环境的版本
## 新的用户界面前端

View File

@@ -13,7 +13,7 @@
> [!NOTE]
> 我们已经着手于基于 KDE 用户界面框架 `Kirigami` 的现代跨平台前端开发, 称作 "KiriMemo", 包名是 "org.kde.kirimemo", 但其并非 KDE 项目
> 它通过 `PyOtherSide` 直接复用 python 内核, 为 Windows, Linux, macOS, Android 和 Plasma Mobile 提供现代用户界面
> 它通过 `PyOtherSide` 直接复用 python 内核, 为 Windows, Linux, macOS, Android, iOS 和 Plasma Mobile 提供现代用户界面
> 如果您善于开发 C++, QML, Qt 与 KDE 框架, 欢迎加入到 KiriMemo 项目的开发
## 特性
@@ -44,6 +44,7 @@
- 在间隔重复研究尚被 SuperMemo 系列独占的时代, Wozniak 就早已表示 "如果不能理解知识, 就无需记忆它". 今天, 我们依然相信理解是记忆的基石
- 云同步与分享优化: 由于我们的记忆数据和单元集文件都是文本文件, 故可进行快速的增量同步而无需完整地上传所有文件, 并且设计天然支持分享内容的版本控制, 如果您想分享单文件, 我们也支持导出为压缩包或合并单文本文件以通过纯文本文件形式在 pastebin 等平台分享
- 性能提升: 得益于现代且支持分块的文件组织结构, 潜进能在保持高自由度的同时仅使用 python 就能达到敏捷且低占用的用户体验
- AI 友好: 想象您有一些 .apkg 牌组或一大段教材内容, 您可以方便且高效率地使用 AI 工具生成可在 HeurAMS 使用的单元集
### 实用用户界面

68
SCREENSHOTS.md Normal file
View File

@@ -0,0 +1,68 @@
# 截图展示
潜进 (HeurAMS) 项目目前有两个前端实现, 此文档用于呈现它们的截图:
- Textual (基本用户界面): 基于 Python Textual 框架构建的程序库内置跨平台 TUI 界面, 支持触屏、鼠标、键盘多操作模式, 是当前开箱即用的默认前端.
- KiriMemo: 基于 KDE `Kirigami` 框架的现代跨平台前端, 使用 C++ 和 QML 构建, 通过 `PyOtherSide` 直接复用 Python 内核,为 Windows、Linux、macOS、Android、iOS 和 Plasma Mobile 提供原生体验(尚未稳定).
欢迎为现有前端贡献代码, 或开发您自己的前端.
详见[贡献指南](CONTRIBUTING.md#新的用户界面前端).
## 基本用户界面前端的截图
### 仪表盘与导航器
仪表盘包含学习面板的总体视图, 包括不同功能区域的操作入口, 统计信息, 以及单元集概览.
导航器是一个实用的模态窗口, 能带您在多种功能间自如切换, 按 `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: 补充截图 -->

View File

@@ -1,6 +1,6 @@
algorithm = "SM-2"
algorithm = "NSP-0"
_algorithm_desc = "记忆调度算法"
scheduled_num = 20
scheduled_num = 35
_scheduled_num_desc = "单次记忆单元数量"
[_algorithm_candidate]

View File

@@ -364,5 +364,119 @@
"ident": "既窈窕以寻壑, 亦崎岖而经丘.",
"added": 1777938937,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "怀良辰以孤往, 或植杖而耘耔.",
"added": 1777968294,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "淮左名都, 竹西佳处, 解鞍少驻初程.",
"added": 1777970555,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "古者富贵而名摩灭, 不可胜记, 唯倜傥非常之人称焉.",
"added": 1778018829,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "盖文王拘而演《周易》; 仲尼厄而作《春秋》; 屈原放逐, 乃赋《离骚》; 左丘失明, 厥有《国语》;",
"added": 1778018908,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "乃如左丘无目, 孙子断足, 终不可用, 退而论书策, 以舒其愤, 思垂空文以自见.",
"added": 1778019166,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "上计轩辕, 下至于兹, 为十表, 本纪十二, 书八章, 世家三十, 列传七十, 凡百三十篇.",
"added": 1778019396,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "草创未就, 会遭此祸, 惜其不成, 是以就极刑而无愠色.",
"added": 1778019511,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "心非木石岂无感, 吞声踯躅不敢言.",
"added": 1778019707,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "仆窃不逊, 近自托于无能之辞, 网罗天下放失旧闻, 略考其行事, 综其终始, 稽其成败兴坏之纪,",
"added": 1778020343,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "仆诚以著此书, 藏之名山, 传之其人, 通邑大都, 则仆偿前辱之责, 虽万被戮, 岂有悔哉!",
"added": 1778020491,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "江畔何人初见月? 江月何年初照人?",
"added": 1778025537,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "不知江月待何人, 但见长江送流水.",
"added": 1778025571,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "斜月沉沉藏海雾, 碣石潇湘无限路.",
"added": 1778025668,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "不知乘月几人归, 落月摇情满江树.",
"added": 1778025686,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "东南形胜, 三吴都会, 钱塘自古繁华.",
"added": 1778025708,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "江娥啼竹素女愁, 李凭中国弹箜篌.",
"added": 1778025886,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "十二门前融冷光, 二十三丝动紫皇.",
"added": 1778025946,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "羌管弄晴, 菱歌泛夜, 嬉嬉钓叟莲娃.",
"added": 1778026396,
"tags": []
},
{
"repo_path": "cngk-t",
"ident": "千骑拥高牙, 乘醉听箫鼓, 吟赏烟霞.",
"added": 1778026427,
"tags": []
}
]

View File

@@ -1,4 +0,0 @@
title = "高考化学"
package = "chmgk"
author = "__heurams__"
desc = "高考古诗文 60 篇"

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +0,0 @@
title = "高考必背古诗文-筛选"
package = "cngk-t"
author = "__heurams__"
desc = "高考古诗文 60 篇"

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +0,0 @@
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" = "总复习"

View File

@@ -1,17 +0,0 @@
[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']"}

View File

@@ -0,0 +1,4 @@
title = "测试用单元集"
package = "stub"
author = "__heurams__"
desc = ""

View File

@@ -14,6 +14,7 @@ license-files = ["LICENSE"]
dependencies = [ # 这些依赖只能驱动 kernel 程序库
"tabulate>=0.10.0",
"textual>=8.2.5",
"toml>=0.10.2",
"transitions>=0.9.3",
]
@@ -49,6 +50,10 @@ basic = ["heurams[algo-fsrs]", "heurams[tts-edgetts]", "heurams[llm]"]
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"
heurams-tui = "heurams.interface.__main__:main"

BIN
screenshots/about_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

BIN
screenshots/dashboard_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

BIN
screenshots/dashboard_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

BIN
screenshots/navigator_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

BIN
screenshots/precache_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

BIN
screenshots/preparation.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

BIN
screenshots/setting_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

BIN
screenshots/setting_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

View File

@@ -22,4 +22,15 @@ NavigatorScreen {
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;
}

View File

@@ -50,36 +50,32 @@ class AboutScreen(Screen):
memory_info = self._get_memory_info()
about_text = f"""
# 关于 "潜进"
# 关于 HeurAMS "潜进"
主程序库版本: `{version.ver}-python`
用户界面分支: `Textual TUI (基本用户界面)`
用户界面版本: `{version.ver}`
API 版本代号: `{version.codename.capitalize()}`
一个基于启发式算法与认知科学理论的辅助记忆调度器, 旨在帮助用户更高效地进行记忆工作与学习规划.
一个开放, 优雅, 易于扩展的间隔重复调度器实验平台, 旨在帮助研究者更高效地进行前沿记忆算法的研究.
> 一个基于启发式算法与认知科学理论的辅助记忆调度器, 旨在帮助用户更高效地进行记忆工作与学习规划.
> 一个开放, 优雅, 易于扩展的间隔重复调度器实验平台, 旨在帮助研究者更高效地进行前沿记忆算法的研究.
正使用的 TUI 用户界面是 python 版本程序库自带的基本用户界面, 以作为第一个全功能前端实现与程序库测试套件, 位于程序库下 interface 目录.
可在项目主页 https://ams.pluv27.top 获取用户指南, 开发文档与软件更新, 并参与到软件的开发与改进工作.
您可在项目主页 https://ams.pluv27.top 获取用户指南, 开发文档与软件更新.
以 GNU Affero 通用公共许可证 (第3版) 开放源代码, 并有一条豁免本机 API 调用的附加条款, 用于其他前端到程序库的接口调用.
以 GNU Affero 通用公共许可证 (第3版) 开放源代码.
您正使用程序库内置的终端用户界面, 它是第一个全功能前端实现与程序库测试套件, 位于程序库的 interface 子目录.
如果您觉得这个软件有用, 可以考虑参与贡献, 或在它的源代码仓库给它添加一个星标 :)
开发人员列表:
您的慷慨支持, 我们必当涌泉相报.
- 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): 中文文献参考
- [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 算法底层实现
# 运行环境信息
@@ -89,7 +85,7 @@ Textual 框架版本: {textual_version}
终端模拟器: {terminal_info}
操作系统版本: {os_version}
存储余量: {disk_usage}
内存大小: {memory_info}
内存总量: {memory_info}
报告问题时, 请复制这些信息到问题描述, 并上传软件日志 `heurams.log` 作为附件, 以协助开发者定位错误
"""

View File

@@ -74,7 +74,7 @@ class MemScreen(Screen):
yield Header(
show_clock=config_var.get()["interface"]["global"]["clock_on_header"]
)
with ScrollableContainer():
with ScrollableContainer(classes="memoqueue-container"):
yield Label(self._get_progress_text(), id="head_stat")
yield ScrollableContainer(id="puzzle_container")
yield Footer()
@@ -105,12 +105,13 @@ class MemScreen(Screen):
return Static(f"无法生成谜题 {e}")
def _get_progress_text(self):
s = f"阶段: {self.procession.route.name}\n"
# 收藏状态
s = ""
if self.repo is not None:
fav_status = "已收藏" if self._is_current_atom_favorited() else "未收藏"
s += f"收藏: {fav_status}\n"
s += f"进度: {self.procession.process() + 1}/{self.procession.total_length()}"
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]"
return s
def update_display(self):

View File

@@ -1,3 +1,3 @@
# Kernel - HeurAMS 核心
记忆规划相关算法与数据结构, 可脱离业务层
包括记忆规划相关状态机, 算法, 仓库系统, 辅助库与数据结构, 可脱离业务层

View File

@@ -1,3 +1,3 @@
# Services - 服务
基础服务相关代码
基础服务相关代码定义

150
tests/README.md Normal file
View File

@@ -0,0 +1,150 @@
# 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 # 运行测试
```

20
uv.lock generated
View File

@@ -439,6 +439,7 @@ version = "0.5.0"
source = { editable = "." }
dependencies = [
{ name = "tabulate" },
{ name = "textual" },
{ name = "toml" },
{ name = "transitions" },
]
@@ -498,6 +499,7 @@ requires-dist = [
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0" },
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.0.0" },
{ name = "tabulate", specifier = ">=0.10.0" },
{ name = "textual", specifier = ">=8.2.5" },
{ name = "textual", marker = "extra == 'interface'", specifier = ">=8.2.3" },
{ name = "toml", specifier = ">=0.10.2" },
{ name = "transitions", specifier = ">=0.9.3" },
@@ -774,7 +776,7 @@ wheels = [
[[package]]
name = "openai"
version = "2.32.0"
version = "2.34.0"
source = { registry = "https://mirrors.ustc.edu.cn/pypi/simple" }
dependencies = [
{ name = "anyio" },
@@ -786,18 +788,18 @@ dependencies = [
{ name = "tqdm" },
{ name = "typing-extensions" },
]
sdist = { url = "https://mirrors.ustc.edu.cn/pypi/packages/ed/59/bdcc6b759b8c42dd73afaf5bf8f902c04b37987a5514dbc1c64dba390fef/openai-2.32.0.tar.gz", hash = "sha256:c54b27a9e4cb8d51f0dd94972ffd1a04437efeb259a9e60d8922b8bd26fe55e0", size = 693286, upload-time = "2026-04-15T22:28:19.434Z" }
sdist = { url = "https://mirrors.ustc.edu.cn/pypi/packages/7b/89/f1e78f5f828f4e97a6ebca8f45c6b35667da12b074ac490dc8362b882279/openai-2.34.0.tar.gz", hash = "sha256:828b4efcbb126352c2b5eb97d33ae890c92a71ab72511aefc1b7fe64aeccb07b", size = 759556, upload-time = "2026-05-04T17:34:08.721Z" }
wheels = [
{ url = "https://mirrors.ustc.edu.cn/pypi/packages/1e/c1/d6e64ccd0536bf616556f0cad2b6d94a8125f508d25cfd814b1d2db4e2f1/openai-2.32.0-py3-none-any.whl", hash = "sha256:4dcc9badeb4bf54ad0d187453742f290226d30150890b7890711bda4f32f192f", size = 1162570, upload-time = "2026-04-15T22:28:17.714Z" },
{ url = "https://mirrors.ustc.edu.cn/pypi/packages/f2/40/f090499f10514515081d09cb9da09f25b821eb20497e9423afe4f07b4ecf/openai-2.34.0-py3-none-any.whl", hash = "sha256:c996a71b1a210f3569844572ad4c609307e978515fb76877cf449b72596e549e", size = 1316535, upload-time = "2026-05-04T17:34:06.773Z" },
]
[[package]]
name = "packaging"
version = "26.1"
version = "26.2"
source = { registry = "https://mirrors.ustc.edu.cn/pypi/simple" }
sdist = { url = "https://mirrors.ustc.edu.cn/pypi/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" }
sdist = { url = "https://mirrors.ustc.edu.cn/pypi/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" }
wheels = [
{ url = "https://mirrors.ustc.edu.cn/pypi/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" },
{ url = "https://mirrors.ustc.edu.cn/pypi/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
]
[[package]]
@@ -1181,7 +1183,7 @@ wheels = [
[[package]]
name = "textual"
version = "8.2.4"
version = "8.2.5"
source = { registry = "https://mirrors.ustc.edu.cn/pypi/simple" }
dependencies = [
{ name = "markdown-it-py", extra = ["linkify"] },
@@ -1191,9 +1193,9 @@ dependencies = [
{ name = "rich" },
{ name = "typing-extensions" },
]
sdist = { url = "https://mirrors.ustc.edu.cn/pypi/packages/19/89/bec5709fb759f9c784bbcb30b2e3497df3f901691d13c2b864dbf6694a17/textual-8.2.4.tar.gz", hash = "sha256:d4e2b2ddd7157191d00b228592b7c739ea080b7d792fd410f23ca75f05ea76c4", size = 1848933, upload-time = "2026-04-19T04:20:45.845Z" }
sdist = { url = "https://mirrors.ustc.edu.cn/pypi/packages/62/1e/1eedc5bac184d00aaa5f9a99095f7e266af3ec46fa926c1051be5d358da1/textual-8.2.5.tar.gz", hash = "sha256:6c894e65a879dadb4f6cf46ddcfedb0173ff7e0cb1fe605ff7b357a597bdbc90", size = 1851596, upload-time = "2026-04-30T08:02:58.956Z" }
wheels = [
{ url = "https://mirrors.ustc.edu.cn/pypi/packages/5c/32/02932f0d597cdbb34e34bf24266ff0f2cf292ccb3aafc37dd9efcb0cc416/textual-8.2.4-py3-none-any.whl", hash = "sha256:a83bd3f0cc7125ca203845af753f9d6b6be030025ecd1b05cc75ebe645b9c4ba", size = 724390, upload-time = "2026-04-19T04:20:49.968Z" },
{ url = "https://mirrors.ustc.edu.cn/pypi/packages/cd/01/c4555f9c8a692ff83d84930150540f743ce94c89234f9e9a15ff4baba3a8/textual-8.2.5-py3-none-any.whl", hash = "sha256:247d2aa2faf222749c321f88a736247f37ee2c023604079c7490bfacddfcd4b2", size = 727050, upload-time = "2026-04-30T08:03:01.421Z" },
]
[[package]]