refactor: 完成 0.4.0 版本更新

完成 0.4.0 版本更新, 为了消除此前提交消息风格不一致与错误提交超大文件的问题, 维持代码统计数据的准确性和提交消息风格的一致性, 重新初始化仓库; 旧的提交历史在 HeurAMS-legacy 仓库(https://gitea.imwangzhiyu.xyz/ajax/HeurAMS-legacy)
This commit is contained in:
2025-12-17 22:31:38 +08:00
commit 2f23cfe174
89 changed files with 6112 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
#!/usr/bin/env python3
from textual.app import ComposeResult
from textual.widgets import (
Header,
Footer,
Label,
Static,
Button,
Markdown,
)
from textual.containers import ScrollableContainer, ScrollableContainer
from textual.screen import Screen
import heurams.services.version as version
from heurams.context import *
class AboutScreen(Screen):
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
with ScrollableContainer(id="about_container"):
yield Label("[b]关于与版本信息[/b]")
about_text = f"""
# 关于 "潜进"
版本 {version.ver} {version.stage.capitalize()}
开发代号: {version.codename.capitalize()}
一个基于启发式算法的开放源代码记忆调度器, 旨在帮助用户更高效地进行记忆工作与学习规划.
以 AGPL-3.0 开放源代码
开发人员:
- Wang Zhiyu([@pluvium27](https://github.com/pluvium27)): 项目作者
特别感谢:
- [Piotr A. Woźniak](https://supermemo.guru/wiki/Piotr_Wozniak): SuperMemo-2 算法
- [Thoughts Memo](https://www.zhihu.com/people/L.M.Sherlock): 文献参考
# 参与贡献
我们是一个年轻且包容的社区, 由技术人员, 设计师, 文书工作者, 以及创意人员共同构成,
通过我们协力开发的软件为所有人谋取福祉.
上述工作不可避免地让我们确立了下列价值观 (取自 KDE 宣言):
- 开放治理 确保更多人能参与我们的领导和决策进程;
- 自由软件 确保我们的工作成果随时能为所有人所用;
- 多样包容 确保所有人都能加入社区并参加工作;
- 创新精神 确保新思路能不断涌现并服务于所有人;
- 共同产权 确保我们能团结一致;
- 迎合用户 确保我们的成果对所有人有用.
综上所述, 在为我们共同目标奋斗的过程中, 我们认为上述价值观反映了我们社区的本质, 是我们始终如一地保持初心的关键所在.
这是一项立足于协作精神的事业, 它的运作和产出不受任何单一个人或者机构的操纵.
我们的共同目标是为人人带来高品质的辅助记忆 & 学习软件.
不管您来自何方, 我们都欢迎您加入社区并做出贡献.
"""
# """
# 学术数据
# "潜进" 的用户数据可用于科学方面的研究, 我们将在未来版本添加学术数据的收集和展示平台
# """
yield Markdown(about_text, classes="about-markdown")
yield Button(
"返回主界面",
id="back_button",
variant="primary",
classes="back-button",
)
yield Footer()
def action_go_back(self):
self.app.pop_screen()
def on_button_pressed(self, event) -> None:
event.stop()
if event.button.id == "back_button":
self.action_go_back()

View File

@@ -0,0 +1,153 @@
#!/usr/bin/env python3
from textual.app import ComposeResult
from textual.widgets import (
Header,
Footer,
Label,
ListView,
ListItem,
Button,
Static,
)
from textual.containers import ScrollableContainer
from textual.screen import Screen
from heurams.kernel.particles import *
from heurams.context import *
import heurams.services.version as version
import heurams.services.timer as timer
from .preparation import PreparationScreen
from .about import AboutScreen
from heurams.services.logger import get_logger
import pathlib
logger = get_logger(__name__)
class DashboardScreen(Screen):
SUB_TITLE = "仪表盘"
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
yield ScrollableContainer(
Label(f'欢迎使用 "潜进" 启发式辅助记忆调度器', classes="title-label"),
Label(f"当前 UNIX 日时间戳: {timer.get_daystamp()}"),
Label(f'时区修正: UTC+{config_var.get()["timezone_offset"] / 3600}'),
Label("选择待学习或待修改的记忆单元集:", classes="title-label"),
ListView(id="union-list", classes="union-list-view"),
Label(
f'"潜进" 启发式辅助记忆调度器 | 版本 {version.ver} {version.codename.capitalize()} 2025'
),
)
yield Footer()
def item_desc_generator(self, filename) -> dict:
"""简单分析以生成项目项显示文本
Returns:
dict: 以数字为列表, 分别呈现单行字符串
"""
res = dict()
filestem = pathlib.Path(filename).stem
res[0] = f"{filename}\0"
from heurams.kernel.particles.loader import load_electron
import heurams.kernel.particles as pt
electron_file_path = pathlib.Path(config_var.get()["paths"]["electron_dir"]) / (
filestem + ".json"
)
logger.debug(f"电子文件路径: {electron_file_path}")
if electron_file_path.exists(): # 未找到则创建电子文件 (json)
pass
else:
electron_file_path.touch()
with open(electron_file_path, "w") as f:
f.write("{}")
electron_dict = load_electron(path=electron_file_path) # TODO: 取消硬编码扩展名
logger.debug(electron_dict)
is_due = 0
is_activated = 0
nextdate = 0x3F3F3F3F
for i in electron_dict.values():
i: pt.Electron
logger.debug(i, i.is_due())
if i.is_due():
is_due = 1
if i.is_activated():
is_activated = 1
nextdate = min(nextdate, i.nextdate())
res[1] = f"下一次复习: {nextdate}\n"
res[1] += f"{is_due if "需要复习" else "当前无需复习"}"
if not is_activated:
res[1] = " 尚未激活"
return res
def on_mount(self) -> None:
union_list_widget = self.query_one("#union-list", ListView)
probe = probe_all(0)
if len(probe["nucleon"]):
for file in probe["nucleon"]:
text = self.item_desc_generator(file)
union_list_widget.append(
ListItem(
Label(text[0] + "\n" + text[1]),
)
)
else:
union_list_widget.append(
ListItem(
Static(
"在 ./nucleon/ 中未找到任何内容源数据文件.\n请放置文件后重启应用.\n或者新建空的单元集."
)
)
)
union_list_widget.disabled = True
def on_list_view_selected(self, event) -> None:
if not isinstance(event.item, ListItem):
return
selected_label = event.item.query_one(Label)
if "未找到任何 .toml 文件" in str(selected_label.renderable): # type: ignore
return
selected_filename = pathlib.Path(
str(selected_label.renderable)
.partition("\0")[0] # 文件名末尾截断, 保留文件名
.replace("*", "")
) # 去除markdown加粗
nucleon_file_path = (
pathlib.Path(config_var.get()["paths"]["nucleon_dir"]) / selected_filename
)
electron_file_path = pathlib.Path(config_var.get()["paths"]["electron_dir"]) / (
str(selected_filename.stem) + ".json"
)
self.app.push_screen(PreparationScreen(nucleon_file_path, electron_file_path))
def on_button_pressed(self, event) -> None:
if event.button.id == "new_nucleon_button":
# 切换到创建单元
from .nucreator import NucleonCreatorScreen
newscr = NucleonCreatorScreen()
self.app.push_screen(newscr)
elif event.button.id == "precache_all_button":
# 切换到缓存管理器
from .precache import PrecachingScreen
precache_screen = PrecachingScreen()
self.app.push_screen(precache_screen)
elif event.button.id == "about_button":
from .about import AboutScreen
about_screen = AboutScreen()
self.app.push_screen(about_screen)
def action_quit_app(self) -> None:
self.app.exit()

View File

@@ -0,0 +1,152 @@
#!/usr/bin/env python3
from textual.app import ComposeResult
from textual.widgets import Header, Footer, Label, Static, Button
from textual.containers import Center, ScrollableContainer
from textual.screen import Screen
from textual.reactive import reactive
from enum import Enum, auto
from heurams.services.logger import get_logger
from heurams.context import config_var
from heurams.kernel.reactor import *
import heurams.kernel.particles as pt
import heurams.kernel.puzzles as pz
from .. import shim
class AtomState(Enum):
FAILED = auto()
NORMAL = auto()
logger = get_logger(__name__)
class MemScreen(Screen):
BINDINGS = [
("q", "pop_screen", "返回"),
# ("p", "prev", "复习上一个"),
("d", "toggle_dark", ""),
("v", "play_voice", "朗读"),
("0,1,2,3", "app.push_screen('about')", ""),
]
if config_var.get()["quick_pass"]:
BINDINGS.append(("k", "quick_pass", "跳过"))
rating = reactive(-1)
def __init__(
self,
atoms: list,
name: str | None = None,
id: str | None = None,
classes: str | None = None,
) -> None:
super().__init__(name, id, classes)
self.atoms = atoms
self.phaser = Phaser(atoms)
# logger.debug(self.phaser.state)
self.procession: Procession = self.phaser.current_procession() # type: ignore
self.atom: pt.Atom = self.procession.current_atom
# logger.debug(self.phaser.state)
# self.procession.forward(1)
for i in atoms:
i.do_eval()
def on_mount(self):
self.load_puzzle()
pass
def puzzle_widget(self):
try:
logger.debug(self.phaser.state)
logger.debug(self.procession.cursor)
logger.debug(self.atom)
self.fission = Fission(self.atom, self.phaser.state)
puzzle_debug = next(self.fission.generate())
# logger.debug(puzzle_debug)
return shim.puzzle2widget[puzzle_debug["puzzle"]](
atom=self.atom, alia=puzzle_debug["alia"]
)
except (KeyError, StopIteration, AttributeError) as e:
logger.debug(f"调度展开出错: {e}")
return Static("无法生成谜题")
# logger.debug(shim.puzzle2widget[puzzle_debug["puzzle"]])
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
with ScrollableContainer():
yield Label(self._get_progress_text(), id="progress")
# self.mount(self.current_widget()) # type: ignore
yield ScrollableContainer(id="puzzle-container")
# yield Button("重新学习此单元", id="re-recognize", variant="warning")
yield Footer()
def _get_progress_text(self):
return f"当前进度: {self.procession.process() + 1}/{self.procession.total_length()}"
def update_display(self):
progress_widget = self.query_one("#progress")
progress_widget.update(self._get_progress_text()) # type: ignore
def load_puzzle(self):
self.atom: pt.Atom = self.procession.current_atom
container = self.query_one("#puzzle-container")
for i in container.children:
i.remove()
container.mount(self.puzzle_widget())
def load_finished_widget(self):
container = self.query_one("#puzzle-container")
for i in container.children:
i.remove()
from heurams.interface.widgets.finished import Finished
container.mount(Finished())
def on_button_pressed(self, event):
event.stop()
def watch_rating(self, old_rating, new_rating) -> None:
if self.procession == 0:
return
if new_rating == -1:
return
forwards = 1 if new_rating >= 4 else 0
self.rating = -1
logger.debug(f"试图前进: {"允许" if forwards else "禁止"}")
if forwards:
ret = self.procession.forward(1)
if ret == 0: # 若结束了此次队列
self.procession = self.phaser.current_procession() # type: ignore
if self.procession == 0: # 若所有队列都结束了
logger.debug(f"记忆进程结束")
for i in self.atoms:
i: pt.Atom
i.revise()
i.persist("electron")
self.load_finished_widget()
return
else:
logger.debug(f"建立新队列 {self.procession.phase}")
self.load_puzzle()
else: # 若不通过
self.procession.append()
self.update_display()
def action_quick_pass(self):
self.rating = 5
self.atom.minimize(5)
self.atom.registry["electron"].activate()
self.atom.lock(1)
def action_play_voice(self):
"""朗读当前内容"""
pass
def action_toggle_dark(self):
self.app.action_toggle_dark()
def action_pop_screen(self):
self.app.pop_screen()

View File

@@ -0,0 +1,171 @@
#!/usr/bin/env python3
from textual.app import ComposeResult
from textual.widgets import (
Header,
Footer,
Label,
Input,
Select,
Button,
Markdown,
)
from textual.containers import ScrollableContainer
from textual.screen import Screen
from heurams.services.version import ver
import toml
from pathlib import Path
from heurams.context import config_var
class NucleonCreatorScreen(Screen):
BINDINGS = [("q", "go_back", "返回")]
SUB_TITLE = "单元集创建向导"
def __init__(self) -> None:
super().__init__(name=None, id=None, classes=None)
def search_templates(self):
from pathlib import Path
from heurams.context import config_var
template_dir = Path(config_var.get()["paths"]["template_dir"])
templates = list()
for i in template_dir.iterdir():
if i.name.endswith(".toml"):
try:
import toml
with open(i, "r") as f:
dic = toml.load(f)
desc = dic["__metadata__.attribution"]["desc"]
templates.append(desc + " (" + i.name + ")")
except Exception as e:
templates.append(f"无描述模板 ({i.name})")
print(e)
return templates
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
with ScrollableContainer(id="vice_container"):
yield Label(f"[b]空白单元集创建向导\n")
yield Markdown(
"> 提示: 你可能注意到当选中文本框时底栏和操作按键绑定将被覆盖 \n只需选中(使用鼠标或 Tab)选择框即可恢复底栏功能"
)
yield Markdown("1. 键入单元集名称")
yield Input(placeholder="单元集名称", id="name_input")
yield Markdown(
"> 单元集名称不应与现有单元集重复. \n> 新的单元集文件将创建在 ./nucleon/你输入的名称.toml"
)
yield Label(f"\n")
yield Markdown("2. 选择单元集模板")
LINES = self.search_templates()
"""带有宏支持的空白单元集 ({ver})
古诗词模板单元集 ({ver})
英语词汇和短语模板单元集 ({ver})
"""
yield Select.from_values(LINES, prompt="选择类型", id="template_select")
yield Markdown("> 新单元集的版本号将和主程序版本保持同步")
yield Label(f"\n")
yield Markdown("3. 输入常见附加元数据 (可选)")
yield Input(placeholder="作者", id="author_input")
yield Input(placeholder="内容描述", id="desc_input")
yield Button(
"新建空白单元集",
id="submit_button",
variant="primary",
classes="start-button",
)
yield Footer()
def on_mount(self):
self.query_one("#submit_button").focus()
def action_go_back(self):
self.app.pop_screen()
def action_quit_app(self):
self.app.exit()
def on_button_pressed(self, event) -> None:
event.stop()
if event.button.id == "submit_button":
# 获取输入值
name_input = self.query_one("#name_input")
template_select = self.query_one("#template_select")
author_input = self.query_one("#author_input")
desc_input = self.query_one("#desc_input")
name = name_input.value.strip() # type: ignore
author = author_input.value.strip() # type: ignore
desc = desc_input.value.strip() # type: ignore
selected = template_select.value # type: ignore
# 验证
if not name:
self.notify("单元集名称不能为空", severity="error")
return
# 获取配置路径
config = config_var.get()
nucleon_dir = Path(config["paths"]["nucleon_dir"])
template_dir = Path(config["paths"]["template_dir"])
# 检查文件是否已存在
nucleon_path = nucleon_dir / f"{name}.toml"
if nucleon_path.exists():
self.notify(f"单元集 '{name}' 已存在", severity="error")
return
# 确定模板文件
if selected is None:
self.notify("请选择一个模板", severity="error")
return
# selected 是描述字符串, 格式如 "描述 (filename.toml)"
# 提取文件名
import re
match = re.search(r"\(([^)]+)\)$", selected)
if not match:
self.notify("模板选择格式无效", severity="error")
return
template_filename = match.group(1)
template_path = template_dir / template_filename
if not template_path.exists():
self.notify(f"模板文件不存在: {template_filename}", severity="error")
return
# 加载模板
try:
with open(template_path, "r", encoding="utf-8") as f:
template_data = toml.load(f)
except Exception as e:
self.notify(f"加载模板失败: {e}", severity="error")
return
# 更新元数据
metadata = template_data.get("__metadata__", {})
attribution = metadata.get("attribution", {})
if author:
attribution["author"] = author
if desc:
attribution["desc"] = desc
attribution["name"] = name
# 可选: 设置版本
attribution["version"] = ver
metadata["attribution"] = attribution
template_data["__metadata__"] = metadata
# 确保 nucleon_dir 存在
nucleon_dir.mkdir(parents=True, exist_ok=True)
# 写入新文件
try:
with open(nucleon_path, "w", encoding="utf-8") as f:
toml.dump(template_data, f)
except Exception as e:
self.notify(f"保存单元集失败: {e}", severity="error")
return
self.notify(f"单元集 '{name}' 创建成功")
self.app.pop_screen()

View File

@@ -0,0 +1,246 @@
#!/usr/bin/env python3
from textual.app import ComposeResult
from textual.widgets import (
Header,
Footer,
Label,
Button,
Static,
ProgressBar,
)
from textual.containers import ScrollableContainer, Horizontal
from textual.containers import ScrollableContainer
from textual.screen import Screen
import pathlib
import heurams.kernel.particles as pt
import heurams.services.hasher as hasher
from heurams.context import *
from textual.worker import get_current_worker
class PrecachingScreen(Screen):
"""预缓存音频文件屏幕
缓存记忆单元音频文件, 全部(默认) 或部分记忆单元(可选参数传入)
Args:
nucleons (list): 可选列表, 仅包含 Nucleon 对象
desc (list): 可选字符串, 包含对此次调用的文字描述
"""
SUB_TITLE = "缓存管理器"
BINDINGS = [("q", "go_back", "返回")]
def __init__(self, nucleons: list = [], desc: str = ""):
super().__init__(name=None, id=None, classes=None)
self.nucleons = nucleons
self.is_precaching = False
self.current_file = ""
self.current_item = ""
self.progress = 0
self.total = len(nucleons)
self.processed = 0
self.precache_worker = None
self.cancel_flag = 0
self.desc = desc
for i in nucleons:
i: pt.Nucleon
i.do_eval()
# print("完成 EVAL")
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
with ScrollableContainer(id="precache_container"):
yield Label("[b]音频预缓存[/b]", classes="title-label")
if self.nucleons:
yield Static(f"目标单元归属: [b]{self.desc}[/b]", classes="target-info")
yield Static(f"单元数量: {len(self.nucleons)}", classes="target-info")
else:
yield Static("目标: 所有单元", classes="target-info")
yield Static(id="status", classes="status-info")
yield Static(id="current_item", classes="current-item")
yield ProgressBar(total=100, show_eta=False, id="progress_bar")
with Horizontal(classes="button-group"):
if not self.is_precaching:
yield Button("开始预缓存", id="start_precache", variant="primary")
else:
yield Button("取消预缓存", id="cancel_precache", variant="error")
yield Button("清空缓存", id="clear_cache", variant="warning")
yield Button("返回", id="go_back", variant="default")
yield Static("若您离开此界面, 未完成的缓存进程会自动停止.")
yield Static('缓存程序支持 "断点续传".')
yield Footer()
def on_mount(self):
"""挂载时初始化状态"""
self.update_status("就绪", "等待开始...")
def update_status(self, status, current_item="", progress=None):
"""更新状态显示"""
status_widget = self.query_one("#status", Static)
item_widget = self.query_one("#current_item", Static)
progress_bar = self.query_one("#progress_bar", ProgressBar)
status_widget.update(f"状态: {status}")
item_widget.update(f"当前项目: {current_item}" if current_item else "")
if progress is not None:
progress_bar.progress = progress
progress_bar.advance(0) # 刷新显示
def precache_by_text(self, text: str):
"""预缓存单段文本的音频"""
from heurams.context import rootdir, workdir, config_var
cache_dir = pathlib.Path(config_var.get()["paths"]["cache_dir"])
cache_dir.mkdir(parents=True, exist_ok=True)
cache_file = cache_dir / f"{hasher.get_md5(text)}.wav"
if not cache_file.exists():
try: # TODO: 调用模块消除tts耦合
import edge_tts as tts
communicate = tts.Communicate(text, "zh-CN-XiaoxiaoNeural")
communicate.save_sync(str(cache_file))
return 1
except Exception as e:
print(f"预缓存失败 '{text}': {e}")
return 0
return 1
def precache_by_nucleon(self, nucleon: pt.Nucleon):
"""依据 Nucleon 缓存"""
# print(nucleon.metadata['formation']['tts_text'])
ret = self.precache_by_text(nucleon.metadata["formation"]["tts_text"])
return ret
# print(f"TTS 缓存: {nucleon.metadata['formation']['tts_text']}")
def precache_by_list(self, nucleons: list):
"""依据 Nucleons 列表缓存"""
for idx, nucleon in enumerate(nucleons):
# print(f"PROC: {nucleon}")
worker = get_current_worker()
if worker and worker.is_cancelled: # 函数在worker中执行且已被取消
return False
text = nucleon.metadata["formation"]["tts_text"]
# self.current_item = text[:30] + "..." if len(text) > 50 else text
# print(text)
self.processed += 1
# print(self.processed)
# print(self.total)
progress = int((self.processed / self.total) * 100) if self.total > 0 else 0
# print(progress)
self.update_status(f"正处理 ({idx + 1}/{len(nucleons)})", text, progress)
ret = self.precache_by_nucleon(nucleon)
if not ret:
self.update_status(
"出错",
f"处理失败, 跳过: {self.current_item}",
)
import time
time.sleep(1)
if self.cancel_flag:
worker.cancel()
self.cancel_flag = 0
return False
return True
def precache_by_nucleons(self):
# print("开始缓存")
ret = self.precache_by_list(self.nucleons)
# print(f"返回 {ret}")
return ret
def precache_by_filepath(self, path: pathlib.Path):
"""预缓存单个文件的所有内容"""
lst = list()
for i in pt.load_nucleon(path):
lst.append(i[0])
return self.precache_by_list(lst)
def precache_all_files(self):
"""预缓存所有文件"""
from heurams.context import rootdir, workdir, config_var
nucleon_path = pathlib.Path(config_var.get()["paths"]["nucleon_dir"])
nucleon_files = [
f for f in nucleon_path.iterdir() if f.suffix == ".toml"
] # TODO: 解耦合
# 计算总项目数
self.total = 0
nu = list()
for file in nucleon_files:
try:
for i in pt.load_nucleon(file):
nu.append(i[0])
except:
continue
self.total = len(nu)
for i in nu:
i: pt.Nucleon
i.do_eval()
return self.precache_by_list(nu)
def on_button_pressed(self, event: Button.Pressed) -> None:
event.stop()
if event.button.id == "start_precache" and not self.is_precaching:
# 开始预缓存
if self.nucleons:
self.precache_worker = self.run_worker(
self.precache_by_nucleons,
thread=True,
exclusive=True,
exit_on_error=True,
)
else:
self.precache_worker = self.run_worker(
self.precache_all_files,
thread=True,
exclusive=True,
exit_on_error=True,
)
elif event.button.id == "cancel_precache" and self.is_precaching:
# 取消预缓存
if self.precache_worker:
self.precache_worker.cancel()
self.is_precaching = False
self.processed = 0
self.progress = 0
self.update_status("已取消", "预缓存操作被用户取消", 0)
elif event.button.id == "clear_cache":
# 清空缓存
try:
import shutil
from heurams.context import rootdir, workdir, config_var
shutil.rmtree(
f"{config_var.get()["paths"]["cache_dir"]}", ignore_errors=True
)
self.update_status("已清空", "音频缓存已清空", 0)
except Exception as e:
self.update_status("错误", f"清空缓存失败: {e}")
self.cancel_flag = 1
self.processed = 0
self.progress = 0
elif event.button.id == "go_back":
self.action_go_back()
def action_go_back(self):
if self.is_precaching and self.precache_worker:
self.precache_worker.cancel()
self.app.pop_screen()
def action_quit_app(self):
if self.is_precaching and self.precache_worker:
self.precache_worker.cancel()
self.app.exit()

View File

@@ -0,0 +1,144 @@
#!/usr/bin/env python3
from textual.app import ComposeResult
from textual.widgets import (
Header,
Footer,
Label,
Static,
Button,
Markdown,
)
from textual.containers import ScrollableContainer
from textual.screen import Screen
from heurams.context import config_var
import heurams.kernel.particles as pt
import heurams.services.hasher as hasher
from heurams.context import *
from textual.reactive import reactive
from textual.widget import Widget
from heurams.services.logger import get_logger
logger = get_logger(__name__)
class PreparationScreen(Screen):
SUB_TITLE = "准备记忆集"
BINDINGS = [
("q", "go_back", "返回"),
("p", "precache", "预缓存音频"),
("d", "toggle_dark", ""),
("0,1,2,3", "app.push_screen('about')", ""),
]
scheduled_num = reactive(config_var.get()["scheduled_num"])
def __init__(self, nucleon_file: pathlib.Path, electron_file: pathlib.Path) -> None:
super().__init__(name=None, id=None, classes=None)
self.nucleon_file = nucleon_file
self.electron_file = electron_file
self.nucleons_with_orbital = pt.load_nucleon(self.nucleon_file)
self.electrons = pt.load_electron(self.electron_file)
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
with ScrollableContainer(id="vice_container"):
yield Label(f"准备就绪: [b]{self.nucleon_file.stem}[/b]\n")
yield Label(
f"内容源文件: {config_var.get()['paths']['nucleon_dir']}/[b]{self.nucleon_file.name}[/b]"
)
yield Label(
f"元数据文件: {config_var.get()['paths']['electron_dir']}/[b]{self.electron_file.name}[/b]"
)
yield Label(f"\n单元数量: {len(self.nucleons_with_orbital)}\n")
yield Label(f"单次记忆数量: {self.scheduled_num}", id="schnum_label")
yield Button(
"开始记忆",
id="start_memorizing_button",
variant="primary",
classes="start-button",
)
yield Button(
"预缓存音频",
id="precache_button",
variant="success",
classes="precache-button",
)
yield Static(f"\n单元预览:\n")
yield Markdown(self._get_full_content().replace("/", ""), classes="full")
yield Footer()
# def watch_scheduled_num(self, old_scheduled_num, new_scheduled_num):
# logger.debug("响应", old_scheduled_num, "->", new_scheduled_num)
# try:
# one = self.query_one("#schnum_label")
# one.update(f"单次记忆数量: {new_scheduled_num}") # type: ignore
# except:
# pass
def _get_full_content(self):
content = ""
for nucleon, orbital in self.nucleons_with_orbital:
nucleon: pt.Nucleon
# print(nucleon.payload)
content += " - " + nucleon["content"] + " \n"
return content
def action_go_back(self):
self.app.pop_screen()
def action_precache(self):
from ..screens.precache import PrecachingScreen
lst = list()
for i in self.nucleons_with_orbital:
lst.append(i[0])
precache_screen = PrecachingScreen(lst)
self.app.push_screen(precache_screen)
def action_quit_app(self):
self.app.exit()
def on_button_pressed(self, event: Button.Pressed) -> None:
event.stop()
logger.debug("按下按钮")
if event.button.id == "start_memorizing_button":
atoms = list()
for nucleon, orbital in self.nucleons_with_orbital:
atom = pt.Atom(nucleon.ident)
atom.link("nucleon", nucleon)
try:
atom.link("electron", self.electrons[nucleon.ident])
except KeyError:
atom.link("electron", pt.Electron(nucleon.ident))
atom.link("orbital", orbital)
atom.link("nucleon_fmt", "toml")
atom.link("electron_fmt", "json")
atom.link("orbital_fmt", "toml")
atom.link("nucleon_path", self.nucleon_file)
atom.link("electron_path", self.electron_file)
atom.link("orbital_path", None)
atoms.append(atom)
atoms_to_provide = list()
left_new = self.scheduled_num
for i in atoms:
i: pt.Atom
if i.registry["electron"].is_due():
atoms_to_provide.append(i)
else:
if i.registry["electron"].is_activated():
pass
else:
left_new -= 1
if left_new >= 0:
atoms_to_provide.append(i)
logger.debug(f"ATP: {atoms_to_provide}")
from .memorizor import MemScreen
memscreen = MemScreen(atoms_to_provide)
self.app.push_screen(memscreen)
elif event.button.id == "precache_button":
self.action_precache()

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env python3
from textual.app import ComposeResult
from textual.widgets import (
Header,
Footer,
Label,
Button,
Static,
ProgressBar,
)
from textual.containers import ScrollableContainer, Horizontal
from textual.containers import ScrollableContainer
from textual.screen import Screen
import pathlib
import heurams.kernel.particles as pt
import heurams.services.hasher as hasher
from heurams.context import *
from textual.worker import get_current_worker
class SyncScreen(Screen):
BINDINGS = [("q", "go_back", "返回")]
def __init__(self, nucleons: list = [], desc: str = ""):
super().__init__(name=None, id=None, classes=None)
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
with ScrollableContainer(id="sync_container"):
pass
yield Footer()
def on_mount(self):
"""挂载时初始化状态"""
def update_status(self, status, current_item="", progress=None):
"""更新状态显示"""
def on_button_pressed(self, event: Button.Pressed) -> None:
event.stop()
def action_go_back(self):
self.app.pop_screen()
def action_quit_app(self):
self.app.exit()

View File