15 Commits

Author SHA1 Message Date
2ad014fcd8 更新文件树 2025-08-16 07:33:42 +08:00
4ad289d02d 改进部署组件 2025-08-14 11:16:43 +08:00
28ccfdd227 删除运行时文件 2025-08-14 11:13:11 +08:00
f83d5c934d 增加若干元数据 2025-08-14 11:12:33 +08:00
4f9eb3b7d1 重命名文件 2025-08-12 19:10:14 +08:00
c44a38f3c8 更新自述文件 2025-08-09 22:44:02 +08:00
f760e7f0fa 预缓存实用程序改动 2025-08-09 08:41:40 +08:00
30eb45e1cb 更新自述文件 2025-08-06 07:52:42 +08:00
2a30f136cb 更新自述文件 2025-08-06 07:52:15 +08:00
051c4847b2 更新自述文件 2025-08-06 07:43:36 +08:00
0873caa5fc 若干善后改进 2025-08-06 07:42:43 +08:00
6d3d2e665c 实装自动评分系统 2025-08-06 06:46:30 +08:00
edf2f0868a 改进 2025-08-06 06:30:41 +08:00
d5ef5e84d0 规范代码 2025-08-06 06:11:30 +08:00
dd74dddf00 规范代码 2025-08-06 06:11:22 +08:00
80 changed files with 472 additions and 359 deletions

View File

@@ -0,0 +1,7 @@
# 贡献指南
## 使用 Nuitka 静态编译
运行
```bash
nuitka --clang --jobs=6 --standalone --onefile main.py
```

View File

@@ -1,10 +1,9 @@
# 潜进 (HeurAMS) - 实验型辅助记忆程序 # 潜进 (HeurAMS) - 启发式辅助记忆程序
> 形人而我无形,**则我专而敌分** > 形人而我无形,**则我专而敌分**
## 概述 ## 概述
"潜进" (HeurAMS, 中文含义: 启发式辅助记忆软件) 是为习题册, 古诗词, 及其他问答/记忆/理解型题目设计的记忆辅助软件, 提供优化记忆方案 "潜进" (HeurAMS) 是为习题册, 古诗词, 及其他问答/记忆/理解型知识设计的辅助记忆软件, 提供动态规划的优化记忆方案
## 技术集成与特性 ## 技术集成与特性
@@ -13,10 +12,10 @@
- 采用经实证的 SM-2 间隔迭代算法, 此算法亦用作 Anki 闪卡记忆软件的默认闪卡调度器 - 采用经实证的 SM-2 间隔迭代算法, 此算法亦用作 Anki 闪卡记忆软件的默认闪卡调度器
> 计划: 将添加 FSRS 算法 (Anki 的新可选闪卡调度器) 与一种 SM-15 变体算法作为后续替代 > 计划: 将添加 FSRS 算法 (Anki 的新可选闪卡调度器) 与一种 SM-15 变体算法作为后续替代
> 参考 https://github.com/slaypni/SM-15 > 参考 https://github.com/slaypni/SM-15
> 为什么使用 SM-15 的变体? > 使用 SM-15 的变体:
> SM-2 后续算法仅有论文, 无具体方程, 故使用一种基于 SM-15 描述实现的变体算法 > SM-2 后续算法并非完全开放, 故使用一种基于 SM-15 描述实现的变体算法
- 动态优化每首诗词的记忆间隔时间表 - 动态规划每个记忆单元的记忆间隔时间表
- 实时跟踪记忆曲线,优化长期记忆保留率与稳定性 - 动态跟踪记忆反馈数据,优化长期记忆保留率与稳定性
### 学习进程优化 ### 学习进程优化
- 逐字解析:支持逐字详细释义解析 - 逐字解析:支持逐字详细释义解析
@@ -30,12 +29,19 @@
- 简洁直观的复习流程设计 - 简洁直观的复习流程设计
## 屏幕截图 ## 屏幕截图
![scrshot2](readme_src/image_2.png)
![scrshot1](readme_src/image_1.png) > 单击图片以放大
<img src="./readme_src/img1.png" alt="img1" style="zoom: 33%;" />
<img src="./readme_src/img2.png" alt="img2" style="zoom:33%;" />
<img src="./readme_src/img3.png" alt="img3" style="zoom:33%;" />
<img src="./readme_src/img4.png" alt="img4" style="zoom:33%;" />
## 技术架构 ## 技术架构
> 有关技术与实现的细节, 请参阅 CONTRIBUTING.md > 有关技术与实现的细节, 请参阅 CONTRIBUTING.md
> 提交拉取请求以参与到此开放源代码项目 > 提交拉取请求以参与到此开放源代码项目
``` mermaid ``` mermaid
graph TD graph TD
subgraph 后端 subgraph 后端
@@ -63,9 +69,3 @@ graph TD
- 平台支持Windows / macOS / Linux / Android (需要 Termux 或 Linux) (终端或浏览器) - 平台支持Windows / macOS / Linux / Android (需要 Termux 或 Linux) (终端或浏览器)
- 网络连接:可预缓存语音文件, 需联网使用大模型服务功能 - 网络连接:可预缓存语音文件, 需联网使用大模型服务功能
## 使用 Nuitka 静态编译
运行
```bash
nuitka --clang --jobs=6 --standalone --onefile main.py
```

View File

@@ -1,33 +1,46 @@
import time import time
import pathlib import pathlib
import toml import toml
import typing
class ConfigFile(): class ConfigFile:
def __init__(self, path): def __init__(self, path: str):
self.path = pathlib.Path(path) self.path = pathlib.Path(path)
if self.path.exists() == 0: if not self.path.exists():
self.path.touch() self.path.touch()
self.data = dict() self.data = dict()
self._load()
def _load(self):
"""从文件加载配置数据"""
with open(self.path, 'r') as f: with open(self.path, 'r') as f:
try:
self.data = toml.load(f) self.data = toml.load(f)
def modify(self, key, value): except toml.TomlDecodeError:
self.data = {}
def modify(self, key: str, value: typing.Any):
"""修改配置值并保存"""
self.data[key] = value self.data[key] = value
self.save() self.save()
def save(self, path=""):
if path == "": def save(self, path: typing.Union[str, pathlib.Path] = ""):
path = self.path """保存配置到文件"""
with open(path, 'w') as f: save_path = pathlib.Path(path) if path else self.path
with open(save_path, 'w') as f:
toml.dump(self.data, f) toml.dump(self.data, f)
def get(self, key, default = None):
def get(self, key: str, default: typing.Any = None) -> typing.Any:
"""获取配置值,如果不存在返回默认值"""
return self.data.get(key, default) return self.data.get(key, default)
def get_daystamp() -> int:
config = ConfigFile("config.toml")
def get_daystamp() -> int:
"""获取当前日戳(以天为单位的整数时间戳)"""
config = ConfigFile("config.toml")
time_override = config.get("time_override", -1) time_override = config.get("time_override", -1)
if time_override is not None and time_override != -1: if time_override != -1:
#print(f"TIME OVERRIDEED TO {time_override}")
return int(time_override) return int(time_override)
return int(time.time() // (24 * 3600)) return int(time.time() // (24 * 3600))

View File

@@ -1,6 +1,16 @@
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 Collapsible, Header, Footer, Markdown, ListView, ListItem, Label, Static, Button from textual.widgets import (
Collapsible,
Header,
Footer,
Markdown,
ListView,
ListItem,
Label,
Static,
Button,
)
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.widget import Widget from textual.widget import Widget
@@ -12,56 +22,70 @@ import re
import random import random
import copy import copy
class Composition():
def __init__(self, screen: Screen, reactor, atom: Tuple[pt.Electron, pt.Nucleon, Dict] = pt.Atom.placeholder()): class Composition:
def __init__(
self,
screen: Screen,
reactor,
atom: Tuple[pt.Electron, pt.Nucleon, Dict] = pt.Atom.placeholder(),
):
self.screen = screen self.screen = screen
self.atom = atom self.atom = atom
from reactor import Reactor from reactor import Reactor
self.reactor: Reactor = reactor self.reactor: Reactor = reactor
self.reg = dict() self.reg = dict()
def regid(self, id_): def regid(self, id_):
self.reg[id_] = id_ + str(uuid.uuid4()) self.reg[id_] = id_ + str(uuid.uuid4())
return self.reg[id_] return self.reg[id_]
def getid(self, id_): def getid(self, id_):
if id_ not in self.reg.keys(): if id_ not in self.reg.keys():
return "None" return "None"
return self.reg[id_] return self.reg[id_]
def recid(self, id_): def recid(self, id_):
return id_[:-36] return id_[:-36]
def compose(self): def compose(self):
yield Label("示例标签", id="testlabel") yield Label("示例标签", id="testlabel")
yield Button("示例按钮", id="testbtn") yield Button("示例按钮", id="testbtn")
def handler(self, event, type_): def handler(self, event, type_):
return 1 return 1
#if hasattr(event, "button"):
#print(event.button.id)
# self.screen.query_one("#testlabel", Label).update("hi")
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]):
super().__init__(screen, reactor, atom) super().__init__(screen, reactor, atom)
def compose(self): def compose(self):
yield Label("本次记忆进程结束", id=self.regid("msg")) yield Label("本次记忆进程结束", id=self.regid("msg"))
#yield Button("示例按钮", id="testbtn")
class Placeholder(Composition): class Placeholder(Composition):
def __init__(self, screen: Screen): def __init__(self, screen: Screen):
self.screen = screen self.screen = screen
def compose(self): def compose(self):
yield Label("示例标签", id="testlabel") yield Label("示例标签", id="testlabel")
yield Button("示例按钮", id="testbtn", classes="choice") yield Button("示例按钮", id="testbtn", classes="choice")
def handler(self, event, type_): def handler(self, event, type_):
#print(event.button.id)
self.screen.query_one("#testlabel", Label).update("hi") self.screen.query_one("#testlabel", Label).update("hi")
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]):
super().__init__(screen, reactor, atom) super().__init__(screen, reactor, atom)
def compose(self): def compose(self):
with Center(): with Center():
yield Static(f"[dim]{self.atom[1]['translation']}[/]") yield Static(f"[dim]{self.atom[1]['translation']}[/]")
yield Label(f"") yield Label(f"")
s = str(self.atom[1]['content']) s = str(self.atom[1]["content"])
replace_dict = { replace_dict = {
", ": ",", ", ": ",",
". ": ".", ". ": ".",
@@ -75,132 +99,157 @@ class Recognition(Composition):
} }
for old, new in replace_dict.items(): for old, new in replace_dict.items():
s = s.replace(old, new) s = s.replace(old, new)
result = re.split(r"(?<=[,;:|])", s.replace('/', ' ')) result = re.split(r"(?<=[,;:|])", s.replace("/", " "))
for i in result: for i in result:
with Center(): with Center():
yield Label(f"[b][b]{i.replace("/", " ")}[/][/]", id=self.regid("sentence"+str(hash(i)))) # 致敬传奇去重串 uuid yield Label(
#with Collapsible(title="附加信息", collapsed=True): f"[b][b]{i.replace('/', ' ')}[/][/]",
id=self.regid("sentence" + str(hash(i))),
)
for i in self.atom[2]["testdata"]["additional_inf"]: for i in self.atom[2]["testdata"]["additional_inf"]:
if self.atom[1][i]: if self.atom[1][i]:
#print(type(self.atom[1][i]))
#print(self.atom[1][i])
if isinstance(self.atom[1][i], list): if isinstance(self.atom[1][i], list):
for j in self.atom[1][i]: for j in self.atom[1][i]:
yield Markdown(f"### {self.atom[2]['keydata'][i]}: {j}") yield Markdown(f"### {self.atom[2]['keydata'][i]}: {j}")
continue continue
if isinstance(self.atom[1][i], Dict): if isinstance(self.atom[1][i], Dict):
t = "" t = ""
for j, k in self.atom[1][i].items(): for j, k in self.atom[1][i].items(): # type: ignore
# 弱智的 Pylance 类型推导
t += f"> **{j}**: {k} \n" t += f"> **{j}**: {k} \n"
yield Markdown(t, id=self.regid("tran")) yield Markdown(t, id=self.regid("tran"))
with Center(): with Center():
yield Button("我已知晓", id=self.regid("ok")) yield Button("我已知晓", id=self.regid("ok"))
def handler(self, event, type_): def handler(self, event, type_):
##print(event)
if type_ == "button": if type_ == "button":
#print(event.button.id)
if event.button.id == self.getid("ok"): if event.button.id == self.getid("ok"):
#print(1) self.reactor.report(self.atom, 5)
""""""#assessment = self.reactor.report(self.reactor.current_atom, -1)
return 0 return 0
if type_ == 1:
pass
return -1 return -1
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]):
super().__init__(screen, reactor, atom) super().__init__(screen, reactor, atom)
def compose(self): def compose(self):
yield Label(self.atom[1]["content"], id="sentence") yield Label(self.atom[1]["content"], id="sentence")
with Container(id="button_container"): with Container(id="button_container"):
btn = {} btn = {}
btn['5'] = Button("完美回想", variant="success", id=self.regid("feedback5"), classes="choice") btn["5"] = Button(
btn['4'] = Button("犹豫后正确", variant="success", id=self.regid("feedback4"), classes="choice") "完美回想", variant="success", id=self.regid("feedback5"), classes="choice"
btn['3'] = Button("困难地正确", variant="warning", id=self.regid("feedback3"), classes="choice") )
btn['2'] = Button("错误但熟悉", variant="warning", id=self.regid("feedback2"), classes="choice") btn["4"] = Button(
btn['1'] = Button("错误且不熟", variant="error", id=self.regid("feedback1"), classes="choice") "犹豫后正确", variant="success", id=self.regid("feedback4"), classes="choice"
btn['0'] = Button("完全空白", variant="error", id=self.regid("feedback0"), classes="choice") )
yield Horizontal(btn['5'], btn['4']) btn["3"] = Button(
yield Horizontal(btn['3'], btn['2']) "困难地正确", variant="warning", id=self.regid("feedback3"), classes="choice"
yield Horizontal(btn['1'], btn['0']) )
btn["2"] = Button(
"错误但熟悉", variant="warning", id=self.regid("feedback2"), classes="choice"
)
btn["1"] = Button(
"错误且不熟", variant="error", id=self.regid("feedback1"), classes="choice"
)
btn["0"] = Button(
"完全空白", variant="error", id=self.regid("feedback0"), classes="choice"
)
yield Horizontal(btn["5"], btn["4"])
yield Horizontal(btn["3"], btn["2"])
yield Horizontal(btn["1"], btn["0"])
def handler(self, event, type_): def handler(self, event, type_):
if "feedback" in event.button.id: if "feedback" in event.button.id:
#print(self.recid(event.button.id)[8:9])
assess = int(self.recid(event.button.id)[8:9]) assess = int(self.recid(event.button.id)[8:9])
ret = self.reactor.report(self.atom, assess) ret = self.reactor.report(self.atom, assess)
return ret return ret
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]):
super().__init__(screen, reactor, atom) super().__init__(screen, reactor, atom)
self.inputlist = [] self.inputlist = []
self.hashtable = {} self.hashtable = {}
self._work() self._work()
def _work(self): def _work(self):
self.puzzle = pz.BlankPuzzle(self.atom[1]["content"], 4) self.puzzle = pz.BlankPuzzle(self.atom[1]["content"], 4)
self.puzzle.refresh() self.puzzle.refresh()
self.ans = copy.copy(self.puzzle.answer) self.ans = copy.copy(self.puzzle.answer)
random.shuffle(self.ans) random.shuffle(self.ans)
def compose(self): def compose(self):
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"))
#yield Label(renderable=f"答案: {self.puzzle.answer}", id=self.regid("ans"))
for i in self.ans: for i in self.ans:
self.hashtable[str(hash(i))] = i self.hashtable[str(hash(i))] = i
yield Button(i, id=self.regid(f"select{hash(i)}")) yield Button(i, id=self.regid(f"select{hash(i)}"))
yield Button("退格", id=self.regid(f"delete")) yield Button("退格", id=self.regid(f"delete"))
def handler(self, event, type_): def handler(self, event, type_):
# TODO: 改动:在线错误纠正
if type_ == "button": if type_ == "button":
if self.recid(event.button.id) == "delete" and len(self.inputlist) > 0: if self.recid(event.button.id) == "delete":
if len(self.inputlist) > 0:
self.inputlist.pop() self.inputlist.pop()
else:
return 1
else: else:
self.inputlist.append(self.hashtable[self.recid(event.button.id)[6:]]) self.inputlist.append(self.hashtable[self.recid(event.button.id)[6:]])
if len(self.inputlist) < len(self.puzzle.answer): if len(self.inputlist) < len(self.puzzle.answer):
return 1 return 1
else: else:
if self.inputlist == self.puzzle.answer: if self.inputlist == self.puzzle.answer:
print("ok") self.reactor.report(self.atom, 4)
return 0 return 0
else: else:
self.inputlist = [] self.inputlist = []
self.reactor.report(self.atom, 2)
return 1 return 1
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]):
super().__init__(screen, reactor, atom) super().__init__(screen, reactor, atom)
self.inputlist = [] self.inputlist = []
self.hashtable = {} self.hashtable = {}
self._work() self._work()
def _work(self): def _work(self):
self.puzzle = pz.SelectionPuzzle(self.atom[1]["keyword_note"], [], 2, "选择正确词义: ") self.puzzle = pz.SelectionPuzzle(self.atom[1]["keyword_note"], [], 2, "选择正确词义: ") # type: ignore
self.puzzle.refresh() self.puzzle.refresh()
def compose(self): def compose(self):
print(len(self.inputlist)) yield Label(self.atom[1].content.replace("/",""), id=self.regid("sentence"))
yield Label(self.atom[1].content, id=self.regid("sentence"))
yield Label(self.puzzle.wording[len(self.inputlist)], id=self.regid("puzzle")) yield Label(self.puzzle.wording[len(self.inputlist)], id=self.regid("puzzle"))
yield Label(f"当前输入: {self.inputlist}", id=self.regid("inputpreview")) yield Label(f"当前输入: {self.inputlist}", id=self.regid("inputpreview"))
yield Label(renderable=f"答案: {self.puzzle.answer}", id=self.regid("ans"))
for i in self.puzzle.options[len(self.inputlist)]: for i in self.puzzle.options[len(self.inputlist)]:
self.hashtable[str(hash(i))] = i self.hashtable[str(hash(i))] = i
yield Button(i, id=self.regid(f"select{hash(i)}")) yield Button(i, id=self.regid(f"select{hash(i)}"))
yield Button("退格", id=self.regid(f"delete")) yield Button("退格", id=self.regid(f"delete"))
def handler(self, event, type_): def handler(self, event, type_):
if type_ == "button": if type_ == "button":
if self.recid(event.button.id) == "delete" and len(self.inputlist) > 0: if self.recid(event.button.id) == "delete":
if len(self.inputlist) > 0:
self.inputlist.pop() self.inputlist.pop()
else:
return 1
else: else:
self.inputlist.append(self.hashtable[self.recid(event.button.id)[6:]]) self.inputlist.append(self.hashtable[self.recid(event.button.id)[6:]])
if len(self.inputlist) < len(self.puzzle.answer): if len(self.inputlist) < len(self.puzzle.answer):
return 1 return 1
else: else:
if self.inputlist == self.puzzle.answer: if self.inputlist == self.puzzle.answer:
print("ok") self.reactor.report(self.atom, 4)
return 0 return 0
else: else:
self.inputlist = [] self.inputlist = []
self.reactor.report(self.atom, 2)
return 1 return 1
registry = { registry = {
"sample": Composition, "sample": Composition,
"recognition": Recognition, "recognition": Recognition,
@@ -210,16 +259,16 @@ registry = {
} }
# TEST
class TestScreen(Screen): class TestScreen(Screen):
def __init__(self): def __init__(self):
super().__init__(name=None, id=None, classes=None) super().__init__(name=None, id=None, classes=None)
self.comp = Recognition(self, None, pt.Atom.advanced_placeholder()) self.comp = Recognition(self, None, pt.Atom.advanced_placeholder())
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Header(show_clock=True) yield Header(show_clock=True)
yield from self.comp.compose() yield from self.comp.compose()
yield Footer() yield Footer()
def on_mount(self) -> None: def on_mount(self) -> None:
pass pass
@@ -229,9 +278,10 @@ class TestScreen(Screen):
def action_quit_app(self) -> None: def action_quit_app(self) -> None:
self.app.exit() self.app.exit()
class AppLauncher(App): class AppLauncher(App):
CSS_PATH = "styles.tcss" CSS_PATH = "styles.css"
TITLE = '测试布局' TITLE = "测试布局"
BINDINGS = [("escape", "quit", "退出"), ("d", "toggle_dark", "改变色调")] BINDINGS = [("escape", "quit", "退出"), ("d", "toggle_dark", "改变色调")]
SCREENS = { SCREENS = {
"testscreen": TestScreen, "testscreen": TestScreen,
@@ -241,6 +291,7 @@ class AppLauncher(App):
self.action_toggle_dark() self.action_toggle_dark()
self.push_screen("testscreen") self.push_screen("testscreen")
if __name__ == "__main__": if __name__ == "__main__":
app = AppLauncher() app = AppLauncher()
app.run() app.run()

View File

@@ -1,8 +1,14 @@
# [调试] 将更改保存到文件 # [调试] 将更改保存到文件
save = 1 save = 1
# [调试] 覆写时间 # [调试] 覆写时间
time_override = 10 time_override = -1
# [调试] 一键通过
quick_pass = 0
# 对于每个项目的新记忆核子数量 # 对于每个项目的新记忆核子数量
tasked_number = 8 tasked_number = 8
# 竖屏适配
# 竖屏适配 (未完成)
mobile_mode = 1 mobile_mode = 1

190
main.py
View File

@@ -1,86 +1,76 @@
from textual.app import App, ComposeResult from textual.app import App, ComposeResult
from textual.widgets import Header, Footer, ListView, ProgressBar, DirectoryTree, ListItem, Label, Static, Button from textual.widgets import (
Header,
Footer,
ListView,
ProgressBar,
DirectoryTree,
ListItem,
Label,
Static,
Button,
)
from textual.containers import Container, Horizontal, Center from textual.containers import Container, Horizontal, Center
from textual.screen import Screen from textual.screen import Screen
import pathlib import pathlib
import threading import threading
import edge_tts as tts 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 from reactor import Reactor, Apparatus
import auxiliary as aux import auxiliary as aux
import compositions as compo import compositions as compo
import builtins import builtins
# 保存原始的 open 函数 # Hook python 的 open() 函数, 使用 utf-8 (兼容 Windows 万年 GBK)
_original_open = builtins.open _original_open = builtins.open
# 定义新的 open 函数,默认使用 UTF-8
def _open(*args, **kwargs): def _open(*args, **kwargs):
if 'encoding' not in kwargs: if "encoding" not in kwargs:
kwargs['encoding'] = 'utf-8' kwargs["encoding"] = "utf-8"
return _original_open(*args, **kwargs) return _original_open(*args, **kwargs)
# 替换全局的 open
builtins.open = _open builtins.open = _open
ver = '0.3.0b' ver = "0.3.0"
config = aux.ConfigFile("config.toml") config = aux.ConfigFile("config.toml")
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", "朗读"),
("0", "press('q0')", None),
("1", "press('q1')", None),
("2", "press('q2')", None),
("3", "press('q3')", None),
("4", "press('q4')", None),
("5", "press('q5')", None),
("[", "press('q5')", None),
("]", "press('q4')", None),
(";", "press('q3')", None),
("'", "press('q2')", None),
(".", "press('q1')", None),
("/", "press('q0')", None),
] ]
if config.get("quick_pass"):
BINDINGS.append(("k", "quick_pass", "快速通过[调试]"))
btn = dict() btn = dict()
def __init__( def __init__(
self, self,
nucleon_file: pt.NucleonUnion, nucleon_file: pt.NucleonUnion,
electron_file: pt.ElectronUnion, electron_file: pt.ElectronUnion,
tasked_num tasked_num,
): ):
super().__init__(name=None, id=None, classes=None) super().__init__(name=None, id=None, classes=None)
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)
##print(self.reactor.procession)
self.reactor.forward() self.reactor.forward()
#self.compo:compo.Composition = compo.Placeholder(self)
self.compo = next(self.reactor.current_appar) self.compo = next(self.reactor.current_appar)
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
#print(self.compo)
yield Header(show_clock=True) yield Header(show_clock=True)
with Center(): with Center():
yield Static(f"{len(self.reactor.procession) - self.reactor.index}/{len(self.reactor.procession)}") yield Static(
#yield ProgressBar(total=len(self.reactor.procession) - 1, show_percentage=False, show_eta=False, id="progress") f"{len(self.reactor.procession) - self.reactor.index}/{len(self.reactor.procession)}"
)
yield from self.compo.compose() yield from self.compo.compose()
yield Footer() yield Footer()
"""
def _get_progress_text(self):
return f"{len(self.reactor.procession) - self.reactor.index}/{len(self.reactor.procession)}"
"""
def on_mount(self): def on_mount(self):
# 首次挂载时调用
pass pass
def on_button_pressed(self, event): def on_button_pressed(self, event):
@@ -90,13 +80,12 @@ class MemScreen(Screen):
def _forward_judge(self, ret): def _forward_judge(self, ret):
if ret == -1: if ret == -1:
return return
if ret == 0: # 成功 if ret == 0:
try: try:
self.compo = next(self.reactor.current_appar) self.compo = next(self.reactor.current_appar)
self.refresh_ui() self.refresh_ui()
except StopIteration: except StopIteration:
nxt = self.reactor.forward(1) nxt = self.reactor.forward(1)
#print(2)
try: try:
self.compo = next(self.reactor.current_appar) self.compo = next(self.reactor.current_appar)
except: except:
@@ -106,13 +95,13 @@ class MemScreen(Screen):
if self.stage == 4: if self.stage == 4:
if config.get("save"): if config.get("save"):
self.reactor.save() self.reactor.save()
self.compo = compo.Finished(self, None, pt.Atom.placeholder()) self.compo = compo.Finished(
self, None, pt.Atom.placeholder()
)
self.refresh_ui() self.refresh_ui()
#self._show_finished_screen("今日目标已完成")
else: else:
self.reactor.set_round_templated(self.stage) self.reactor.set_round_templated(self.stage)
self.reactor.forward(1) self.reactor.forward(1)
#self._update_ui()
self.stage += 1 self.stage += 1
self.compo = next(self.reactor.current_appar) self.compo = next(self.reactor.current_appar)
self.refresh_ui() self.refresh_ui()
@@ -121,71 +110,46 @@ class MemScreen(Screen):
else: else:
self.refresh_ui() self.refresh_ui()
return return
if ret == 1: # 不允许前进 if ret == 1:
self.refresh_ui() self.refresh_ui()
return return
def refresh_ui(self): def refresh_ui(self):
self.call_later(self.recompose) self.call_later(self.recompose)
#self.call_later(lambda: self.query_one("#progress", expect_type=ProgressBar).advance(self.reactor.index))
##print(area.children)
#for child in list(area.children):
# child.remove() # 致敬传奇组件树 DOM
##print(1,list(self.compo.compose()))
#area.mount(*list(self.compo.compose()))
def report(self, quality):
assessment = self.reactor.report(self.reactor.current_atom, quality)
return assessment
"""if assessment == 1:
# 需要复习
feedback_label.update(f"评分为 {quality}, 已经加入至复习, 请重复记忆")
else:
ret = self.reactor.forward(1)
if ret == -1:
if self.reactor.round_set == 0:
if self.stage == 4:
# NOTE #
if config.get("save"):
self.reactor.save()
self._show_finished_screen("今日目标已完成")
else:
self.reactor.set_round_templated(self.stage)
self.reactor.forward(1)
self._update_ui()
self.stage += 1
return
#feedback_label.update("") # 清除反馈消息
self._update_ui()"""
#def action_press(self, btnid):
# self.on_button_pressed(btnid)
def action_play_voice(self): def action_play_voice(self):
def play(): def play():
cache_dir = pathlib.Path(f"./cache/voice/") cache_dir = pathlib.Path(f"./cache/voice/")
cache_dir.mkdir(parents=True, exist_ok=True) cache_dir.mkdir(parents=True, exist_ok=True)
cache = cache_dir / f"{self.reactor.current_atom[1].content.replace("/","")}.wav" cache = cache_dir / f"{self.reactor.current_atom[1].content.replace('/','')}.wav"
if not cache.exists(): if not cache.exists():
communicate = tts.Communicate(self.reactor.current_atom[1].content.replace("/",""), "zh-CN-YunjianNeural") communicate = tts.Communicate(
communicate.save_sync(f"./cache/voice/{self.reactor.current_atom[1].content.replace("/","")}.wav") self.reactor.current_atom[1].content.replace("/", ""),
"zh-CN-YunjianNeural",
)
communicate.save_sync(
f"./cache/voice/{self.reactor.current_atom[1].content.replace('/','')}.wav"
)
playsound(str(cache)) playsound(str(cache))
threading.Thread(target=play).start() threading.Thread(target=play).start()
def action_quick_pass(self):
self.reactor.report(self.reactor.current_atom, 5)
self._forward_judge(0)
def action_toggle_dark(self): def action_toggle_dark(self):
self.app.action_toggle_dark() self.app.action_toggle_dark()
def action_pop_screen(self): def action_pop_screen(self):
"""返回到上一个屏幕"""
self.app.pop_screen() self.app.pop_screen()
class PreparationScreen(Screen):
BINDINGS = [
("q", "go_back", "返回"),
("escape", "quit_app", "退出")
]
def __init__(self, nucleon_file: pt.NucleonUnion, electron_file: pt.ElectronUnion) -> None: class PreparationScreen(Screen):
BINDINGS = [("q", "go_back", "返回"), ("escape", "quit_app", "退出")]
def __init__(
self, nucleon_file: pt.NucleonUnion, electron_file: pt.ElectronUnion
) -> None:
super().__init__(name=None, id=None, classes=None) super().__init__(name=None, id=None, classes=None)
self.nucleon_file = nucleon_file self.nucleon_file = nucleon_file
self.electron_file = electron_file self.electron_file = electron_file
@@ -194,18 +158,27 @@ class PreparationScreen(Screen):
yield Header(show_clock=True) yield Header(show_clock=True)
with Container(id="learning_screen_container"): with Container(id="learning_screen_container"):
yield Label(f"记忆项目: [b]{self.nucleon_file.name}[/b]\n") yield Label(f"记忆项目: [b]{self.nucleon_file.name}[/b]\n")
yield Label(f"核子文件对象: ./nucleon/[b]{self.nucleon_file.name}[/b].toml") yield Label(
yield Label(f"子文件对象: ./electron/[b]{self.electron_file.name}[/b].toml") f"子文件对象: ./nucleon/[b]{self.nucleon_file.name}[/b].toml"
)
yield Label(
f"电子文件对象: ./electron/[b]{self.electron_file.name}[/b].toml"
)
yield Label(f"核子数量:{len(self.nucleon_file)}") yield Label(f"核子数量:{len(self.nucleon_file)}")
yield Button("开始记忆", id="start_memorizing_button", variant="primary", classes="start-button") yield Button(
"开始记忆",
id="start_memorizing_button",
variant="primary",
classes="start-button",
)
yield Static(f"\n全文如下:\n") yield Static(f"\n全文如下:\n")
yield Static(self._get_full_content(), classes="full") yield Static(self._get_full_content().replace("/", ""), classes="full")
yield Footer() yield Footer()
def _get_full_content(self): def _get_full_content(self):
content = "" content = ""
for i in self.nucleon_file.nucleons: for i in self.nucleon_file.nucleons:
content += i['content'] content += i["content"]
return content return content
def action_go_back(self): def action_go_back(self):
@@ -215,76 +188,79 @@ class PreparationScreen(Screen):
self.app.exit() self.app.exit()
def on_button_pressed(self, event: Button.Pressed) -> None: def on_button_pressed(self, event: Button.Pressed) -> None:
pass
if event.button.id == "start_memorizing_button": if event.button.id == "start_memorizing_button":
#init_file(Path(self.atom_file).name) newscr = MemScreen(
newscr = MemScreen(self.nucleon_file, self.electron_file, config.get("tasked_number", 8)) self.nucleon_file, self.electron_file, config.get("tasked_number", 8)
self.app.push_screen(
newscr
) )
#if event.button.id == "edit_metadata_button": self.app.push_screen(newscr)
# init_file(Path(self.atom_file).name)
# os.system("reset;nano ./data/" + str(Path(self.atom_file).name.replace(".txt", "_atoms.json")))
class FileSelectorScreen(Screen): class FileSelectorScreen(Screen):
global ver global ver
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Header(show_clock=True) yield Header(show_clock=True)
yield Container( yield Container(
Label(f'欢迎使用 "潜进" 辅助记忆软件, 版本 {ver}', classes="title-label"), Label(f'欢迎使用 "潜进" 辅助记忆软件, 版本 {ver}', classes="title-label"),
Label("选择要学习的文件:", classes="title-label"), Label("选择要学习的文件:", classes="title-label"),
ListView(id="file-list", classes="file-list-view") ListView(id="file-list", classes="file-list-view"),
) )
yield Footer() yield Footer()
def on_mount(self) -> None: def on_mount(self) -> None:
file_list_widget = self.query_one("#file-list", ListView) file_list_widget = self.query_one("#file-list", ListView)
nucleon_path = pathlib.Path("./nucleon") nucleon_path = pathlib.Path("./nucleon")
nucleon_files = sorted([f.name for f in nucleon_path.iterdir() if f.suffix == ".toml"]) nucleon_files = sorted(
[f.name for f in nucleon_path.iterdir() if f.suffix == ".toml"]
)
if nucleon_files: if nucleon_files:
for filename in nucleon_files: for filename in nucleon_files:
file_list_widget.append(ListItem(Label(filename))) file_list_widget.append(ListItem(Label(filename)))
else: else:
file_list_widget.append(ListItem(Static("在 ./nucleon/ 中未找到任何核子文件. 请放置文件后重启应用."))) file_list_widget.append(
ListItem(Static("在 ./nucleon/ 中未找到任何核子文件. 请放置文件后重启应用."))
)
file_list_widget.disabled = True file_list_widget.disabled = True
def on_list_view_selected(self, event: ListView.Selected) -> None: def on_list_view_selected(self, event: ListView.Selected) -> None:
if not isinstance(event.item, ListItem): if not isinstance(event.item, ListItem):
self.notify("无法选择此项。", severity="error")
return return
selected_label = event.item.query_one(Label) selected_label = event.item.query_one(Label)
if "未找到任何 .toml 文件" in str(selected_label.renderable): if "未找到任何 .toml 文件" in str(selected_label.renderable):
self.notify("请先在 `./atoms/` 目录中放置 .toml 文件。", severity="warning")
return return
selected_filename = str(selected_label.renderable) selected_filename = str(selected_label.renderable)
nucleon_file = pt.NucleonUnion(pathlib.Path("./nucleon") / selected_filename) nucleon_file = pt.NucleonUnion(
pathlib.Path("./nucleon") / selected_filename
)
electron_file_path = pathlib.Path("./electron") / selected_filename electron_file_path = pathlib.Path("./electron") / selected_filename
if electron_file_path.exists(): if electron_file_path.exists():
pass pass
else: else:
electron_file_path.touch() electron_file_path.touch()
electron_file = pt.ElectronUnion(pathlib.Path("./electron") / selected_filename) electron_file = pt.ElectronUnion(
# self.notify(f"已选择: {selected_filename}", timeout=2) pathlib.Path("./electron") / selected_filename
)
self.app.push_screen(PreparationScreen(nucleon_file, electron_file)) self.app.push_screen(PreparationScreen(nucleon_file, electron_file))
def action_quit_app(self) -> None: def action_quit_app(self) -> None:
self.app.exit() self.app.exit()
class AppLauncher(App): class AppLauncher(App):
CSS_PATH = "styles.tcss" CSS_PATH = "styles.css"
TITLE = '潜进 - 辅助记忆程序' TITLE = "潜进 - 辅助记忆程序"
BINDINGS = [("escape", "quit", "退出"), ("d", "toggle_dark", "改变色调")] BINDINGS = [("escape", "quit", "退出"), ("d", "toggle_dark", "改变色调")]
SCREENS = { SCREENS = {
"file_selection_screen": FileSelectorScreen, "file_selection_screen": FileSelectorScreen,
} }
def on_mount(self) -> None: def on_mount(self) -> None:
#self.action_toggle_dark()
self.push_screen("file_selection_screen") self.push_screen("file_selection_screen")
if __name__ == "__main__": if __name__ == "__main__":
app = AppLauncher() app = AppLauncher()
app.run() app.run()

View File

0
nucleon/书愤.toml Normal file
View File

View File

0
nucleon/六国论.toml Normal file
View File

0
nucleon/劝学.toml Normal file
View File

0
nucleon/声声慢.toml Normal file
View File

0
nucleon/客至.toml Normal file
View File

0
nucleon/将进酒.toml Normal file
View File

View File

View File

0
nucleon/师说.toml Normal file
View File

View File

View File

View File

View File

0
nucleon/扬州慢.toml Normal file
View File

View File

View File

0
nucleon/无衣.toml Normal file
View File

View File

0
nucleon/望海潮.toml Normal file
View File

0
nucleon/朝天子.toml Normal file
View File

View File

0
nucleon/桂枝香.toml Normal file
View File

View File

0
nucleon/永遇乐.toml Normal file
View File

0
nucleon/江城子.toml Normal file
View File

View File

0
nucleon/燕歌行.toml Normal file
View File

0
nucleon/琵琶行.toml Normal file
View File

View File

0
nucleon/登快阁.toml Normal file
View File

View File

0
nucleon/登高.toml Normal file
View File

0
nucleon/短歌行.toml Normal file
View File

View File

22
nucleon/示例.toml Normal file
View File

@@ -0,0 +1,22 @@
# 文件头, 按部就班复制即可
["keydata"]
note = "笔记"
keyword_note = "关键词翻译"
translation = "语句翻译"
["testdata"]
additional_inf = ["translation","keyword_note", "note"]
fill_blank_test = {"from"=["content"], "hint"=["translation"]}
#可重复的单元
draw_card_test = {"from"=["keyword_note"]}
[CONTENT]
note = []
translation = "TRANSLATION"
keyword_note = {"KN_KEY": "KN_VALUE"}
#这是一个示例:(不要求附加在生成文本中)
["臣/密/言: /臣/以/险衅/, 夙/遭/闵凶./"]
note = []
translation = "臣子李密陈言: 我因命运不好, 小时候遭遇到了不幸"
keyword_note = {"险衅"="凶险祸患(这里指命运不好)", "夙"="早时, 这里指年幼的时候", "闵"="通'悯', 指可忧患的事", "凶"="不幸, 指丧父"}

0
nucleon/礼运.toml Normal file
View File

0
nucleon/离骚.toml Normal file
View File

View File

View File

0
nucleon/苏幕遮.toml Normal file
View File

0
nucleon/菩萨蛮.toml Normal file
View File

0
nucleon/虞美人.toml Normal file
View File

0
nucleon/蜀相.toml Normal file
View File

0
nucleon/蜀道难.toml Normal file
View File

0
nucleon/论语.toml Normal file
View File

View File

0
nucleon/贺新郎.toml Normal file
View File

0
nucleon/赤壁赋.toml Normal file
View File

0
nucleon/过秦论.toml Normal file
View File

0
nucleon/锦瑟.toml Normal file
View File

View File

View File

0
nucleon/青玉案.toml Normal file
View File

0
nucleon/静女.toml Normal file
View File

View File

0
nucleon/鹊桥仙.toml Normal file
View File

View File

@@ -3,9 +3,8 @@ import toml
import time import time
import auxiliary as aux import auxiliary as aux
import time
class Electron(): class Electron:
"""电子: 记忆分析元数据及算法""" """电子: 记忆分析元数据及算法"""
algorithm = "SM-2" # 暂时使用 SM-2 算法进行记忆拟合, 考虑 SM-15 替代 algorithm = "SM-2" # 暂时使用 SM-2 算法进行记忆拟合, 考虑 SM-15 替代
@@ -49,10 +48,13 @@ class Electron():
Args: Args:
quality (int): 记忆保留率量化参数 quality (int): 记忆保留率量化参数
""" """
print(f"REVISOR: {quality}, {is_new_activation}")
if quality == -1: if quality == -1:
return -1 return -1
self.metadata['efactor'] = self.metadata['efactor'] + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02)) self.metadata['efactor'] = self.metadata['efactor'] + (
0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02)
)
self.metadata['efactor'] = max(1.3, self.metadata['efactor']) self.metadata['efactor'] = max(1.3, self.metadata['efactor'])
if quality < 3: if quality < 3:
@@ -73,19 +75,23 @@ class Electron():
elif self.metadata['rept'] == 1: elif self.metadata['rept'] == 1:
self.metadata['interval'] = 6 # I(2) 经验公式 self.metadata['interval'] = 6 # I(2) 经验公式
else: else:
self.metadata['interval'] = round(self.metadata['interval'] * self.metadata['efactor']) self.metadata['interval'] = round(
self.metadata['interval'] * self.metadata['efactor']
)
self.metadata['last_date'] = aux.get_daystamp() self.metadata['last_date'] = aux.get_daystamp()
self.metadata['next_date'] = aux.get_daystamp() + self.metadata['interval'] self.metadata['next_date'] = aux.get_daystamp() + self.metadata['interval']
self.metadata['last_modify'] = time.time() self.metadata['last_modify'] = time.time()
def __str__(self): def __str__(self):
return (f"记忆单元预览 \n" return (
f"记忆单元预览 \n"
f"内容: '{self.content}' \n" f"内容: '{self.content}' \n"
f"易度系数: {self.metadata['efactor']:.2f} \n" f"易度系数: {self.metadata['efactor']:.2f} \n"
f"已经重复的次数: {self.metadata['rept']} \n" f"已经重复的次数: {self.metadata['rept']} \n"
f"下次间隔: {self.metadata['interval']}\n" f"下次间隔: {self.metadata['interval']}\n"
f"下次复习日期时间戳: {self.metadata['next_date']}") f"下次复习日期时间戳: {self.metadata['next_date']}"
)
def __eq__(self, other): def __eq__(self, other):
if self.content == other.content: if self.content == other.content:
@@ -106,10 +112,6 @@ class Electron():
def __setitem__(self, key, value): def __setitem__(self, key, value):
if key == "content": if key == "content":
raise AttributeError("content 应为只读") raise AttributeError("content 应为只读")
# 可以在此处添加更复杂的验证逻辑,例如只允许修改预定义的 metadata 键
# 或者根据键进行类型检查等。
self.metadata[key] = value self.metadata[key] = value
self.metadata['last_modify'] = time.time() self.metadata['last_modify'] = time.time()
@@ -123,6 +125,7 @@ class Electron():
def placeholder(): def placeholder():
return Electron("电子对象样例内容", {}) return Electron("电子对象样例内容", {})
class Nucleon: class Nucleon:
"""核子: 材料元数据""" """核子: 材料元数据"""
@@ -151,7 +154,8 @@ class Nucleon:
def placeholder(): def placeholder():
return Nucleon("核子对象样例内容", {}) return Nucleon("核子对象样例内容", {})
class NucleonUnion():
class NucleonUnion:
""" """
替代原有 NucleonFile 类, 支持复杂逻辑 替代原有 NucleonFile 类, 支持复杂逻辑
@@ -166,6 +170,7 @@ class NucleonUnion():
Parameters: Parameters:
path (Path): 包含核子数据的文件路径。 path (Path): 包含核子数据的文件路径。
""" """
def __init__(self, path): def __init__(self, path):
self.path = path self.path = path
self.name = path.name.replace(path.suffix, "") self.name = path.name.replace(path.suffix, "")
@@ -189,8 +194,10 @@ class NucleonUnion():
tmp = {i.content: i.metadata for i in self.nucleons} tmp = {i.content: i.metadata for i in self.nucleons}
toml.dump(tmp, f) toml.dump(tmp, f)
class ElectronUnion():
"取代原有 ElectronFile 类, 以支持复杂逻辑" class ElectronUnion:
"""取代原有 ElectronFile 类, 以支持复杂逻辑"""
def __init__(self, path): def __init__(self, path):
self.path = path self.path = path
self.name = path.name.replace(path.suffix, "") self.name = path.name.replace(path.suffix, "")
@@ -213,7 +220,9 @@ class ElectronUnion():
# print(tmp) # print(tmp)
toml.dump(tmp, f) toml.dump(tmp, f)
"""class AtomicFile():
"""
class AtomicFile():
def __init__(self, path, type_="unknown"): def __init__(self, path, type_="unknown"):
self.path = path self.path = path
self.type_ = type_ self.type_ = type_
@@ -237,10 +246,10 @@ class ElectronUnion():
return "" return ""
def get_len(self): def get_len(self):
return len(self.datalist) return len(self.datalist)
""" """
class Atom():
class Atom:
@staticmethod @staticmethod
def placeholder(): def placeholder():
return (Electron.placeholder(), Nucleon.placeholder(), {}) return (Electron.placeholder(), Nucleon.placeholder(), {})
@@ -249,18 +258,30 @@ class Atom():
def advanced_placeholder(): def advanced_placeholder():
return ( return (
Electron("两只黄鹤鸣翠柳", {}), Electron("两只黄鹤鸣翠柳", {}),
Nucleon("两只黄鹤鸣翠柳", {"note": [], Nucleon(
"两只黄鹤鸣翠柳",
{
"note": [],
"translation": "臣子李密陈言:我因命运不好,小时候遭遇到了不幸", "translation": "臣子李密陈言:我因命运不好,小时候遭遇到了不幸",
"keyword_note": {"险衅":"凶险祸患(这里指命运不好)", "":"早时,这里指年幼的时候", "":"'',指可忧患的事", "":"不幸,指丧父"}}), "keyword_note": {
"险衅": "凶险祸患(这里指命运不好)",
"": "早时,这里指年幼的时候",
"": "'',指可忧患的事",
"": "不幸,指丧父"
}
}
),
{ {
"keydata": { "keydata": {
"note": "笔记", "note": "笔记",
"keyword_note": "关键词翻译", "keyword_note": "关键词翻译",
"translation": "语句翻译"}, "translation": "语句翻译"
},
"testdata": { "testdata": {
"additional_inf": ["translation", "note", "keyword_note"], "additional_inf": ["translation", "note", "keyword_note"],
"fill_blank_test": ["translation"], "fill_blank_test": ["translation"],
"draw_card_test": ["keyword_note"] "draw_card_test": ["keyword_note"]
}, },
"is_new_activation": 0 "is_new_activation": 0
}) }
)

52
precache.py Normal file
View File

@@ -0,0 +1,52 @@
# 音频预缓存实用程序, 独立于主程序之外, 但依赖 particles 组件
import particles as pt
import edge_tts as tts
from pathlib import Path
import shutil
def precache(text: str):
"""预缓存单个文本的音频"""
cache_dir = Path("./cache/voice/")
cache_dir.mkdir(parents=True, exist_ok=True)
cache = cache_dir / f"{text}.wav"
if not cache.exists():
communicate = tts.Communicate(text, "zh-CN-YunjianNeural")
communicate.save_sync(f"./cache/voice/{text}.wav")
def proc_file(path: Path):
"""处理单个文件"""
nu = pt.NucleonUnion(path)
c = 0
for i in nu.nucleons:
c += 1
print(f"预缓存 [{nu.name}] ({c}/{len(nu)}): {i['content'].replace('/', '')}")
precache(i['content'].replace('/', ''))
def walk(path_str: str):
"""遍历目录处理所有文件"""
path = Path(path_str)
print(f"正在遍历目录: {path}")
for item in path.iterdir():
if item.is_file() and item.suffix == ".toml":
print(f"正预缓存文件: {item.name}")
proc_file(item)
elif item.is_dir():
print(f"进入目录: {item.name}")
if __name__ == "__main__":
print("音频预缓存实用程序")
print("A: 全部缓存")
print("C: 清空缓存")
choice = input("输入选项 $ ").upper()
if choice == "A":
walk("./nucleon")
elif choice == "C":
shutil.rmtree("./cache/voice", ignore_errors=True)
print("缓存已清空")

View File

@@ -1,43 +0,0 @@
# 音频预缓存实用程序, 独立于主程序之外, 但依赖 particles 组件
import particles as pt
import edge_tts as tts
from pathlib import Path
import shutil
def precache(text):
cache_dir = Path(f"./cache/voice/")
cache_dir.mkdir(parents = True, exist_ok = True)
cache = cache_dir / f"{text}.wav"
if not cache.exists():
communicate = tts.Communicate(text, "zh-CN-YunjianNeural")
communicate.save_sync(f"./cache/voice/{text}.wav")
def proc_file(path):
nu = pt.NucleonUnion(path)
c = 0
for i in nu.nucleons:
c += 1
print(f"预缓存 [{nu.name}] ({c}/{len(nu)}): {i["content"]}")
precache(f"{i["content"]}")
def walk(path_str):
path = Path(path_str)
print(f"正在遍历目录: {path}")
for item in path.iterdir():
if item.is_file():
if item.suffix == ".toml":
print(f"正预缓存文件: {item.name}")
proc_file(item)
elif item.is_dir():
print(f"进入目录: {item.name}")
print("音频预缓存实用程序")
print("需要?")
print("全部缓存: A")
print("清空缓存: C")
choice = input("输入选项 $ ")
if choice == "a" or choice == "A":
walk("./nucleon")
if choice == "c" or choice == "C":
shutil.rmtree("./cache/voice")

View File

@@ -1,8 +1,10 @@
import random import random
class Puzzle():
class Puzzle:
pass pass
class BlankPuzzle(Puzzle): class BlankPuzzle(Puzzle):
"""填空题谜题生成器 """填空题谜题生成器
@@ -10,7 +12,8 @@ class BlankPuzzle(Puzzle):
text: 原始字符串(需要 "/" 分割句子, 末尾应有 "/") text: 原始字符串(需要 "/" 分割句子, 末尾应有 "/")
min_denominator: 最小概率倒数(如占所有可生成填空数的 1/7 中的 7, 若期望值小于 1, 则取 1) min_denominator: 最小概率倒数(如占所有可生成填空数的 1/7 中的 7, 若期望值小于 1, 则取 1)
""" """
def __init__(self, text, min_denominator):
def __init__(self, text: str, min_denominator: int):
self.text = text self.text = text
self.min_denominator = min_denominator self.min_denominator = min_denominator
self.wording = "填空题 - 尚未刷新谜题" self.wording = "填空题 - 尚未刷新谜题"
@@ -21,7 +24,7 @@ class BlankPuzzle(Puzzle):
tmp_text = self.text.replace("/", placeholder) tmp_text = self.text.replace("/", placeholder)
words = tmp_text.split(placeholder) words = tmp_text.split(placeholder)
if not words: if not words:
return "" return
words = [word for word in words if word] words = [word for word in words if word]
num_blanks = min(max(1, len(words) // self.min_denominator), len(words)) num_blanks = min(max(1, len(words) // self.min_denominator), len(words))
indices_to_blank = random.sample(range(len(words)), num_blanks) indices_to_blank = random.sample(range(len(words)), num_blanks)
@@ -31,15 +34,13 @@ class BlankPuzzle(Puzzle):
for index in indices_to_blank: for index in indices_to_blank:
blanked_words[index] = "__" * len(words[index]) blanked_words[index] = "__" * len(words[index])
answer.append(words[index]) answer.append(words[index])
result = []
for word in blanked_words:
result.append(word)
self.answer = answer self.answer = answer
self.wording = "".join(result) self.wording = "".join(blanked_words)
def __str__(self): def __str__(self):
return f"{self.wording}\n{str(self.answer)}" return f"{self.wording}\n{str(self.answer)}"
class SelectionPuzzle(Puzzle): class SelectionPuzzle(Puzzle):
"""选择题谜题生成器 """选择题谜题生成器
@@ -49,12 +50,20 @@ class SelectionPuzzle(Puzzle):
max_riddles_num: 最大生成谜题数 (默认2个) max_riddles_num: 最大生成谜题数 (默认2个)
prefix: 问题前缀 prefix: 问题前缀
""" """
def __init__(self, mapping, jammer: list, max_riddles_num: int = 2, prefix: str = ""):
jammer += ["1","2","3","4"] def __init__(
self,
mapping: dict,
jammer: list,
max_riddles_num: int = 2,
prefix: str = ""
):
self.prefix = prefix self.prefix = prefix
self.mapping = mapping self.mapping = mapping
self.jammer = list(set(jammer + list(mapping.values()))) # 合并干扰项和正确答案并去重 self.jammer = list(set(jammer + list(mapping.values())))
self.max_riddles_num = max(1, min(max_riddles_num, 5)) # 限制1-5个谜题 while len(self.jammer) < 4:
self.jammer.append(" ")
self.max_riddles_num = max(1, min(max_riddles_num, 5))
self.wording = "选择题 - 尚未刷新谜题" self.wording = "选择题 - 尚未刷新谜题"
self.answer = ["选择题 - 尚未刷新谜题"] self.answer = ["选择题 - 尚未刷新谜题"]
self.options = [] self.options = []
@@ -67,35 +76,29 @@ class SelectionPuzzle(Puzzle):
self.options = [] self.options = []
return return
# 确定实际生成的谜题数量
num_questions = min(self.max_riddles_num, len(self.mapping)) num_questions = min(self.max_riddles_num, len(self.mapping))
questions = random.sample(list(self.mapping.items()), num_questions) questions = random.sample(list(self.mapping.items()), num_questions)
# 生成谜题
puzzles = [] puzzles = []
answers = [] answers = []
all_options = [] all_options = []
for question, correct_answer in questions: for question, correct_answer in questions:
# 生成选项 (正确答案 + 3个干扰项)
options = [correct_answer] options = [correct_answer]
available_jammers = [j for j in self.jammer if j != correct_answer] available_jammers = [
j for j in self.jammer if j != correct_answer
]
if len(available_jammers) >= 3: if len(available_jammers) >= 3:
selected_jammers = random.sample(available_jammers, 3) selected_jammers = random.sample(available_jammers, 3)
else: else:
selected_jammers = random.choices(available_jammers, k=3) selected_jammers = random.choices(available_jammers, k=3)
options.extend(selected_jammers) options.extend(selected_jammers)
random.shuffle(options) random.shuffle(options)
puzzles.append(question) puzzles.append(question)
answers.append(correct_answer) answers.append(correct_answer)
all_options.append(options) all_options.append(options)
question_texts = [] question_texts = []
for i, (puzzle, options) in enumerate(zip(puzzles, all_options)): for i, puzzle in enumerate(puzzles):
#options_text = "\n".join([f" {chr(97+j)}. {opt}" for j, opt in enumerate(options)])
question_texts.append(f"{self.prefix}:\n {i+1}. {puzzle}") question_texts.append(f"{self.prefix}:\n {i+1}. {puzzle}")
self.wording = question_texts self.wording = question_texts
@@ -105,9 +108,13 @@ class SelectionPuzzle(Puzzle):
def __str__(self): def __str__(self):
return f"{self.wording}\n正确答案: {', '.join(self.answer)}" return f"{self.wording}\n正确答案: {', '.join(self.answer)}"
if __name__ == "__main__":
puz = SelectionPuzzle(
puz = SelectionPuzzle({"1+1":"2", "1+2":"3", "1+3": "4"}, ["2","5","0"], 3, '求值: ') {"1+1": "2", "1+2": "3", "1+3": "4"},
["2", "5", "0"],
3,
'求值: '
)
puz.refresh() puz.refresh()
print(puz.wording) print(puz.wording)
print(puz.answer) print(puz.answer)

View File

@@ -32,7 +32,6 @@ 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.reported = set()
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
@@ -41,6 +40,7 @@ class Reactor():
counter = self.tasked_num counter = self.tasked_num
self.screen = screen self.screen = screen
self.electron_dict = electron_file.electrons_dict self.electron_dict = electron_file.electrons_dict
self.quality_dict = {}
def electron_dict_get_fallback(key) -> pt.Electron: def electron_dict_get_fallback(key) -> pt.Electron:
value = self.electron_dict.get(key) value = self.electron_dict.get(key)
# 如果值不存在,则设置默认值 # 如果值不存在,则设置默认值
@@ -71,7 +71,6 @@ class Reactor():
self.procession: list self.procession: list
self.failed: list self.failed: list
self.round_title: str self.round_title: str
self.reported: set
self.current_atom: typing.Tuple[pt.Electron, pt.Nucleon, dict] self.current_atom: typing.Tuple[pt.Electron, pt.Nucleon, dict]
self.round_set = 0 self.round_set = 0
self.current_atom = pt.Atom.placeholder() self.current_atom = pt.Atom.placeholder()
@@ -127,23 +126,25 @@ class Reactor():
return 0 return 0
def save(self): def save(self):
self._deploy_report()
print("Progress saved") print("Progress saved")
# self.nucleon_file.save() # self.nucleon_file.save()
self.electron_file.save() self.electron_file.save()
def _deploy_report(self):
"部署所有 _report"
for e, q in self.quality_dict.items():
if q == -1:
e.revisor(5, True)
continue
e.revisor(q)
def report(self, atom, quality): def report(self, atom, quality):
""" "向反应器和最低质量记录汇报"
0: 初次激活/通过
1: 不通过
"""
if atom in self.atoms_new: if atom in self.atoms_new:
atom[0].revisor(quality, True) self.quality_dict[atom[0]] = -1
return 0 print(self.quality_dict)
if atom[0] not in self.reported: return
atom[0].revisor(quality) self.quality_dict[atom[0]] = min(quality, self.quality_dict.get(atom[0], 5))
self.reported.add(atom[0])
if quality <= 3: if quality <= 3:
self.failed.append(atom) self.failed.append(atom)
return 1 print(self.quality_dict)
else:
return 0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

BIN
readme_src/img1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
readme_src/img2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 KiB

BIN
readme_src/img3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

BIN
readme_src/img4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

View File

@@ -0,0 +1,17 @@
function getStartUrl() {
const url = new URL(window.location.href);
const params = new URLSearchParams(url.search);
params.delete("delay");
return url.pathname + "?" + params.toString();
}
async function refresh() {
const ping_url = document.body.dataset.pingurl;
if (ping_url) {
await fetch(ping_url, {
method: "GET",
mode: "no-cors",
});
}
window.location.href = getStartUrl();
}

View File

@@ -4,6 +4,7 @@
<link rel="stylesheet" href="{{ config.static.url }}css/xterm.css" /> <link rel="stylesheet" href="{{ config.static.url }}css/xterm.css" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto%20Mono"/> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto%20Mono"/>
<script src="{{ config.static.url }}js/textual.js"></script> <script src="{{ config.static.url }}js/textual.js"></script>
<script src="{{ config.static.url }}js/script.js"></script>
<style> <style>
body { body {
background: #000000; background: #000000;
@@ -99,24 +100,6 @@
z-index: 5; z-index: 5;
} }
</style> </style>
<script>
function getStartUrl() {
const url = new URL(window.location.href);
const params = new URLSearchParams(url.search);
params.delete("delay");
return url.pathname + "?" + params.toString();
}
async function refresh() {
const ping_url = document.body.dataset.pingurl;
if (ping_url) {
await fetch(ping_url, {
method: "GET",
mode: "no-cors",
});
}
window.location.href = getStartUrl();
}
</script>
</head> </head>
<body data-pingurl="{{ ping_url }}"> <body data-pingurl="{{ ping_url }}">
<div class="dialog-container intro-dialog"> <div class="dialog-container intro-dialog">