14 Commits

Author SHA1 Message Date
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 412 additions and 32 deletions

2
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,6 @@ time_override = -1
# [调试] 一键通过
quick_pass = 0
# 对于每个项目的新记忆核子数量
tasked_number = 6
tasked_number = 8
# UTC 时间戳修正 用于 UNIX 日时间戳的生成修正, 单位为秒
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
import screens
import os

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,9 @@
#!/usr/bin/env python3
from textual.app import App, ComposeResult
from textual.widgets import (
Header,
Footer,
Input,
ListView,
ProgressBar,
DirectoryTree,
@@ -10,9 +12,11 @@ from textual.widgets import (
Markdown,
Static,
Button,
Select,
)
from textual.containers import Container, Horizontal, Center
from textual.screen import Screen
from textual.worker import Worker, get_current_worker
import pathlib
import threading
import edge_tts as tts
@@ -23,14 +27,201 @@ import auxiliary as aux
import compositions as compo
import builtins
import metadata
import time
import shutil
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:
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):
BINDINGS = [
("d", "toggle_dark", "改变色调"),
("q", "pop_screen", "返回主菜单"),
("v", "play_voice", "朗读"),
# ("p", "precache_current", "预缓存当前单元集"), # 新增预缓存快捷键
]
if config.get("quick_pass"):
BINDINGS.append(("k", "quick_pass", "快速通过[调试]"))
@@ -43,6 +234,8 @@ class MemScreen(Screen):
tasked_num,
):
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.stage = 1
self.stage += self.reactor.set_round_templated(self.stage)
@@ -54,6 +247,11 @@ class MemScreen(Screen):
print(self.reactor.forward())
#self._forward_judge(first_forward)
self.compo = next(self.reactor.current_appar)
self.feedback_state = 0 # 默认状态
self.feedback_state_map = {
0: "",
255: "回答有误, 请重试. 或者重新学习此单元",
}
def compose(self) -> ComposeResult:
if type(self.compo).__name__ == "Recognition":
@@ -61,19 +259,27 @@ class MemScreen(Screen):
yield Header(show_clock=True)
with Center():
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()
if self.feedback_state == 255:
yield Button("重新学习此单元", id="re-recognize", variant="warning")
yield Footer()
def on_mount(self):
pass
def on_button_pressed(self, event):
try:
if event.button.id == "re-recognize":
return
except:
pass
ret = self.compo.handler(event, "button")
self._forward_judge(ret)
def _forward_judge(self, ret):
self.feedback_state = 0
if ret == -1:
return
if ret == 0:
@@ -106,7 +312,11 @@ class MemScreen(Screen):
else:
self.refresh_ui()
return
if ret == 1:
if ret >= 1:
if ret == 2:
self.feedback_state = 255 # 表示错误
else:
self.feedback_state = 0
self.refresh_ui()
return
@@ -131,6 +341,11 @@ class MemScreen(Screen):
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):
self.reactor.report(self.reactor.current_atom, 5)
self._forward_judge(0)
@@ -140,9 +355,12 @@ class MemScreen(Screen):
def action_pop_screen(self):
self.app.pop_screen()
class PreparationScreen(Screen):
BINDINGS = [("q", "go_back", "返回"), ("escape", "quit_app", "退出")]
BINDINGS = [
("q", "go_back", "返回"),
("escape", "quit_app", "退出"),
("p", "precache", "预缓存音频"), # 新增预缓存快捷键
]
def __init__(
self, nucleon_file: pt.NucleonUnion, electron_file: pt.ElectronUnion
@@ -158,12 +376,20 @@ class PreparationScreen(Screen):
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"\n单元数量:{len(self.nucleon_file)}\n")
yield Button(
"开始记忆",
id="start_memorizing_button",
variant="primary",
classes="start-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()
@@ -177,6 +403,11 @@ class PreparationScreen(Screen):
def action_go_back(self):
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):
self.app.exit()
@@ -186,16 +417,64 @@ class PreparationScreen(Screen):
self.nucleon_file, self.electron_file, config.get("tasked_number", 6)
)
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):
#BINDINGS = [("p", "precache_all", "预缓存所有音频")] # 新增全局预缓存快捷键
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
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'包含时间戳修正: UTC+{config.get("timezone_offset")/3600}'),
Label("选择待学习的记忆单元:", classes="title-label"),
Label("选择待学习或待修改的记忆单元:", classes="title-label"),
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"),
)
yield Footer()
@@ -230,7 +509,7 @@ class DashboardScreen(Screen):
))
else:
file_list_widget.append(
ListItem(Static("在 ./nucleon/ 中未找到任何内容源数据文件.\n请放置文件后重启应用."))
ListItem(Static("在 ./nucleon/ 中未找到任何内容源数据文件.\n请放置文件后重启应用.\n或者新建空的单元集."))
)
file_list_widget.disabled = True
@@ -256,6 +535,17 @@ class DashboardScreen(Screen):
)
self.app.push_screen(PreparationScreen(nucleon_file, electron_file))
def action_quit_app(self) -> None:
self.app.exit()
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:
self.app.exit()

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
from webshare import server
server = server.Server("python3 main.py", title="辅助记忆程序", host="0.0.0.0")
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 shutil
import subprocess