Compare commits
5 Commits
cf2f29cbd7
...
e65db69fc6
| Author | SHA1 | Date | |
|---|---|---|---|
| e65db69fc6 | |||
| 9dd6733063 | |||
| bcdfddce10 | |||
| 6f183e4d9d | |||
| 4246061c29 |
@@ -1,42 +1,93 @@
|
||||
# 贡献指南
|
||||
# 贡献指南与二次开发
|
||||
|
||||
欢迎为此项目做出贡献!\
|
||||
本项目是一个开源项目, 我们鼓励社区成员参与改进.
|
||||
欢迎为此项目做出贡献!
|
||||
|
||||
## 开发规范
|
||||
|
||||
1. 分支划分:
|
||||
- `main` 分支: 稳定版本
|
||||
- `dev` 分支: 开发版本
|
||||
- 功能分支: 从 `dev` 分支创建, 命名格式为 `feature/描述` 或 `fix/描述` 或 `refactor/描述`
|
||||
1. 代码风格:
|
||||
- 请使用 Black 格式化代码
|
||||
- 遵循 PEP 8 规范
|
||||
- 添加适当的文档字符串
|
||||
1. 提交消息:
|
||||
分支划分:
|
||||
|
||||
- `main` 分支: 稳定版本, 仅当稳定版本释出或修补版本时将 `dev` 合并到 `main` 上
|
||||
- `dev` 分支: 主线开发版本, 自身仅用于非重构的问题修复和整合功能分支
|
||||
- 功能与重构分支: 从 `dev` 分支创建, 命名格式为 `feature/描述` 或 `fix/描述` 或 `refactor/v版本号`()
|
||||
- 不要将功能与重构分支先应被合并至 `dev` 后在 `dev` 完成文档开发后再释出至 `main`
|
||||
代码格式化:
|
||||
- 对于 Python, 使用 `black` 格式化
|
||||
- 对于 Markdown, 使用 `mdformat` 格式化
|
||||
- 对于 Textual CSS, 使用 `prettier` 格式化
|
||||
- 格式化不是必需的, 可以整合入一次 `style` 提交, 但 `main` 和 `dev` 分支上的代码应尽量整洁, 以便合并时审查
|
||||
提交消息:
|
||||
- 使用简体中文或英文撰写清晰的提交消息
|
||||
- 格式: 遵循 Conventional Commits 规范
|
||||
1. 合并方式:
|
||||
- 不使用 Fast-forward 合并
|
||||
- 提交消息格式: 遵循 [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) 规范, 建议使用 `koji` 工具
|
||||
合并方式:
|
||||
- 为了一致性和可追溯性, 项目自 v0.4.0 重构后重新初始化仓库起就禁止使用 Fast-forward 合并
|
||||
- 可以设置 `git config merge.ff false`
|
||||
|
||||
## 设置开发环境
|
||||
|
||||
```bash
|
||||
# 克隆仓库
|
||||
git clone https://gitea.imwangzhiyu.xyz/ajax/HeurAMS
|
||||
|
||||
git clone https://git.pluv27.top/pluv/HeurAMS
|
||||
cd HeurAMS
|
||||
|
||||
# 你可能需要切换分支
|
||||
# 可能需要切换到 dev 分支
|
||||
git checkout dev
|
||||
|
||||
# 安装依赖
|
||||
pip install -r requirements.txt
|
||||
# 如果决定使用 uv (推荐)
|
||||
|
||||
# 安装开发版本
|
||||
pip install -e .
|
||||
## 首先要安装uv, 例如通过 pip 或者其他包管理器
|
||||
python3 -m pip install uv
|
||||
|
||||
uv sync # 同步开发运行环境
|
||||
|
||||
uv run heurams # 验证包安装
|
||||
|
||||
uv run tui # 启动 TUI
|
||||
|
||||
# 如果决定使用原生 python 环境 (不推荐, 但我们保留了这种方式以便在不支持 uv 的环境运行 HeurAMS)
|
||||
|
||||
## 安装依赖并将 HeurAMS 安装为本地包
|
||||
python3 -m pip install -r requirements.txt
|
||||
python3 -m pip install -e .
|
||||
|
||||
python3 -m heurams # 验证安装
|
||||
python3 -m heurams.__interface__ # 启动 TUI
|
||||
```
|
||||
|
||||
## 许可证
|
||||
## 许可证与外部引用
|
||||
|
||||
贡献者同意其贡献将在 AGPL-3.0 许可证下发布.
|
||||
贡献者拥有其贡献部分的版权同意其贡献将在 AGPL-3.0 许可证下发布.
|
||||
|
||||
如果您认为有必要引入其他开源的 vendor, 请在 PR 中注明或手动联系以便我们审查 vendor 许可证并更改此处和网站上的关于与版权声明
|
||||
|
||||
如果您认为有必要引入其他专有的网络服务(就像现在项目中的 edgetts), 请也在 PR 中注明
|
||||
|
||||
如果您认为有必要升级某个依赖或运行环境的版本, 请也在 PR 中注明
|
||||
|
||||
## 新的用户界面前端与其他语言移植
|
||||
|
||||
HeurAMS 被设计为一个可独立于前端的程序库, 这意味着:
|
||||
|
||||
- 我们的内置 Textual TUI 前端不是唯一可用的前端
|
||||
|
||||
- 您可以在自己的项目中以独立进程/服务调用 HeurAMS (但不能在代码中链接), 而免于受 AGPL-3.0 "污染"
|
||||
|
||||
- 如果您有一个自己开发的且可用的 HeurAMS 前端 (例如我们暂未实现的 flutter 前端), 并且以 AGPL-3.0/GPL-3.0 开放源代码, 可以联系我们将它转移到 HeurAMS 的官方仓库中以便共同维护, 您将保留您的版权并可主导该仓库下的开发工作 :)
|
||||
|
||||
- 如果您通过独立进程/服务调用方式开发了另外的软件, 开源但不愿使用 AGPL-3.0/GPL-3.0 许可证, 也可以联系我们, 我们乐于将您的项目链接添加到友链中
|
||||
|
||||
- 如果您想创建程序库的其他语言 (例如 dart) 版本以协助此语言下的方便集成, 并且同样以 AGPL-3.0/GPL-3.0 开放源代码, 也可以联系我们将它转移到 HeurAMS 的官方仓库中以便共同维护, 您将保留您的版权并可主导该仓库下的开发工作 :)
|
||||
|
||||
## 软件开发之外的贡献
|
||||
|
||||
即使您不是软件开发人员, 我们也欢迎您加入贡献!
|
||||
|
||||
您可以:
|
||||
|
||||
- 协助创建各种语言的翻译来翻译软件的界面 (但我们目前还没有 i18n 平台, 所以如果您想贡献翻译, 可能需要手动联系我们)
|
||||
- 制作图像、主题、音效乃至制作开放的记忆单元集给其他用户使用
|
||||
- 改进软件配套的文档
|
||||
- 维护软件的开发/交流群组
|
||||
- 给其他用户答疑解惑或分享自己的经验
|
||||
- 在讨论区提出新想法或反馈问题
|
||||
|
||||
您的角色您来定!
|
||||
|
||||
@@ -2,7 +2,7 @@ zmq_debug = true
|
||||
_zmq_debug_desc = "[调试] ZeroMQ 调试服务器, 这会在 zmq_debug_port 上打开一个服务器\n调试工具可远程在 HeurAMS 内执行任意 python 代码, 无必要请关闭"
|
||||
zmq_debug_port = 5555
|
||||
_zmq_debug_port_desc = "[调试] ZeroMQ 调试服务器端口"
|
||||
enable_built_in_interface = false
|
||||
enable_built_in_interface = true
|
||||
_enable_built_in_interface_desc = "启用内置基本用户界面\n(当且仅当 HeurAMS 作为程序库时禁用, 以跳过用户界面逻辑)"
|
||||
_paths_desc = "用户数据路径定义"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_global_desc = "用户界面通用设置"
|
||||
_widgets_desc = "各组件设置"
|
||||
_screens_desc = "各界面设置"
|
||||
_widgets_desc = "组件设置"
|
||||
_screens_desc = "界面设置"
|
||||
_puzzles_desc = "谜题生成器设置"
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
show_header = true
|
||||
_show_header_desc = "展示界面顶部的标题栏\n如果您想节省这一行空间, 可以禁用它"
|
||||
clock_on_header = true
|
||||
_clock_on_header_desc = "在界面顶部的标题栏显示时间"
|
||||
change_window_title = true
|
||||
_change_window_title_desc = "更改终端模拟器窗口的标题\n如果禁用了 header, 则建议启用"
|
||||
persist_to_file = true
|
||||
_persist_to_file_desc = "[调试] 将记忆更改保存到文件"
|
||||
quick_pass = true
|
||||
@@ -5,13 +11,15 @@ _quick_pass_desc = "[调试] 启用快速应答功能(跳过测验)"
|
||||
auto_pass = false
|
||||
_auto_pass_desc = "[调试] 自动通过测试模式"
|
||||
scheduled_num = 420
|
||||
_scheduled_num_desc = "默认记忆单元数量(可被单元集设置覆盖)"
|
||||
_scheduled_num_desc = "默认记忆单元数量\n可被单元集设置覆盖"
|
||||
refresh_on_resume = true
|
||||
_refresh_on_resume_desc = "[调试] 每当 Screen 激活后都刷新状态"
|
||||
algorithm = "SM-2"
|
||||
_algorithm_desc = "默认记忆调度算法(可被单元集设置覆盖)"
|
||||
_algorithm_desc = "默认记忆调度算法\n可被单元集设置覆盖"
|
||||
|
||||
[_algorithm_candidate]
|
||||
NSP-0 = "筛选用非间隔重复调度器"
|
||||
none = "不设置默认调度器"
|
||||
SM-2 = "第二代 SuperMemo 简单间隔重复调度器"
|
||||
SM-15M = "第15代 SuperMemo 复杂间隔重复调度器 (不稳定且逆向工程)"
|
||||
SM-2 = "第二代 SuperMemo 简单间隔重复调度器\nWozniak 于 1987 年提出, Anki 的默认算法"
|
||||
SM-15M = "类第15代 SuperMemo 复杂间隔重复调度器\n不稳定且逆向工程"
|
||||
FSRS = "先进开放间隔重复调度器"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
schedule = ["quick_review", "recognition", "final_review"]
|
||||
|
||||
[phases]
|
||||
[routes]
|
||||
quick_review = [["FillBlank", "1.0"], ["Recognition", "1.0"]]
|
||||
recognition = [["FillBlank", "1.0"]]
|
||||
final_review = [["FillBlank", "1.0"], ["Recognition", "1.0"]]
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# encoding=utf-8
|
||||
import jieba
|
||||
|
||||
# jieba.enable_paddle()# 启动paddle模式。 0.40版之后开始支持,早期版本不支持
|
||||
strs = ["我来到北京清华大学", "乒乓球拍卖完了", "中国科学技术大学"]
|
||||
# for str in strs:
|
||||
# seg_list = jieba.cut(str,use_paddle=True) # 使用paddle模式
|
||||
# print("Paddle Mode: " + '/'.join(list(seg_list)))
|
||||
|
||||
seg_list = jieba.cut("秦孝公据崤函之固, 拥雍州之地", cut_all=False)
|
||||
print("Default Mode: " + "/ ".join(seg_list)) # 精确模式
|
||||
|
||||
seg_list = jieba.cut("他来到了网易杭研大厦") # 默认是精确模式
|
||||
print(", ".join(seg_list))
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1,3 +0,0 @@
|
||||
title = "测试单元: 过秦论"
|
||||
author = "__heurams__"
|
||||
desc = "高考古诗文: 过秦论"
|
||||
@@ -1,11 +0,0 @@
|
||||
["秦孝公据崤函之固, 拥雍州之地,"]
|
||||
note = []
|
||||
content = "秦孝公/据/崤函/之固/, 拥/雍州/之地,/"
|
||||
translation = "秦孝公占据着崤山和函谷关的险固地势,拥有雍州的土地,"
|
||||
keyword_note = {"据"="占据", "崤函"="崤山和函谷关", "雍州"="古代九州之一"}
|
||||
|
||||
["君臣固守以窥周室,"]
|
||||
note = []
|
||||
content = "君臣/固守/以窥/周室,/"
|
||||
translation = "君臣牢固地守卫着,借以窥视周王室的权力,"
|
||||
keyword_note = {"窥"="窥视"}
|
||||
@@ -1,5 +0,0 @@
|
||||
schedule = ["quick_review", "recognition", "final_review"]
|
||||
[phases]
|
||||
quick_review = [["FillBlank", "1.0"], ["SelectMeaning", "0.5"], ["Recognition", "1.0"]]
|
||||
recognition = [["Recognition", "1.0"]]
|
||||
final_review = [["FillBlank", "0.7"], ["SelectMeaning", "0.7"], ["Recognition", "1.0"]]
|
||||
@@ -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']"}
|
||||
15
glossary.md
15
glossary.md
@@ -1,15 +0,0 @@
|
||||
# 运行时对象
|
||||
|
||||
Atom: 原子, 由核子, 电子, 轨道对象一并构成, 用于处理记忆所需一系列对象
|
||||
Nucleon: 核子, 负责解析文件动态内容, 并储存记忆材料内容与谜题定义, 是静态只读但可临时覆盖内容的\
|
||||
Electron: 电子, 负责处理记忆算法数据\
|
||||
Orbital: 轨道, 储存记忆阶段信息与谜题阶段内出现配置
|
||||
|
||||
# 状态机对象
|
||||
|
||||
Transitions: 一种状态机框架库
|
||||
Reactor: 状态机库
|
||||
Phaser...
|
||||
|
||||
rating: 用户评估生成的值
|
||||
quality: 用于单元反馈的值
|
||||
@@ -25,6 +25,7 @@ from .screens.navigator import NavigatorScreen
|
||||
from .screens.precache import PrecachingScreen
|
||||
from .screens.setting import SettingScreen
|
||||
from .screens.synctool import SyncScreen
|
||||
from . import shim
|
||||
|
||||
_end = perf_counter()
|
||||
print(f"已完成! (耗时: {round(1000 * (_end - _start))}ms)")
|
||||
@@ -56,6 +57,9 @@ class HeurAMSApp(App):
|
||||
"setting": SettingScreen,
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.push_screen("dashboard")
|
||||
|
||||
|
||||
@@ -2,22 +2,6 @@ NavigatorScreen {
|
||||
align: center middle;
|
||||
}
|
||||
|
||||
.infview {
|
||||
width: 5fr
|
||||
}
|
||||
.dataview {
|
||||
width: 3fr
|
||||
}
|
||||
|
||||
.repo_listitem {
|
||||
layout: grid;
|
||||
grid-size: 2;
|
||||
}
|
||||
|
||||
.repo_listitem_btn {
|
||||
dock: right;
|
||||
offset: -5% 0
|
||||
}
|
||||
|
||||
#dialog {
|
||||
grid-size: 2;
|
||||
@@ -29,33 +13,3 @@ NavigatorScreen {
|
||||
border: thick $background 80%;
|
||||
background: $surface;
|
||||
}
|
||||
|
||||
#dashboardtop {
|
||||
height: 4
|
||||
}
|
||||
|
||||
#input-container {
|
||||
height: 3;
|
||||
margin-top: 1;
|
||||
align: center middle;
|
||||
}
|
||||
.container {
|
||||
height: auto;
|
||||
padding: 0 0 1 0;
|
||||
}
|
||||
#message-input {
|
||||
width: 1fr;
|
||||
margin-right: 1;
|
||||
}
|
||||
|
||||
#status-bar {
|
||||
height: 1;
|
||||
margin-top: 1;
|
||||
text-style: italic;
|
||||
color: $text-muted;
|
||||
}
|
||||
|
||||
.session-label {
|
||||
color: $primary;
|
||||
text-style: bold;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
}
|
||||
|
||||
#header {
|
||||
height: 4;
|
||||
height: 3;
|
||||
}
|
||||
|
||||
.repo-list-item {
|
||||
layout: grid;
|
||||
grid-size: 2;
|
||||
grid-size: 1;
|
||||
height: 3;
|
||||
}
|
||||
|
||||
|
||||
4
src/heurams/interface/css/screens/memoqueue.tcss
Normal file
4
src/heurams/interface/css/screens/memoqueue.tcss
Normal file
@@ -0,0 +1,4 @@
|
||||
#puzzle_container > * {
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
||||
24
src/heurams/interface/css/screens/preparation.tcss
Normal file
24
src/heurams/interface/css/screens/preparation.tcss
Normal file
@@ -0,0 +1,24 @@
|
||||
#operations {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin: 0 1 0 0;
|
||||
padding: 0 1 0 1;
|
||||
}
|
||||
|
||||
#main_container {
|
||||
|
||||
}
|
||||
|
||||
#previewer_container {
|
||||
|
||||
}
|
||||
|
||||
.unit-statline {
|
||||
|
||||
}
|
||||
|
||||
#title {
|
||||
padding: 0;
|
||||
}
|
||||
27
src/heurams/interface/css/screens/setting.tcss
Normal file
27
src/heurams/interface/css/screens/setting.tcss
Normal file
@@ -0,0 +1,27 @@
|
||||
.foot {
|
||||
align-vertical: bottom;
|
||||
}
|
||||
|
||||
Switch {
|
||||
dock: right;
|
||||
}
|
||||
|
||||
Label {
|
||||
width: auto
|
||||
}
|
||||
|
||||
Select {
|
||||
width: 55%;
|
||||
dock: right;
|
||||
}
|
||||
|
||||
Input {
|
||||
width: 55%;
|
||||
dock: right;
|
||||
}
|
||||
|
||||
Horizontal {
|
||||
width: 100%;
|
||||
height: 4;
|
||||
padding: 0 0 1 0;
|
||||
}
|
||||
@@ -5,6 +5,8 @@ from textual.containers import ScrollableContainer
|
||||
from textual.screen import Screen
|
||||
from textual.widgets import Button, Footer, Header, Label, Markdown, Static
|
||||
|
||||
from textual import events, on
|
||||
|
||||
import heurams.services.version as version
|
||||
from heurams.context import *
|
||||
import platform
|
||||
@@ -19,8 +21,19 @@ class AboutScreen(Screen):
|
||||
("q", "go_back", "返回"),
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@on(events.ScreenResume)
|
||||
def post_active(self, event):
|
||||
from heurams.interface import shim
|
||||
|
||||
shim.set_term_title(f"{self.app.TITLE} - {self.SUB_TITLE}")
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header(show_clock=True)
|
||||
|
||||
if config_var.get()['interface']['global']['show_header']:
|
||||
yield Header(show_clock=config_var.get()['interface']['global']['clock_on_header'])
|
||||
with ScrollableContainer(id="about_container"):
|
||||
yield Label("[b]关于与版本信息[/b]")
|
||||
|
||||
@@ -41,30 +54,27 @@ class AboutScreen(Screen):
|
||||
API 版本代号: `{version.codename.capitalize()}`
|
||||
|
||||
一个基于启发式算法与认知科学理论的辅助记忆调度器, 旨在帮助用户更高效地进行记忆工作与学习规划.
|
||||
一个开放, 优雅, 易于扩展的间隔重复调度器实验平台, 旨在帮助研究者更高效地进行前沿记忆算法的研究.
|
||||
|
||||
以 AGPL-3.0 开放源代码, 这直接意味着任何个体直接基于此代码对外或内部提供的应用和服务, 无论本地或网络, 必须向所有用户公开完整修改后的源代码, 且继续沿用 AGPL-3.0 协议.
|
||||
|
||||
您正使用的 TUI 用户界面是 python 版本程序库自带的基本用户界面, 作为基本的全功能前端实现与程序库测试, 如果您想去除它, 请移除程序库根目录中的 interface 文件夹.
|
||||
您正使用的 TUI 用户界面是 python 版本程序库自带的基本用户界面, 以作为基本的全功能前端实现与程序库测试, 位于程序库根目录中的 interface 文件夹.
|
||||
|
||||
您可在项目主页 https://ams.pluv27.top 获取用户指南, 开发文档与软件更新.
|
||||
|
||||
如果您觉得这个软件有用, 可以在它的源代码仓库给它添加一个星标 :)
|
||||
如果您觉得这个软件有用, 可以考虑参与贡献, 或在它的源代码仓库给它添加一个星标 :)
|
||||
|
||||
> 潜进(HeurAMS), 以及它作为一个"程序库"是自由且免费的, 但是开发工作必须投入大量精力.
|
||||
> 您可以加入各种语言的翻译团队来翻译软件的界面, 您还可以制作图像、主题、音效, 或者改进软件配套的文档……
|
||||
> 不管您来自何方, 我们都欢迎您加入社区并做出贡献.
|
||||
> 我们的共同目标是为人人带来高品质的辅助记忆 & 学习软件.
|
||||
> 您的慷慨支持, 我们必当涌泉相报.
|
||||
您的慷慨支持, 我们必当涌泉相报.
|
||||
|
||||
开发人员列表:
|
||||
|
||||
- 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): 文献参考
|
||||
- [Thoughts Memo](https://www.zhihu.com/people/L.M.Sherlock): 中文文献参考
|
||||
|
||||
|
||||
# 运行环境信息
|
||||
@@ -85,6 +95,7 @@ Textual 框架版本: {textual_version}
|
||||
"返回主界面",
|
||||
id="back_button",
|
||||
variant="primary",
|
||||
flat=True,
|
||||
classes="back-button",
|
||||
)
|
||||
yield Footer()
|
||||
|
||||
@@ -10,6 +10,8 @@ from textual.containers import ScrollableContainer, Container, Horizontal, Verti
|
||||
from textual.screen import Screen
|
||||
from textual.widgets import Button, Footer, Header, Label, ListItem, ListView, Static
|
||||
from textual.layouts import horizontal
|
||||
from textual import events, on
|
||||
from textual.reactive import reactive
|
||||
|
||||
import heurams.kernel.particles as pt
|
||||
import heurams.services.timer as timer
|
||||
@@ -35,7 +37,9 @@ class DashboardScreen(Screen):
|
||||
("q", "go_back", "返回"),
|
||||
]
|
||||
|
||||
CSS_PATH = Path(__file__).parent.parent / "css" / "screens" / "dashboard.tcss"
|
||||
CSS_PATH = rootdir / "interface" / "css" / "screens" / "dashboard.tcss"
|
||||
|
||||
repolink = reactive({})
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -44,22 +48,21 @@ class DashboardScreen(Screen):
|
||||
classes: str | None = None,
|
||||
) -> None:
|
||||
super().__init__(name, id, classes)
|
||||
self.repolink = {}
|
||||
self._load_data()
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""组合界面组件"""
|
||||
self._load_data()
|
||||
yield Header(show_clock=True)
|
||||
if config_var.get()['interface']['global']['show_header']:
|
||||
yield Header(show_clock=config_var.get()['interface']['global']['clock_on_header'])
|
||||
with ScrollableContainer():
|
||||
yield Horizontal( # 顶部的状态
|
||||
Vertical(
|
||||
Label(f'欢迎使用 "潜进" 版本 {version.ver}'),
|
||||
Label(f"当前 UNIX 日时间戳: {timer.get_daystamp()}"),
|
||||
Label(f"当前日时间戳: {timer.get_daystamp()}"),
|
||||
Label(
|
||||
f"应用时区修正: UTC+{config_var.get()['services']['timer']['timezone_offset'] / 3600}"
|
||||
f"应用时区修正: UTC+{str(config_var.get()['services']['timer']['timezone_offset'] / 3600).rstrip('.0')}"
|
||||
),
|
||||
Label(
|
||||
f"默认算法设置: {config_var.get()['interface']['global']['algorithm']}"
|
||||
f"默认算法设置: {config_var.get()['interface']['global']['algorithm']}",
|
||||
),
|
||||
classes="left",
|
||||
),
|
||||
@@ -79,11 +82,16 @@ class DashboardScreen(Screen):
|
||||
|
||||
yield ListView(id="repo_list", classes="repo-list") # 单元集选择
|
||||
|
||||
yield Label(
|
||||
f'"潜进" 启发式辅助记忆调度器 版本 {version.ver} {version.stage.capitalize()}'
|
||||
) # 版本信息
|
||||
yield Label(f"版本 {version.ver} {version.stage.capitalize()}") # 版本信息
|
||||
yield Footer()
|
||||
|
||||
@on(events.ScreenResume)
|
||||
def post_active(self, event):
|
||||
from heurams.interface import shim
|
||||
shim.set_term_title(f"{self.app.TITLE} - {self.SUB_TITLE}")
|
||||
# https://github.com/Textualize/textual/discussions/4268
|
||||
# self.refresh(recompose=True) 此函数有问题且官方不管 而且性能低
|
||||
|
||||
def _load_data(self):
|
||||
repo_dirs = Repo.probe_valid_repos_in_dir(
|
||||
Path(config_var.get()["global"]["paths"]["repo"])
|
||||
@@ -96,7 +104,6 @@ class DashboardScreen(Screen):
|
||||
# need_review: 需要/不需要学习
|
||||
# nearest_review_time: 最近下次学习时间
|
||||
# progress: 进度
|
||||
# algotype: 算法类型
|
||||
## initial_time: 起始时间
|
||||
# package: 包名
|
||||
# prompt: 最终呈现信息
|
||||
@@ -107,6 +114,10 @@ class DashboardScreen(Screen):
|
||||
"touched": 0,
|
||||
"have_activated_ever": 0,
|
||||
}
|
||||
repo.preview = {
|
||||
"review": 0,
|
||||
"new": repo.config["scheduled_num"],
|
||||
}
|
||||
initial_time = float("inf")
|
||||
for i in range(repo.data_length):
|
||||
e = pt.Electron.from_data(
|
||||
@@ -118,11 +129,13 @@ class DashboardScreen(Screen):
|
||||
repo.progress["have_activated_ever"] = 1
|
||||
repo.progress["touched"] += 1
|
||||
repo.nearest_review_time = min(repo.nearest_review_time, e.nextdate())
|
||||
if timer.get_daystamp() >= e.nextdate():
|
||||
repo.preview["review"] += 1
|
||||
# initial_time = min(initial_time, e.)
|
||||
repo.need_review = timer.get_daystamp() >= repo.nearest_review_time
|
||||
repo.prompt = f"""{repo.manifest['title']} ({repo.config['algorithm']})
|
||||
repo.prompt = f"""{repo.manifest['title']} \\[{repo.config['algorithm']}]
|
||||
[d]进度: {repo.progress['touched']}/{repo.progress['total']} ({round(repo.progress['touched']/repo.progress['total']*100, 1)}%)[/d]
|
||||
[d]{'需要复习' if repo.need_review else ("暂未开始" if not repo.progress['have_activated_ever'] else '无需操作')}[/d]"""
|
||||
[d]{f'需要学习: {repo.preview['review']}R + {repo.preview['new']}U' if repo.need_review else (f"暂未开始: 0R + {repo.preview['new']}U" if not repo.progress['have_activated_ever'] else '无需操作')}[/d]"""
|
||||
|
||||
def on_mount(self) -> None:
|
||||
"""挂载组件时初始化"""
|
||||
@@ -152,7 +165,7 @@ class DashboardScreen(Screen):
|
||||
for r in self.repos:
|
||||
self.repolink[str(id(r))] = r # 用于规避 ctype id 对象还原
|
||||
list_item = ListItem(
|
||||
Label(r.prompt),
|
||||
*[Label(line) for line in r.prompt.splitlines()],
|
||||
Button(
|
||||
f"开始学习",
|
||||
flat=True,
|
||||
|
||||
@@ -4,6 +4,8 @@ import base64
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
from textual import events, on
|
||||
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import ScrollableContainer
|
||||
from textual.screen import Screen
|
||||
@@ -18,6 +20,7 @@ from textual.widgets import (
|
||||
Static,
|
||||
)
|
||||
|
||||
from textual import events, on
|
||||
from heurams.context import config_var
|
||||
from heurams.kernel.repolib import Repo
|
||||
from heurams.services.favorite_service import FavoriteItem, favorite_manager
|
||||
@@ -53,7 +56,9 @@ class FavoriteManagerScreen(Screen):
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""组合界面组件"""
|
||||
yield Header(show_clock=True)
|
||||
|
||||
if config_var.get()['interface']['global']['show_header']:
|
||||
yield Header(show_clock=config_var.get()['interface']['global']['clock_on_header'])
|
||||
with ScrollableContainer(id="favorites-container"):
|
||||
if not self.favorites:
|
||||
yield Label("暂无收藏", classes="empty-label")
|
||||
@@ -63,6 +68,12 @@ class FavoriteManagerScreen(Screen):
|
||||
yield ListView(id="favorites-list")
|
||||
yield Footer()
|
||||
|
||||
@on(events.ScreenResume)
|
||||
def post_active(self, event):
|
||||
from heurams.interface import shim
|
||||
|
||||
shim.set_term_title(f"{self.app.TITLE} - {self.SUB_TITLE}")
|
||||
|
||||
def on_mount(self) -> None:
|
||||
"""挂载后填充列表"""
|
||||
if self.favorites:
|
||||
|
||||
@@ -5,26 +5,24 @@ from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import Center, ScrollableContainer
|
||||
from textual.containers import Center, ScrollableContainer, Container
|
||||
from textual.reactive import reactive
|
||||
from textual.screen import Screen
|
||||
from textual.widgets import Button, Footer, Header, Label, Static
|
||||
|
||||
from textual import events, on
|
||||
|
||||
import heurams.kernel.particles as pt
|
||||
import heurams.kernel.puzzles as pz
|
||||
from heurams.context import config_var
|
||||
from heurams.context import config_var, rootdir
|
||||
from heurams.kernel.reactor import *
|
||||
from heurams.services.favorite_service import favorite_manager
|
||||
from heurams.services.logger import get_logger
|
||||
|
||||
import pickle
|
||||
|
||||
from .. import shim
|
||||
|
||||
|
||||
class AtomState(Enum):
|
||||
FAILED = auto()
|
||||
NORMAL = auto()
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@@ -35,50 +33,61 @@ class MemScreen(Screen):
|
||||
("d", "toggle_dark", ""),
|
||||
("v", "play_voice", "朗读"),
|
||||
("*", "toggle_favorite", "收藏"),
|
||||
("0,1,2,3", "app.push_screen('about')", ""),
|
||||
("n", "block_prompt"),
|
||||
("s", "block_prompt"),
|
||||
("z", "block_prompt"),
|
||||
]
|
||||
|
||||
SUB_TITLE = "学习中"
|
||||
CSS_PATH = rootdir / "interface" / "css" / "screens" / "memoqueue.tcss"
|
||||
|
||||
if config_var.get()["interface"]["global"]["quick_pass"]:
|
||||
BINDINGS.append(("k", "quick_pass", "正确应答"))
|
||||
BINDINGS.append(("f", "quick_fail", "错误应答"))
|
||||
|
||||
rating = reactive(-1)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
phaser: Phaser,
|
||||
save_func: Callable,
|
||||
router: Router,
|
||||
repo=None,
|
||||
name=None,
|
||||
id=None,
|
||||
classes=None,
|
||||
) -> None:
|
||||
super().__init__(name, id, classes)
|
||||
self.phaser = phaser
|
||||
self.save_func = save_func
|
||||
self.router = router
|
||||
self.repo = repo
|
||||
self.update_state()
|
||||
self.fission: Fission
|
||||
self.expander: Expander
|
||||
|
||||
@on(events.ScreenResume)
|
||||
def post_active(self, event):
|
||||
from heurams.interface import shim
|
||||
|
||||
shim.set_term_title(f"{self.app.TITLE} - {self.SUB_TITLE}")
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header(show_clock=True)
|
||||
if config_var.get()['interface']['global']['show_header']:
|
||||
yield Header(show_clock=config_var.get()['interface']['global']['clock_on_header'])
|
||||
with ScrollableContainer():
|
||||
yield Label(self._get_progress_text(), id="progress")
|
||||
yield ScrollableContainer(id="puzzle-container")
|
||||
yield Label(self._get_progress_text(), id="head_stat")
|
||||
yield ScrollableContainer(id="puzzle_container")
|
||||
yield Footer()
|
||||
|
||||
def update_state(self):
|
||||
"""更新状态机"""
|
||||
self.procession: Procession = self.phaser.current_procession() # type: ignore
|
||||
self.procession: Procession = self.router.current_procession() # type: ignore
|
||||
self.atom: pt.Atom = self.procession.current_atom # type: ignore
|
||||
|
||||
def on_mount(self):
|
||||
self.fission = self.procession.get_fission()
|
||||
self.expander = self.procession.get_expander()
|
||||
self.mount_puzzle()
|
||||
self.update_display()
|
||||
|
||||
def puzzle_widget(self):
|
||||
try:
|
||||
puzzle = self.fission.get_current_puzzle_inf()
|
||||
puzzle = self.expander.get_current_puzzle_inf()
|
||||
return shim.puzzle2widget[puzzle["puzzle"]]( # type: ignore
|
||||
atom=self.atom, alia=puzzle["alia"] # type: ignore
|
||||
)
|
||||
@@ -87,53 +96,32 @@ class MemScreen(Screen):
|
||||
return Static(f"无法生成谜题 {e}")
|
||||
|
||||
def _get_progress_text(self):
|
||||
s = f"阶段: {self.procession.phase.name}\n"
|
||||
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}\n"
|
||||
"""if config_var.get().get("debug_topline", 0):
|
||||
try:
|
||||
alia = self.fission.get_current_puzzle_inf()["alia"] # type: ignore
|
||||
s += f"谜题: {alia}\n"
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
stat = self.phaser.__repr__("simple", "")
|
||||
s += f"{stat}\n"
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
stat = self.procession.__repr__("simple", "")
|
||||
s += f"{stat}\n"
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
stat = self.fission.__repr__("simple", "")
|
||||
s += f"{stat}\n"
|
||||
except Exception as e:
|
||||
s = str(e)"""
|
||||
s += f"进度: {self.procession.process() + 1}/{self.procession.total_length()}"
|
||||
return s
|
||||
|
||||
def update_display(self):
|
||||
"""更新进度显示"""
|
||||
progress_widget = self.query_one("#progress")
|
||||
progress_widget = self.query_one("#head_stat")
|
||||
progress_widget.update(self._get_progress_text()) # type: ignore
|
||||
|
||||
def mount_puzzle(self):
|
||||
"""挂载当前谜题组件"""
|
||||
if self.procession.phase == PhaserState.FINISHED:
|
||||
if self.procession.route == RouterState.FINISHED:
|
||||
self.mount_finished_widget()
|
||||
return
|
||||
container = self.query_one("#puzzle-container")
|
||||
container = self.query_one("#puzzle_container")
|
||||
for i in container.children:
|
||||
i.remove()
|
||||
container.mount(self.puzzle_widget())
|
||||
|
||||
def mount_finished_widget(self):
|
||||
"""挂载已完成组件"""
|
||||
container = self.query_one("#puzzle-container")
|
||||
container = self.query_one("#puzzle_container")
|
||||
for i in container.children:
|
||||
i.remove()
|
||||
from heurams.interface.widgets.finished import Finished
|
||||
@@ -169,10 +157,10 @@ class MemScreen(Screen):
|
||||
if new_rating == -1: # 安全值
|
||||
return
|
||||
self.update_state()
|
||||
if self.procession.phase == PhaserState.FINISHED:
|
||||
if self.procession.route == RouterState.FINISHED:
|
||||
rating = -1
|
||||
return
|
||||
self.fission.report(new_rating)
|
||||
self.expander.report(new_rating)
|
||||
self.forward(new_rating)
|
||||
self.rating = -1
|
||||
|
||||
@@ -180,9 +168,9 @@ class MemScreen(Screen):
|
||||
self.update_state()
|
||||
allow_forward = 1 if rating >= 4 else 0
|
||||
if allow_forward:
|
||||
self.fission.forward()
|
||||
if self.fission.state == "retronly":
|
||||
self.forward_atom(self.fission.get_quality())
|
||||
self.expander.forward()
|
||||
if self.expander.state == "retronly":
|
||||
self.forward_atom(self.expander.get_quality())
|
||||
self.update_state()
|
||||
self.mount_puzzle()
|
||||
self.update_display()
|
||||
@@ -207,7 +195,7 @@ class MemScreen(Screen):
|
||||
self.update_state() # 刷新状态
|
||||
self.procession.forward(1)
|
||||
self.update_state() # 刷新状态
|
||||
self.fission = self.procession.get_fission()
|
||||
self.expander = self.procession.get_expander()
|
||||
|
||||
def action_go_back(self):
|
||||
self.app.pop_screen()
|
||||
@@ -254,3 +242,6 @@ class MemScreen(Screen):
|
||||
self.app.notify(f"已收藏:{ident}", severity="information")
|
||||
# 更新显示(如果需要)
|
||||
self.update_display()
|
||||
|
||||
def action_block_prompt(self):
|
||||
self.app.notify("功能在记忆界面中不可用, 完成或返回后再试", severity="error")
|
||||
|
||||
@@ -5,6 +5,8 @@ from textual.containers import Grid, ScrollableContainer
|
||||
from textual.screen import ModalScreen
|
||||
from textual.widgets import Button, Footer, Header, Label, ListItem, ListView, Static
|
||||
|
||||
from textual import events, on
|
||||
|
||||
from heurams.context import *
|
||||
from heurams.services.logger import get_logger
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ from textual.screen import Screen
|
||||
from textual.widgets import Button, Footer, Header, Label, ProgressBar, Static
|
||||
from textual.worker import get_current_worker
|
||||
|
||||
from textual import events, on
|
||||
|
||||
import heurams.kernel.particles as pt
|
||||
import heurams.services.hasher as hasher
|
||||
from heurams.context import *
|
||||
@@ -17,7 +19,7 @@ paths = config_var.get()["global"]["paths"]
|
||||
cache_dir = pathlib.Path(paths.get("cache", paths["data"] + "/cache")) / "voice"
|
||||
|
||||
|
||||
def format_size(bytes_num: int) -> str:
|
||||
def human_size(bytes_num: int) -> str:
|
||||
"""将字节数格式化为人类可读的字符串"""
|
||||
for unit in ["B", "KB", "MB", "GB", "TB"]:
|
||||
if bytes_num < 1024.0:
|
||||
@@ -80,6 +82,12 @@ class PrecachingScreen(Screen):
|
||||
continue
|
||||
return total
|
||||
|
||||
@on(events.ScreenResume)
|
||||
def post_active(self, event):
|
||||
from heurams.interface import shim
|
||||
|
||||
shim.set_term_title(f"{self.app.TITLE} - {self.SUB_TITLE}")
|
||||
|
||||
def _update_cache_stats(self) -> None:
|
||||
"""更新缓存统计信息"""
|
||||
total_size = 0
|
||||
@@ -97,13 +105,15 @@ class PrecachingScreen(Screen):
|
||||
|
||||
self.cache_stats["total_size"] = total_size
|
||||
self.cache_stats["file_count"] = file_count
|
||||
self.cache_stats["human_size"] = format_size(total_size)
|
||||
self.cache_stats["human_size"] = human_size(total_size)
|
||||
self.cache_stats["cached_units"] = cached_units
|
||||
self.cache_stats["total_units"] = total_units
|
||||
self.cache_stats["cache_rate"] = cache_rate
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header(show_clock=True)
|
||||
|
||||
if config_var.get()['interface']['global']['show_header']:
|
||||
yield Header(show_clock=config_var.get()['interface']['global']['clock_on_header'])
|
||||
with ScrollableContainer(id="precache_container"):
|
||||
yield Label("[b]音频预缓存[/b]", classes="title-label")
|
||||
with Container():
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""记忆准备界面"""
|
||||
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import ScrollableContainer
|
||||
from textual.containers import ScrollableContainer, Container, Horizontal
|
||||
from textual.reactive import reactive
|
||||
from textual.screen import Screen
|
||||
from textual.widget import Widget
|
||||
@@ -15,12 +15,16 @@ from textual.widgets import (
|
||||
Rule,
|
||||
Sparkline,
|
||||
)
|
||||
from textual.lazy import Reveal, Lazy
|
||||
|
||||
from textual import events, on
|
||||
|
||||
import heurams.kernel.particles as pt
|
||||
import heurams.services.hasher as hasher
|
||||
from heurams.context import *
|
||||
from heurams.context import config_var
|
||||
from heurams.kernel.repolib import *
|
||||
from heurams.kernel.algorithms import algorithms
|
||||
from heurams.services.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
@@ -32,46 +36,66 @@ class PreparationScreen(Screen):
|
||||
|
||||
BINDINGS = [
|
||||
("q", "go_back", "返回"),
|
||||
("p", "precache", "预缓存音频"),
|
||||
("p", "precache", "缓存"),
|
||||
("d", "toggle_dark", ""),
|
||||
("0,1,2,3", "app.push_screen('about')", ""),
|
||||
]
|
||||
|
||||
scheduled_num = reactive(config_var.get()["interface"]["global"]["scheduled_num"])
|
||||
CSS_PATH = rootdir / "interface" / "css" / "screens" / "preparation.tcss"
|
||||
|
||||
def __init__(self, repo: Repo) -> None:
|
||||
super().__init__(name=None, id=None, classes=None)
|
||||
self.repo = repo
|
||||
self.load_data()
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header(show_clock=True)
|
||||
with ScrollableContainer(id="vice_container"):
|
||||
yield Label(f"准备就绪: [b]{self.repo.manifest['title']}[/b]\n")
|
||||
yield Label(f"[b]仓库路径: {self.repo.source}[/b]")
|
||||
yield Label(f"\n单元数量: {len(self.repo)}\n")
|
||||
yield Label(f"最小记忆分组: {self.scheduled_num}\n", id="schnum_label")
|
||||
@on(events.ScreenResume)
|
||||
def post_active(self, event):
|
||||
from heurams.interface import shim
|
||||
|
||||
yield Button(
|
||||
shim.set_term_title(f"{self.app.TITLE} - {self.SUB_TITLE}")
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
|
||||
if config_var.get()['interface']['global']['show_header']:
|
||||
yield Header(show_clock=config_var.get()['interface']['global']['clock_on_header'])
|
||||
with ScrollableContainer(id="main_container"):
|
||||
yield Markdown(
|
||||
f"**准备就绪**: `{self.repo.manifest['title']}`\n", id="title"
|
||||
)
|
||||
yield Label(f"单元集路径: {self.repo.source}")
|
||||
yield Label(
|
||||
f"学习完成度: {self.repo.progress['touched']}/{len(self.repo)} [d]\\[{round(self.repo.progress['touched']/self.repo.progress['total']*100, 1)}%][/d]"
|
||||
)
|
||||
yield Label(
|
||||
f"调度算法: {self.repo.config["algorithm"]} {algorithms[self.repo.config["algorithm"]].desc}"
|
||||
)
|
||||
yield Label(
|
||||
f"学习数量: {self.repo.preview['review'] + self.scheduled_num} = {self.repo.preview['review']} [d][复习][/d] + {self.scheduled_num} [d][新识记][/d]\n",
|
||||
id="schnum_label",
|
||||
)
|
||||
|
||||
yield Horizontal(
|
||||
Button(
|
||||
"开始记忆",
|
||||
id="start_memorizing_button",
|
||||
variant="primary",
|
||||
classes="start-button",
|
||||
)
|
||||
yield Button(
|
||||
"预缓存音频",
|
||||
classes="btn",
|
||||
),
|
||||
Button(
|
||||
"管理缓存",
|
||||
id="precache_button",
|
||||
variant="success",
|
||||
classes="precache-button",
|
||||
classes="btn",
|
||||
),
|
||||
id="operations",
|
||||
)
|
||||
|
||||
yield Static()
|
||||
yield Sparkline(self.spark_line_arr, summary_function=max)
|
||||
yield Rule()
|
||||
# yield Static(str(self.spark_line_arr))
|
||||
yield Static(f"单元状态预览:\n")
|
||||
with Reveal(ScrollableContainer(id="previewer_container")):
|
||||
for i in self.content.splitlines():
|
||||
yield Static(i, classes="full")
|
||||
yield Static(i, classes="unit-statline")
|
||||
yield Footer()
|
||||
|
||||
# def watch_scheduled_num(self, old_scheduled_num, new_scheduled_num):
|
||||
@@ -83,6 +107,7 @@ class PreparationScreen(Screen):
|
||||
# pass
|
||||
|
||||
def load_data(self):
|
||||
self.scheduled_num = self.repo.config["scheduled_num"]
|
||||
content = ""
|
||||
spark_line_arr = []
|
||||
for i in self.repo.ident_index:
|
||||
@@ -166,7 +191,6 @@ def launch(repo, app, scheduled_num):
|
||||
|
||||
from .memoqueue import MemScreen
|
||||
|
||||
pheser = rt.Phaser(atoms_to_provide)
|
||||
save_func = repo.persist_to_repodir
|
||||
memscreen = MemScreen(pheser, save_func, repo=repo)
|
||||
router = rt.Router(atoms_to_provide)
|
||||
memscreen = MemScreen(router=router, repo=repo)
|
||||
app.push_screen(memscreen)
|
||||
|
||||
@@ -23,6 +23,8 @@ from textual.widgets import (
|
||||
)
|
||||
from textual.layouts import horizontal
|
||||
|
||||
from textual import events, on
|
||||
|
||||
import heurams.kernel.particles as pt
|
||||
import heurams.services.timer as timer
|
||||
import heurams.services.version as version
|
||||
@@ -44,6 +46,7 @@ class SettingScreen(Screen):
|
||||
BINDINGS = [
|
||||
("q", "go_back", "返回"),
|
||||
]
|
||||
CSS_PATH = rootdir / "interface" / "css" / "screens" / "setting.tcss"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -53,9 +56,16 @@ class SettingScreen(Screen):
|
||||
) -> None:
|
||||
super().__init__(name, id, classes)
|
||||
|
||||
@on(events.ScreenResume)
|
||||
def post_active(self, event):
|
||||
from heurams.interface import shim
|
||||
|
||||
shim.set_term_title(f"{self.app.TITLE} - {self.SUB_TITLE}")
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""组合界面组件"""
|
||||
yield Header(show_clock=True)
|
||||
if config_var.get()['interface']['global']['show_header']:
|
||||
yield Header(show_clock=config_var.get()['interface']['global']['clock_on_header'])
|
||||
with ScrollableContainer():
|
||||
yield Label("[b]设置页面[/b]")
|
||||
for i in config_var.get():
|
||||
@@ -64,7 +74,11 @@ class SettingScreen(Screen):
|
||||
a = self._get_subcfg(f"{i}")
|
||||
if a:
|
||||
yield Collapsible(
|
||||
*a, title=i + f'\n{config_var.get().get(f"_{i}_desc", "")}'
|
||||
*a, title=i + f'\n[d]{config_var.get().get(f"_{i}_desc", "")}[/d]'
|
||||
)
|
||||
yield Label(
|
||||
"退出页面时, 所作的更改会立即保存, 但仍建议重启软件以确保新的配置得到应用",
|
||||
classes="foot",
|
||||
)
|
||||
yield Footer()
|
||||
|
||||
@@ -80,7 +94,7 @@ class SettingScreen(Screen):
|
||||
if a:
|
||||
lst.append(
|
||||
Collapsible(
|
||||
*a, title=i + f'\n{parent.get(f"_{i}_desc", "")}'
|
||||
*a, title=i + f'\n[d]{parent.get(f"_{i}_desc", "")}[/d]'
|
||||
)
|
||||
)
|
||||
return lst
|
||||
@@ -96,85 +110,79 @@ class SettingScreen(Screen):
|
||||
if a:
|
||||
lst.append(
|
||||
Collapsible(
|
||||
*a, title=i + f'\n{parent.get(f"_{i}_desc", "")}'
|
||||
*a, title=i + f'\n[d]{parent.get(f"_{i}_desc", "")}[/d]'
|
||||
)
|
||||
)
|
||||
elif f"_{i}_candidate" in parent: # 选择框模式
|
||||
if isinstance(parent[f"_{i}_candidate"], dict):
|
||||
lst.append(
|
||||
Horizontal(
|
||||
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
|
||||
Label(i + f'\n[d]{parent.get(f"_{i}_desc", "")}[/d]'),
|
||||
Select(
|
||||
(
|
||||
(f"{j} ({k})", j)
|
||||
(f"{j}\n[d]{k}[/d]", j)
|
||||
for j, k in parent[f"_{i}_candidate"].items()
|
||||
),
|
||||
prompt=f'{parent.get(f"{i}", "")}',
|
||||
id=domize(f"{parent_epath}.{i}"),
|
||||
),
|
||||
classes="container",
|
||||
)
|
||||
)
|
||||
elif isinstance(parent[f"_{i}_candidate"], list):
|
||||
lst.append(
|
||||
Horizontal(
|
||||
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
|
||||
Label(i + f'\n[d]{parent.get(f"_{i}_desc", "")}[/d]'),
|
||||
Select(
|
||||
((j, j) for j in parent[f"_{i}_candidate"]),
|
||||
prompt=f'{parent.get(f"{i}", "")}',
|
||||
id=domize(f"{parent_epath}.{i}"),
|
||||
),
|
||||
classes="container",
|
||||
)
|
||||
)
|
||||
else:
|
||||
if isinstance(parent[i], float):
|
||||
lst.append(
|
||||
Horizontal(
|
||||
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
|
||||
Label(i + f'\n[d]{parent.get(f"_{i}_desc", "")}[/d]'),
|
||||
Input(
|
||||
value=str(parent[i]),
|
||||
placeholder="要求一个浮点数",
|
||||
type="number",
|
||||
id=domize(f"{parent_epath}.{i}"),
|
||||
),
|
||||
classes="container",
|
||||
)
|
||||
)
|
||||
elif isinstance(parent[i], str):
|
||||
lst.append(
|
||||
Horizontal(
|
||||
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
|
||||
Label(i + f'\n[d]{parent.get(f"_{i}_desc", "")}[/d]'),
|
||||
Input(
|
||||
value=parent[i],
|
||||
placeholder="要求一个字符串",
|
||||
type="text",
|
||||
id=domize(f"{parent_epath}.{i}"),
|
||||
),
|
||||
classes="container",
|
||||
)
|
||||
)
|
||||
elif isinstance(parent[i], bool):
|
||||
lst.append(
|
||||
Horizontal(
|
||||
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
|
||||
Label(i + f'\n[d]{parent.get(f"_{i}_desc", "")}[/d]'),
|
||||
Switch(
|
||||
value=parent[i], id=domize(f"{parent_epath}.{i}")
|
||||
),
|
||||
classes="container",
|
||||
)
|
||||
)
|
||||
elif isinstance(parent[i], int):
|
||||
lst.append(
|
||||
Horizontal(
|
||||
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
|
||||
Label(i + f'\n[d]{parent.get(f"_{i}_desc", "")}[/d]'),
|
||||
Input(
|
||||
value=str(parent[i]),
|
||||
placeholder="要求一个整数",
|
||||
type="integer",
|
||||
id=domize(f"{parent_epath}.{i}"),
|
||||
),
|
||||
classes="container",
|
||||
)
|
||||
)
|
||||
elif isinstance(parent[i], list):
|
||||
|
||||
@@ -9,6 +9,8 @@ from textual.screen import Screen
|
||||
from textual.widgets import Button, Footer, Header, Label, ProgressBar, Static
|
||||
from textual.worker import get_current_worker
|
||||
|
||||
from textual import events, on
|
||||
|
||||
import heurams.kernel.particles as pt
|
||||
import heurams.services.hasher as hasher
|
||||
from heurams.context import *
|
||||
@@ -26,8 +28,16 @@ class SyncScreen(Screen):
|
||||
self.log_messages = []
|
||||
self.max_log_lines = 50
|
||||
|
||||
@on(events.ScreenResume)
|
||||
def post_active(self, event):
|
||||
from heurams.interface import shim
|
||||
|
||||
shim.set_term_title(f"{self.app.TITLE} - {self.SUB_TITLE}")
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header(show_clock=True)
|
||||
|
||||
if config_var.get()['interface']['global']['show_header']:
|
||||
yield Header(show_clock=config_var.get()['interface']['global']['clock_on_header'])
|
||||
with ScrollableContainer(id="sync_container"):
|
||||
# 标题和连接状态
|
||||
yield Static("同步工具", classes="title")
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
import heurams.interface.widgets as pzw
|
||||
import heurams.kernel.puzzles as pz
|
||||
import platform, os, sys
|
||||
from heurams.context import config_var
|
||||
|
||||
puzzle2widget = {
|
||||
pz.RecognitionPuzzle: pzw.Recognition,
|
||||
@@ -9,3 +11,13 @@ puzzle2widget = {
|
||||
pz.MCQPuzzle: pzw.MCQPuzzle,
|
||||
pz.BasePuzzle: pzw.BasePuzzleWidget,
|
||||
}
|
||||
|
||||
|
||||
def set_term_title(title):
|
||||
if not config_var.get()['interface']['global']['change_window_title']:
|
||||
return
|
||||
system = platform.system()
|
||||
if system == "Windows":
|
||||
os.system(f"title {title}")
|
||||
else: # Linux, Mac, etc.
|
||||
os.write(2, f"\033]2;{title}\007".encode("utf-8"))
|
||||
|
||||
@@ -2,7 +2,7 @@ import copy
|
||||
import random
|
||||
from typing import TypedDict
|
||||
|
||||
from textual.containers import Container
|
||||
from textual.containers import Container, ScrollableContainer
|
||||
from textual.message import Message
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Button, Label, Markdown
|
||||
@@ -68,7 +68,7 @@ class ClozePuzzle(BasePuzzleWidget):
|
||||
yield Label(self.puzzle.wording, id="sentence")
|
||||
yield Markdown(f"> {self.listprint(self.inputlist)}", id="inputpreview")
|
||||
# 渲染当前问题的选项
|
||||
with Container(id="btn-container"):
|
||||
with ScrollableContainer(id="btn-container"):
|
||||
for i in self.ans:
|
||||
h = str(hash(i))
|
||||
self.hashmap[h] = i
|
||||
|
||||
@@ -28,7 +28,7 @@ class Finished(Widget):
|
||||
def compose(self):
|
||||
yield Label("本次记忆进程结束", id="finished_msg")
|
||||
yield Label(f"算法数据{'已保存' if self.is_saved else "未能保存"}")
|
||||
yield Button("返回上一级", id="back-to-menu")
|
||||
yield Button("返回上一级", flat=True, id="back-to-menu")
|
||||
|
||||
def on_button_pressed(self, event):
|
||||
button_id = event.button.id
|
||||
|
||||
@@ -75,7 +75,7 @@ class MCQPuzzle(BasePuzzleWidget):
|
||||
yield Label(f"当前输入: {self.inputlist}", id="inputpreview")
|
||||
|
||||
# 渲染当前问题的选项
|
||||
with Container(id="btn-container"):
|
||||
with ScrollableContainer(id="btn-container"):
|
||||
for i in current_options:
|
||||
self.hashmap[str(hash(i))] = i
|
||||
btnid = f"sel{str(self.cursor).zfill(3)}-{hash(i)}"
|
||||
|
||||
@@ -1271,7 +1271,7 @@ class SM:
|
||||
self.ofm = OFM(self)
|
||||
|
||||
def _find_index_to_insert(self, item, r=None):
|
||||
"""Binary search to find insertion index for sorted queue."""
|
||||
"""Binary search to find insertion index for sorted procession."""
|
||||
if r is None:
|
||||
r = list(range(len(self.q)))
|
||||
|
||||
@@ -1290,7 +1290,7 @@ class SM:
|
||||
return self._find_index_to_insert(item, r[i:])
|
||||
|
||||
def add_item(self, value):
|
||||
"""Add a new item to the queue."""
|
||||
"""Add a new item to the procession."""
|
||||
item = Item(self, value)
|
||||
index = self._find_index_to_insert(item)
|
||||
self.q.insert(index, item)
|
||||
@@ -1330,7 +1330,7 @@ class SM:
|
||||
item.answer(grade, now)
|
||||
|
||||
def discard(self, item):
|
||||
"""Remove item from queue."""
|
||||
"""Remove item from procession."""
|
||||
if item in self.q:
|
||||
self.q.remove(item)
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from .evalizor import Evalizer
|
||||
from .lict import Lict
|
||||
from .refvar import RefVar
|
||||
|
||||
__all__ = ["Evalizer", "Lict", "RefVar"]
|
||||
__all__ = ["Evalizer", "Lict"]
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
# 已弃用
|
||||
from collections import UserList
|
||||
from typing import Any, Iterator
|
||||
|
||||
|
||||
class Lict(UserList): # TODO: 优化同步(惰性同步), 当前性能为 O(n)
|
||||
""" "列典" 对象
|
||||
|
||||
同时兼容字典和列表大多数 API, 两边数据同步的容器
|
||||
列表数据是 dictobj.items() 的格式
|
||||
支持根据字典或列表初始化
|
||||
限制要求:
|
||||
- 键名一定唯一, 且仅能为字符串
|
||||
- 值一定是引用对象
|
||||
- 不使用并发
|
||||
- 不在乎列表顺序语义(严格按键名字符序排列)和列表索引查找, 因此外部的 sort, index 等功能不可用
|
||||
- append 的元组中, 表示键名的元素不能重复, 否则会导致覆盖行为
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
initlist: list | None = None,
|
||||
initdict: dict | None = None,
|
||||
):
|
||||
self.dicted_data = {}
|
||||
if initdict != None:
|
||||
initlist = list(initdict.items())
|
||||
super().__init__(initlist=initlist)
|
||||
self._sync_based_on_list()
|
||||
self.data.sort()
|
||||
|
||||
def _sync_based_on_dict(self):
|
||||
self.data = list(self.dicted_data.items())
|
||||
self.data.sort()
|
||||
|
||||
def _sync_based_on_list(self):
|
||||
self.dicted_data = {}
|
||||
for i in self.data:
|
||||
self.dicted_data[i[0]] = i[1]
|
||||
|
||||
def __iter__(self) -> Iterator:
|
||||
return self.data.__iter__()
|
||||
|
||||
def __getitem__(self, i):
|
||||
if isinstance(i, str):
|
||||
return self.dicted_data[i]
|
||||
else:
|
||||
return super().__getitem__(i)
|
||||
|
||||
def get_itemic_unit(self, ident):
|
||||
return (ident, self.dicted_data[ident])
|
||||
|
||||
def __setitem__(self, i, item):
|
||||
if isinstance(i, str):
|
||||
self.dicted_data[i] = item
|
||||
self._sync_based_on_dict()
|
||||
else:
|
||||
if item != (item[0], item[1]):
|
||||
raise NotImplementedError
|
||||
super().__setitem__(i, item)
|
||||
self._sync_based_on_list()
|
||||
|
||||
def __delitem__(self, i):
|
||||
if isinstance(i, str):
|
||||
del self.dicted_data[i]
|
||||
self._sync_based_on_dict()
|
||||
else:
|
||||
super().__delitem__(i)
|
||||
self._sync_based_on_list()
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self.data or item in self.keys() or item in self.values()
|
||||
|
||||
def append(self, item: Any) -> None:
|
||||
if item != (item[0], item[1]):
|
||||
raise NotImplementedError
|
||||
super().append(item)
|
||||
self._sync_based_on_list()
|
||||
self.data.sort()
|
||||
|
||||
def append_new(self, item: Any):
|
||||
if item != (item[0], item[1]):
|
||||
raise NotImplementedError
|
||||
if item[0] not in self:
|
||||
super().append(item)
|
||||
self._sync_based_on_list()
|
||||
self.data.sort()
|
||||
|
||||
def insert(self, i: int, item: Any) -> None:
|
||||
if item != (item[0], item[1]): # 确保 item 是遵从限制的元组
|
||||
raise NotImplementedError
|
||||
super().insert(i, item)
|
||||
self._sync_based_on_list()
|
||||
self.data.sort()
|
||||
|
||||
def pop(self, i: int = -1) -> Any:
|
||||
res = super().pop(i)
|
||||
self._sync_based_on_list()
|
||||
return res
|
||||
|
||||
def remove(self, item: Any) -> None:
|
||||
if isinstance(item, str):
|
||||
item = (item, self.dicted_data[item])
|
||||
if item != (item[0], item[1]):
|
||||
raise NotImplementedError
|
||||
super().remove(item)
|
||||
self._sync_based_on_list()
|
||||
self.data.sort()
|
||||
|
||||
def clear(self) -> None:
|
||||
super().clear()
|
||||
self._sync_based_on_list()
|
||||
|
||||
def index(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def extend(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def sort(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def reverse(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def keys(self):
|
||||
return self.dicted_data.keys()
|
||||
|
||||
def values(self):
|
||||
return self.dicted_data.values()
|
||||
|
||||
def items(self):
|
||||
return self.data
|
||||
|
||||
def keys_equal_with(self, other):
|
||||
return self.key_equality(self, other)
|
||||
|
||||
@classmethod
|
||||
def key_equality(cls, a, b):
|
||||
return a.keys() == b.keys()
|
||||
@@ -1,6 +1,7 @@
|
||||
from collections.abc import MutableSequence
|
||||
from typing import Any, Iterator, Optional
|
||||
|
||||
|
||||
class Lict(MutableSequence):
|
||||
""" "列典" 对象
|
||||
|
||||
|
||||
@@ -1,242 +0,0 @@
|
||||
# 已弃用
|
||||
class RefVar:
|
||||
def __init__(self, initvalue) -> None:
|
||||
self.data = initvalue
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"RefVar({repr(self.data)})"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.data)
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
return RefVar(self.data + other.data)
|
||||
return RefVar(self.data + other)
|
||||
|
||||
def __radd__(self, other):
|
||||
return RefVar(other + self.data)
|
||||
|
||||
def __sub__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
return RefVar(self.data - other.data)
|
||||
return RefVar(self.data - other)
|
||||
|
||||
def __rsub__(self, other):
|
||||
return RefVar(other - self.data)
|
||||
|
||||
def __mul__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
return RefVar(self.data * other.data)
|
||||
return RefVar(self.data * other)
|
||||
|
||||
def __rmul__(self, other):
|
||||
return RefVar(other * self.data)
|
||||
|
||||
def __truediv__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
return RefVar(self.data / other.data)
|
||||
return RefVar(self.data / other)
|
||||
|
||||
def __rtruediv__(self, other):
|
||||
return RefVar(other / self.data)
|
||||
|
||||
def __floordiv__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
return RefVar(self.data // other.data)
|
||||
return RefVar(self.data // other)
|
||||
|
||||
def __rfloordiv__(self, other):
|
||||
return RefVar(other // self.data)
|
||||
|
||||
def __mod__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
return RefVar(self.data % other.data)
|
||||
return RefVar(self.data % other)
|
||||
|
||||
def __rmod__(self, other):
|
||||
return RefVar(other % self.data)
|
||||
|
||||
def __pow__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
return RefVar(self.data**other.data)
|
||||
return RefVar(self.data**other)
|
||||
|
||||
def __rpow__(self, other):
|
||||
return RefVar(other**self.data)
|
||||
|
||||
def __neg__(self):
|
||||
return RefVar(-self.data)
|
||||
|
||||
def __pos__(self):
|
||||
return RefVar(+self.data)
|
||||
|
||||
def __abs__(self):
|
||||
return RefVar(abs(self.data))
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
return self.data == other.data
|
||||
return self.data == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __lt__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
return self.data < other.data
|
||||
return self.data < other
|
||||
|
||||
def __le__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
return self.data <= other.data
|
||||
return self.data <= other
|
||||
|
||||
def __gt__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
return self.data > other.data
|
||||
return self.data > other
|
||||
|
||||
def __ge__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
return self.data >= other.data
|
||||
return self.data >= other
|
||||
|
||||
# 位运算
|
||||
def __and__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
return RefVar(self.data & other.data)
|
||||
return RefVar(self.data & other)
|
||||
|
||||
def __rand__(self, other):
|
||||
return RefVar(other & self.data)
|
||||
|
||||
def __or__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
return RefVar(self.data | other.data)
|
||||
return RefVar(self.data | other)
|
||||
|
||||
def __ror__(self, other):
|
||||
return RefVar(other | self.data)
|
||||
|
||||
def __xor__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
return RefVar(self.data ^ other.data)
|
||||
return RefVar(self.data ^ other)
|
||||
|
||||
def __rxor__(self, other):
|
||||
return RefVar(other ^ self.data)
|
||||
|
||||
def __lshift__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
return RefVar(self.data << other.data)
|
||||
return RefVar(self.data << other)
|
||||
|
||||
def __rlshift__(self, other):
|
||||
return RefVar(other << self.data)
|
||||
|
||||
def __rshift__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
return RefVar(self.data >> other.data)
|
||||
return RefVar(self.data >> other)
|
||||
|
||||
def __rrshift__(self, other):
|
||||
return RefVar(other >> self.data)
|
||||
|
||||
def __invert__(self):
|
||||
return RefVar(~self.data)
|
||||
|
||||
# 类型转换
|
||||
def __int__(self):
|
||||
return int(self.data)
|
||||
|
||||
def __float__(self):
|
||||
return float(self.data)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.data)
|
||||
|
||||
def __complex__(self):
|
||||
return complex(self.data)
|
||||
|
||||
def __bytes__(self):
|
||||
return bytes(self.data)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.data)
|
||||
|
||||
# 容器操作(如果底层数据支持)
|
||||
def __len__(self):
|
||||
return len(self.data)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.data[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.data[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self.data[key]
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self.data
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.data)
|
||||
|
||||
def __iadd__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
self.data += other.data
|
||||
else:
|
||||
self.data += other
|
||||
return self
|
||||
|
||||
def __isub__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
self.data -= other.data
|
||||
else:
|
||||
self.data -= other
|
||||
return self
|
||||
|
||||
def __imul__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
self.data *= other.data
|
||||
else:
|
||||
self.data *= other
|
||||
return self
|
||||
|
||||
def __itruediv__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
self.data /= other.data
|
||||
else:
|
||||
self.data /= other
|
||||
return self
|
||||
|
||||
def __ifloordiv__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
self.data //= other.data
|
||||
else:
|
||||
self.data //= other
|
||||
return self
|
||||
|
||||
def __imod__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
self.data %= other.data
|
||||
else:
|
||||
self.data %= other
|
||||
return self
|
||||
|
||||
def __ipow__(self, other):
|
||||
if isinstance(other, RefVar):
|
||||
self.data **= other.data
|
||||
else:
|
||||
self.data **= other
|
||||
return self
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
if callable(self.data):
|
||||
return self.data(*args, **kwargs)
|
||||
raise TypeError(f"'{type(self.data).__name__}' object is not callable")
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.data, name)
|
||||
@@ -7,8 +7,8 @@
|
||||
orbital, 即轨道, 是定义队列式复习阶段流程的数据结构, 其实就是个字典, 至于为何不用typeddict, 因为懒.
|
||||
|
||||
orbital_example = {
|
||||
"schedule": [列表 存储阶段(phases)名称]
|
||||
"phases":{
|
||||
"schedule": [列表 存储阶段(routes)名称]
|
||||
"routes":{
|
||||
阶段名称 = [["谜题(puzzle 现称 Puzzles 评估器)名称", "概率系数 可大于1(整数部分为重复次数) 注意使用字符串包裹(toml 规范)"], ...],
|
||||
...
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ from .nucleon import Nucleon
|
||||
|
||||
orbital_placeholder = {
|
||||
"schedule": ["quick_review", "recognition", "final_review"],
|
||||
"phases": {
|
||||
"routes": {
|
||||
"quick_review": [
|
||||
["FillBlank", 1.0],
|
||||
["SelectMeaning", 0.5],
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
Reactor 是 HeurAMS 的记忆流程状态机模块, 和界面 (interface) 的实现是解耦的, 以便后期与其他框架的适配.\
|
||||
得益于 Pickle, 状态机模块支持快照!
|
||||
|
||||
## Phaser - 全局阶段控制器
|
||||
## Router - 全局阶段控制器
|
||||
|
||||
在一次队列记忆流程中, Phaser 代表记忆流程本身.
|
||||
在一次队列记忆流程中, Router 代表记忆流程本身.
|
||||
|
||||
### 属性
|
||||
|
||||
@@ -25,7 +25,7 @@ Reactor 是 HeurAMS 的记忆流程状态机模块, 和界面 (interface) 的实
|
||||
|
||||
在初始化 Procession 时, 每个 Procession 被赋予一个不重复的状态属性 作为"阶段状态"属性, 以此标识 Procession 的阶段属性, 因为每个 Procession 管理一个阶段下的复习进程.
|
||||
|
||||
你可以用 state 属性获取 Phaser 的当前状态.
|
||||
你可以用 state 属性获取 Router 的当前状态.
|
||||
|
||||
#### Procession 属性
|
||||
|
||||
@@ -34,26 +34,26 @@ Reactor 是 HeurAMS 的记忆流程状态机模块, 和界面 (interface) 的实
|
||||
|
||||
### 初始化
|
||||
|
||||
Phaser 接受一个存储 Atom 对象的列表, 作为组织记忆流程的材料\
|
||||
Router 接受一个存储 Atom 对象的列表, 作为组织记忆流程的材料\
|
||||
在内部, 根据是否激活将其分为 new_atoms 与 old_atoms.\
|
||||
因此, 如果你传入的列表中有算法上"无所事事"的 Atom, 流程会对其进行"加强复习"
|
||||
由此创建 Procession.
|
||||
|
||||
### 直接输出呈现形式
|
||||
|
||||
Phaser 的 __repr__ 定义了此对象"官方的显示"用作直观的调试.\
|
||||
Router 的 __repr__ 定义了此对象"官方的显示"用作直观的调试.\
|
||||
其以 ascii 表格形式输出, 格式也符合 markdown 表格规范, 你可以直接复制到 markdown.\
|
||||
示例:
|
||||
|
||||
```text
|
||||
| Type | State | Processions | Current Procession |
|
||||
|:-------|:--------|:-----------------------|:---------------------|
|
||||
| Phaser | unsure | ['新记忆', '总体复习'] | 新记忆 |
|
||||
| :----- | :----- | :--------------------- | :----------------- |
|
||||
| Router | unsure | ['新记忆', '总体复习'] | 新记忆 |
|
||||
```
|
||||
|
||||
| Type | State | Processions | Current Procession |
|
||||
|:-------|:--------|:-----------------------|:---------------------|
|
||||
| Phaser | unsure | ['新记忆', '总体复习'] | 新记忆 |
|
||||
| :----- | :----- | :--------------------- | :----------------- |
|
||||
| Router | unsure | ['新记忆', '总体复习'] | 新记忆 |
|
||||
|
||||
### 方法
|
||||
|
||||
@@ -83,30 +83,30 @@ Phaser 的 __repr__ 定义了此对象"官方的显示"用作直观的调试.\
|
||||
- current_atom: 当前记忆原子的引用
|
||||
- atoms: 队列中所有原子列表
|
||||
- cursor: 指针, 是当前原子在 atoms 列表中的索引
|
||||
- phase: "阶段属性"
|
||||
- route: "阶段属性"
|
||||
|
||||
> 注意区分 "Phaser" 和 "Phase", 其中 "Phase" 表示 "Phaser State".
|
||||
> 注意区分 "Router" 和 "Route", 其中 "Route" 表示 "Router State".
|
||||
|
||||
- name\_: 阶段的命名
|
||||
- state: 当前状态属性
|
||||
|
||||
### 初始化
|
||||
|
||||
接受一个 atoms 列表与 phase_state (PhaserState Enum 类型)对象
|
||||
接受一个 atoms 列表与 route_state (RouterState Enum 类型)对象
|
||||
|
||||
### 直接输出呈现形式
|
||||
|
||||
同 Phaser, 但显示数据有所不同\
|
||||
与 Phaser 不同, Procession 显示队列会对过长的 atom.ident 进行缩略(末尾 `>` 符号)
|
||||
同 Router, 但显示数据有所不同\
|
||||
与 Router 不同, Procession 显示队列会对过长的 atom.ident 进行缩略(末尾 `>` 符号)
|
||||
|
||||
```text
|
||||
| Type | Name | State | Progress | Queue | Current Atom |
|
||||
|:-----------|:-------|:--------|:-----------|:-----------------------|:------------------------------|
|
||||
| Type | Name | State | Progress | Procession | Current Atom |
|
||||
| :--------- | :----- | :----- | :------- | :--------------------- | :---------------------------- |
|
||||
| Procession | 新记忆 | active | 1 / 2 | ['秦孝公>', '君臣固>'] | 秦孝公据崤函之固, 拥雍州之地, |
|
||||
```
|
||||
|
||||
| Type | Name | State | Progress | Queue | Current Atom |
|
||||
|:-----------|:-------|:--------|:-----------|:-----------------------|:------------------------------|
|
||||
| Type | Name | State | Progress | Procession | Current Atom |
|
||||
| :--------- | :----- | :----- | :------- | :--------------------- | :---------------------------- |
|
||||
| Procession | 新记忆 | active | 1 / 2 | ['秦孝公>', '君臣固>'] | 秦孝公据崤函之固, 拥雍州之地, |
|
||||
|
||||
### 方法
|
||||
@@ -142,11 +142,11 @@ Phaser 的 __repr__ 定义了此对象"官方的显示"用作直观的调试.\
|
||||
|
||||
判断是否为空队列(传入原子列表对象是空列表的队列)
|
||||
|
||||
#### get_fission(self)
|
||||
#### get_expander(self)
|
||||
|
||||
获取当前原子的 Fission 对象, 用于单原子调度展开
|
||||
获取当前原子的 Expander 对象, 用于单原子调度展开
|
||||
|
||||
## Fission - 单原子调度控制器
|
||||
## Expander - 单原子调度控制器
|
||||
|
||||
### 属性
|
||||
|
||||
@@ -166,7 +166,7 @@ Phaser 的 __repr__ 定义了此对象"官方的显示"用作直观的调试.\
|
||||
|
||||
### 初始化
|
||||
|
||||
接受 atom 对象和 phase 参数
|
||||
接受 atom 对象和 route 参数
|
||||
|
||||
### 方法
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from heurams.services.logger import get_logger
|
||||
|
||||
from .fission import Fission
|
||||
from .phaser import Phaser
|
||||
from .expander import Expander
|
||||
from .router import Router
|
||||
from .procession import Procession
|
||||
from .states import PhaserState, ProcessionState
|
||||
from .states import RouterState, ProcessionState
|
||||
|
||||
__all__ = ["PhaserState", "ProcessionState", "Procession", "Fission", "Phaser"]
|
||||
__all__ = ["RouterState", "ProcessionState", "Procession", "Expander", "Router"]
|
||||
|
||||
@@ -8,42 +8,42 @@ import heurams.kernel.particles as pt
|
||||
import heurams.kernel.puzzles as puz
|
||||
from heurams.services.logger import get_logger
|
||||
|
||||
from .states import FissionState, PhaserState
|
||||
from .states import ExpanderState, RouterState
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class Fission(Machine):
|
||||
class Expander(Machine):
|
||||
"""单原子调度展开器"""
|
||||
|
||||
def __init__(self, atom: pt.Atom, phase=PhaserState.RECOGNITION):
|
||||
self.phase = phase
|
||||
def __init__(self, atom: pt.Atom, route=RouterState.RECOGNITION):
|
||||
self.route = route
|
||||
self.cursor = 0
|
||||
self.atom = atom
|
||||
self.current_puzzle_inf: dict
|
||||
# phase 为 PhaserState 枚举实例, 需要获取其value
|
||||
phase_value = phase.value
|
||||
# route 为 RouterState 枚举实例, 需要获取其value
|
||||
route_value = route.value
|
||||
states = [
|
||||
{"name": FissionState.EXAMMODE.value},
|
||||
{"name": FissionState.RETRONLY.value},
|
||||
{"name": ExpanderState.EXAMMODE.value},
|
||||
{"name": ExpanderState.RETRONLY.value},
|
||||
]
|
||||
|
||||
transitions = [
|
||||
{
|
||||
"trigger": "finish",
|
||||
"source": FissionState.EXAMMODE.value,
|
||||
"dest": FissionState.RETRONLY.value,
|
||||
"source": ExpanderState.EXAMMODE.value,
|
||||
"dest": ExpanderState.RETRONLY.value,
|
||||
},
|
||||
]
|
||||
if phase == PhaserState.FINISHED:
|
||||
if route == RouterState.FINISHED:
|
||||
Machine.__init__(
|
||||
self,
|
||||
states=states,
|
||||
transitions=transitions,
|
||||
initial=FissionState.EXAMMODE.value,
|
||||
initial=ExpanderState.EXAMMODE.value,
|
||||
)
|
||||
return
|
||||
orbital_schedule = atom.registry["orbital"]["phases"][phase_value] # type: ignore
|
||||
orbital_schedule = atom.registry["orbital"]["routes"][route_value] # type: ignore
|
||||
orbital_puzzles = atom.registry["nucleon"]["puzzles"]
|
||||
self.puzzles_inf = list()
|
||||
self.min_ratings = []
|
||||
@@ -81,7 +81,7 @@ class Fission(Machine):
|
||||
self,
|
||||
states=states,
|
||||
transitions=transitions,
|
||||
initial=FissionState.EXAMMODE.value,
|
||||
initial=ExpanderState.EXAMMODE.value,
|
||||
)
|
||||
|
||||
def get_puzzles_inf(self):
|
||||
@@ -118,11 +118,13 @@ class Fission(Machine):
|
||||
|
||||
dic = [
|
||||
{
|
||||
"Type": "Fission",
|
||||
"Type": "Expander",
|
||||
"Atom": truncate(self.atom.ident),
|
||||
"State": self.state,
|
||||
"Progress": f"{self.cursor + 1} / {len(self.puzzles_inf)}",
|
||||
"Queue": list(map(lambda f: truncate(f["alia"]), self.puzzles_inf)),
|
||||
"Procession": list(
|
||||
map(lambda f: truncate(f["alia"]), self.puzzles_inf)
|
||||
),
|
||||
"Current Puzzle": f"{self.current_puzzle_inf['alia']}@{self.current_puzzle_inf['puzzle'].__name__}", # type: ignore
|
||||
}
|
||||
]
|
||||
@@ -4,8 +4,8 @@ from transitions import Machine
|
||||
import heurams.kernel.particles as pt
|
||||
from heurams.services.logger import get_logger
|
||||
|
||||
from .fission import Fission
|
||||
from .states import PhaserState, ProcessionState
|
||||
from .expander import Expander
|
||||
from .states import RouterState, ProcessionState
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -13,11 +13,11 @@ logger = get_logger(__name__)
|
||||
class Procession(Machine):
|
||||
"""队列: 标识单次记忆流程"""
|
||||
|
||||
def __init__(self, atoms: list, phase_state: PhaserState, name_: str = ""):
|
||||
def __init__(self, atoms: list, route_state: RouterState, name_: str = ""):
|
||||
logger.debug(
|
||||
"Procession.__init__: 原子数量=%d, phase=%s, name='%s'",
|
||||
"Procession.__init__: 原子数量=%d, route=%s, name='%s'",
|
||||
len(atoms),
|
||||
phase_state.value,
|
||||
route_state.value,
|
||||
name_,
|
||||
)
|
||||
self.current_atom: pt.Atom | None
|
||||
@@ -25,7 +25,7 @@ class Procession(Machine):
|
||||
self.current_atom = atoms[0] if atoms else None
|
||||
self.cursor = 0
|
||||
self.name_ = name_
|
||||
self.phase = phase_state
|
||||
self.route = route_state
|
||||
|
||||
states = [
|
||||
{"name": ProcessionState.ACTIVE.value, "on_enter": "on_active"},
|
||||
@@ -113,8 +113,8 @@ class Procession(Machine):
|
||||
logger.debug("Procession.is_empty: %s", empty)
|
||||
return empty
|
||||
|
||||
def get_fission(self):
|
||||
return Fission(atom=self.current_atom, phase=self.phase) # type: ignore
|
||||
def get_expander(self):
|
||||
return Expander(atom=self.current_atom, route=self.route) # type: ignore
|
||||
|
||||
def __repr__(self, style="pipe", ends="\n"):
|
||||
from heurams.services.textproc import truncate
|
||||
@@ -125,7 +125,7 @@ class Procession(Machine):
|
||||
"Name": self.name_,
|
||||
"State": self.state,
|
||||
"Progress": f"{self.cursor + 1} / {len(self.atoms)}",
|
||||
"Queue": list(map(lambda f: truncate(f.ident), self.atoms)),
|
||||
"Procession": list(map(lambda f: truncate(f.ident), self.atoms)),
|
||||
"Current Atom": self.current_atom.ident, # type: ignore
|
||||
}
|
||||
]
|
||||
|
||||
@@ -5,16 +5,16 @@ from heurams.kernel.particles.placeholders import AtomPlaceholder
|
||||
from heurams.services.logger import get_logger
|
||||
|
||||
from .procession import Procession
|
||||
from .states import PhaserState, ProcessionState
|
||||
from .states import RouterState, ProcessionState
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class Phaser(Machine):
|
||||
"""全局调度阶段管理器"""
|
||||
class Router(Machine):
|
||||
"""全局调度阶段路由器"""
|
||||
|
||||
def __init__(self, atoms: list[pt.Atom]) -> None:
|
||||
logger.debug("Phaser.__init__: 原子数量=%d", len(atoms))
|
||||
logger.debug(f"Router.__init__: 原子数量={len(atoms)}")
|
||||
|
||||
self.atoms = atoms
|
||||
new_atoms = list()
|
||||
@@ -26,56 +26,57 @@ class Phaser(Machine):
|
||||
else:
|
||||
old_atoms.append(i)
|
||||
|
||||
logger.debug("新原子数量=%d, 旧原子数量=%d", len(new_atoms), len(old_atoms))
|
||||
logger.debug(f"新原子数量={len(new_atoms)}, 旧原子数量={len(old_atoms)}")
|
||||
|
||||
self.processions = list()
|
||||
"""路由中的所有队列"""
|
||||
# TODO: 改进为基于配置文件的可选复习阶段
|
||||
if len(old_atoms):
|
||||
self.processions.append(
|
||||
Procession(old_atoms, PhaserState.QUICK_REVIEW, "初始复习")
|
||||
Procession(old_atoms, RouterState.QUICK_REVIEW, "初始复习")
|
||||
)
|
||||
logger.debug("创建初始复习 Procession")
|
||||
|
||||
if len(new_atoms):
|
||||
self.processions.append(
|
||||
Procession(new_atoms, PhaserState.RECOGNITION, "新记忆")
|
||||
Procession(new_atoms, RouterState.RECOGNITION, "新记忆")
|
||||
)
|
||||
logger.debug("创建新记忆 Procession")
|
||||
|
||||
self.processions.append(Procession(atoms, PhaserState.FINAL_REVIEW, "总体复习"))
|
||||
self.processions.append(Procession(atoms, RouterState.FINAL_REVIEW, "总体复习"))
|
||||
logger.debug("创建总体复习 Procession")
|
||||
logger.debug("Phaser 初始化完成, processions 数量=%d", len(self.processions))
|
||||
logger.debug("Router 初始化完成, processions 数量=%d", len(self.processions))
|
||||
|
||||
# 设置transitions状态机
|
||||
states = [
|
||||
{"name": PhaserState.UNSURE.value, "on_enter": "on_unsure"},
|
||||
{"name": PhaserState.QUICK_REVIEW.value, "on_enter": "on_quick_review"},
|
||||
{"name": PhaserState.RECOGNITION.value, "on_enter": "on_recognition"},
|
||||
{"name": PhaserState.FINAL_REVIEW.value, "on_enter": "on_final_review"},
|
||||
{"name": PhaserState.FINISHED.value, "on_enter": "on_finished"},
|
||||
{"name": RouterState.UNSURE.value, "on_enter": "on_unsure"},
|
||||
{"name": RouterState.QUICK_REVIEW.value, "on_enter": "on_quick_review"},
|
||||
{"name": RouterState.RECOGNITION.value, "on_enter": "on_recognition"},
|
||||
{"name": RouterState.FINAL_REVIEW.value, "on_enter": "on_final_review"},
|
||||
{"name": RouterState.FINISHED.value, "on_enter": "on_finished"},
|
||||
]
|
||||
|
||||
transitions = [
|
||||
{"trigger": "to_unsure", "source": "*", "dest": PhaserState.UNSURE.value},
|
||||
{"trigger": "to_unsure", "source": "*", "dest": RouterState.UNSURE.value},
|
||||
{
|
||||
"trigger": "to_quick_review",
|
||||
"source": "*",
|
||||
"dest": PhaserState.QUICK_REVIEW.value,
|
||||
"dest": RouterState.QUICK_REVIEW.value,
|
||||
},
|
||||
{
|
||||
"trigger": "to_recognition",
|
||||
"source": "*",
|
||||
"dest": PhaserState.RECOGNITION.value,
|
||||
"dest": RouterState.RECOGNITION.value,
|
||||
},
|
||||
{
|
||||
"trigger": "to_final_review",
|
||||
"source": "*",
|
||||
"dest": PhaserState.FINAL_REVIEW.value,
|
||||
"dest": RouterState.FINAL_REVIEW.value,
|
||||
},
|
||||
{
|
||||
"trigger": "to_finished",
|
||||
"source": "*",
|
||||
"dest": PhaserState.FINISHED.value,
|
||||
"dest": RouterState.FINISHED.value,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -83,54 +84,54 @@ class Phaser(Machine):
|
||||
self,
|
||||
states=states,
|
||||
transitions=transitions,
|
||||
initial=PhaserState.UNSURE.value,
|
||||
initial=RouterState.UNSURE.value,
|
||||
)
|
||||
|
||||
self.to_unsure()
|
||||
|
||||
def on_unsure(self):
|
||||
"""进入UNSURE状态时的回调"""
|
||||
logger.debug("Phaser 进入 UNSURE 状态")
|
||||
logger.debug("Router 进入 UNSURE 状态")
|
||||
|
||||
def on_quick_review(self):
|
||||
"""进入QUICK_REVIEW状态时的回调"""
|
||||
logger.debug("Phaser 进入 QUICK_REVIEW 状态")
|
||||
logger.debug("Router 进入 QUICK_REVIEW 状态")
|
||||
|
||||
def on_recognition(self):
|
||||
"""进入RECOGNITION状态时的回调"""
|
||||
logger.debug("Phaser 进入 RECOGNITION 状态")
|
||||
logger.debug("Router 进入 RECOGNITION 状态")
|
||||
|
||||
def on_final_review(self):
|
||||
"""进入FINAL_REVIEW状态时的回调"""
|
||||
logger.debug("Phaser 进入 FINAL_REVIEW 状态")
|
||||
logger.debug("Router 进入 FINAL_REVIEW 状态")
|
||||
|
||||
def on_finished(self):
|
||||
"""进入FINISHED状态时的回调"""
|
||||
for i in self.atoms:
|
||||
i.lock(1)
|
||||
i.revise()
|
||||
logger.debug("Phaser 进入 FINISHED 状态")
|
||||
logger.debug("Router 进入 FINISHED 状态")
|
||||
|
||||
def current_procession(self):
|
||||
logger.debug("Phaser.current_procession 被调用")
|
||||
logger.debug("Router.current_procession 被调用")
|
||||
for i in self.processions:
|
||||
i: Procession
|
||||
if i.state != ProcessionState.FINISHED.value:
|
||||
# if i.phase == PhaserState.UNSURE: 此判断是不必要的 因为没有这种 Procession
|
||||
if i.phase == PhaserState.QUICK_REVIEW:
|
||||
# if i.route == RouterState.UNSURE: 此判断是不必要的 因为没有这种 Procession
|
||||
if i.route == RouterState.QUICK_REVIEW:
|
||||
self.to_quick_review()
|
||||
elif i.phase == PhaserState.RECOGNITION:
|
||||
elif i.route == RouterState.RECOGNITION:
|
||||
self.to_recognition()
|
||||
elif i.phase == PhaserState.FINAL_REVIEW:
|
||||
elif i.route == RouterState.FINAL_REVIEW:
|
||||
self.to_final_review()
|
||||
|
||||
logger.debug("找到未完成的 Procession: phase=%s", i.phase)
|
||||
logger.debug("找到未完成的 Procession: route=%s", i.route)
|
||||
return i
|
||||
|
||||
# 所有Procession都已完成
|
||||
self.to_finished()
|
||||
logger.debug("所有 Procession 已完成, 状态设置为 FINISHED")
|
||||
return Procession([AtomPlaceholder()], PhaserState.FINISHED)
|
||||
return Procession([AtomPlaceholder()], RouterState.FINISHED)
|
||||
|
||||
def __repr__(self, style="pipe", ends="\n"):
|
||||
from tabulate import tabulate as tabu
|
||||
@@ -139,7 +140,7 @@ class Phaser(Machine):
|
||||
|
||||
lst = [
|
||||
{
|
||||
"Type": "Phaser",
|
||||
"Type": "Router",
|
||||
"State": self.state,
|
||||
"Processions": list(map(lambda f: (f.name_), self.processions)),
|
||||
"Current Procession": "None" if not self.current_procession() else self.current_procession().name_, # type: ignore
|
||||
@@ -5,7 +5,7 @@ from heurams.services.logger import get_logger
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class PhaserState(Enum):
|
||||
class RouterState(Enum):
|
||||
UNSURE = "unsure"
|
||||
QUICK_REVIEW = "quick_review"
|
||||
RECOGNITION = "recognition"
|
||||
@@ -18,7 +18,7 @@ class ProcessionState(Enum):
|
||||
FINISHED = "finished"
|
||||
|
||||
|
||||
class FissionState(Enum):
|
||||
class ExpanderState(Enum):
|
||||
EXAMMODE = "exammode"
|
||||
RETRONLY = "retronly"
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ def get_daystamp() -> int:
|
||||
|
||||
def get_timestamp() -> float:
|
||||
"""获取 UNIX 时间戳"""
|
||||
# 搞这个类的原因是要支持可复现操作
|
||||
# 搞这个函数的原因是要支持可复现操作
|
||||
time_override = config_var.get()["services"]["timer"]["timestamp_override"]
|
||||
if time_override != -1:
|
||||
logger.debug("使用覆盖的时间戳: %f", time_override)
|
||||
|
||||
3
src/heurams/unifront/session.py
Normal file
3
src/heurams/unifront/session.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""会话模块"""
|
||||
class Session:
|
||||
pass
|
||||
Reference in New Issue
Block a user