style: 代码格式化
This commit is contained in:
@@ -21,7 +21,7 @@ tts_text = "eval:nucleon['content'].replace('/', '')"
|
|||||||
["__metadata__.orbital.puzzles"] # 谜题定义
|
["__metadata__.orbital.puzzles"] # 谜题定义
|
||||||
# 我们称 "Recognition" 为 recognition 谜题的 alia
|
# 我们称 "Recognition" 为 recognition 谜题的 alia
|
||||||
"Recognition" = { __origin__ = "recognition", __hint__ = "", primary = "eval:nucleon['content']", secondary = ["eval:nucleon['keyword_note']", "eval:nucleon['note']"], top_dim = ["eval:nucleon['translation']"] }
|
"Recognition" = { __origin__ = "recognition", __hint__ = "", primary = "eval:nucleon['content']", secondary = ["eval:nucleon['keyword_note']", "eval:nucleon['note']"], top_dim = ["eval:nucleon['translation']"] }
|
||||||
"SelectMeaning" = { __origin__ = "mcq", __hint__ = "eval:nucleon['content']", mapping = "eval:nucleon['keyword_note']", jammer = "eval:nucleon['keyword_note']", max_riddles_num = "eval:default['mcq']['max_riddles_num']", prefix = "选择正确项: " }
|
"SelectMeaning" = { __origin__ = "mcq", __hint__ = "eval:nucleon['content']", primary = "eval:nucleon['content']", mapping = "eval:nucleon['keyword_note']", jammer = "eval:nucleon['keyword_note']", max_riddles_num = "eval:default['mcq']['max_riddles_num']", prefix = "选择正确项: " }
|
||||||
"FillBlank" = { __origin__ = "cloze", __hint__ = "", text = "eval:nucleon['content']", delimiter = "eval:metadata['formation']['delimiter']", min_denominator = "eval:default['cloze']['min_denominator']"}
|
"FillBlank" = { __origin__ = "cloze", __hint__ = "", text = "eval:nucleon['content']", delimiter = "eval:metadata['formation']['delimiter']", min_denominator = "eval:default['cloze']['min_denominator']"}
|
||||||
|
|
||||||
["__metadata__.orbital.schedule"] # 内置的推荐学习方案
|
["__metadata__.orbital.schedule"] # 内置的推荐学习方案
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
全局上下文管理模块
|
全局上下文管理模块
|
||||||
以及基准路径
|
以及基准路径
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from contextvars import ContextVar
|
from contextvars import ContextVar
|
||||||
import pathlib
|
import pathlib
|
||||||
from heurams.services.config import ConfigFile
|
from heurams.services.config import ConfigFile
|
||||||
@@ -11,18 +12,23 @@ from heurams.services.config import ConfigFile
|
|||||||
# 数据文件路径规定: 以运行目录为准
|
# 数据文件路径规定: 以运行目录为准
|
||||||
|
|
||||||
rootdir = pathlib.Path(__file__).parent
|
rootdir = pathlib.Path(__file__).parent
|
||||||
print(f'rootdir: {rootdir}')
|
print(f"rootdir: {rootdir}")
|
||||||
workdir = pathlib.Path.cwd()
|
workdir = pathlib.Path.cwd()
|
||||||
print(f'workdir: {workdir}')
|
print(f"workdir: {workdir}")
|
||||||
config_var: ContextVar[ConfigFile] = ContextVar('config_var', default=ConfigFile(rootdir / "default" / "config" / "config.toml"))
|
config_var: ContextVar[ConfigFile] = ContextVar(
|
||||||
|
"config_var", default=ConfigFile(rootdir / "default" / "config" / "config.toml")
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
config_var: ContextVar[ConfigFile] = ContextVar('config_var', default=ConfigFile(workdir / "config" / "config.toml")) # 配置文件
|
config_var: ContextVar[ConfigFile] = ContextVar(
|
||||||
print('已加载自定义用户配置')
|
"config_var", default=ConfigFile(workdir / "config" / "config.toml")
|
||||||
|
) # 配置文件
|
||||||
|
print("已加载自定义用户配置")
|
||||||
except:
|
except:
|
||||||
print('未能加载自定义用户配置')
|
print("未能加载自定义用户配置")
|
||||||
|
|
||||||
# runtime_var: ContextVar = ContextVar('runtime_var', default=dict()) # 运行时共享数据
|
# runtime_var: ContextVar = ContextVar('runtime_var', default=dict()) # 运行时共享数据
|
||||||
|
|
||||||
|
|
||||||
class ConfigContext:
|
class ConfigContext:
|
||||||
"""
|
"""
|
||||||
功能完备的上下文管理器
|
功能完备的上下文管理器
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ from .screens.dashboard import DashboardScreen
|
|||||||
from .screens.nucreator import NucleonCreatorScreen
|
from .screens.nucreator import NucleonCreatorScreen
|
||||||
from .screens.precache import PrecachingScreen
|
from .screens.precache import PrecachingScreen
|
||||||
from .screens.about import AboutScreen
|
from .screens.about import AboutScreen
|
||||||
|
|
||||||
|
|
||||||
class HeurAMSApp(App):
|
class HeurAMSApp(App):
|
||||||
TITLE = "潜进"
|
TITLE = "潜进"
|
||||||
# CSS_PATH = str(cxt.rootdir / "interface" / "css" / "main.css")
|
# CSS_PATH = str(cxt.rootdir / "interface" / "css" / "main.css")
|
||||||
SUB_TITLE = "启发式先进记忆调度器"
|
SUB_TITLE = "启发式先进记忆调度器"
|
||||||
BINDINGS = [("q", "quit", "退出"),
|
BINDINGS = [
|
||||||
|
("q", "quit", "退出"),
|
||||||
("d", "toggle_dark", "改变色调"),
|
("d", "toggle_dark", "改变色调"),
|
||||||
("1", "app.push_screen('dashboard')", "仪表盘"),
|
("1", "app.push_screen('dashboard')", "仪表盘"),
|
||||||
("2", "app.push_screen('precache_all')", "缓存管理器"),
|
("2", "app.push_screen('precache_all')", "缓存管理器"),
|
||||||
@@ -28,8 +31,10 @@ class HeurAMSApp(App):
|
|||||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
self.exit(event.button.id)
|
self.exit(event.button.id)
|
||||||
|
|
||||||
|
|
||||||
def environment_check():
|
def environment_check():
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
for i in config_var.get()["paths"].values():
|
for i in config_var.get()["paths"].values():
|
||||||
i = Path(i)
|
i = Path(i)
|
||||||
if not i.exists():
|
if not i.exists():
|
||||||
@@ -38,6 +43,7 @@ def environment_check():
|
|||||||
else:
|
else:
|
||||||
print(f"找到 {i}")
|
print(f"找到 {i}")
|
||||||
|
|
||||||
|
|
||||||
def is_subdir(parent, child):
|
def is_subdir(parent, child):
|
||||||
try:
|
try:
|
||||||
child.relative_to(parent)
|
child.relative_to(parent)
|
||||||
@@ -45,11 +51,13 @@ def is_subdir(parent, child):
|
|||||||
except:
|
except:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
# 开发模式
|
# 开发模式
|
||||||
from heurams.context import rootdir, workdir, config_var
|
from heurams.context import rootdir, workdir, config_var
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from heurams.context import rootdir
|
from heurams.context import rootdir
|
||||||
import os
|
import os
|
||||||
|
|
||||||
if is_subdir(Path(rootdir), Path(os.getcwd())):
|
if is_subdir(Path(rootdir), Path(os.getcwd())):
|
||||||
os.chdir(Path(rootdir) / ".." / "..")
|
os.chdir(Path(rootdir) / ".." / "..")
|
||||||
print(f'转入开发数据目录: {Path(rootdir)/".."/".."}')
|
print(f'转入开发数据目录: {Path(rootdir)/".."/".."}')
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from textual.screen import Screen
|
|||||||
import heurams.services.version as version
|
import heurams.services.version as version
|
||||||
from heurams.context import *
|
from heurams.context import *
|
||||||
|
|
||||||
|
|
||||||
class AboutScreen(Screen):
|
class AboutScreen(Screen):
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from .about import AboutScreen
|
|||||||
|
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
class DashboardScreen(Screen):
|
class DashboardScreen(Screen):
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
@@ -31,7 +32,9 @@ class DashboardScreen(Screen):
|
|||||||
Label(f'时区修正: UTC+{config_var.get()["timezone_offset"] / 3600}'),
|
Label(f'时区修正: UTC+{config_var.get()["timezone_offset"] / 3600}'),
|
||||||
Label("选择待学习或待修改的记忆单元集:", classes="title-label"),
|
Label("选择待学习或待修改的记忆单元集:", classes="title-label"),
|
||||||
ListView(id="union-list", classes="union-list-view"),
|
ListView(id="union-list", classes="union-list-view"),
|
||||||
Label(f'"潜进" 开放源代码软件项目 | 版本 {version.ver} {version.codename.capitalize()} | Wang Zhiyu 2025'),
|
Label(
|
||||||
|
f'"潜进" 开放源代码软件项目 | 版本 {version.ver} {version.codename.capitalize()} | Wang Zhiyu 2025'
|
||||||
|
),
|
||||||
)
|
)
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
|
||||||
@@ -46,17 +49,20 @@ class DashboardScreen(Screen):
|
|||||||
res[0] = f"{filename}\0"
|
res[0] = f"{filename}\0"
|
||||||
from heurams.kernel.particles.loader import load_electron
|
from heurams.kernel.particles.loader import load_electron
|
||||||
import heurams.kernel.particles as pt
|
import heurams.kernel.particles as pt
|
||||||
electron_file_path = (pathlib.Path(config_var.get()["paths"]["electron_dir"]) / (filestem + ".json"))
|
|
||||||
|
electron_file_path = pathlib.Path(config_var.get()["paths"]["electron_dir"]) / (
|
||||||
|
filestem + ".json"
|
||||||
|
)
|
||||||
if electron_file_path.exists(): # 未找到则创建电子文件 (json)
|
if electron_file_path.exists(): # 未找到则创建电子文件 (json)
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
electron_file_path.touch()
|
electron_file_path.touch()
|
||||||
with open(electron_file_path, 'w') as f:
|
with open(electron_file_path, "w") as f:
|
||||||
f.write("{}")
|
f.write("{}")
|
||||||
electron_dict = load_electron(path=electron_file_path) # TODO: 取消硬编码扩展名
|
electron_dict = load_electron(path=electron_file_path) # TODO: 取消硬编码扩展名
|
||||||
is_due = 0
|
is_due = 0
|
||||||
is_activated = 0
|
is_activated = 0
|
||||||
nextdate = 0x3f3f3f3f
|
nextdate = 0x3F3F3F3F
|
||||||
for i in electron_dict.values():
|
for i in electron_dict.values():
|
||||||
i: pt.Electron
|
i: pt.Electron
|
||||||
if i.is_due():
|
if i.is_due():
|
||||||
@@ -78,12 +84,18 @@ class DashboardScreen(Screen):
|
|||||||
if len(probe["nucleon"]):
|
if len(probe["nucleon"]):
|
||||||
for file in probe["nucleon"]:
|
for file in probe["nucleon"]:
|
||||||
text = self.item_desc_generator(file)
|
text = self.item_desc_generator(file)
|
||||||
union_list_widget.append(ListItem(
|
union_list_widget.append(
|
||||||
Label(text[0] + '\n' + text[1]),
|
ListItem(
|
||||||
))
|
Label(text[0] + "\n" + text[1]),
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
union_list_widget.append(
|
union_list_widget.append(
|
||||||
ListItem(Static("在 ./nucleon/ 中未找到任何内容源数据文件.\n请放置文件后重启应用.\n或者新建空的单元集."))
|
ListItem(
|
||||||
|
Static(
|
||||||
|
"在 ./nucleon/ 中未找到任何内容源数据文件.\n请放置文件后重启应用.\n或者新建空的单元集."
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
union_list_widget.disabled = True
|
union_list_widget.disabled = True
|
||||||
|
|
||||||
@@ -95,27 +107,36 @@ class DashboardScreen(Screen):
|
|||||||
if "未找到任何 .toml 文件" in str(selected_label.renderable):
|
if "未找到任何 .toml 文件" in str(selected_label.renderable):
|
||||||
return
|
return
|
||||||
|
|
||||||
selected_filename = pathlib.Path(str(selected_label.renderable)
|
selected_filename = pathlib.Path(
|
||||||
.partition('\0')[0] # 文件名末尾截断, 保留文件名
|
str(selected_label.renderable)
|
||||||
.replace('*', "")) # 去除markdown加粗
|
.partition("\0")[0] # 文件名末尾截断, 保留文件名
|
||||||
|
.replace("*", "")
|
||||||
|
) # 去除markdown加粗
|
||||||
|
|
||||||
nucleon_file_path = pathlib.Path(config_var.get()["paths"]["nucleon_dir"]) / selected_filename
|
nucleon_file_path = (
|
||||||
electron_file_path = pathlib.Path(config_var.get()["paths"]["electron_dir"]) / (str(selected_filename.stem) + ".json")
|
pathlib.Path(config_var.get()["paths"]["nucleon_dir"]) / selected_filename
|
||||||
|
)
|
||||||
|
electron_file_path = pathlib.Path(config_var.get()["paths"]["electron_dir"]) / (
|
||||||
|
str(selected_filename.stem) + ".json"
|
||||||
|
)
|
||||||
self.app.push_screen(PreparationScreen(nucleon_file_path, electron_file_path))
|
self.app.push_screen(PreparationScreen(nucleon_file_path, electron_file_path))
|
||||||
|
|
||||||
def on_button_pressed(self, event) -> None:
|
def on_button_pressed(self, event) -> None:
|
||||||
if event.button.id == "new_nucleon_button":
|
if event.button.id == "new_nucleon_button":
|
||||||
# 切换到创建单元
|
# 切换到创建单元
|
||||||
from .nucreator import NucleonCreatorScreen
|
from .nucreator import NucleonCreatorScreen
|
||||||
|
|
||||||
newscr = NucleonCreatorScreen()
|
newscr = NucleonCreatorScreen()
|
||||||
self.app.push_screen(newscr)
|
self.app.push_screen(newscr)
|
||||||
elif event.button.id == "precache_all_button":
|
elif event.button.id == "precache_all_button":
|
||||||
# 切换到缓存管理器
|
# 切换到缓存管理器
|
||||||
from .precache import PrecachingScreen
|
from .precache import PrecachingScreen
|
||||||
|
|
||||||
precache_screen = PrecachingScreen()
|
precache_screen = PrecachingScreen()
|
||||||
self.app.push_screen(precache_screen)
|
self.app.push_screen(precache_screen)
|
||||||
elif event.button.id == "about_button":
|
elif event.button.id == "about_button":
|
||||||
from .about import AboutScreen
|
from .about import AboutScreen
|
||||||
|
|
||||||
about_screen = AboutScreen()
|
about_screen = AboutScreen()
|
||||||
self.app.push_screen(about_screen)
|
self.app.push_screen(about_screen)
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ import heurams.kernel.particles as pt
|
|||||||
import heurams.kernel.puzzles as pz
|
import heurams.kernel.puzzles as pz
|
||||||
from .. import shim
|
from .. import shim
|
||||||
|
|
||||||
|
|
||||||
class AtomState(Enum):
|
class AtomState(Enum):
|
||||||
FAILED = auto()
|
FAILED = auto()
|
||||||
NORMAL = auto()
|
NORMAL = auto()
|
||||||
|
|
||||||
|
|
||||||
class MemScreen(Screen):
|
class MemScreen(Screen):
|
||||||
BINDINGS = [
|
BINDINGS = [
|
||||||
("q", "pop_screen", "返回"),
|
("q", "pop_screen", "返回"),
|
||||||
@@ -27,7 +29,14 @@ class MemScreen(Screen):
|
|||||||
if config_var.get()["quick_pass"]:
|
if config_var.get()["quick_pass"]:
|
||||||
BINDINGS.append(("k", "quick_pass", "跳过"))
|
BINDINGS.append(("k", "quick_pass", "跳过"))
|
||||||
rating = reactive(-1)
|
rating = reactive(-1)
|
||||||
def __init__(self, atoms: list, name: str | None = None, id: str | None = None, classes: str | None = None) -> None:
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
atoms: list,
|
||||||
|
name: str | None = None,
|
||||||
|
id: str | None = None,
|
||||||
|
classes: str | None = None,
|
||||||
|
) -> None:
|
||||||
super().__init__(name, id, classes)
|
super().__init__(name, id, classes)
|
||||||
self.atoms = atoms
|
self.atoms = atoms
|
||||||
self.phaser = Phaser(atoms)
|
self.phaser = Phaser(atoms)
|
||||||
@@ -36,7 +45,6 @@ class MemScreen(Screen):
|
|||||||
# print(self.phaser.state)
|
# print(self.phaser.state)
|
||||||
# self.procession.forward(1)
|
# self.procession.forward(1)
|
||||||
|
|
||||||
|
|
||||||
def on_mount(self):
|
def on_mount(self):
|
||||||
self.load_puzzle()
|
self.load_puzzle()
|
||||||
pass
|
pass
|
||||||
@@ -48,7 +56,9 @@ class MemScreen(Screen):
|
|||||||
# print(1)
|
# print(1)
|
||||||
puzzle_info = next(self.fission.generate())
|
puzzle_info = next(self.fission.generate())
|
||||||
print(puzzle_info)
|
print(puzzle_info)
|
||||||
return shim.puzzle2widget[puzzle_info["puzzle"]](atom = self.procession.current_atom, alia = puzzle_info["alia"])
|
return shim.puzzle2widget[puzzle_info["puzzle"]](
|
||||||
|
atom=self.procession.current_atom, alia=puzzle_info["alia"]
|
||||||
|
)
|
||||||
except (KeyError, StopIteration, AttributeError) as e:
|
except (KeyError, StopIteration, AttributeError) as e:
|
||||||
print(f"调度展开出错: {e}")
|
print(f"调度展开出错: {e}")
|
||||||
return Static("无法生成谜题")
|
return Static("无法生成谜题")
|
||||||
@@ -57,7 +67,9 @@ class MemScreen(Screen):
|
|||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Header(show_clock=True)
|
yield Header(show_clock=True)
|
||||||
with Center():
|
with Center():
|
||||||
yield Static(f"当前进度: {self.procession.process()}/{self.procession.total_length()}")
|
yield Static(
|
||||||
|
f"当前进度: {self.procession.process()}/{self.procession.total_length()}"
|
||||||
|
)
|
||||||
# self.mount(self.current_widget()) # type: ignore
|
# self.mount(self.current_widget()) # type: ignore
|
||||||
yield Container(id="puzzle-container")
|
yield Container(id="puzzle-container")
|
||||||
# yield Button("重新学习此单元", id="re-recognize", variant="warning")
|
# yield Button("重新学习此单元", id="re-recognize", variant="warning")
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from textual.screen import Screen
|
|||||||
|
|
||||||
from heurams.services.version import ver
|
from heurams.services.version import ver
|
||||||
|
|
||||||
|
|
||||||
class NucleonCreatorScreen(Screen):
|
class NucleonCreatorScreen(Screen):
|
||||||
BINDINGS = [("q", "go_back", "返回")]
|
BINDINGS = [("q", "go_back", "返回")]
|
||||||
|
|
||||||
@@ -23,18 +24,20 @@ class NucleonCreatorScreen(Screen):
|
|||||||
def search_templates(self):
|
def search_templates(self):
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from heurams.context import config_var
|
from heurams.context import config_var
|
||||||
template_dir = Path(config_var.get()['paths']['template_dir'])
|
|
||||||
|
template_dir = Path(config_var.get()["paths"]["template_dir"])
|
||||||
templates = list()
|
templates = list()
|
||||||
for i in template_dir.iterdir():
|
for i in template_dir.iterdir():
|
||||||
if i.name.endswith('.toml'):
|
if i.name.endswith(".toml"):
|
||||||
try:
|
try:
|
||||||
import toml
|
import toml
|
||||||
with open(i, 'r') as f:
|
|
||||||
|
with open(i, "r") as f:
|
||||||
dic = toml.load(f)
|
dic = toml.load(f)
|
||||||
desc = dic['__metadata__.attribution']['desc']
|
desc = dic["__metadata__.attribution"]["desc"]
|
||||||
templates.append(desc + ' (' + i.name + ')')
|
templates.append(desc + " (" + i.name + ")")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
templates.append(f'无描述模板 ({i.name})')
|
templates.append(f"无描述模板 ({i.name})")
|
||||||
print(e)
|
print(e)
|
||||||
print(templates)
|
print(templates)
|
||||||
return templates
|
return templates
|
||||||
@@ -43,10 +46,14 @@ class NucleonCreatorScreen(Screen):
|
|||||||
yield Header(show_clock=True)
|
yield Header(show_clock=True)
|
||||||
with Container(id="vice_container"):
|
with Container(id="vice_container"):
|
||||||
yield Label(f"[b]空白单元集创建向导\n")
|
yield Label(f"[b]空白单元集创建向导\n")
|
||||||
yield Markdown("> 提示: 你可能注意到当选中文本框时底栏和操作按键绑定将被覆盖 \n只需选中(使用鼠标或 Tab)选择框即可恢复底栏功能")
|
yield Markdown(
|
||||||
|
"> 提示: 你可能注意到当选中文本框时底栏和操作按键绑定将被覆盖 \n只需选中(使用鼠标或 Tab)选择框即可恢复底栏功能"
|
||||||
|
)
|
||||||
yield Markdown("1. 键入单元集名称")
|
yield Markdown("1. 键入单元集名称")
|
||||||
yield Input(placeholder="单元集名称")
|
yield Input(placeholder="单元集名称")
|
||||||
yield Markdown("> 单元集名称不应与现有单元集重复. \n> 新的单元集文件将创建在 ./nucleon/你输入的名称.toml")
|
yield Markdown(
|
||||||
|
"> 单元集名称不应与现有单元集重复. \n> 新的单元集文件将创建在 ./nucleon/你输入的名称.toml"
|
||||||
|
)
|
||||||
yield Label(f"\n")
|
yield Label(f"\n")
|
||||||
yield Markdown("2. 选择单元集模板")
|
yield Markdown("2. 选择单元集模板")
|
||||||
LINES = self.search_templates()
|
LINES = self.search_templates()
|
||||||
@@ -79,5 +86,5 @@ class NucleonCreatorScreen(Screen):
|
|||||||
|
|
||||||
def on_button_pressed(self, event) -> None:
|
def on_button_pressed(self, event) -> None:
|
||||||
event.stop()
|
event.stop()
|
||||||
if event.button.id == 'submit_button':
|
if event.button.id == "submit_button":
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import heurams.services.hasher as hasher
|
|||||||
from heurams.context import *
|
from heurams.context import *
|
||||||
from textual.worker import Worker, get_current_worker
|
from textual.worker import Worker, get_current_worker
|
||||||
|
|
||||||
|
|
||||||
class PrecachingScreen(Screen):
|
class PrecachingScreen(Screen):
|
||||||
"""预缓存音频文件屏幕
|
"""预缓存音频文件屏幕
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@ class PrecachingScreen(Screen):
|
|||||||
nucleons (list): 可选列表, 仅包含 Nucleon 对象
|
nucleons (list): 可选列表, 仅包含 Nucleon 对象
|
||||||
desc (list): 可选字符串, 包含对此次调用的文字描述
|
desc (list): 可选字符串, 包含对此次调用的文字描述
|
||||||
"""
|
"""
|
||||||
|
|
||||||
BINDINGS = [("q", "go_back", "返回")]
|
BINDINGS = [("q", "go_back", "返回")]
|
||||||
|
|
||||||
def __init__(self, nucleons: list = [], desc: str = ""):
|
def __init__(self, nucleons: list = [], desc: str = ""):
|
||||||
@@ -73,7 +75,7 @@ class PrecachingScreen(Screen):
|
|||||||
yield Button("返回", id="go_back", variant="default")
|
yield Button("返回", id="go_back", variant="default")
|
||||||
|
|
||||||
yield Static("若您离开此界面, 未完成的缓存进程会自动停止.")
|
yield Static("若您离开此界面, 未完成的缓存进程会自动停止.")
|
||||||
yield Static("缓存程序支持 \"断点续传\".")
|
yield Static('缓存程序支持 "断点续传".')
|
||||||
|
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
|
||||||
@@ -97,12 +99,14 @@ class PrecachingScreen(Screen):
|
|||||||
def precache_by_text(self, text: str):
|
def precache_by_text(self, text: str):
|
||||||
"""预缓存单段文本的音频"""
|
"""预缓存单段文本的音频"""
|
||||||
from heurams.context import rootdir, workdir, config_var
|
from heurams.context import rootdir, workdir, config_var
|
||||||
|
|
||||||
cache_dir = pathlib.Path(config_var.get()["paths"]["cache_dir"])
|
cache_dir = pathlib.Path(config_var.get()["paths"]["cache_dir"])
|
||||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||||
cache_file = cache_dir / f"{hasher.get_md5(text)}.wav"
|
cache_file = cache_dir / f"{hasher.get_md5(text)}.wav"
|
||||||
if not cache_file.exists():
|
if not cache_file.exists():
|
||||||
try: # TODO: 调用模块消除tts耦合
|
try: # TODO: 调用模块消除tts耦合
|
||||||
import edge_tts as tts
|
import edge_tts as tts
|
||||||
|
|
||||||
communicate = tts.Communicate(text, "zh-CN-XiaoxiaoNeural")
|
communicate = tts.Communicate(text, "zh-CN-XiaoxiaoNeural")
|
||||||
communicate.save_sync(str(cache_file))
|
communicate.save_sync(str(cache_file))
|
||||||
return 1
|
return 1
|
||||||
@@ -114,7 +118,7 @@ class PrecachingScreen(Screen):
|
|||||||
def precache_by_nucleon(self, nucleon: pt.Nucleon):
|
def precache_by_nucleon(self, nucleon: pt.Nucleon):
|
||||||
"""依据 Nucleon 缓存"""
|
"""依据 Nucleon 缓存"""
|
||||||
# print(nucleon.metadata['formation']['tts_text'])
|
# print(nucleon.metadata['formation']['tts_text'])
|
||||||
ret = self.precache_by_text(nucleon.metadata['formation']['tts_text'])
|
ret = self.precache_by_text(nucleon.metadata["formation"]["tts_text"])
|
||||||
return ret
|
return ret
|
||||||
# print(f"TTS 缓存: {nucleon.metadata['formation']['tts_text']}")
|
# print(f"TTS 缓存: {nucleon.metadata['formation']['tts_text']}")
|
||||||
|
|
||||||
@@ -125,7 +129,7 @@ class PrecachingScreen(Screen):
|
|||||||
worker = get_current_worker()
|
worker = get_current_worker()
|
||||||
if worker and worker.is_cancelled: # 函数在worker中执行且已被取消
|
if worker and worker.is_cancelled: # 函数在worker中执行且已被取消
|
||||||
return False
|
return False
|
||||||
text = nucleon.metadata['formation']['tts_text']
|
text = nucleon.metadata["formation"]["tts_text"]
|
||||||
# self.current_item = text[:30] + "..." if len(text) > 50 else text
|
# self.current_item = text[:30] + "..." if len(text) > 50 else text
|
||||||
# print(text)
|
# print(text)
|
||||||
self.processed += 1
|
self.processed += 1
|
||||||
@@ -133,11 +137,7 @@ class PrecachingScreen(Screen):
|
|||||||
# print(self.total)
|
# print(self.total)
|
||||||
progress = int((self.processed / self.total) * 100) if self.total > 0 else 0
|
progress = int((self.processed / self.total) * 100) if self.total > 0 else 0
|
||||||
# print(progress)
|
# print(progress)
|
||||||
self.update_status(
|
self.update_status(f"正处理 ({idx + 1}/{len(nucleons)})", text, progress)
|
||||||
f"正处理 ({idx + 1}/{len(nucleons)})",
|
|
||||||
text,
|
|
||||||
progress
|
|
||||||
)
|
|
||||||
ret = self.precache_by_nucleon(nucleon)
|
ret = self.precache_by_nucleon(nucleon)
|
||||||
if not ret:
|
if not ret:
|
||||||
self.update_status(
|
self.update_status(
|
||||||
@@ -145,6 +145,7 @@ class PrecachingScreen(Screen):
|
|||||||
f"处理失败, 跳过: {self.current_item}",
|
f"处理失败, 跳过: {self.current_item}",
|
||||||
)
|
)
|
||||||
import time
|
import time
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
if self.cancel_flag:
|
if self.cancel_flag:
|
||||||
worker.cancel()
|
worker.cancel()
|
||||||
@@ -165,12 +166,14 @@ class PrecachingScreen(Screen):
|
|||||||
lst.append(i[0])
|
lst.append(i[0])
|
||||||
return self.precache_by_list(lst)
|
return self.precache_by_list(lst)
|
||||||
|
|
||||||
|
|
||||||
def precache_all_files(self):
|
def precache_all_files(self):
|
||||||
"""预缓存所有文件"""
|
"""预缓存所有文件"""
|
||||||
from heurams.context import rootdir, workdir, config_var
|
from heurams.context import rootdir, workdir, config_var
|
||||||
|
|
||||||
nucleon_path = pathlib.Path(config_var.get()["paths"]["nucleon_dir"])
|
nucleon_path = pathlib.Path(config_var.get()["paths"]["nucleon_dir"])
|
||||||
nucleon_files = [f for f in nucleon_path.iterdir() if f.suffix == ".toml"] # TODO: 解耦合
|
nucleon_files = [
|
||||||
|
f for f in nucleon_path.iterdir() if f.suffix == ".toml"
|
||||||
|
] # TODO: 解耦合
|
||||||
|
|
||||||
# 计算总项目数
|
# 计算总项目数
|
||||||
self.total = 0
|
self.total = 0
|
||||||
@@ -192,9 +195,19 @@ class PrecachingScreen(Screen):
|
|||||||
if event.button.id == "start_precache" and not self.is_precaching:
|
if event.button.id == "start_precache" and not self.is_precaching:
|
||||||
# 开始预缓存
|
# 开始预缓存
|
||||||
if self.nucleons:
|
if self.nucleons:
|
||||||
self.precache_worker = self.run_worker(self.precache_by_nucleons, thread=True, exclusive=True, exit_on_error=True)
|
self.precache_worker = self.run_worker(
|
||||||
|
self.precache_by_nucleons,
|
||||||
|
thread=True,
|
||||||
|
exclusive=True,
|
||||||
|
exit_on_error=True,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.precache_worker = self.run_worker(self.precache_all_files, thread=True, exclusive=True, exit_on_error=True)
|
self.precache_worker = self.run_worker(
|
||||||
|
self.precache_all_files,
|
||||||
|
thread=True,
|
||||||
|
exclusive=True,
|
||||||
|
exit_on_error=True,
|
||||||
|
)
|
||||||
|
|
||||||
elif event.button.id == "cancel_precache" and self.is_precaching:
|
elif event.button.id == "cancel_precache" and self.is_precaching:
|
||||||
# 取消预缓存
|
# 取消预缓存
|
||||||
@@ -210,7 +223,10 @@ class PrecachingScreen(Screen):
|
|||||||
try:
|
try:
|
||||||
import shutil
|
import shutil
|
||||||
from heurams.context import rootdir, workdir, config_var
|
from heurams.context import rootdir, workdir, config_var
|
||||||
shutil.rmtree(f"{config_var.get()["paths"]["cache_dir"]}", ignore_errors=True)
|
|
||||||
|
shutil.rmtree(
|
||||||
|
f"{config_var.get()["paths"]["cache_dir"]}", ignore_errors=True
|
||||||
|
)
|
||||||
self.update_status("已清空", "音频缓存已清空", 0)
|
self.update_status("已清空", "音频缓存已清空", 0)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.update_status("错误", f"清空缓存失败: {e}")
|
self.update_status("错误", f"清空缓存失败: {e}")
|
||||||
|
|||||||
@@ -15,15 +15,11 @@ import heurams.kernel.particles as pt
|
|||||||
import heurams.services.hasher as hasher
|
import heurams.services.hasher as hasher
|
||||||
from heurams.context import *
|
from heurams.context import *
|
||||||
|
|
||||||
class PreparationScreen(Screen):
|
|
||||||
BINDINGS = [
|
|
||||||
("q", "go_back", "返回"),
|
|
||||||
("p", "precache", "预缓存音频")
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(
|
class PreparationScreen(Screen):
|
||||||
self, nucleon_file: pathlib.Path, electron_file: pathlib.Path
|
BINDINGS = [("q", "go_back", "返回"), ("p", "precache", "预缓存音频")]
|
||||||
) -> None:
|
|
||||||
|
def __init__(self, nucleon_file: pathlib.Path, electron_file: pathlib.Path) -> 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
|
||||||
@@ -68,6 +64,7 @@ class PreparationScreen(Screen):
|
|||||||
|
|
||||||
def action_precache(self):
|
def action_precache(self):
|
||||||
from ..screens.precache import PrecachingScreen
|
from ..screens.precache import PrecachingScreen
|
||||||
|
|
||||||
lst = list()
|
lst = list()
|
||||||
for i in self.nucleons_with_orbital:
|
for i in self.nucleons_with_orbital:
|
||||||
lst.append(i[0])
|
lst.append(i[0])
|
||||||
@@ -97,8 +94,8 @@ class PreparationScreen(Screen):
|
|||||||
atom.link("orbital_path", None)
|
atom.link("orbital_path", None)
|
||||||
atoms.append(atom)
|
atoms.append(atom)
|
||||||
from .memorizor import MemScreen
|
from .memorizor import MemScreen
|
||||||
|
|
||||||
memscreen = MemScreen(atoms)
|
memscreen = MemScreen(atoms)
|
||||||
self.app.push_screen(memscreen)
|
self.app.push_screen(memscreen)
|
||||||
elif event.button.id == "precache_button":
|
elif event.button.id == "precache_button":
|
||||||
self.action_precache()
|
self.action_precache()
|
||||||
|
|
||||||
@@ -1,21 +1,33 @@
|
|||||||
"""Kernel 操作先进函数库"""
|
"""Kernel 操作先进函数库"""
|
||||||
|
|
||||||
import random
|
import random
|
||||||
import heurams.kernel.particles as pt
|
import heurams.kernel.particles as pt
|
||||||
import heurams.kernel.puzzles as pz
|
import heurams.kernel.puzzles as pz
|
||||||
import heurams.interface.widgets as pzw
|
import heurams.interface.widgets as pzw
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
staging = {} # 细粒度缓存区, 是 ident -> quality 的封装
|
staging = {} # 细粒度缓存区, 是 ident -> quality 的封装
|
||||||
|
|
||||||
|
|
||||||
def report_to_staging(atom: pt.Atom, quality):
|
def report_to_staging(atom: pt.Atom, quality):
|
||||||
staging[atom.ident] = min(quality, staging[atom.ident])
|
staging[atom.ident] = min(quality, staging[atom.ident])
|
||||||
|
|
||||||
|
|
||||||
def clear():
|
def clear():
|
||||||
staging = dict()
|
staging = dict()
|
||||||
|
|
||||||
|
|
||||||
def deploy_to_electron():
|
def deploy_to_electron():
|
||||||
for atom_ident, quality in staging.items():
|
for atom_ident, quality in staging.items():
|
||||||
if pt.atom_registry[atom_ident].registry['electron'].is_activated:
|
if pt.atom_registry[atom_ident].registry["electron"].is_activated:
|
||||||
pt.atom_registry[atom_ident].registry['electron'].revisor(quality=quality)
|
pt.atom_registry[atom_ident].registry["electron"].revisor(quality=quality)
|
||||||
else:
|
else:
|
||||||
pt.atom_registry[atom_ident].registry['electron'].revisor(quality=quality, is_new_activation=True)
|
pt.atom_registry[atom_ident].registry["electron"].revisor(
|
||||||
|
quality=quality, is_new_activation=True
|
||||||
|
)
|
||||||
clear()
|
clear()
|
||||||
|
|
||||||
|
|
||||||
puzzle2widget = {
|
puzzle2widget = {
|
||||||
pz.RecognitionPuzzle: pzw.Recognition,
|
pz.RecognitionPuzzle: pzw.Recognition,
|
||||||
pz.ClozePuzzle: pzw.ClozePuzzle,
|
pz.ClozePuzzle: pzw.ClozePuzzle,
|
||||||
|
|||||||
@@ -1,7 +1,24 @@
|
|||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
import heurams.kernel.particles as pt
|
import heurams.kernel.particles as pt
|
||||||
|
|
||||||
|
|
||||||
class BasePuzzleWidget(Widget):
|
class BasePuzzleWidget(Widget):
|
||||||
def __init__(self, *children: Widget, atom: pt.Atom, name: str | None = None, id: str | None = None, classes: str | None = None, disabled: bool = False, markup: bool = True) -> None:
|
def __init__(
|
||||||
super().__init__(*children, name=name, id=id, classes=classes, disabled=disabled, markup=markup)
|
self,
|
||||||
|
*children: Widget,
|
||||||
|
atom: pt.Atom,
|
||||||
|
name: str | None = None,
|
||||||
|
id: str | None = None,
|
||||||
|
classes: str | None = None,
|
||||||
|
disabled: bool = False,
|
||||||
|
markup: bool = True
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
*children,
|
||||||
|
name=name,
|
||||||
|
id=id,
|
||||||
|
classes=classes,
|
||||||
|
disabled=disabled,
|
||||||
|
markup=markup
|
||||||
|
)
|
||||||
self.atom = atom
|
self.atom = atom
|
||||||
@@ -9,9 +9,28 @@ import heurams.kernel.particles as pt
|
|||||||
from .base_puzzle_widget import BasePuzzleWidget
|
from .base_puzzle_widget import BasePuzzleWidget
|
||||||
from textual.message import Message
|
from textual.message import Message
|
||||||
|
|
||||||
|
|
||||||
class BasicEvaluation(BasePuzzleWidget):
|
class BasicEvaluation(BasePuzzleWidget):
|
||||||
def __init__(self, *children: Widget, atom: pt.Atom, alia: str = "", name: str | None = None, id: str | None = None, classes: str | None = None, disabled: bool = False, markup: bool = True) -> None:
|
def __init__(
|
||||||
super().__init__(*children, atom=atom, name=name, id=id, classes=classes, disabled=disabled, markup=markup)
|
self,
|
||||||
|
*children: Widget,
|
||||||
|
atom: pt.Atom,
|
||||||
|
alia: str = "",
|
||||||
|
name: str | None = None,
|
||||||
|
id: str | None = None,
|
||||||
|
classes: str | None = None,
|
||||||
|
disabled: bool = False,
|
||||||
|
markup: bool = True,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
*children,
|
||||||
|
atom=atom,
|
||||||
|
name=name,
|
||||||
|
id=id,
|
||||||
|
classes=classes,
|
||||||
|
disabled=disabled,
|
||||||
|
markup=markup,
|
||||||
|
)
|
||||||
|
|
||||||
class RatingChanged(Message):
|
class RatingChanged(Message):
|
||||||
def __init__(self, rating: int) -> None:
|
def __init__(self, rating: int) -> None:
|
||||||
@@ -69,9 +88,11 @@ class BasicEvaluation(BasePuzzleWidget):
|
|||||||
if button_id in self.feedback_mapping:
|
if button_id in self.feedback_mapping:
|
||||||
feedback_info = self.feedback_mapping[button_id]
|
feedback_info = self.feedback_mapping[button_id]
|
||||||
|
|
||||||
self.post_message(self.RatingChanged(
|
self.post_message(
|
||||||
|
self.RatingChanged(
|
||||||
rating=feedback_info["rating"],
|
rating=feedback_info["rating"],
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
event.button.add_class("selected")
|
event.button.add_class("selected")
|
||||||
|
|
||||||
@@ -91,6 +112,8 @@ class BasicEvaluation(BasePuzzleWidget):
|
|||||||
button_id = f"feedback_{event.key}"
|
button_id = f"feedback_{event.key}"
|
||||||
if button_id in self.feedback_mapping:
|
if button_id in self.feedback_mapping:
|
||||||
# 模拟按钮点击
|
# 模拟按钮点击
|
||||||
self.post_message(self.RatingChanged(
|
self.post_message(
|
||||||
|
self.RatingChanged(
|
||||||
rating=self.feedback_mapping[button_id]["rating"],
|
rating=self.feedback_mapping[button_id]["rating"],
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|||||||
@@ -10,10 +10,29 @@ import copy
|
|||||||
import random
|
import random
|
||||||
from textual.message import Message
|
from textual.message import Message
|
||||||
|
|
||||||
|
|
||||||
class ClozePuzzle(BasePuzzleWidget):
|
class ClozePuzzle(BasePuzzleWidget):
|
||||||
|
|
||||||
def __init__(self, *children: Widget, atom: pt.Atom, alia: str = "", name: str | None = None, id: str | None = None, classes: str | None = None, disabled: bool = False, markup: bool = True) -> None:
|
def __init__(
|
||||||
super().__init__(*children, atom=atom, name=name, id=id, classes=classes, disabled=disabled, markup=markup)
|
self,
|
||||||
|
*children: Widget,
|
||||||
|
atom: pt.Atom,
|
||||||
|
alia: str = "",
|
||||||
|
name: str | None = None,
|
||||||
|
id: str | None = None,
|
||||||
|
classes: str | None = None,
|
||||||
|
disabled: bool = False,
|
||||||
|
markup: bool = True,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
*children,
|
||||||
|
atom=atom,
|
||||||
|
name=name,
|
||||||
|
id=id,
|
||||||
|
classes=classes,
|
||||||
|
disabled=disabled,
|
||||||
|
markup=markup,
|
||||||
|
)
|
||||||
self.inputlist = list()
|
self.inputlist = list()
|
||||||
self.hashtable = {}
|
self.hashtable = {}
|
||||||
self.alia = alia
|
self.alia = alia
|
||||||
@@ -21,7 +40,11 @@ class ClozePuzzle(BasePuzzleWidget):
|
|||||||
|
|
||||||
def _work(self):
|
def _work(self):
|
||||||
cfg = self.atom.registry["orbital"]["puzzles"][self.alia]
|
cfg = self.atom.registry["orbital"]["puzzles"][self.alia]
|
||||||
self.puzzle = pz.ClozePuzzle(text=cfg["content"], delimiter=cfg["delimiter"], min_denominator=cfg["min_denominator"])
|
self.puzzle = pz.ClozePuzzle(
|
||||||
|
text=cfg["content"],
|
||||||
|
delimiter=cfg["delimiter"],
|
||||||
|
min_denominator=cfg["min_denominator"],
|
||||||
|
)
|
||||||
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)
|
||||||
@@ -35,6 +58,7 @@ class ClozePuzzle(BasePuzzleWidget):
|
|||||||
|
|
||||||
class InputChanged(Message):
|
class InputChanged(Message):
|
||||||
"""输入变化消息"""
|
"""输入变化消息"""
|
||||||
|
|
||||||
def __init__(self, current_input: list, max_length: int) -> None:
|
def __init__(self, current_input: list, max_length: int) -> None:
|
||||||
self.current_input = current_input # 当前输入
|
self.current_input = current_input # 当前输入
|
||||||
self.max_length = max_length # 最大长度
|
self.max_length = max_length # 最大长度
|
||||||
@@ -53,10 +77,11 @@ class ClozePuzzle(BasePuzzleWidget):
|
|||||||
preview = self.query_one("#inputpreview")
|
preview = self.query_one("#inputpreview")
|
||||||
preview.update(f"当前输入: {self.inputlist}") # type: ignore
|
preview.update(f"当前输入: {self.inputlist}") # type: ignore
|
||||||
|
|
||||||
self.post_message(self.InputChanged(
|
self.post_message(
|
||||||
current_input=self.inputlist.copy(),
|
self.InputChanged(
|
||||||
max_length=len(self.puzzle.answer)
|
current_input=self.inputlist.copy(), max_length=len(self.puzzle.answer)
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
button_id = event.button.id
|
button_id = event.button.id
|
||||||
@@ -74,11 +99,11 @@ class ClozePuzzle(BasePuzzleWidget):
|
|||||||
is_correct = self.inputlist == self.puzzle.answer
|
is_correct = self.inputlist == self.puzzle.answer
|
||||||
rating = 4 if is_correct else 2
|
rating = 4 if is_correct else 2
|
||||||
|
|
||||||
self.post_message(self.RatingChanged(
|
self.post_message(
|
||||||
atom=self.atom,
|
self.RatingChanged(
|
||||||
rating=rating,
|
atom=self.atom, rating=rating, is_correct=is_correct
|
||||||
is_correct=is_correct
|
)
|
||||||
))
|
)
|
||||||
|
|
||||||
if not is_correct:
|
if not is_correct:
|
||||||
self.inputlist = []
|
self.inputlist = []
|
||||||
|
|||||||
@@ -4,10 +4,27 @@ from textual.widgets import (
|
|||||||
)
|
)
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
|
|
||||||
|
|
||||||
class Finished(Widget):
|
class Finished(Widget):
|
||||||
def __init__(self, *children: Widget, alia = "", name: str | None = None, id: str | None = None, classes: str | None = None, disabled: bool = False, markup: bool = True) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
*children: Widget,
|
||||||
|
alia="",
|
||||||
|
name: str | None = None,
|
||||||
|
id: str | None = None,
|
||||||
|
classes: str | None = None,
|
||||||
|
disabled: bool = False,
|
||||||
|
markup: bool = True
|
||||||
|
) -> None:
|
||||||
self.alia = alia
|
self.alia = alia
|
||||||
super().__init__(*children, name=name, id=id, classes=classes, disabled=disabled, markup=markup)
|
super().__init__(
|
||||||
|
*children,
|
||||||
|
name=name,
|
||||||
|
id=id,
|
||||||
|
classes=classes,
|
||||||
|
disabled=disabled,
|
||||||
|
markup=markup
|
||||||
|
)
|
||||||
|
|
||||||
def compose(self):
|
def compose(self):
|
||||||
yield Label("本次记忆进程结束", id="finished_msg")
|
yield Label("本次记忆进程结束", id="finished_msg")
|
||||||
@@ -15,5 +32,5 @@ class Finished(Widget):
|
|||||||
|
|
||||||
def on_button_pressed(self, event):
|
def on_button_pressed(self, event):
|
||||||
button_id = event.button.id
|
button_id = event.button.id
|
||||||
if button_id == 'back-to-menu':
|
if button_id == "back-to-menu":
|
||||||
self.app.pop_screen()
|
self.app.pop_screen()
|
||||||
|
|||||||
@@ -1,70 +1,62 @@
|
|||||||
from textual.app import App, ComposeResult
|
|
||||||
from textual.events import Event
|
|
||||||
from textual.widgets import (
|
from textual.widgets import (
|
||||||
Collapsible,
|
|
||||||
Header,
|
|
||||||
Footer,
|
|
||||||
Markdown,
|
|
||||||
ListView,
|
|
||||||
ListItem,
|
|
||||||
Label,
|
Label,
|
||||||
Static,
|
|
||||||
Button,
|
Button,
|
||||||
)
|
)
|
||||||
from textual.containers import Container, Horizontal, Center
|
|
||||||
from textual.screen import Screen
|
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
from typing import Tuple, Dict
|
|
||||||
import heurams.kernel.particles as pt
|
import heurams.kernel.particles as pt
|
||||||
import heurams.kernel.puzzles as pz
|
import heurams.kernel.puzzles as pz
|
||||||
from .base_puzzle_widget import BasePuzzleWidget
|
from .base_puzzle_widget import BasePuzzleWidget
|
||||||
import copy
|
from typing import TypedDict
|
||||||
import random
|
|
||||||
from textual.message import Message
|
|
||||||
|
class Setting(TypedDict):
|
||||||
|
__origin__: str
|
||||||
|
__hint__: str
|
||||||
|
primary: str # 显示的提示文本
|
||||||
|
mapping: dict # 谜题到答案的映射
|
||||||
|
jammer: list # 干扰项
|
||||||
|
max_riddles_num: int # 最大谜题数量
|
||||||
|
prefix: str # 提示词前缀
|
||||||
|
|
||||||
|
|
||||||
class MCQPuzzle(BasePuzzleWidget):
|
class MCQPuzzle(BasePuzzleWidget):
|
||||||
def __init__(self, *children: Widget, atom: pt.Atom, alia: str = "", name: str | None = None, id: str | None = None, classes: str | None = None, disabled: bool = False, markup: bool = True) -> None:
|
def __init__(
|
||||||
super().__init__(*children, atom=atom, name=name, id=id, classes=classes, disabled=disabled, markup=markup)
|
self,
|
||||||
|
*children: Widget,
|
||||||
|
atom: pt.Atom,
|
||||||
|
alia: str = "",
|
||||||
|
name: str | None = None,
|
||||||
|
id: str | None = None,
|
||||||
|
classes: str | None = None,
|
||||||
|
disabled: bool = False,
|
||||||
|
markup: bool = True,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
*children,
|
||||||
|
atom=atom,
|
||||||
|
name=name,
|
||||||
|
id=id,
|
||||||
|
classes=classes,
|
||||||
|
disabled=disabled,
|
||||||
|
markup=markup,
|
||||||
|
)
|
||||||
self.inputlist = []
|
self.inputlist = []
|
||||||
self.alia = alia
|
self.alia = alia
|
||||||
self.hashtable = {}
|
self.hashtable = {}
|
||||||
self._work()
|
self._load()
|
||||||
|
|
||||||
def _work(self):
|
def _load(self):
|
||||||
cfg = self.atom.registry["orbital"]["puzzles"][self.alia]
|
cfg = self.atom.registry["orbital"]["puzzles"][self.alia]
|
||||||
self.puzzle = pz.MCQPuzzle(cfg["mapping"], cfg["jammer"], cfg["max_riddles_num"], cfg['prefix'])
|
self.puzzle = pz.MCQPuzzle(
|
||||||
|
cfg["mapping"], cfg["jammer"], cfg["max_riddles_num"], cfg["prefix"]
|
||||||
|
)
|
||||||
self.puzzle.refresh()
|
self.puzzle.refresh()
|
||||||
|
|
||||||
class PuzzleCompleted(Message):
|
|
||||||
"""选择题完成消息"""
|
|
||||||
def __init__(self, atom: pt.Atom, rating: int, is_correct: bool, user_answers: list, correct_answers: list) -> None:
|
|
||||||
self.atom = atom
|
|
||||||
self.rating = rating # 评分
|
|
||||||
self.is_correct = is_correct # 是否正确
|
|
||||||
self.user_answers = user_answers # 用户答案
|
|
||||||
self.correct_answers = correct_answers # 正确答案
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
class InputChanged(Message):
|
|
||||||
"""输入变化消息"""
|
|
||||||
def __init__(self, current_input: list, current_question: int, total_questions: int, current_question_text: str) -> None:
|
|
||||||
self.current_input = current_input # 当前输入
|
|
||||||
self.current_question = current_question # 当前题号
|
|
||||||
self.total_questions = total_questions # 总题数
|
|
||||||
self.current_question_text = current_question_text # 当前问题文本
|
|
||||||
self.progress = current_question / total_questions # 进度
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
class QuestionAdvanced(Message):
|
|
||||||
"""题目切换消息"""
|
|
||||||
def __init__(self, question_index: int, question_text: str, options: list) -> None:
|
|
||||||
self.question_index = question_index # 题目索引
|
|
||||||
self.question_text = question_text
|
|
||||||
self.options = options # 选项列表
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
def compose(self):
|
def compose(self):
|
||||||
yield Label(self.atom[1].content.replace("/",""), id="sentence")
|
setting: Setting = self.atom.registry["nucleon"].metadata["orbital"]["puzzle"][
|
||||||
|
self.alia
|
||||||
|
]
|
||||||
|
yield Label(setting["primary"], id="sentence")
|
||||||
yield Label(self.puzzle.wording[len(self.inputlist)], id="puzzle")
|
yield Label(self.puzzle.wording[len(self.inputlist)], id="puzzle")
|
||||||
yield Label(f"当前输入: {self.inputlist}", id="inputpreview")
|
yield Label(f"当前输入: {self.inputlist}", id="inputpreview")
|
||||||
|
|
||||||
@@ -88,20 +80,8 @@ class MCQPuzzle(BasePuzzleWidget):
|
|||||||
puzzle_label.update(self.puzzle.wording[current_question_index]) # type: ignore
|
puzzle_label.update(self.puzzle.wording[current_question_index]) # type: ignore
|
||||||
|
|
||||||
# 发送输入变化消息
|
# 发送输入变化消息
|
||||||
self.post_message(self.InputChanged(
|
|
||||||
current_input=self.inputlist.copy(),
|
|
||||||
current_question=current_question_index,
|
|
||||||
total_questions=len(self.puzzle.answer),
|
|
||||||
current_question_text=self.puzzle.wording[current_question_index] if current_question_index < len(self.puzzle.wording) else ""
|
|
||||||
))
|
|
||||||
|
|
||||||
# 如果还有下一题,发送题目切换消息
|
# 如果还有下一题,发送题目切换消息
|
||||||
if current_question_index < len(self.puzzle.options):
|
|
||||||
self.post_message(self.QuestionAdvanced(
|
|
||||||
question_index=current_question_index,
|
|
||||||
question_text=self.puzzle.wording[current_question_index],
|
|
||||||
options=self.puzzle.options[current_question_index]
|
|
||||||
))
|
|
||||||
|
|
||||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
"""处理按钮点击事件"""
|
"""处理按钮点击事件"""
|
||||||
@@ -124,13 +104,15 @@ class MCQPuzzle(BasePuzzleWidget):
|
|||||||
rating = 4 if is_correct else 2
|
rating = 4 if is_correct else 2
|
||||||
|
|
||||||
# 发送完成消息
|
# 发送完成消息
|
||||||
self.post_message(self.PuzzleCompleted(
|
self.post_message(
|
||||||
|
self.PuzzleCompleted(
|
||||||
atom=self.atom,
|
atom=self.atom,
|
||||||
rating=rating,
|
rating=rating,
|
||||||
is_correct=is_correct,
|
is_correct=is_correct,
|
||||||
user_answers=self.inputlist.copy(),
|
user_answers=self.inputlist.copy(),
|
||||||
correct_answers=self.puzzle.answer.copy()
|
correct_answers=self.puzzle.answer.copy(),
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# 重置输入(如果回答错误)
|
# 重置输入(如果回答错误)
|
||||||
if not is_correct:
|
if not is_correct:
|
||||||
@@ -145,7 +127,11 @@ class MCQPuzzle(BasePuzzleWidget):
|
|||||||
def refresh_buttons(self):
|
def refresh_buttons(self):
|
||||||
"""刷新按钮显示(用于题目切换)"""
|
"""刷新按钮显示(用于题目切换)"""
|
||||||
# 移除所有选项按钮
|
# 移除所有选项按钮
|
||||||
buttons_to_remove = [child for child in self.children if hasattr(child, 'id') and child.id and child.id.startswith('select')]
|
buttons_to_remove = [
|
||||||
|
child
|
||||||
|
for child in self.children
|
||||||
|
if hasattr(child, "id") and child.id and child.id.startswith("select")
|
||||||
|
]
|
||||||
for button in buttons_to_remove:
|
for button in buttons_to_remove:
|
||||||
self.remove_child(button) # type: ignore
|
self.remove_child(button) # type: ignore
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,24 @@ from textual.widget import Widget
|
|||||||
|
|
||||||
|
|
||||||
class Placeholder(Widget):
|
class Placeholder(Widget):
|
||||||
def __init__(self, *children: Widget, name: str | None = None, alia: str = "", id: str | None = None, classes: str | None = None, disabled: bool = False, markup: bool = True) -> None:
|
def __init__(
|
||||||
super().__init__(*children, name=name, id=id, classes=classes, disabled=disabled, markup=markup)
|
self,
|
||||||
|
*children: Widget,
|
||||||
|
name: str | None = None,
|
||||||
|
alia: str = "",
|
||||||
|
id: str | None = None,
|
||||||
|
classes: str | None = None,
|
||||||
|
disabled: bool = False,
|
||||||
|
markup: bool = True
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
*children,
|
||||||
|
name=name,
|
||||||
|
id=id,
|
||||||
|
classes=classes,
|
||||||
|
disabled=disabled,
|
||||||
|
markup=markup
|
||||||
|
)
|
||||||
|
|
||||||
def compose(self):
|
def compose(self):
|
||||||
yield Label("示例标签", id="testlabel")
|
yield Label("示例标签", id="testlabel")
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from .base_puzzle_widget import BasePuzzleWidget
|
|||||||
from typing import TypedDict, List
|
from typing import TypedDict, List
|
||||||
from textual.message import Message
|
from textual.message import Message
|
||||||
|
|
||||||
|
|
||||||
class RecognitionConfig(TypedDict):
|
class RecognitionConfig(TypedDict):
|
||||||
__origin__: str
|
__origin__: str
|
||||||
__hint__: str
|
__hint__: str
|
||||||
@@ -21,16 +22,35 @@ class RecognitionConfig(TypedDict):
|
|||||||
secondary: List[str]
|
secondary: List[str]
|
||||||
top_dim: List[str]
|
top_dim: List[str]
|
||||||
|
|
||||||
|
|
||||||
class Recognition(BasePuzzleWidget):
|
class Recognition(BasePuzzleWidget):
|
||||||
def __init__(self, *children: Widget, atom: pt.Atom, alia: str = "", name: str | None = None, id: str | None = None, classes: str | None = None, disabled: bool = False, markup: bool = True) -> None:
|
def __init__(
|
||||||
super().__init__(*children, atom=atom, name=name, id=id, classes=classes, disabled=disabled, markup=markup)
|
self,
|
||||||
|
*children: Widget,
|
||||||
|
atom: pt.Atom,
|
||||||
|
alia: str = "",
|
||||||
|
name: str | None = None,
|
||||||
|
id: str | None = None,
|
||||||
|
classes: str | None = None,
|
||||||
|
disabled: bool = False,
|
||||||
|
markup: bool = True,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
*children,
|
||||||
|
atom=atom,
|
||||||
|
name=name,
|
||||||
|
id=id,
|
||||||
|
classes=classes,
|
||||||
|
disabled=disabled,
|
||||||
|
markup=markup,
|
||||||
|
)
|
||||||
if alia == "":
|
if alia == "":
|
||||||
alia = "Recognition"
|
alia = "Recognition"
|
||||||
self.alia = alia
|
self.alia = alia
|
||||||
|
|
||||||
def compose(self):
|
def compose(self):
|
||||||
cfg: RecognitionConfig = self.atom.registry["orbital"]["puzzles"][self.alia]
|
cfg: RecognitionConfig = self.atom.registry["orbital"]["puzzles"][self.alia]
|
||||||
delim = self.atom.registry['nucleon'].metadata["formation"]["delimiter"]
|
delim = self.atom.registry["nucleon"].metadata["formation"]["delimiter"]
|
||||||
replace_dict = {
|
replace_dict = {
|
||||||
", ": ",",
|
", ": ",",
|
||||||
". ": ".",
|
". ": ".",
|
||||||
@@ -43,8 +63,8 @@ class Recognition(BasePuzzleWidget):
|
|||||||
f":{delim}": ":",
|
f":{delim}": ":",
|
||||||
}
|
}
|
||||||
|
|
||||||
nucleon = self.atom.registry['nucleon']
|
nucleon = self.atom.registry["nucleon"]
|
||||||
metadata = self.atom.registry['nucleon'].metadata
|
metadata = self.atom.registry["nucleon"].metadata
|
||||||
primary = cfg["primary"]
|
primary = cfg["primary"]
|
||||||
|
|
||||||
with Center():
|
with Center():
|
||||||
@@ -53,7 +73,7 @@ class Recognition(BasePuzzleWidget):
|
|||||||
|
|
||||||
for old, new in replace_dict.items():
|
for old, new in replace_dict.items():
|
||||||
primary = primary.replace(old, new)
|
primary = primary.replace(old, new)
|
||||||
primary_splited = re.split(r"(?<=[,;:|])", cfg['primary'])
|
primary_splited = re.split(r"(?<=[,;:|])", cfg["primary"])
|
||||||
for item in primary_splited:
|
for item in primary_splited:
|
||||||
with Center():
|
with Center():
|
||||||
yield Label(
|
yield Label(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from .sm2 import SM2Algorithm
|
from .sm2 import SM2Algorithm
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'SM2Algorithm',
|
"SM2Algorithm",
|
||||||
]
|
]
|
||||||
|
|
||||||
algorithms = {
|
algorithms = {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import heurams.services.timer as timer
|
import heurams.services.timer as timer
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
|
|
||||||
class BaseAlgorithm:
|
class BaseAlgorithm:
|
||||||
algo_name = "BaseAlgorithm"
|
algo_name = "BaseAlgorithm"
|
||||||
|
|
||||||
@@ -15,17 +16,19 @@ class BaseAlgorithm:
|
|||||||
last_modify: float
|
last_modify: float
|
||||||
|
|
||||||
defaults = {
|
defaults = {
|
||||||
'real_rept': 0,
|
"real_rept": 0,
|
||||||
'rept': 0,
|
"rept": 0,
|
||||||
'interval': 0,
|
"interval": 0,
|
||||||
'last_date': 0,
|
"last_date": 0,
|
||||||
'next_date': 0,
|
"next_date": 0,
|
||||||
'is_activated': 0,
|
"is_activated": 0,
|
||||||
'last_modify': timer.get_timestamp()
|
"last_modify": timer.get_timestamp(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def revisor(cls, algodata: dict, feedback: int = 5, is_new_activation: bool = False) -> None:
|
def revisor(
|
||||||
|
cls, algodata: dict, feedback: int = 5, is_new_activation: bool = False
|
||||||
|
) -> None:
|
||||||
"""迭代记忆数据"""
|
"""迭代记忆数据"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from .base import BaseAlgorithm
|
|||||||
import heurams.services.timer as timer
|
import heurams.services.timer as timer
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
|
|
||||||
class SM2Algorithm(BaseAlgorithm):
|
class SM2Algorithm(BaseAlgorithm):
|
||||||
algo_name = "SM-2"
|
algo_name = "SM-2"
|
||||||
|
|
||||||
@@ -16,18 +17,20 @@ class SM2Algorithm(BaseAlgorithm):
|
|||||||
last_modify: float
|
last_modify: float
|
||||||
|
|
||||||
defaults = {
|
defaults = {
|
||||||
'efactor': 2.5,
|
"efactor": 2.5,
|
||||||
'real_rept': 0,
|
"real_rept": 0,
|
||||||
'rept': 0,
|
"rept": 0,
|
||||||
'interval': 0,
|
"interval": 0,
|
||||||
'last_date': 0,
|
"last_date": 0,
|
||||||
'next_date': 0,
|
"next_date": 0,
|
||||||
'is_activated': 0,
|
"is_activated": 0,
|
||||||
'last_modify': timer.get_timestamp()
|
"last_modify": timer.get_timestamp(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def revisor(cls, algodata: dict, feedback: int = 5, is_new_activation: bool = False):
|
def revisor(
|
||||||
|
cls, algodata: dict, feedback: int = 5, is_new_activation: bool = False
|
||||||
|
):
|
||||||
"""SM-2 算法迭代决策机制实现
|
"""SM-2 算法迭代决策机制实现
|
||||||
根据 quality(0 ~ 5) 进行参数迭代最佳间隔
|
根据 quality(0 ~ 5) 进行参数迭代最佳间隔
|
||||||
quality 由主程序评估
|
quality 由主程序评估
|
||||||
@@ -38,44 +41,48 @@ class SM2Algorithm(BaseAlgorithm):
|
|||||||
if feedback == -1:
|
if feedback == -1:
|
||||||
return
|
return
|
||||||
|
|
||||||
algodata[cls.algo_name]['efactor'] = algodata[cls.algo_name]['efactor'] + (
|
algodata[cls.algo_name]["efactor"] = algodata[cls.algo_name]["efactor"] + (
|
||||||
0.1 - (5 - feedback) * (0.08 + (5 - feedback) * 0.02)
|
0.1 - (5 - feedback) * (0.08 + (5 - feedback) * 0.02)
|
||||||
)
|
)
|
||||||
algodata[cls.algo_name]['efactor'] = max(1.3, algodata[cls.algo_name]['efactor'])
|
algodata[cls.algo_name]["efactor"] = max(
|
||||||
|
1.3, algodata[cls.algo_name]["efactor"]
|
||||||
if feedback < 3:
|
|
||||||
algodata[cls.algo_name]['rept'] = 0
|
|
||||||
algodata[cls.algo_name]['interval'] = 0
|
|
||||||
else:
|
|
||||||
algodata[cls.algo_name]['rept'] += 1
|
|
||||||
|
|
||||||
algodata[cls.algo_name]['real_rept'] += 1
|
|
||||||
|
|
||||||
if is_new_activation:
|
|
||||||
algodata[cls.algo_name]['rept'] = 0
|
|
||||||
algodata[cls.algo_name]['efactor'] = 2.5
|
|
||||||
|
|
||||||
if algodata[cls.algo_name]['rept'] == 0:
|
|
||||||
algodata[cls.algo_name]['interval'] = 1
|
|
||||||
elif algodata[cls.algo_name]['rept'] == 1:
|
|
||||||
algodata[cls.algo_name]['interval'] = 6
|
|
||||||
else:
|
|
||||||
algodata[cls.algo_name]['interval'] = round(
|
|
||||||
algodata[cls.algo_name]['interval'] * algodata[cls.algo_name]['efactor']
|
|
||||||
)
|
)
|
||||||
|
|
||||||
algodata[cls.algo_name]['last_date'] = timer.get_daystamp()
|
if feedback < 3:
|
||||||
algodata[cls.algo_name]['next_date'] = timer.get_daystamp() + algodata[cls.algo_name]['interval']
|
algodata[cls.algo_name]["rept"] = 0
|
||||||
algodata[cls.algo_name]['last_modify'] = timer.get_timestamp()
|
algodata[cls.algo_name]["interval"] = 0
|
||||||
|
else:
|
||||||
|
algodata[cls.algo_name]["rept"] += 1
|
||||||
|
|
||||||
|
algodata[cls.algo_name]["real_rept"] += 1
|
||||||
|
|
||||||
|
if is_new_activation:
|
||||||
|
algodata[cls.algo_name]["rept"] = 0
|
||||||
|
algodata[cls.algo_name]["efactor"] = 2.5
|
||||||
|
|
||||||
|
if algodata[cls.algo_name]["rept"] == 0:
|
||||||
|
algodata[cls.algo_name]["interval"] = 1
|
||||||
|
elif algodata[cls.algo_name]["rept"] == 1:
|
||||||
|
algodata[cls.algo_name]["interval"] = 6
|
||||||
|
else:
|
||||||
|
algodata[cls.algo_name]["interval"] = round(
|
||||||
|
algodata[cls.algo_name]["interval"] * algodata[cls.algo_name]["efactor"]
|
||||||
|
)
|
||||||
|
|
||||||
|
algodata[cls.algo_name]["last_date"] = timer.get_daystamp()
|
||||||
|
algodata[cls.algo_name]["next_date"] = (
|
||||||
|
timer.get_daystamp() + algodata[cls.algo_name]["interval"]
|
||||||
|
)
|
||||||
|
algodata[cls.algo_name]["last_modify"] = timer.get_timestamp()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_due(cls, algodata):
|
def is_due(cls, algodata):
|
||||||
return (algodata[cls.algo_name]['next_date'] <= timer.get_daystamp())
|
return algodata[cls.algo_name]["next_date"] <= timer.get_daystamp()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def rate(cls, algodata):
|
def rate(cls, algodata):
|
||||||
return str(algodata[cls.algo_name]['efactor'])
|
return str(algodata[cls.algo_name]["efactor"])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def nextdate(cls, algodata) -> int:
|
def nextdate(cls, algodata) -> int:
|
||||||
return algodata[cls.algo_name]['next_date']
|
return algodata[cls.algo_name]["next_date"]
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import json
|
|||||||
import bidict
|
import bidict
|
||||||
from heurams.context import config_var
|
from heurams.context import config_var
|
||||||
|
|
||||||
|
|
||||||
class AtomRegister(TypedDict):
|
class AtomRegister(TypedDict):
|
||||||
nucleon: Nucleon
|
nucleon: Nucleon
|
||||||
nucleon_path: pathlib.Path
|
nucleon_path: pathlib.Path
|
||||||
@@ -21,7 +22,8 @@ class AtomRegister(TypedDict):
|
|||||||
orbital_fmt: str
|
orbital_fmt: str
|
||||||
runtime: dict
|
runtime: dict
|
||||||
|
|
||||||
class Atom():
|
|
||||||
|
class Atom:
|
||||||
"""
|
"""
|
||||||
统一处理一系列对象的所有信息与持久化:
|
统一处理一系列对象的所有信息与持久化:
|
||||||
关联电子 (算法数据)
|
关联电子 (算法数据)
|
||||||
@@ -59,10 +61,11 @@ class Atom():
|
|||||||
执行并以结果替换当前单元的所有 eval 语句
|
执行并以结果替换当前单元的所有 eval 语句
|
||||||
TODO: 带有限制的 eval, 异步/多线程执行避免堵塞
|
TODO: 带有限制的 eval, 异步/多线程执行避免堵塞
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# eval 环境设置
|
# eval 环境设置
|
||||||
def eval_with_env(s: str):
|
def eval_with_env(s: str):
|
||||||
try:
|
try:
|
||||||
nucleon = self.registry['nucleon']
|
nucleon = self.registry["nucleon"]
|
||||||
default = config_var.get()["puzzles"]
|
default = config_var.get()["puzzles"]
|
||||||
metadata = nucleon.metadata
|
metadata = nucleon.metadata
|
||||||
except:
|
except:
|
||||||
@@ -93,7 +96,6 @@ class Atom():
|
|||||||
traverse(self.registry["nucleon"], eval_with_env)
|
traverse(self.registry["nucleon"], eval_with_env)
|
||||||
traverse(self.registry["orbital"], eval_with_env)
|
traverse(self.registry["orbital"], eval_with_env)
|
||||||
|
|
||||||
|
|
||||||
def persist(self, key):
|
def persist(self, key):
|
||||||
path: pathlib.Path | None = self.registry[key + "_path"]
|
path: pathlib.Path | None = self.registry[key + "_path"]
|
||||||
if isinstance(path, pathlib.Path):
|
if isinstance(path, pathlib.Path):
|
||||||
@@ -125,4 +127,5 @@ class Atom():
|
|||||||
def placeholder():
|
def placeholder():
|
||||||
return (Electron.placeholder(), Nucleon.placeholder(), {})
|
return (Electron.placeholder(), Nucleon.placeholder(), {})
|
||||||
|
|
||||||
|
|
||||||
atom_registry: bidict.bidict[str, Atom] = bidict.bidict()
|
atom_registry: bidict.bidict[str, Atom] = bidict.bidict()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import heurams.services.timer as timer
|
|||||||
from heurams.context import config_var
|
from heurams.context import config_var
|
||||||
from heurams.kernel.algorithms import algorithms
|
from heurams.kernel.algorithms import algorithms
|
||||||
|
|
||||||
|
|
||||||
class Electron:
|
class Electron:
|
||||||
"""电子: 记忆分析元数据及算法"""
|
"""电子: 记忆分析元数据及算法"""
|
||||||
|
|
||||||
@@ -28,14 +29,14 @@ class Electron:
|
|||||||
|
|
||||||
def activate(self):
|
def activate(self):
|
||||||
"""激活此电子"""
|
"""激活此电子"""
|
||||||
self.algodata[self.algo]['is_activated'] = 1
|
self.algodata[self.algo]["is_activated"] = 1
|
||||||
self.algodata[self.algo]['last_modify'] = timer.get_timestamp()
|
self.algodata[self.algo]["last_modify"] = timer.get_timestamp()
|
||||||
|
|
||||||
def modify(self, var: str, value):
|
def modify(self, var: str, value):
|
||||||
"""修改 algodata[algo] 中子字典数据"""
|
"""修改 algodata[algo] 中子字典数据"""
|
||||||
if var in self.algodata[self.algo]:
|
if var in self.algodata[self.algo]:
|
||||||
self.algodata[self.algo][var] = value
|
self.algodata[self.algo][var] = value
|
||||||
self.algodata[self.algo]['last_modify'] = timer.get_timestamp()
|
self.algodata[self.algo]["last_modify"] = timer.get_timestamp()
|
||||||
else:
|
else:
|
||||||
print(f"警告: '{var}' 非已知元数据字段")
|
print(f"警告: '{var}' 非已知元数据字段")
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ class Electron:
|
|||||||
return self.algo.is_due(self.algodata)
|
return self.algo.is_due(self.algodata)
|
||||||
|
|
||||||
def is_activated(self):
|
def is_activated(self):
|
||||||
return self.algodata[self.algo]['is_activated']
|
return self.algodata[self.algo]["is_activated"]
|
||||||
|
|
||||||
def rate(self):
|
def rate(self):
|
||||||
"评价"
|
"评价"
|
||||||
@@ -93,7 +94,7 @@ class Electron:
|
|||||||
if key == "ident":
|
if key == "ident":
|
||||||
raise AttributeError("ident 应为只读")
|
raise AttributeError("ident 应为只读")
|
||||||
self.algodata[self.algo][key] = value
|
self.algodata[self.algo][key] = value
|
||||||
self.algodata[self.algo]['last_modify'] = timer.get_timestamp()
|
self.algodata[self.algo]["last_modify"] = timer.get_timestamp()
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
"""仅返回当前算法的配置数量"""
|
"""仅返回当前算法的配置数量"""
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import toml
|
|||||||
import json
|
import json
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
|
|
||||||
def load_nucleon(path: pathlib.Path, fmt="toml"):
|
def load_nucleon(path: pathlib.Path, fmt="toml"):
|
||||||
with open(path, "r") as f:
|
with open(path, "r") as f:
|
||||||
dictdata = dict()
|
dictdata = dict()
|
||||||
@@ -15,8 +16,8 @@ def load_nucleon(path: pathlib.Path, fmt = "toml"):
|
|||||||
# 修正 toml 解析器的不管嵌套行为
|
# 修正 toml 解析器的不管嵌套行为
|
||||||
for key, value in dictdata.items():
|
for key, value in dictdata.items():
|
||||||
if "__metadata__" in key: # 以免影响句号
|
if "__metadata__" in key: # 以免影响句号
|
||||||
if '.' in key:
|
if "." in key:
|
||||||
parts = key.split('.')
|
parts = key.split(".")
|
||||||
current = nested_data
|
current = nested_data
|
||||||
for part in parts[:-1]:
|
for part in parts[:-1]:
|
||||||
if part not in current:
|
if part not in current:
|
||||||
@@ -29,9 +30,17 @@ def load_nucleon(path: pathlib.Path, fmt = "toml"):
|
|||||||
for item, attr in nested_data.items():
|
for item, attr in nested_data.items():
|
||||||
if item == "__metadata__":
|
if item == "__metadata__":
|
||||||
continue
|
continue
|
||||||
lst.append((Nucleon(hasher.hash(item), attr, deepcopy(nested_data['__metadata__'])), deepcopy(nested_data["__metadata__"]["orbital"])))
|
lst.append(
|
||||||
|
(
|
||||||
|
Nucleon(
|
||||||
|
hasher.hash(item), attr, deepcopy(nested_data["__metadata__"])
|
||||||
|
),
|
||||||
|
deepcopy(nested_data["__metadata__"]["orbital"]),
|
||||||
|
)
|
||||||
|
)
|
||||||
return lst
|
return lst
|
||||||
|
|
||||||
|
|
||||||
def load_electron(path: pathlib.Path, fmt="json") -> dict:
|
def load_electron(path: pathlib.Path, fmt="json") -> dict:
|
||||||
"""从文件路径加载电子对象
|
"""从文件路径加载电子对象
|
||||||
|
|
||||||
@@ -47,5 +56,5 @@ def load_electron(path: pathlib.Path, fmt = "json") -> dict:
|
|||||||
dictdata = json.load(f) # type: ignore
|
dictdata = json.load(f) # type: ignore
|
||||||
dic = dict()
|
dic = dict()
|
||||||
for item, attr in dictdata.items():
|
for item, attr in dictdata.items():
|
||||||
dic[item] = (Electron(hasher.hash(item), attr))
|
dic[item] = Electron(hasher.hash(item), attr)
|
||||||
return dic
|
return dic
|
||||||
@@ -35,6 +35,7 @@ class Nucleon:
|
|||||||
执行并以结果替换当前单元的所有 eval 语句
|
执行并以结果替换当前单元的所有 eval 语句
|
||||||
TODO: 带有限制的 eval, 异步/多线程执行避免堵塞
|
TODO: 带有限制的 eval, 异步/多线程执行避免堵塞
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# eval 环境设置
|
# eval 环境设置
|
||||||
def eval_with_env(s: str):
|
def eval_with_env(s: str):
|
||||||
try:
|
try:
|
||||||
@@ -63,6 +64,7 @@ class Nucleon:
|
|||||||
|
|
||||||
traverse(self.payload, eval_with_env)
|
traverse(self.payload, eval_with_env)
|
||||||
traverse(self.metadata, eval_with_env)
|
traverse(self.metadata, eval_with_env)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def placeholder():
|
def placeholder():
|
||||||
"""生成一个占位原子核"""
|
"""生成一个占位原子核"""
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
|
|
||||||
class OrbitalSchedule(TypedDict):
|
class OrbitalSchedule(TypedDict):
|
||||||
quick_review: list
|
quick_review: list
|
||||||
recognition: list
|
recognition: list
|
||||||
final_review: list
|
final_review: list
|
||||||
|
|
||||||
|
|
||||||
class Orbital(TypedDict):
|
class Orbital(TypedDict):
|
||||||
schedule: OrbitalSchedule
|
schedule: OrbitalSchedule
|
||||||
puzzles: dict
|
puzzles: dict
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from heurams.context import config_var
|
from heurams.context import config_var
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
def probe_by_filename(filename):
|
def probe_by_filename(filename):
|
||||||
"""探测指定文件 (无扩展名) 的所有信息"""
|
"""探测指定文件 (无扩展名) 的所有信息"""
|
||||||
paths: dict = config_var.get().get("paths")
|
paths: dict = config_var.get().get("paths")
|
||||||
@@ -8,11 +9,12 @@ def probe_by_filename(filename):
|
|||||||
result = {}
|
result = {}
|
||||||
for item, attr in paths.items():
|
for item, attr in paths.items():
|
||||||
for i in formats:
|
for i in formats:
|
||||||
attr: pathlib.Path = pathlib.Path(attr) / filename + '.' + i
|
attr: pathlib.Path = pathlib.Path(attr) / filename + "." + i
|
||||||
if attr.exists():
|
if attr.exists():
|
||||||
result[item.replace("_dir", "")] = str(attr)
|
result[item.replace("_dir", "")] = str(attr)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def probe_all(is_stem=1):
|
def probe_all(is_stem=1):
|
||||||
"""依据目录探测所有信息
|
"""依据目录探测所有信息
|
||||||
|
|
||||||
@@ -35,7 +37,9 @@ def probe_all(is_stem = 1):
|
|||||||
result[item.replace("_dir", "")].append(str(i.name))
|
result[item.replace("_dir", "")].append(str(i.name))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import os
|
import os
|
||||||
|
|
||||||
print(os.getcwd())
|
print(os.getcwd())
|
||||||
print(probe_all())
|
print(probe_all())
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ from .mcq import MCQPuzzle
|
|||||||
from .recognition import RecognitionPuzzle
|
from .recognition import RecognitionPuzzle
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'BasePuzzle',
|
"BasePuzzle",
|
||||||
'ClozePuzzle',
|
"ClozePuzzle",
|
||||||
'MCQPuzzle',
|
"MCQPuzzle",
|
||||||
'RecognitionPuzzle',
|
"RecognitionPuzzle",
|
||||||
]
|
]
|
||||||
|
|
||||||
puzzles = {
|
puzzles = {
|
||||||
@@ -23,6 +23,7 @@ puzzles = {
|
|||||||
"base": BasePuzzle,
|
"base": BasePuzzle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_by_dict(config_dict: dict) -> BasePuzzle:
|
def create_by_dict(config_dict: dict) -> BasePuzzle:
|
||||||
"""
|
"""
|
||||||
@@ -37,19 +38,19 @@ def create_by_dict(config_dict: dict) -> BasePuzzle:
|
|||||||
Raises:
|
Raises:
|
||||||
ValueError: 当配置无效时抛出
|
ValueError: 当配置无效时抛出
|
||||||
"""
|
"""
|
||||||
puzzle_type = config_dict.get('type')
|
puzzle_type = config_dict.get("type")
|
||||||
|
|
||||||
if puzzle_type == 'cloze':
|
if puzzle_type == "cloze":
|
||||||
return puzzles["cloze"](
|
return puzzles["cloze"](
|
||||||
text=config_dict['text'],
|
text=config_dict["text"],
|
||||||
min_denominator=config_dict.get('min_denominator', 7)
|
min_denominator=config_dict.get("min_denominator", 7),
|
||||||
)
|
)
|
||||||
elif puzzle_type == 'mcq':
|
elif puzzle_type == "mcq":
|
||||||
return puzzles["mcq"](
|
return puzzles["mcq"](
|
||||||
mapping=config_dict['mapping'],
|
mapping=config_dict["mapping"],
|
||||||
jammer=config_dict.get('jammer', []),
|
jammer=config_dict.get("jammer", []),
|
||||||
max_riddles_num=config_dict.get('max_riddles_num', 2),
|
max_riddles_num=config_dict.get("max_riddles_num", 2),
|
||||||
prefix=config_dict.get('prefix', '')
|
prefix=config_dict.get("prefix", ""),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"未知的谜题类型: {puzzle_type}")
|
raise ValueError(f"未知的谜题类型: {puzzle_type}")
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from .base import BasePuzzle
|
from .base import BasePuzzle
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
|
||||||
class ClozePuzzle(BasePuzzle):
|
class ClozePuzzle(BasePuzzle):
|
||||||
"""填空题谜题生成器
|
"""填空题谜题生成器
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,12 @@
|
|||||||
from .base import BasePuzzle
|
from .base import BasePuzzle
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
|
||||||
class MCQPuzzle(BasePuzzle):
|
class MCQPuzzle(BasePuzzle):
|
||||||
"""选择题谜题生成器"""
|
"""选择题谜题生成器"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self, mapping: dict, jammer: list, max_riddles_num: int = 2, prefix: str = ""
|
||||||
mapping: dict,
|
|
||||||
jammer: list,
|
|
||||||
max_riddles_num: int = 2,
|
|
||||||
prefix: str = ""
|
|
||||||
):
|
):
|
||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
self.mapping = mapping
|
self.mapping = mapping
|
||||||
@@ -38,9 +35,7 @@ class MCQPuzzle(BasePuzzle):
|
|||||||
|
|
||||||
for question, correct_answer in questions:
|
for question, correct_answer in questions:
|
||||||
options = [correct_answer]
|
options = [correct_answer]
|
||||||
available_jammers = [
|
available_jammers = [j for j in self.jammer if j != correct_answer]
|
||||||
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:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
from .base import BasePuzzle
|
from .base import BasePuzzle
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
|
||||||
class RecognitionPuzzle(BasePuzzle):
|
class RecognitionPuzzle(BasePuzzle):
|
||||||
"""识别占位符"""
|
"""识别占位符"""
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,4 @@ from .procession import Procession
|
|||||||
from .fission import Fission
|
from .fission import Fission
|
||||||
from .phaser import Phaser
|
from .phaser import Phaser
|
||||||
|
|
||||||
__all__ = [
|
__all__ = ["PhaserState", "ProcessionState", "Procession", "Fission", "Phaser"]
|
||||||
"PhaserState",
|
|
||||||
"ProcessionState",
|
|
||||||
"Procession",
|
|
||||||
"Fission",
|
|
||||||
"Phaser"
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ import heurams.kernel.puzzles as puz
|
|||||||
import random
|
import random
|
||||||
from .states import PhaserState
|
from .states import PhaserState
|
||||||
|
|
||||||
class Fission():
|
|
||||||
|
class Fission:
|
||||||
"""裂变器: 单原子调度展开器"""
|
"""裂变器: 单原子调度展开器"""
|
||||||
|
|
||||||
def __init__(self, atom: pt.Atom, phase=PhaserState.RECOGNITION):
|
def __init__(self, atom: pt.Atom, phase=PhaserState.RECOGNITION):
|
||||||
self.atom = atom
|
self.atom = atom
|
||||||
self.orbital_schedule = atom.registry["orbital"]["schedule"][phase.value] # type: ignore
|
self.orbital_schedule = atom.registry["orbital"]["schedule"][phase.value] # type: ignore
|
||||||
@@ -15,15 +17,20 @@ class Fission():
|
|||||||
if not isinstance(possibility, float):
|
if not isinstance(possibility, float):
|
||||||
possibility = float(possibility)
|
possibility = float(possibility)
|
||||||
while possibility > 1:
|
while possibility > 1:
|
||||||
self.puzzles.append({
|
self.puzzles.append(
|
||||||
|
{
|
||||||
"puzzle": puz.puzzles[self.orbital_puzzles[item]["__origin__"]],
|
"puzzle": puz.puzzles[self.orbital_puzzles[item]["__origin__"]],
|
||||||
"alia": item
|
"alia": item,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
possibility -= 1
|
possibility -= 1
|
||||||
if random.random() <= possibility:
|
if random.random() <= possibility:
|
||||||
self.puzzles.append({
|
self.puzzles.append(
|
||||||
|
{
|
||||||
"puzzle": puz.puzzles[self.orbital_puzzles[item]["__origin__"]],
|
"puzzle": puz.puzzles[self.orbital_puzzles[item]["__origin__"]],
|
||||||
"alia": item
|
"alia": item,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
yield from self.puzzles
|
yield from self.puzzles
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import heurams.kernel.particles as pt
|
|||||||
from .states import PhaserState, ProcessionState
|
from .states import PhaserState, ProcessionState
|
||||||
from .procession import Procession
|
from .procession import Procession
|
||||||
|
|
||||||
class Phaser():
|
|
||||||
|
class Phaser:
|
||||||
"""移相器: 全局调度阶段管理器"""
|
"""移相器: 全局调度阶段管理器"""
|
||||||
|
|
||||||
def __init__(self, atoms: list[pt.Atom]) -> None:
|
def __init__(self, atoms: list[pt.Atom]) -> None:
|
||||||
new_atoms = list()
|
new_atoms = list()
|
||||||
old_atoms = list()
|
old_atoms = list()
|
||||||
@@ -17,9 +19,13 @@ class Phaser():
|
|||||||
old_atoms.append(i)
|
old_atoms.append(i)
|
||||||
self.processions = list()
|
self.processions = list()
|
||||||
if len(old_atoms):
|
if len(old_atoms):
|
||||||
self.processions.append(Procession(old_atoms, PhaserState.QUICK_REVIEW, "初始复习"))
|
self.processions.append(
|
||||||
|
Procession(old_atoms, PhaserState.QUICK_REVIEW, "初始复习")
|
||||||
|
)
|
||||||
if len(new_atoms):
|
if len(new_atoms):
|
||||||
self.processions.append(Procession(new_atoms,PhaserState.RECOGNITION, "新记忆"))
|
self.processions.append(
|
||||||
|
Procession(new_atoms, PhaserState.RECOGNITION, "新记忆")
|
||||||
|
)
|
||||||
self.processions.append(Procession(atoms, PhaserState.FINAL_REVIEW, "总体复习"))
|
self.processions.append(Procession(atoms, PhaserState.FINAL_REVIEW, "总体复习"))
|
||||||
|
|
||||||
def current_procession(self):
|
def current_procession(self):
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import heurams.kernel.particles as pt
|
import heurams.kernel.particles as pt
|
||||||
from .states import PhaserState, ProcessionState
|
from .states import PhaserState, ProcessionState
|
||||||
|
|
||||||
class Procession():
|
|
||||||
|
class Procession:
|
||||||
"""队列: 标识单次记忆流程"""
|
"""队列: 标识单次记忆流程"""
|
||||||
|
|
||||||
def __init__(self, atoms: list, phase: PhaserState, name: str = ""):
|
def __init__(self, atoms: list, phase: PhaserState, name: str = ""):
|
||||||
self.atoms = atoms
|
self.atoms = atoms
|
||||||
self.queue = atoms.copy()
|
self.queue = atoms.copy()
|
||||||
@@ -32,10 +34,10 @@ class Procession():
|
|||||||
self.queue.append(atom)
|
self.queue.append(atom)
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return (len(self.queue) - self.cursor)
|
return len(self.queue) - self.cursor
|
||||||
|
|
||||||
def process(self):
|
def process(self):
|
||||||
return (self.cursor)
|
return self.cursor
|
||||||
|
|
||||||
def total_length(self):
|
def total_length(self):
|
||||||
return len(self.queue)
|
return len(self.queue)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
|
|
||||||
|
|
||||||
class PhaserState(Enum):
|
class PhaserState(Enum):
|
||||||
UNSURE = "unsure"
|
UNSURE = "unsure"
|
||||||
QUICK_REVIEW = "quick_review"
|
QUICK_REVIEW = "quick_review"
|
||||||
@@ -7,6 +8,7 @@ class PhaserState(Enum):
|
|||||||
FINAL_REVIEW = "final_review"
|
FINAL_REVIEW = "final_review"
|
||||||
FINISHED = "finished"
|
FINISHED = "finished"
|
||||||
|
|
||||||
|
|
||||||
class ProcessionState(Enum):
|
class ProcessionState(Enum):
|
||||||
RUNNING = auto()
|
RUNNING = auto()
|
||||||
FINISHED = auto()
|
FINISHED = auto()
|
||||||
@@ -7,7 +7,4 @@ __all__ = [
|
|||||||
"playsound_audio",
|
"playsound_audio",
|
||||||
]
|
]
|
||||||
|
|
||||||
providers = {
|
providers = {"termux": termux_audio, "playsound": playsound_audio}
|
||||||
"termux": termux_audio,
|
|
||||||
"playsound": playsound_audio
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ import os
|
|||||||
import pathlib
|
import pathlib
|
||||||
import playsound
|
import playsound
|
||||||
|
|
||||||
|
|
||||||
def play_by_path(path: pathlib.Path):
|
def play_by_path(path: pathlib.Path):
|
||||||
playsound.playsound(str(path))
|
playsound.playsound(str(path))
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
from typing import Protocol
|
from typing import Protocol
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
class PlayFunctionProtocol(Protocol):
|
class PlayFunctionProtocol(Protocol):
|
||||||
def __call__(self, path: pathlib.Path) -> None: ...
|
def __call__(self, path: pathlib.Path) -> None: ...
|
||||||
@@ -5,7 +5,9 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
# from .protocol import PlayFunctionProtocol
|
# from .protocol import PlayFunctionProtocol
|
||||||
|
|
||||||
|
|
||||||
def play_by_path(path: pathlib.Path):
|
def play_by_path(path: pathlib.Path):
|
||||||
os.system(f"play-audio {path}")
|
os.system(f"play-audio {path}")
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
class BaseTTS:
|
class BaseTTS:
|
||||||
name = "BaseTTS"
|
name = "BaseTTS"
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from .base import BaseTTS
|
|||||||
import pathlib
|
import pathlib
|
||||||
import edge_tts
|
import edge_tts
|
||||||
|
|
||||||
|
|
||||||
class EdgeTTS(BaseTTS):
|
class EdgeTTS(BaseTTS):
|
||||||
name = "EdgeTTS"
|
name = "EdgeTTS"
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import pathlib
|
|||||||
import toml
|
import toml
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
|
||||||
class ConfigFile:
|
class ConfigFile:
|
||||||
def __init__(self, path: pathlib.Path):
|
def __init__(self, path: pathlib.Path):
|
||||||
self.path = path
|
self.path = path
|
||||||
@@ -13,7 +14,7 @@ class ConfigFile:
|
|||||||
|
|
||||||
def _load(self):
|
def _load(self):
|
||||||
"""从文件加载配置数据"""
|
"""从文件加载配置数据"""
|
||||||
with open(self.path, 'r') as f:
|
with open(self.path, "r") as f:
|
||||||
try:
|
try:
|
||||||
self.data = toml.load(f)
|
self.data = toml.load(f)
|
||||||
except toml.TomlDecodeError as e:
|
except toml.TomlDecodeError as e:
|
||||||
@@ -28,7 +29,7 @@ class ConfigFile:
|
|||||||
def save(self, path: typing.Union[str, pathlib.Path] = ""):
|
def save(self, path: typing.Union[str, pathlib.Path] = ""):
|
||||||
"""保存配置到文件"""
|
"""保存配置到文件"""
|
||||||
save_path = pathlib.Path(path) if path else self.path
|
save_path = pathlib.Path(path) if path else self.path
|
||||||
with open(save_path, 'w') as f:
|
with open(save_path, "w") as f:
|
||||||
toml.dump(self.data, f)
|
toml.dump(self.data, f)
|
||||||
|
|
||||||
def get(self, key: str, default: typing.Any = None) -> typing.Any:
|
def get(self, key: str, default: typing.Any = None) -> typing.Any:
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
# 哈希服务
|
# 哈希服务
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
|
|
||||||
def get_md5(text):
|
def get_md5(text):
|
||||||
return hashlib.md5(text.encode('utf-8')).hexdigest()
|
return hashlib.md5(text.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def hash(text):
|
def hash(text):
|
||||||
return hashlib.md5(text.encode('utf-8')).hexdigest()
|
return hashlib.md5(text.encode("utf-8")).hexdigest()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
from heurams.context import config_var
|
from heurams.context import config_var
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
def get_daystamp() -> int:
|
def get_daystamp() -> int:
|
||||||
"""获取当前日戳(以天为单位的整数时间戳)"""
|
"""获取当前日戳(以天为单位的整数时间戳)"""
|
||||||
time_override = config_var.get().get("daystamp_override", -1)
|
time_override = config_var.get().get("daystamp_override", -1)
|
||||||
@@ -10,6 +11,7 @@ def get_daystamp() -> int:
|
|||||||
|
|
||||||
return int((time.time() + config_var.get().get("timezone_offset")) // (24 * 3600))
|
return int((time.time() + config_var.get().get("timezone_offset")) // (24 * 3600))
|
||||||
|
|
||||||
|
|
||||||
def get_timestamp() -> float:
|
def get_timestamp() -> float:
|
||||||
"""获取 UNIX 时间戳"""
|
"""获取 UNIX 时间戳"""
|
||||||
# 搞这个类的原因是要支持可复现操作
|
# 搞这个类的原因是要支持可复现操作
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Test configuration and fixtures for HeurAMS tests.
|
Test configuration and fixtures for HeurAMS tests.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import tempfile
|
import tempfile
|
||||||
import os
|
import os
|
||||||
@@ -10,14 +11,16 @@ from pathlib import Path
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def temp_config_file():
|
def temp_config_file():
|
||||||
"""Create a temporary config file for testing."""
|
"""Create a temporary config file for testing."""
|
||||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
||||||
f.write('''{
|
f.write(
|
||||||
|
"""{
|
||||||
"algorithm": "sm2",
|
"algorithm": "sm2",
|
||||||
"default_ease": 2.5,
|
"default_ease": 2.5,
|
||||||
"learning_steps": [1, 10],
|
"learning_steps": [1, 10],
|
||||||
"graduating_interval": 1,
|
"graduating_interval": 1,
|
||||||
"easy_interval": 4
|
"easy_interval": 4
|
||||||
}''')
|
}"""
|
||||||
|
)
|
||||||
temp_path = f.name
|
temp_path = f.name
|
||||||
|
|
||||||
yield temp_path
|
yield temp_path
|
||||||
@@ -31,21 +34,13 @@ def temp_config_file():
|
|||||||
def sample_atom_data():
|
def sample_atom_data():
|
||||||
"""Sample atom data for testing."""
|
"""Sample atom data for testing."""
|
||||||
return {
|
return {
|
||||||
"nucleon": {
|
"nucleon": {"content": "What is the capital of France?", "answer": "Paris"},
|
||||||
"content": "What is the capital of France?",
|
"electron": {"ease": 2.5, "interval": 1, "repetitions": 0, "last_review": None},
|
||||||
"answer": "Paris"
|
|
||||||
},
|
|
||||||
"electron": {
|
|
||||||
"ease": 2.5,
|
|
||||||
"interval": 1,
|
|
||||||
"repetitions": 0,
|
|
||||||
"last_review": None
|
|
||||||
},
|
|
||||||
"orbital": {
|
"orbital": {
|
||||||
"learning_steps": [1, 10],
|
"learning_steps": [1, 10],
|
||||||
"graduating_interval": 1,
|
"graduating_interval": 1,
|
||||||
"easy_interval": 4
|
"easy_interval": 4,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ Examples and usage patterns for HeurAMS modules.
|
|||||||
This file demonstrates how to use the various HeurAMS components
|
This file demonstrates how to use the various HeurAMS components
|
||||||
in common scenarios and workflows.
|
in common scenarios and workflows.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -28,10 +29,7 @@ class BasicUsageExamples:
|
|||||||
print("=== Creating Basic Atom ===")
|
print("=== Creating Basic Atom ===")
|
||||||
|
|
||||||
# Create the components
|
# Create the components
|
||||||
nucleon = Nucleon(
|
nucleon = Nucleon(content="What is the capital of France?", answer="Paris")
|
||||||
content="What is the capital of France?",
|
|
||||||
answer="Paris"
|
|
||||||
)
|
|
||||||
electron = Electron() # Uses default values
|
electron = Electron() # Uses default values
|
||||||
orbital = Orbital() # Uses default values
|
orbital = Orbital() # Uses default values
|
||||||
|
|
||||||
@@ -55,7 +53,7 @@ class BasicUsageExamples:
|
|||||||
|
|
||||||
nucleon = Nucleon(
|
nucleon = Nucleon(
|
||||||
content="The {{c1::capital}} of {{c2::France}} is {{c3::Paris}}.",
|
content="The {{c1::capital}} of {{c2::France}} is {{c3::Paris}}.",
|
||||||
answer="capital, France, Paris"
|
answer="capital, France, Paris",
|
||||||
)
|
)
|
||||||
electron = Electron()
|
electron = Electron()
|
||||||
orbital = Orbital()
|
orbital = Orbital()
|
||||||
@@ -145,20 +143,17 @@ class ReactorExamples:
|
|||||||
atom = Atom("test_atom")
|
atom = Atom("test_atom")
|
||||||
|
|
||||||
# Create nucleon with content
|
# Create nucleon with content
|
||||||
nucleon = Nucleon("nucleon_id", {
|
nucleon = Nucleon(
|
||||||
"content": "What is the capital of Germany?",
|
"nucleon_id",
|
||||||
"answer": "Berlin"
|
{"content": "What is the capital of Germany?", "answer": "Berlin"},
|
||||||
})
|
)
|
||||||
|
|
||||||
# Create electron with algorithm data
|
# Create electron with algorithm data
|
||||||
electron = Electron("electron_id")
|
electron = Electron("electron_id")
|
||||||
|
|
||||||
# Create orbital configuration
|
# Create orbital configuration
|
||||||
orbital = Orbital(
|
orbital = Orbital(
|
||||||
quick_view=[["cloze", 1]],
|
quick_view=[["cloze", 1]], recognition=[], final_review=[], puzzle_config={}
|
||||||
recognition=[],
|
|
||||||
final_review=[],
|
|
||||||
puzzle_config={}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Link components to atom
|
# Link components to atom
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ Test runner script for HeurAMS.
|
|||||||
|
|
||||||
This script runs all unit tests and provides a summary report.
|
This script runs all unit tests and provides a summary report.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import pytest
|
import pytest
|
||||||
import os
|
import os
|
||||||
@@ -25,7 +26,7 @@ def run_tests():
|
|||||||
"-v", # Verbose output
|
"-v", # Verbose output
|
||||||
"--tb=short", # Short traceback format
|
"--tb=short", # Short traceback format
|
||||||
"--color=yes", # Color output
|
"--color=yes", # Color output
|
||||||
"tests/" # Test directory
|
"tests/", # Test directory
|
||||||
]
|
]
|
||||||
|
|
||||||
print(f"Running tests from: {os.path.abspath('tests')}")
|
print(f"Running tests from: {os.path.abspath('tests')}")
|
||||||
@@ -92,6 +93,7 @@ def run_examples():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
from tests.examples import run_all_examples
|
from tests.examples import run_all_examples
|
||||||
|
|
||||||
run_all_examples()
|
run_all_examples()
|
||||||
return 0
|
return 0
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -103,31 +105,21 @@ if __name__ == "__main__":
|
|||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="HeurAMS Test Runner")
|
parser = argparse.ArgumentParser(description="HeurAMS Test Runner")
|
||||||
|
parser.add_argument("--all", action="store_true", help="Run all tests (default)")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--all",
|
"--file", type=str, help="Run specific test file (e.g., test_particles.py)"
|
||||||
action="store_true",
|
|
||||||
help="Run all tests (default)"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--file",
|
|
||||||
type=str,
|
|
||||||
help="Run specific test file (e.g., test_particles.py)"
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--class",
|
"--class",
|
||||||
dest="test_class",
|
dest="test_class",
|
||||||
type=str,
|
type=str,
|
||||||
help="Run specific test class (requires --file)"
|
help="Run specific test class (requires --file)",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--method",
|
"--method", type=str, help="Run specific test method (requires --class)"
|
||||||
type=str,
|
|
||||||
help="Run specific test method (requires --class)"
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--examples",
|
"--examples", action="store_true", help="Run examples instead of tests"
|
||||||
action="store_true",
|
|
||||||
help="Run examples instead of tests"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@@ -136,9 +128,7 @@ if __name__ == "__main__":
|
|||||||
exit_code = run_examples()
|
exit_code = run_examples()
|
||||||
elif args.file:
|
elif args.file:
|
||||||
exit_code = run_specific_test(
|
exit_code = run_specific_test(
|
||||||
test_file=args.file,
|
test_file=args.file, test_class=args.test_class, test_method=args.method
|
||||||
test_class=args.test_class,
|
|
||||||
test_method=args.method
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
exit_code = run_tests()
|
exit_code = run_tests()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Unit tests for algorithm modules: BaseAlgorithm, SM2Algorithm
|
Unit tests for algorithm modules: BaseAlgorithm, SM2Algorithm
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Unit tests for particle modules: Atom, Electron, Nucleon, Orbital, Probe, Loader
|
Unit tests for particle modules: Atom, Electron, Nucleon, Orbital, Probe, Loader
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -10,6 +11,7 @@ from src.heurams.kernel.particles.atom import Atom
|
|||||||
from src.heurams.kernel.particles.electron import Electron
|
from src.heurams.kernel.particles.electron import Electron
|
||||||
from src.heurams.kernel.particles.nucleon import Nucleon
|
from src.heurams.kernel.particles.nucleon import Nucleon
|
||||||
from src.heurams.kernel.particles.orbital import Orbital
|
from src.heurams.kernel.particles.orbital import Orbital
|
||||||
|
|
||||||
# Probe module doesn't have a Probe class, only functions
|
# Probe module doesn't have a Probe class, only functions
|
||||||
# Loader module doesn't have a Loader class, only functions
|
# Loader module doesn't have a Loader class, only functions
|
||||||
|
|
||||||
@@ -32,21 +34,18 @@ class TestAtom:
|
|||||||
def test_atom_from_dict(self):
|
def test_atom_from_dict(self):
|
||||||
"""Test creating Atom from dictionary."""
|
"""Test creating Atom from dictionary."""
|
||||||
data = {
|
data = {
|
||||||
"nucleon": {
|
"nucleon": {"content": "What is 2+2?", "answer": "4"},
|
||||||
"content": "What is 2+2?",
|
|
||||||
"answer": "4"
|
|
||||||
},
|
|
||||||
"electron": {
|
"electron": {
|
||||||
"ease": 2.5,
|
"ease": 2.5,
|
||||||
"interval": 1,
|
"interval": 1,
|
||||||
"repetitions": 0,
|
"repetitions": 0,
|
||||||
"last_review": None
|
"last_review": None,
|
||||||
},
|
},
|
||||||
"orbital": {
|
"orbital": {
|
||||||
"learning_steps": [1, 10],
|
"learning_steps": [1, 10],
|
||||||
"graduating_interval": 1,
|
"graduating_interval": 1,
|
||||||
"easy_interval": 4
|
"easy_interval": 4,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
atom = Atom.from_dict(data)
|
atom = Atom.from_dict(data)
|
||||||
@@ -87,12 +86,7 @@ class TestElectron:
|
|||||||
def test_electron_custom_values(self):
|
def test_electron_custom_values(self):
|
||||||
"""Test Electron with custom values."""
|
"""Test Electron with custom values."""
|
||||||
test_time = datetime.now(timezone.utc)
|
test_time = datetime.now(timezone.utc)
|
||||||
electron = Electron(
|
electron = Electron(ease=3.0, interval=10, repetitions=5, last_review=test_time)
|
||||||
ease=3.0,
|
|
||||||
interval=10,
|
|
||||||
repetitions=5,
|
|
||||||
last_review=test_time
|
|
||||||
)
|
|
||||||
|
|
||||||
assert electron.ease == 3.0
|
assert electron.ease == 3.0
|
||||||
assert electron.interval == 10
|
assert electron.interval == 10
|
||||||
@@ -144,10 +138,7 @@ class TestNucleon:
|
|||||||
|
|
||||||
def test_nucleon_from_dict(self):
|
def test_nucleon_from_dict(self):
|
||||||
"""Test creating Nucleon from dictionary."""
|
"""Test creating Nucleon from dictionary."""
|
||||||
data = {
|
data = {"content": "What is Python?", "answer": "A programming language"}
|
||||||
"content": "What is Python?",
|
|
||||||
"answer": "A programming language"
|
|
||||||
}
|
|
||||||
|
|
||||||
nucleon = Nucleon.from_dict(data)
|
nucleon = Nucleon.from_dict(data)
|
||||||
|
|
||||||
@@ -178,9 +169,7 @@ class TestOrbital:
|
|||||||
def test_orbital_custom_values(self):
|
def test_orbital_custom_values(self):
|
||||||
"""Test Orbital with custom values."""
|
"""Test Orbital with custom values."""
|
||||||
orbital = Orbital(
|
orbital = Orbital(
|
||||||
learning_steps=[2, 15],
|
learning_steps=[2, 15], graduating_interval=2, easy_interval=6
|
||||||
graduating_interval=2,
|
|
||||||
easy_interval=6
|
|
||||||
)
|
)
|
||||||
|
|
||||||
assert orbital.learning_steps == [2, 15]
|
assert orbital.learning_steps == [2, 15]
|
||||||
@@ -189,11 +178,7 @@ class TestOrbital:
|
|||||||
|
|
||||||
def test_orbital_from_dict(self):
|
def test_orbital_from_dict(self):
|
||||||
"""Test creating Orbital from dictionary."""
|
"""Test creating Orbital from dictionary."""
|
||||||
data = {
|
data = {"learning_steps": [3, 20], "graduating_interval": 3, "easy_interval": 8}
|
||||||
"learning_steps": [3, 20],
|
|
||||||
"graduating_interval": 3,
|
|
||||||
"easy_interval": 8
|
|
||||||
}
|
|
||||||
|
|
||||||
orbital = Orbital.from_dict(data)
|
orbital = Orbital.from_dict(data)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Unit tests for puzzle modules: BasePuzzle, ClozePuzzle, MCQPuzzle
|
Unit tests for puzzle modules: BasePuzzle, ClozePuzzle, MCQPuzzle
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Unit tests for reactor modules: Phaser, Procession, Fission, States
|
Unit tests for reactor modules: Phaser, Procession, Fission, States
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
@@ -298,8 +299,7 @@ class TestFission:
|
|||||||
"""Test generating learning puzzle with cloze content."""
|
"""Test generating learning puzzle with cloze content."""
|
||||||
fission = Fission()
|
fission = Fission()
|
||||||
nucleon = Nucleon(
|
nucleon = Nucleon(
|
||||||
content="The capital of {{c1::France}} is Paris.",
|
content="The capital of {{c1::France}} is Paris.", answer="France"
|
||||||
answer="France"
|
|
||||||
)
|
)
|
||||||
electron = Electron()
|
electron = Electron()
|
||||||
orbital = Orbital()
|
orbital = Orbital()
|
||||||
@@ -316,10 +316,7 @@ class TestFission:
|
|||||||
def test_fission_generate_learning_puzzle_mcq(self):
|
def test_fission_generate_learning_puzzle_mcq(self):
|
||||||
"""Test generating learning puzzle with MCQ content."""
|
"""Test generating learning puzzle with MCQ content."""
|
||||||
fission = Fission()
|
fission = Fission()
|
||||||
nucleon = Nucleon(
|
nucleon = Nucleon(content="What is the capital of France?", answer="Paris")
|
||||||
content="What is the capital of France?",
|
|
||||||
answer="Paris"
|
|
||||||
)
|
|
||||||
electron = Electron()
|
electron = Electron()
|
||||||
orbital = Orbital()
|
orbital = Orbital()
|
||||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||||
@@ -336,10 +333,7 @@ class TestFission:
|
|||||||
def test_fission_generate_review_puzzle(self):
|
def test_fission_generate_review_puzzle(self):
|
||||||
"""Test generating review puzzle."""
|
"""Test generating review puzzle."""
|
||||||
fission = Fission()
|
fission = Fission()
|
||||||
nucleon = Nucleon(
|
nucleon = Nucleon(content="What is the capital of France?", answer="Paris")
|
||||||
content="What is the capital of France?",
|
|
||||||
answer="Paris"
|
|
||||||
)
|
|
||||||
electron = Electron(interval=10, repetitions=5) # In review phase
|
electron = Electron(interval=10, repetitions=5) # In review phase
|
||||||
orbital = Orbital()
|
orbital = Orbital()
|
||||||
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
atom = Atom(nucleon=nucleon, electron=electron, orbital=orbital)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Unit tests for service modules: Config, Hasher, Timer, Version, AudioService, TTSService
|
Unit tests for service modules: Config, Hasher, Timer, Version, AudioService, TTSService
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import json
|
import json
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Working unit tests for algorithm modules based on actual module structure.
|
Working unit tests for algorithm modules based on actual module structure.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from src.heurams.kernel.algorithms.sm2 import SM2Algorithm
|
from src.heurams.kernel.algorithms.sm2 import SM2Algorithm
|
||||||
@@ -29,12 +30,7 @@ class TestSM2Algorithm:
|
|||||||
|
|
||||||
def test_sm2_is_due(self):
|
def test_sm2_is_due(self):
|
||||||
"""Test SM2Algorithm is_due method."""
|
"""Test SM2Algorithm is_due method."""
|
||||||
algodata = {
|
algodata = {"SM-2": {"next_date": 0, "is_activated": 1}} # Past date
|
||||||
"SM-2": {
|
|
||||||
"next_date": 0, # Past date
|
|
||||||
"is_activated": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result = SM2Algorithm.is_due(algodata)
|
result = SM2Algorithm.is_due(algodata)
|
||||||
|
|
||||||
@@ -42,13 +38,7 @@ class TestSM2Algorithm:
|
|||||||
|
|
||||||
def test_sm2_rate(self):
|
def test_sm2_rate(self):
|
||||||
"""Test SM2Algorithm rate method."""
|
"""Test SM2Algorithm rate method."""
|
||||||
algodata = {
|
algodata = {"SM-2": {"efactor": 2.5, "rept": 5, "interval": 10}}
|
||||||
"SM-2": {
|
|
||||||
"efactor": 2.5,
|
|
||||||
"rept": 5,
|
|
||||||
"interval": 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result = SM2Algorithm.rate(algodata)
|
result = SM2Algorithm.rate(algodata)
|
||||||
|
|
||||||
@@ -56,11 +46,7 @@ class TestSM2Algorithm:
|
|||||||
|
|
||||||
def test_sm2_nextdate(self):
|
def test_sm2_nextdate(self):
|
||||||
"""Test SM2Algorithm nextdate method."""
|
"""Test SM2Algorithm nextdate method."""
|
||||||
algodata = {
|
algodata = {"SM-2": {"next_date": 100}}
|
||||||
"SM-2": {
|
|
||||||
"next_date": 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result = SM2Algorithm.nextdate(algodata)
|
result = SM2Algorithm.nextdate(algodata)
|
||||||
|
|
||||||
@@ -75,7 +61,7 @@ class TestSM2Algorithm:
|
|||||||
"real_rept": 0,
|
"real_rept": 0,
|
||||||
"interval": 1,
|
"interval": 1,
|
||||||
"is_activated": 1,
|
"is_activated": 1,
|
||||||
"last_modify": 0
|
"last_modify": 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Working unit tests for particle modules based on actual module structure.
|
Working unit tests for particle modules based on actual module structure.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from src.heurams.kernel.particles.atom import Atom
|
from src.heurams.kernel.particles.atom import Atom
|
||||||
@@ -14,10 +15,7 @@ class TestNucleon:
|
|||||||
|
|
||||||
def test_nucleon_creation(self):
|
def test_nucleon_creation(self):
|
||||||
"""Test basic Nucleon creation."""
|
"""Test basic Nucleon creation."""
|
||||||
payload = {
|
payload = {"content": "Test content", "answer": "Test answer"}
|
||||||
"content": "Test content",
|
|
||||||
"answer": "Test answer"
|
|
||||||
}
|
|
||||||
nucleon = Nucleon("test_id", payload)
|
nucleon = Nucleon("test_id", payload)
|
||||||
|
|
||||||
assert nucleon.ident == "test_id"
|
assert nucleon.ident == "test_id"
|
||||||
@@ -123,7 +121,10 @@ class TestOrbital:
|
|||||||
quick_view=[["cloze", 1], ["mcq", 0.5]],
|
quick_view=[["cloze", 1], ["mcq", 0.5]],
|
||||||
recognition=[["recognition", 1]],
|
recognition=[["recognition", 1]],
|
||||||
final_review=[["cloze", 0.7], ["mcq", 0.7]],
|
final_review=[["cloze", 0.7], ["mcq", 0.7]],
|
||||||
puzzle_config={"cloze": {"from": "content"}, "mcq": {"from": "keyword_note"}}
|
puzzle_config={
|
||||||
|
"cloze": {"from": "content"},
|
||||||
|
"mcq": {"from": "keyword_note"},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert isinstance(orbital, dict)
|
assert isinstance(orbital, dict)
|
||||||
@@ -138,7 +139,7 @@ class TestOrbital:
|
|||||||
quick_view=[["cloze", 1], ["mcq", 0.5]],
|
quick_view=[["cloze", 1], ["mcq", 0.5]],
|
||||||
recognition=[],
|
recognition=[],
|
||||||
final_review=[],
|
final_review=[],
|
||||||
puzzle_config={}
|
puzzle_config={},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(orbital["quick_view"]) == 2
|
assert len(orbital["quick_view"]) == 2
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Working unit tests for service modules based on actual module structure.
|
Working unit tests for service modules based on actual module structure.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
# Version import commented out - actual module only has variables
|
# Version import commented out - actual module only has variables
|
||||||
|
|||||||
Reference in New Issue
Block a user