15 Commits
v0.3.6 ... main

Author SHA1 Message Date
ebeb62f72d 性能改进 2025-10-07 23:30:16 +08:00
d71d8eb1ec 更新 2025-10-07 23:23:17 +08:00
4ac12479cc 修改 2025-10-07 03:00:42 +08:00
7fc53e4113 算法与交互改进, 以及同步原型 2025-10-07 02:49:26 +08:00
06c62e284d 更新版本号 2025-10-03 13:41:41 +08:00
d5d31eb5fe 改进 2025-10-02 16:49:27 +08:00
156f558a45 重构预缓存实用程序, 并保留分离式旧版本 2025-10-02 14:55:24 +08:00
e96832a60c 若干改进 2025-10-02 14:40:40 +08:00
e1a5de74ad 更新自述文件 2025-10-02 00:59:02 +08:00
90339be30e 更新自述文件 2025-10-02 00:58:30 +08:00
4da80d26a3 添加规划计算器实用程序, 并添加 shebang 和一些优化 2025-10-02 00:40:41 +08:00
cb062788a7 增加再学习机制和状态反馈 2025-10-01 12:59:41 +08:00
acfd179435 改进 2025-09-30 22:27:34 +08:00
d1b606782f 新建单元集界面改进 2025-09-30 00:38:48 +08:00
784cf41003 新建单元集界面 2025-09-30 00:13:54 +08:00
16 changed files with 414 additions and 33 deletions

2
.gitignore vendored
View File

@@ -5,3 +5,5 @@ __pycache__/
scripts/ scripts/
.idea .idea
cache cache
nucleon/test.toml
electron/test.toml

View File

@@ -1,5 +1,6 @@
# 潜进 (HeurAMS) - 启发式辅助记忆程序 # 潜进 (HeurAMS) - 启发式辅助记忆程序
> 形人而我无形,**则我专而敌分** > 惟算法之可恃兮, **筹来者于未央**
> 扶大厦之将倾兮, **挽狂澜于既倒**
## 概述 ## 概述
"潜进" (HeurAMS: Heuristic Auxiliary Memorizing Scheduler, 启发式记忆辅助调度器) 是为习题册, 古诗词, 及其他问答/记忆/理解型知识设计的辅助记忆软件, 提供动态规划的优化记忆方案 "潜进" (HeurAMS: Heuristic Auxiliary Memorizing Scheduler, 启发式记忆辅助调度器) 是为习题册, 古诗词, 及其他问答/记忆/理解型知识设计的辅助记忆软件, 提供动态规划的优化记忆方案

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
import time import time
import pathlib import pathlib
import toml import toml

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
from textual.app import App, ComposeResult from textual.app import App, ComposeResult
from textual.events import Event from textual.events import Event
from textual.widgets import ( from textual.widgets import (
@@ -29,6 +30,7 @@ class Composition:
screen: Screen, screen: Screen,
reactor, reactor,
atom: Tuple[pt.Electron, pt.Nucleon, Dict] = pt.Atom.placeholder(), atom: Tuple[pt.Electron, pt.Nucleon, Dict] = pt.Atom.placeholder(),
extra = {}
): ):
self.screen = screen self.screen = screen
self.atom = atom self.atom = atom
@@ -58,7 +60,7 @@ class Composition:
class Finished(Composition): class Finished(Composition):
def __init__(self, screen: Screen, reactor, atom: Tuple[pt.Electron, pt.Nucleon, Dict]): def __init__(self, screen: Screen, reactor, atom: Tuple[pt.Electron, pt.Nucleon, Dict], extra = {}):
super().__init__(screen, reactor, atom) super().__init__(screen, reactor, atom)
def compose(self): def compose(self):
@@ -66,7 +68,7 @@ class Finished(Composition):
class Placeholder(Composition): class Placeholder(Composition):
def __init__(self, screen: Screen): def __init__(self, screen: Screen, extra = {}):
self.screen = screen self.screen = screen
def compose(self): def compose(self):
@@ -78,7 +80,7 @@ class Placeholder(Composition):
class Recognition(Composition): class Recognition(Composition):
def __init__(self, screen: Screen, reactor, atom: Tuple[pt.Electron, pt.Nucleon, Dict]): def __init__(self, screen: Screen, reactor, atom: Tuple[pt.Electron, pt.Nucleon, Dict], extra = {}):
super().__init__(screen, reactor, atom) super().__init__(screen, reactor, atom)
def compose(self): def compose(self):
@@ -130,7 +132,7 @@ class Recognition(Composition):
class BasicEvaluation(Composition): class BasicEvaluation(Composition):
def __init__(self, screen: Screen, reactor, atom: Tuple[pt.Electron, pt.Nucleon, Dict]): def __init__(self, screen: Screen, reactor, atom: Tuple[pt.Electron, pt.Nucleon, Dict], extra = {}):
super().__init__(screen, reactor, atom) super().__init__(screen, reactor, atom)
def compose(self): def compose(self):
@@ -167,8 +169,9 @@ class BasicEvaluation(Composition):
class FillBlank(Composition): class FillBlank(Composition):
def __init__(self, screen: Screen, reactor, atom: Tuple[pt.Electron, pt.Nucleon, Dict]): def __init__(self, screen: Screen, reactor, atom: Tuple[pt.Electron, pt.Nucleon, Dict], extra:Dict = {}):
super().__init__(screen, reactor, atom) super().__init__(screen, reactor, atom)
self.extra = extra
self.inputlist = [] self.inputlist = []
self.hashtable = {} self.hashtable = {}
self._work() self._work()
@@ -180,6 +183,8 @@ class FillBlank(Composition):
random.shuffle(self.ans) random.shuffle(self.ans)
def compose(self): def compose(self):
if self.extra.get("feedback_msg"):
yield Label("反馈提示:" + self.extra["feedback_msg"])
yield Label(self.puzzle.wording, id=self.regid("sentence")) yield Label(self.puzzle.wording, id=self.regid("sentence"))
yield Label(f"当前输入: {self.inputlist}", id=self.regid("inputpreview")) yield Label(f"当前输入: {self.inputlist}", id=self.regid("inputpreview"))
for i in self.ans: for i in self.ans:
@@ -206,11 +211,11 @@ class FillBlank(Composition):
else: else:
self.inputlist = [] self.inputlist = []
self.reactor.report(self.atom, 2) self.reactor.report(self.atom, 2)
return 1 return 2
class DrawCard(Composition): class DrawCard(Composition):
def __init__(self, screen: Screen, reactor, atom: Tuple[pt.Electron, pt.Nucleon, Dict]): def __init__(self, screen: Screen, reactor, atom: Tuple[pt.Electron, pt.Nucleon, Dict], extra = {}):
super().__init__(screen, reactor, atom) super().__init__(screen, reactor, atom)
self.inputlist = [] self.inputlist = []
self.hashtable = {} self.hashtable = {}
@@ -247,7 +252,7 @@ class DrawCard(Composition):
else: else:
self.inputlist = [] self.inputlist = []
self.reactor.report(self.atom, 2) self.reactor.report(self.atom, 2)
return 1 return 2
registry = { registry = {

View File

@@ -5,6 +5,6 @@ time_override = -1
# [调试] 一键通过 # [调试] 一键通过
quick_pass = 0 quick_pass = 0
# 对于每个项目的新记忆核子数量 # 对于每个项目的新记忆核子数量
tasked_number = 6 tasked_number = 8
# UTC 时间戳修正 用于 UNIX 日时间戳的生成修正, 单位为秒 # UTC 时间戳修正 用于 UNIX 日时间戳的生成修正, 单位为秒
timezone_offset = +28800 # 中国标准时间 (UTC+8) timezone_offset = +28800 # 中国标准时间 (UTC+8)

67
estimator.py Normal file
View File

@@ -0,0 +1,67 @@
#!/usr/bin/env python3
# 模拟规划完成所有记忆单元集文件的最小时间
import os
import particles as pt
import datetime
import pathlib
import math
import time
MINIMAL_REPEATATION = 2
FILTER = "ALL"
WORST_EF = 5
PAYLOAD = 16
print("SM-2 任务预规划实用程序")
print(f"运行时刻: {datetime.datetime.now()}")
print(f" > 筛选器模式: {FILTER}")
print(f" > 最小重复次数设置: {MINIMAL_REPEATATION}")
print(f" > 最坏难度系数: {WORST_EF}")
print(f" > 单日单元负荷: {PAYLOAD}")
print("--------")
filelist = []
if FILTER == "ALL":
for i in os.listdir('./nucleon'):
if "toml" in i:
print("扫描到记忆单元集: " + i)
filelist.append(i)
print(f"共需记忆 {len(filelist)} 个记忆单元集")
else:
filelist = [FILTER]
print("--------")
time_counter = 0
text_counter = 0
content_counter = 0
for i in filelist:
print(f"处理: {i}")
nucu = pt.NucleonUnion(pathlib.Path("./nucleon/" + i))
print(f"记忆单元数: {len(nucu.nucleons)}")
content_cnt = 0
metadata_trsl_cnt = 0
metadata_note_cnt = 0
metadata_kwrd_cnt = 0
for i in nucu.nucleons:
if i.content[-1] != "/":
print('检测到不规范字符串')
print(i.content)
content_cnt += len(i.content)
metadata_trsl_cnt += len(str(i.metadata["translation"]))
metadata_kwrd_cnt += len(str(i.metadata["keyword_note"]))
metadata_note_cnt += len(str(i.metadata["note"]))
print(" - 原文文字数: " + str(content_cnt))
metadata_cnt = metadata_kwrd_cnt + metadata_note_cnt + metadata_trsl_cnt
print(" - 元数据字数: " + str(metadata_cnt) + f"\n = {metadata_trsl_cnt}[翻译] + {metadata_kwrd_cnt}[关键词] + {metadata_note_cnt}[笔记]")
print(f" - 总文字数: {content_cnt + metadata_cnt}")
print(f"独占记忆时间: {len(nucu.nucleons) / PAYLOAD} -> {math.ceil(len(nucu.nucleons) / PAYLOAD)}")
#time.sleep(0.1)
text_counter += (content_cnt + metadata_cnt)
content_counter += content_cnt
time_counter += math.ceil(len(nucu.nucleons) / PAYLOAD)
print("--------")
for i in range(MINIMAL_REPEATATION):
print(f"若按计划进行, 最长需要 {time_counter + (i + 1) * 6} 天完成全部内容的第 {i + 1} 次记忆")
print(f"规划包含 {content_counter} 字, 附加了 {text_counter - content_counter} 字的元数据内容, 共计 {text_counter}")

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
from textual.app import App from textual.app import App
import screens import screens
import os import os

View File

@@ -1,2 +1,3 @@
ver = "0.3.6" #!/usr/bin/env python3
stage = "stable" ver = "0.3.8"
stage = "production"

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
import pathlib import pathlib
import toml import toml
import time import time

View File

@@ -1,4 +1,5 @@
# 音频预缓存实用程序, 独立于主程序之外, 但依赖其他组件 #!/usr/bin/env python3
# 音频预缓存实用程序(旧版), 独立于主程序之外, 但依赖其他组件
import particles as pt import particles as pt
import auxiliary as aux import auxiliary as aux
import edge_tts as tts import edge_tts as tts
@@ -44,7 +45,7 @@ def walk(path_str: str):
walk(path_str) walk(path_str)
if __name__ == "__main__": if __name__ == "__main__":
print("音频预缓存实用程序") print("音频预缓存实用程序(旧版)")
print("A: 全部缓存") print("A: 全部缓存")
print("C: 清空缓存") print("C: 清空缓存")

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
import random import random

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
import typing import typing
import particles as pt import particles as pt
import pathlib import pathlib
@@ -40,7 +41,7 @@ class Glimpse():
class Apparatus(): class Apparatus():
"""反应器对象, 决策一个原子的不同记忆方式, 并反馈到布局""" """反应器对象, 决策一个原子的不同记忆方式, 并反馈到布局"""
def __init__(self, screen, reactor, atom): def __init__(self, screen, reactor, atom, is_review = 0):
self.electron: pt.Electron = atom[0] self.electron: pt.Electron = atom[0]
self.nucleon: pt.Nucleon = atom[1] self.nucleon: pt.Nucleon = atom[1]
self.positron: dict = atom[2] self.positron: dict = atom[2]
@@ -55,11 +56,14 @@ class Apparatus():
continue continue
if i == "fill_blank_test": # 加深 if i == "fill_blank_test": # 加深
self.procession.append(comps.registry[i](screen, reactor, atom)) self.procession.append(comps.registry[i](screen, reactor, atom))
self.procession.append(comps.registry[i](screen, reactor, atom)) # self.procession.append(comps.registry[i](screen, reactor, atom))
self.procession.append(comps.registry[i](screen, reactor, atom)) self.procession.append(comps.registry[i](screen, reactor, atom))
# self.procession.reverse() # self.procession.reverse()
random.shuffle(self.procession) random.shuffle(self.procession)
if self.positron["is_new_activation"] == 0:
self.procession.append(comps.registry['recognition'](screen, reactor, atom))
if is_review == 1:
self.procession = [self.procession[-2], self.procession[-1]]
def iterator(self): def iterator(self):
yield from self.procession yield from self.procession
@@ -68,6 +72,7 @@ class Reactor():
"""反应堆对象, 处理和分配一次文件记忆流程的资源与策略""" """反应堆对象, 处理和分配一次文件记忆流程的资源与策略"""
def __init__(self, nucleon_file: pt.NucleonUnion, electron_file: pt.ElectronUnion, screen, tasked_num): def __init__(self, nucleon_file: pt.NucleonUnion, electron_file: pt.ElectronUnion, screen, tasked_num):
# 导入原子对象 # 导入原子对象
self.stage = 0
self.nucleon_file = nucleon_file self.nucleon_file = nucleon_file
self.electron_file = electron_file self.electron_file = electron_file
self.tasked_num = tasked_num self.tasked_num = tasked_num
@@ -131,6 +136,7 @@ class Reactor():
2: self.atoms_new, 2: self.atoms_new,
3: (self.atoms_new + self.atoms_review) 3: (self.atoms_new + self.atoms_review)
} }
self.stage = stage
ret = 1 ret = 1
if stage == 1 and len(processions[1]) == 0: if stage == 1 and len(processions[1]) == 0:
stage = 2 stage = 2
@@ -162,7 +168,7 @@ class Reactor():
return -1 # 此轮已完成 return -1 # 此轮已完成
self.index += step self.index += step
self.current_atom = self.procession[self.index] self.current_atom = self.procession[self.index]
self.current_appar = Apparatus(self.screen, self, self.current_atom).iterator() self.current_appar = Apparatus(self.screen, self, self.current_atom, (self.stage == 1)).iterator()
return 0 return 0
def save(self): def save(self):

View File

@@ -1,7 +1,9 @@
#!/usr/bin/env python3
from textual.app import App, ComposeResult from textual.app import App, ComposeResult
from textual.widgets import ( from textual.widgets import (
Header, Header,
Footer, Footer,
Input,
ListView, ListView,
ProgressBar, ProgressBar,
DirectoryTree, DirectoryTree,
@@ -10,12 +12,13 @@ from textual.widgets import (
Markdown, Markdown,
Static, Static,
Button, Button,
Select,
) )
from textual.containers import Container, Horizontal, Center from textual.containers import Container, Horizontal, Center
from textual.screen import Screen from textual.screen import Screen
from textual.worker import Worker, get_current_worker
import pathlib import pathlib
import threading import threading
import edge_tts as tts
from playsound import playsound from playsound import playsound
import particles as pt import particles as pt
from reactor import Reactor, Apparatus, Glimpse from reactor import Reactor, Apparatus, Glimpse
@@ -23,14 +26,202 @@ import auxiliary as aux
import compositions as compo import compositions as compo
import builtins import builtins
import metadata import metadata
import time
import shutil
config = aux.ConfigFile("config.toml") config = aux.ConfigFile("config.toml")
class PrecachingScreen(Screen):
"""预缓存音频文件屏幕"""
BINDINGS = [("q", "go_back", "返回"), ("escape", "quit_app", "退出")]
def __init__(self, nucleon_file = None):
super().__init__(name=None, id=None, classes=None)
self.nucleon_file = nucleon_file
self.is_precaching = False
self.current_file = ""
self.current_item = ""
self.progress = 0
self.total = 0
self.processed = 0
self.precache_worker = None
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
with Container(id="precache_container"):
yield Label("[b]音频预缓存[/b]", classes="title-label")
if self.nucleon_file:
yield Static(f"目标单元集: [b]{self.nucleon_file.name}[/b]", classes="target-info")
yield Static(f"单元数量: {len(self.nucleon_file.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 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_single_text(self, text: str):
"""预缓存单个文本的音频"""
cache_dir = pathlib.Path("./cache/voice/")
cache_dir.mkdir(parents=True, exist_ok=True)
cache = cache_dir / f"{aux.get_md5(text)}.wav"
if not cache.exists():
try:
import edge_tts as tts
communicate = tts.Communicate(text, "zh-CN-XiaoxiaoNeural")
communicate.save_sync(str(cache))
return True
except Exception as e:
print(f"预缓存失败 '{text}': {e}")
return False
return True
def precache_file(self, nucleon_union: pt.NucleonUnion):
"""预缓存单个文件的所有内容"""
self.current_file = nucleon_union.name
total_items = len(nucleon_union.nucleons)
for idx, nucleon in enumerate(nucleon_union.nucleons):
# 检查是否被取消
worker = get_current_worker()
if worker and worker.is_cancelled:
return False
text = nucleon['content'].replace('/', '')
self.current_item = text[:50] + "..." if len(text) > 50 else text
self.processed += 1
# 更新进度
progress = int((self.processed / self.total) * 100) if self.total > 0 else 0
self.update_status(
f"处理中: {nucleon_union.name} ({idx+1}/{total_items})",
self.current_item,
progress
)
# 预缓存音频
success = self.precache_single_text(text)
if not success:
self.update_status("错误", f"处理失败: {self.current_item}")
time.sleep(1) # 短暂暂停以便用户看到错误信息
return True
def precache_all_files(self):
"""预缓存所有文件"""
nucleon_path = pathlib.Path("./nucleon")
nucleon_files = [f for f in nucleon_path.iterdir() if f.suffix == ".toml"]
# 计算总项目数
self.total = 0
for file in nucleon_files:
try:
nu = pt.NucleonUnion(file)
self.total += len(nu.nucleons)
except:
continue
self.processed = 0
self.is_precaching = True
for file in nucleon_files:
try:
nu = pt.NucleonUnion(file)
if not self.precache_file(nu):
break # 用户取消
except Exception as e:
print(f"处理文件失败 {file}: {e}")
continue
self.is_precaching = False
self.update_status("完成", "所有音频文件已预缓存", 100)
def precache_single_file(self):
"""预缓存单个文件"""
if not self.nucleon_file:
return
self.total = len(self.nucleon_file.nucleons)
self.processed = 0
self.is_precaching = True
success = self.precache_file(self.nucleon_file)
self.is_precaching = False
if success:
self.update_status("完成", f"'{self.nucleon_file.name}' 音频文件已预缓存", 100)
else:
self.update_status("已取消", "预缓存操作被用户取消", 0)
def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "start_precache" and not self.is_precaching:
# 开始预缓存
if self.nucleon_file:
self.precache_worker = self.run_worker(self.precache_single_file, thread=True)
else:
self.precache_worker = self.run_worker(self.precache_all_files, thread=True)
elif event.button.id == "cancel_precache" and self.is_precaching:
# 取消预缓存
if self.precache_worker:
self.precache_worker.cancel()
self.is_precaching = False
self.update_status("已取消", "预缓存操作被用户取消", 0)
elif event.button.id == "clear_cache":
# 清空缓存
try:
shutil.rmtree("./cache/voice", ignore_errors=True)
self.update_status("已清空", "音频缓存已清空", 0)
except Exception as e:
self.update_status("错误", f"清空缓存失败: {e}")
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()
class MemScreen(Screen): class MemScreen(Screen):
BINDINGS = [ BINDINGS = [
("d", "toggle_dark", "改变色调"), ("d", "toggle_dark", "改变色调"),
("q", "pop_screen", "返回主菜单"), ("q", "pop_screen", "返回主菜单"),
("v", "play_voice", "朗读"), ("v", "play_voice", "朗读"),
# ("p", "precache_current", "预缓存当前单元集"), # 新增预缓存快捷键
] ]
if config.get("quick_pass"): if config.get("quick_pass"):
BINDINGS.append(("k", "quick_pass", "快速通过[调试]")) BINDINGS.append(("k", "quick_pass", "快速通过[调试]"))
@@ -43,6 +234,8 @@ class MemScreen(Screen):
tasked_num, tasked_num,
): ):
super().__init__(name=None, id=None, classes=None) super().__init__(name=None, id=None, classes=None)
self.nucleon_file = nucleon_file
self.electron_file = electron_file
self.reactor = Reactor(nucleon_file, electron_file, self, tasked_num) self.reactor = Reactor(nucleon_file, electron_file, self, tasked_num)
self.stage = 1 self.stage = 1
self.stage += self.reactor.set_round_templated(self.stage) self.stage += self.reactor.set_round_templated(self.stage)
@@ -54,6 +247,11 @@ class MemScreen(Screen):
print(self.reactor.forward()) print(self.reactor.forward())
#self._forward_judge(first_forward) #self._forward_judge(first_forward)
self.compo = next(self.reactor.current_appar) self.compo = next(self.reactor.current_appar)
self.feedback_state = 0 # 默认状态
self.feedback_state_map = {
0: "",
255: "回答有误, 请重试. 或者重新学习此单元",
}
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
if type(self.compo).__name__ == "Recognition": if type(self.compo).__name__ == "Recognition":
@@ -61,19 +259,27 @@ class MemScreen(Screen):
yield Header(show_clock=True) yield Header(show_clock=True)
with Center(): with Center():
yield Static( yield Static(
f"{len(self.reactor.procession) - self.reactor.index}/{len(self.reactor.procession)}" f"当前进度: {len(self.reactor.procession) - self.reactor.index}/{len(self.reactor.procession)}"
) )
yield Label(self.feedback_state_map[self.feedback_state])
yield from self.compo.compose() yield from self.compo.compose()
if self.feedback_state == 255:
yield Button("重新学习此单元", id="re-recognize", variant="warning")
yield Footer() yield Footer()
def on_mount(self): def on_mount(self):
pass pass
def on_button_pressed(self, event): def on_button_pressed(self, event):
try:
if event.button.id == "re-recognize":
return
except:
pass
ret = self.compo.handler(event, "button") ret = self.compo.handler(event, "button")
self._forward_judge(ret) self._forward_judge(ret)
def _forward_judge(self, ret): def _forward_judge(self, ret):
self.feedback_state = 0
if ret == -1: if ret == -1:
return return
if ret == 0: if ret == 0:
@@ -106,7 +312,11 @@ class MemScreen(Screen):
else: else:
self.refresh_ui() self.refresh_ui()
return return
if ret == 1: if ret >= 1:
if ret == 2:
self.feedback_state = 255 # 表示错误
else:
self.feedback_state = 0
self.refresh_ui() self.refresh_ui()
return return
@@ -120,6 +330,7 @@ class MemScreen(Screen):
cache_dir.mkdir(parents=True, exist_ok=True) cache_dir.mkdir(parents=True, exist_ok=True)
cache = cache_dir / f"{aux.get_md5(self.reactor.current_atom[1].content.replace('/',''))}.wav" cache = cache_dir / f"{aux.get_md5(self.reactor.current_atom[1].content.replace('/',''))}.wav"
if not cache.exists(): if not cache.exists():
import edge_tts as tts
communicate = tts.Communicate( communicate = tts.Communicate(
self.reactor.current_atom[1].content.replace("/", ""), self.reactor.current_atom[1].content.replace("/", ""),
"zh-CN-XiaoxiaoNeural", "zh-CN-XiaoxiaoNeural",
@@ -131,6 +342,11 @@ class MemScreen(Screen):
threading.Thread(target=play).start() threading.Thread(target=play).start()
def action_precache_current(self):
"""预缓存当前单元集的音频"""
precache_screen = PrecachingScreen(self.nucleon_file)
self.app.push_screen(precache_screen)
def action_quick_pass(self): def action_quick_pass(self):
self.reactor.report(self.reactor.current_atom, 5) self.reactor.report(self.reactor.current_atom, 5)
self._forward_judge(0) self._forward_judge(0)
@@ -140,9 +356,12 @@ class MemScreen(Screen):
def action_pop_screen(self): def action_pop_screen(self):
self.app.pop_screen() self.app.pop_screen()
class PreparationScreen(Screen): class PreparationScreen(Screen):
BINDINGS = [("q", "go_back", "返回"), ("escape", "quit_app", "退出")] BINDINGS = [
("q", "go_back", "返回"),
("escape", "quit_app", "退出"),
("p", "precache", "预缓存音频"), # 新增预缓存快捷键
]
def __init__( def __init__(
self, nucleon_file: pt.NucleonUnion, electron_file: pt.ElectronUnion self, nucleon_file: pt.NucleonUnion, electron_file: pt.ElectronUnion
@@ -158,12 +377,20 @@ class PreparationScreen(Screen):
yield Label(f"内容源文件对象: ./nucleon/[b]{self.nucleon_file.name}[/b].toml") yield Label(f"内容源文件对象: ./nucleon/[b]{self.nucleon_file.name}[/b].toml")
yield Label(f"元数据文件对象: ./electron/[b]{self.electron_file.name}[/b].toml") yield Label(f"元数据文件对象: ./electron/[b]{self.electron_file.name}[/b].toml")
yield Label(f"\n单元数量:{len(self.nucleon_file)}\n") yield Label(f"\n单元数量:{len(self.nucleon_file)}\n")
yield Button( yield Button(
"开始记忆", "开始记忆",
id="start_memorizing_button", id="start_memorizing_button",
variant="primary", variant="primary",
classes="start-button", classes="start-button",
) )
yield Button(
"预缓存音频",
id="precache_button",
variant="success",
classes="precache-button",
)
yield Static(f"\n单元预览:\n") yield Static(f"\n单元预览:\n")
yield Markdown(self._get_full_content().replace("/", ""), classes="full") yield Markdown(self._get_full_content().replace("/", ""), classes="full")
yield Footer() yield Footer()
@@ -177,6 +404,11 @@ class PreparationScreen(Screen):
def action_go_back(self): def action_go_back(self):
self.app.pop_screen() self.app.pop_screen()
def action_precache(self):
"""预缓存当前单元集的音频"""
precache_screen = PrecachingScreen(self.nucleon_file)
self.app.push_screen(precache_screen)
def action_quit_app(self): def action_quit_app(self):
self.app.exit() self.app.exit()
@@ -186,16 +418,64 @@ class PreparationScreen(Screen):
self.nucleon_file, self.electron_file, config.get("tasked_number", 6) self.nucleon_file, self.electron_file, config.get("tasked_number", 6)
) )
self.app.push_screen(newscr) self.app.push_screen(newscr)
elif event.button.id == "precache_button":
self.action_precache()
class NewNucleonScreen(Screen):
BINDINGS = [("q", "go_back", "返回"), ("escape", "quit_app", "退出")]
def __init__(self) -> None:
super().__init__(name=None, id=None, classes=None)
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
with Container(id="vice_container"):
yield Label(f"[b]新建空的单元集\n")
yield Markdown("1. 键入单元集名称")
yield Input(placeholder="单元集名称")
yield Markdown("> 单元集名称不应与现有单元集重复, 新的单元集文件将创建在 ./nucleon/你输入的名称.toml")
yield Label(f"\n")
yield Markdown("2. 选择单元集类型")
LINES = """
单一字符串
主字符串(带有附加属性)
动态单元集(使用宏)
""".splitlines()
yield Select.from_values(LINES, prompt="选择类型")
yield Label(f"\n")
yield Markdown("3. 输入附加元数据 (可选)")
yield Input(placeholder="作者")
yield Input(placeholder="内容描述")
yield Button(
"新建空单元集",
id="submit_button",
variant="primary",
classes="start-button",
)
yield Footer()
def action_go_back(self):
self.app.pop_screen()
def action_quit_app(self):
self.app.exit()
def on_button_pressed(self, event: Button.Pressed) -> None:
pass
class DashboardScreen(Screen): class DashboardScreen(Screen):
#BINDINGS = [("p", "precache_all", "预缓存所有音频")] # 新增全局预缓存快捷键
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Header(show_clock=True) yield Header(show_clock=True)
yield Container( yield Container(
Label(f'欢迎使用 "潜进" 启发式辅助记忆调度器, 版本 {metadata.ver}', classes="title-label"), Label(f'欢迎使用 "潜进" 启发式辅助记忆调度器, 版本 {metadata.ver}, 使用 {pt.Electron.algorithm} 调度算法', classes="title-label"),
Label(f"当前的 UNIX 日时间戳: {aux.get_daystamp()}"), Label(f"当前的 UNIX 日时间戳: {aux.get_daystamp()}"),
Label(f'包含时间戳修正: UTC+{config.get("timezone_offset")/3600}'), Label(f'包含时间戳修正: UTC+{config.get("timezone_offset")/3600}'),
Label("选择待学习的记忆单元:", classes="title-label"), Label("选择待学习或待修改的记忆单元:", classes="title-label"),
ListView(id="file-list", classes="file-list-view"), ListView(id="file-list", classes="file-list-view"),
#Button("新建空的单元集", id="new_nucleon_button"),
Button("音频预缓存实用程序", id="precache_all_button"),
Label(f"\"潜进\" 开放源代码软件项目 | 版本 {metadata.ver} {metadata.stage.capitalize()} | Wang Zhiyu 2025"), Label(f"\"潜进\" 开放源代码软件项目 | 版本 {metadata.ver} {metadata.stage.capitalize()} | Wang Zhiyu 2025"),
) )
yield Footer() yield Footer()
@@ -230,7 +510,7 @@ class DashboardScreen(Screen):
)) ))
else: else:
file_list_widget.append( file_list_widget.append(
ListItem(Static("在 ./nucleon/ 中未找到任何内容源数据文件.\n请放置文件后重启应用.")) ListItem(Static("在 ./nucleon/ 中未找到任何内容源数据文件.\n请放置文件后重启应用.\n或者新建空的单元集."))
) )
file_list_widget.disabled = True file_list_widget.disabled = True
@@ -256,6 +536,17 @@ class DashboardScreen(Screen):
) )
self.app.push_screen(PreparationScreen(nucleon_file, electron_file)) self.app.push_screen(PreparationScreen(nucleon_file, electron_file))
def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "new_nucleon_button":
newscr = NewNucleonScreen()
self.app.push_screen(newscr)
elif event.button.id == "precache_all_button":
self.action_precache_all()
def action_precache_all(self):
"""预缓存所有单元集的音频"""
precache_screen = PrecachingScreen()
self.app.push_screen(precache_screen)
def action_quit_app(self) -> None: def action_quit_app(self) -> None:
self.app.exit() self.app.exit()

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
from webshare import server from webshare import server
server = server.Server("python3 main.py", title="辅助记忆程序", host="0.0.0.0") server = server.Server("python3 main.py", title="辅助记忆程序", host="0.0.0.0")
server.serve() server.serve()

1
sync_t.py Normal file
View File

@@ -0,0 +1 @@
# 同步工具 原型

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
import os import os
import shutil import shutil
import subprocess import subprocess