Compare commits
18 Commits
2cf2cdb33f
...
v0.3.1
Author | SHA1 | Date | |
---|---|---|---|
39459a0f6e | |||
cccf7189e3 | |||
2c51f2cea3 | |||
2ad014fcd8 | |||
4ad289d02d | |||
28ccfdd227 | |||
f83d5c934d | |||
4f9eb3b7d1 | |||
c44a38f3c8 | |||
f760e7f0fa | |||
30eb45e1cb | |||
2a30f136cb | |||
051c4847b2 | |||
0873caa5fc | |||
6d3d2e665c | |||
edf2f0868a | |||
d5ef5e84d0 | |||
dd74dddf00 |
2
.playsound.py
Normal file
2
.playsound.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
def playsound(p):
|
||||||
|
print(p)
|
@@ -0,0 +1,7 @@
|
|||||||
|
# 贡献指南
|
||||||
|
## 使用 Nuitka 静态编译
|
||||||
|
运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nuitka --clang --jobs=6 --standalone --onefile main.py
|
||||||
|
```
|
30
README.md
30
README.md
@@ -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 @@
|
|||||||
- 简洁直观的复习流程设计
|
- 简洁直观的复习流程设计
|
||||||
|
|
||||||
## 屏幕截图
|
## 屏幕截图
|
||||||

|
|
||||||

|
> 单击图片以放大
|
||||||
|
|
||||||
|
<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
|
|
||||||
```
|
|
53
auxiliary.py
53
auxiliary.py
@@ -1,33 +1,60 @@
|
|||||||
import time
|
import time
|
||||||
import pathlib
|
import pathlib
|
||||||
import toml
|
import toml
|
||||||
|
import typing
|
||||||
|
import playsound
|
||||||
|
import threading
|
||||||
|
import edge_tts as tts
|
||||||
|
|
||||||
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:
|
def action_play_voice(content):
|
||||||
config = ConfigFile("config.toml")
|
def play():
|
||||||
|
communicate = tts.Communicate(
|
||||||
|
content,
|
||||||
|
"zh-CN-YunjianNeural",
|
||||||
|
)
|
||||||
|
communicate.save_sync(
|
||||||
|
f"./cache/voice/{content}"
|
||||||
|
)
|
||||||
|
playsound()
|
||||||
|
threading.Thread(target=play).start()
|
||||||
|
|
||||||
|
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))
|
141
compositions.py
141
compositions.py
@@ -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()
|
||||||
|
10
config.toml
10
config.toml
@@ -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
|
199
main.py
199
main.py
@@ -1,86 +1,67 @@
|
|||||||
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 函数
|
ver = "0.3.1"
|
||||||
_original_open = builtins.open
|
|
||||||
|
|
||||||
# 定义新的 open 函数,默认使用 UTF-8
|
|
||||||
def _open(*args, **kwargs):
|
|
||||||
if 'encoding' not in kwargs:
|
|
||||||
kwargs['encoding'] = 'utf-8'
|
|
||||||
return _original_open(*args, **kwargs)
|
|
||||||
|
|
||||||
# 替换全局的 open
|
|
||||||
builtins.open = _open
|
|
||||||
|
|
||||||
ver = '0.3.0b'
|
|
||||||
|
|
||||||
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)
|
if type(self.compo).__name__ == "Recognition":
|
||||||
|
self.action_play_voice()
|
||||||
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 +71,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 +86,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 +101,48 @@ 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(type(self.compo).__name__)
|
||||||
##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):
|
||||||
|
print("VOICE")
|
||||||
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 +151,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 +181,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()
|
@@ -8,7 +8,7 @@ translation = "语句翻译"
|
|||||||
["testdata"]
|
["testdata"]
|
||||||
# 记忆时显示的额外信息
|
# 记忆时显示的额外信息
|
||||||
additional_inf = ["translation","keyword_note", "note"]
|
additional_inf = ["translation","keyword_note", "note"]
|
||||||
# 填空测试, content指代键名
|
# 填空测试, content 指代键名
|
||||||
fill_blank_test = {"from"=["content"], "hint"=["translation"]}
|
fill_blank_test = {"from"=["content"], "hint"=["translation"]}
|
||||||
# 选择题测试
|
# 选择题测试
|
||||||
draw_card_test = {"from"=["keyword_note"]}
|
draw_card_test = {"from"=["keyword_note"]}
|
||||||
|
0
nucleon_todo/临安春雨初霁.toml
Normal file
0
nucleon_todo/临安春雨初霁.toml
Normal file
0
nucleon_todo/书愤.toml
Normal file
0
nucleon_todo/书愤.toml
Normal file
0
nucleon_todo/五代史伶官传序.toml
Normal file
0
nucleon_todo/五代史伶官传序.toml
Normal file
0
nucleon_todo/六国论.toml
Normal file
0
nucleon_todo/六国论.toml
Normal file
0
nucleon_todo/劝学.toml
Normal file
0
nucleon_todo/劝学.toml
Normal file
0
nucleon_todo/声声慢.toml
Normal file
0
nucleon_todo/声声慢.toml
Normal file
0
nucleon_todo/子路曾皙冉有公西华侍坐.toml
Normal file
0
nucleon_todo/子路曾皙冉有公西华侍坐.toml
Normal file
0
nucleon_todo/客至.toml
Normal file
0
nucleon_todo/客至.toml
Normal file
0
nucleon_todo/将进酒.toml
Normal file
0
nucleon_todo/将进酒.toml
Normal file
0
nucleon_todo/屈原列传.toml
Normal file
0
nucleon_todo/屈原列传.toml
Normal file
0
nucleon_todo/山居秋暝.toml
Normal file
0
nucleon_todo/山居秋暝.toml
Normal file
0
nucleon_todo/师说.toml
Normal file
0
nucleon_todo/师说.toml
Normal file
0
nucleon_todo/归去来兮辞.toml
Normal file
0
nucleon_todo/归去来兮辞.toml
Normal file
0
nucleon_todo/归田园居.toml
Normal file
0
nucleon_todo/归田园居.toml
Normal file
0
nucleon_todo/念奴娇赤壁怀古.toml
Normal file
0
nucleon_todo/念奴娇赤壁怀古.toml
Normal file
0
nucleon_todo/念奴娇过洞庭.toml
Normal file
0
nucleon_todo/念奴娇过洞庭.toml
Normal file
0
nucleon_todo/扬州慢.toml
Normal file
0
nucleon_todo/扬州慢.toml
Normal file
0
nucleon_todo/报任安书.toml
Normal file
0
nucleon_todo/报任安书.toml
Normal file
0
nucleon_todo/拟行路难.toml
Normal file
0
nucleon_todo/拟行路难.toml
Normal file
0
nucleon_todo/无衣.toml
Normal file
0
nucleon_todo/无衣.toml
Normal file
0
nucleon_todo/春江花月夜.toml
Normal file
0
nucleon_todo/春江花月夜.toml
Normal file
0
nucleon_todo/望海潮.toml
Normal file
0
nucleon_todo/望海潮.toml
Normal file
0
nucleon_todo/朝天子.toml
Normal file
0
nucleon_todo/朝天子.toml
Normal file
0
nucleon_todo/李凭箜篌引.toml
Normal file
0
nucleon_todo/李凭箜篌引.toml
Normal file
0
nucleon_todo/桂枝香.toml
Normal file
0
nucleon_todo/桂枝香.toml
Normal file
0
nucleon_todo/梦游天姥吟留别.toml
Normal file
0
nucleon_todo/梦游天姥吟留别.toml
Normal file
0
nucleon_todo/永遇乐.toml
Normal file
0
nucleon_todo/永遇乐.toml
Normal file
0
nucleon_todo/江城子.toml
Normal file
0
nucleon_todo/江城子.toml
Normal file
0
nucleon_todo/涉江采芙蓉.toml
Normal file
0
nucleon_todo/涉江采芙蓉.toml
Normal file
0
nucleon_todo/燕歌行.toml
Normal file
0
nucleon_todo/燕歌行.toml
Normal file
0
nucleon_todo/琵琶行.toml
Normal file
0
nucleon_todo/琵琶行.toml
Normal file
0
nucleon_todo/登岳阳楼.toml
Normal file
0
nucleon_todo/登岳阳楼.toml
Normal file
0
nucleon_todo/登快阁.toml
Normal file
0
nucleon_todo/登快阁.toml
Normal file
0
nucleon_todo/登泰山记.toml
Normal file
0
nucleon_todo/登泰山记.toml
Normal file
0
nucleon_todo/登高.toml
Normal file
0
nucleon_todo/登高.toml
Normal file
0
nucleon_todo/短歌行.toml
Normal file
0
nucleon_todo/短歌行.toml
Normal file
0
nucleon_todo/石钟山记.toml
Normal file
0
nucleon_todo/石钟山记.toml
Normal file
0
nucleon_todo/礼运.toml
Normal file
0
nucleon_todo/礼运.toml
Normal file
0
nucleon_todo/离骚.toml
Normal file
0
nucleon_todo/离骚.toml
Normal file
0
nucleon_todo/种树郭橐驼传.toml
Normal file
0
nucleon_todo/种树郭橐驼传.toml
Normal file
0
nucleon_todo/答司马谏议书.toml
Normal file
0
nucleon_todo/答司马谏议书.toml
Normal file
0
nucleon_todo/苏幕遮.toml
Normal file
0
nucleon_todo/苏幕遮.toml
Normal file
0
nucleon_todo/菩萨蛮.toml
Normal file
0
nucleon_todo/菩萨蛮.toml
Normal file
0
nucleon_todo/虞美人.toml
Normal file
0
nucleon_todo/虞美人.toml
Normal file
0
nucleon_todo/蜀相.toml
Normal file
0
nucleon_todo/蜀相.toml
Normal file
0
nucleon_todo/蜀道难.toml
Normal file
0
nucleon_todo/蜀道难.toml
Normal file
0
nucleon_todo/论语.toml
Normal file
0
nucleon_todo/论语.toml
Normal file
0
nucleon_todo/谏太宗十思疏.toml
Normal file
0
nucleon_todo/谏太宗十思疏.toml
Normal file
0
nucleon_todo/贺新郎.toml
Normal file
0
nucleon_todo/贺新郎.toml
Normal file
0
nucleon_todo/赤壁赋.toml
Normal file
0
nucleon_todo/赤壁赋.toml
Normal file
0
nucleon_todo/过秦论.toml
Normal file
0
nucleon_todo/过秦论.toml
Normal file
0
nucleon_todo/锦瑟.toml
Normal file
0
nucleon_todo/锦瑟.toml
Normal file
0
nucleon_todo/长亭送别.toml
Normal file
0
nucleon_todo/长亭送别.toml
Normal file
0
nucleon_todo/阿房宫赋.toml
Normal file
0
nucleon_todo/阿房宫赋.toml
Normal file
0
nucleon_todo/青玉案.toml
Normal file
0
nucleon_todo/青玉案.toml
Normal file
0
nucleon_todo/静女.toml
Normal file
0
nucleon_todo/静女.toml
Normal file
0
nucleon_todo/项脊轩志.toml
Normal file
0
nucleon_todo/项脊轩志.toml
Normal file
0
nucleon_todo/鹊桥仙.toml
Normal file
0
nucleon_todo/鹊桥仙.toml
Normal file
73
particles.py
73
particles.py
@@ -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 替代
|
||||||
|
|
||||||
@@ -13,7 +12,7 @@ class Electron():
|
|||||||
self.content = content
|
self.content = content
|
||||||
self.metadata = metadata
|
self.metadata = metadata
|
||||||
if metadata == {}:
|
if metadata == {}:
|
||||||
#print("NULL")
|
# print("NULL")
|
||||||
self._default_init()
|
self._default_init()
|
||||||
|
|
||||||
def _default_init(self):
|
def _default_init(self):
|
||||||
@@ -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, "")
|
||||||
@@ -207,13 +214,15 @@ class ElectronUnion():
|
|||||||
self.electrons = self.electrons_dict.values()
|
self.electrons = self.electrons_dict.values()
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
#print(1)
|
# print(1)
|
||||||
with open(self.path, 'w') as f:
|
with open(self.path, 'w') as f:
|
||||||
tmp = {i.content: i.metadata for i in self.electrons}
|
tmp = {i.content: i.metadata for i in self.electrons}
|
||||||
#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(
|
||||||
"translation": "臣子李密陈言:我因命运不好,小时候遭遇到了不幸",
|
"两只黄鹤鸣翠柳",
|
||||||
"keyword_note": {"险衅":"凶险祸患(这里指命运不好)", "夙":"早时,这里指年幼的时候", "闵":"通'悯',指可忧患的事", "凶":"不幸,指丧父"}}),
|
|
||||||
{
|
{
|
||||||
"keydata":{
|
"note": [],
|
||||||
|
"translation": "臣子李密陈言:我因命运不好,小时候遭遇到了不幸",
|
||||||
|
"keyword_note": {
|
||||||
|
"险衅": "凶险祸患(这里指命运不好)",
|
||||||
|
"夙": "早时,这里指年幼的时候",
|
||||||
|
"闵": "通'悯',指可忧患的事",
|
||||||
|
"凶": "不幸,指丧父"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"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
52
precache.py
Normal 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("缓存已清空")
|
@@ -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")
|
|
63
puzzles.py
63
puzzles.py
@@ -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,10 +108,14 @@ 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"},
|
||||||
puz.refresh()
|
["2", "5", "0"],
|
||||||
print(puz.wording)
|
3,
|
||||||
print(puz.answer)
|
'求值: '
|
||||||
print(puz.options)
|
)
|
||||||
|
puz.refresh()
|
||||||
|
print(puz.wording)
|
||||||
|
print(puz.answer)
|
||||||
|
print(puz.options)
|
29
reactor.py
29
reactor.py
@@ -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
BIN
readme_src/img1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 120 KiB |
BIN
readme_src/img2.png
Normal file
BIN
readme_src/img2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 559 KiB |
BIN
readme_src/img3.png
Normal file
BIN
readme_src/img3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 177 KiB |
BIN
readme_src/img4.png
Normal file
BIN
readme_src/img4.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 161 KiB |
17
webshare/static/js/script.js
Normal file
17
webshare/static/js/script.js
Normal 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();
|
||||||
|
}
|
@@ -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">
|
||||||
|
Reference in New Issue
Block a user