feat: 代码格式化, 改进仪表盘, 新增多CSS支持
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
from time import sleep, perf_counter
|
||||
|
||||
print("欢迎使用基本用户界面!")
|
||||
print("加载配置与上下文... ", end="", flush=True)
|
||||
_start1 = perf_counter()
|
||||
_start = perf_counter()
|
||||
from heurams.context import *
|
||||
|
||||
_end = perf_counter()
|
||||
print(f"已完成! (耗时: {round(1000 * (_end - _start))}ms)")
|
||||
|
||||
@@ -11,6 +13,7 @@ print("加载用户界面框架... ", end="", flush=True)
|
||||
_start = perf_counter()
|
||||
from textual.app import App
|
||||
from textual.widgets import Button
|
||||
|
||||
_end = perf_counter()
|
||||
print(f"已完成! (耗时: {round(1000 * (_end - _start))}ms)")
|
||||
|
||||
@@ -22,6 +25,7 @@ from .screens.navigator import NavigatorScreen
|
||||
from .screens.precache import PrecachingScreen
|
||||
from .screens.setting import SettingScreen
|
||||
from .screens.synctool import SyncScreen
|
||||
|
||||
_end = perf_counter()
|
||||
print(f"已完成! (耗时: {round(1000 * (_end - _start))}ms)")
|
||||
|
||||
@@ -29,9 +33,12 @@ print(f"组件目录: {rootdir}")
|
||||
print(f"工作目录: {workdir}")
|
||||
_end1 = perf_counter()
|
||||
print(f"前置工作共计耗时: {round(1000 * (_end1 - _start1))}ms")
|
||||
|
||||
|
||||
class HeurAMSApp(App):
|
||||
TITLE = "潜进"
|
||||
CSS_PATH = "css/main.tcss"
|
||||
css_dir = pathlib.Path("css").resolve()
|
||||
SUB_TITLE = "启发式辅助记忆调度器"
|
||||
BINDINGS = [
|
||||
("q", "go_back", "退出"),
|
||||
@@ -72,4 +79,4 @@ class HeurAMSApp(App):
|
||||
|
||||
def panic(self, *args):
|
||||
self._close_messages_no_wait()
|
||||
raise self._exception
|
||||
raise self._exception
|
||||
|
||||
@@ -7,6 +7,7 @@ import pickle
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def environment_check():
|
||||
from pathlib import Path
|
||||
|
||||
@@ -23,11 +24,12 @@ def environment_check():
|
||||
print(f"找到 {i}")
|
||||
logger.debug("环境检查完成")
|
||||
|
||||
|
||||
def start_debug_server(app):
|
||||
logger = get_logger("zmq_debug")
|
||||
context = zmq.Context()
|
||||
socket = context.socket(zmq.REP)
|
||||
port = config_var.get()['global'].get('zmq_debug_port', 5555)
|
||||
port = config_var.get()["global"].get("zmq_debug_port", 5555)
|
||||
socket.bind(f"tcp://*:{port}")
|
||||
logger.info(f"ZMQ Debug server started on port {port}")
|
||||
first = 1
|
||||
@@ -36,7 +38,7 @@ def start_debug_server(app):
|
||||
code = pickle.loads(msg)
|
||||
namespace = {"app": app, "logger": logger, "config_var": config_var}
|
||||
if first:
|
||||
app.title += ' [调试已连接]'
|
||||
app.title += " [调试已连接]"
|
||||
first = 0
|
||||
try:
|
||||
# 先尝试 eval
|
||||
@@ -52,15 +54,17 @@ def start_debug_server(app):
|
||||
except Exception as e:
|
||||
socket.send(pickle.dumps(f"错误: {e}"))
|
||||
|
||||
|
||||
def main():
|
||||
environment_check()
|
||||
|
||||
|
||||
app = HeurAMSApp()
|
||||
|
||||
if config_var.get()['global'].get('zmq_debug', False):
|
||||
|
||||
if config_var.get()["global"].get("zmq_debug", False):
|
||||
threading.Thread(target=start_debug_server, args=(app,), daemon=True).start()
|
||||
|
||||
|
||||
app.run(inline=False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
18
src/heurams/interface/css/screens/dashboard.tcss
Normal file
18
src/heurams/interface/css/screens/dashboard.tcss
Normal file
@@ -0,0 +1,18 @@
|
||||
.repo-list {
|
||||
|
||||
}
|
||||
|
||||
#header {
|
||||
height: 4;
|
||||
}
|
||||
|
||||
.repo-list-item {
|
||||
layout: grid;
|
||||
grid-size: 2;
|
||||
height: 3;
|
||||
}
|
||||
|
||||
.repo-list-item-shortcut {
|
||||
dock: right;
|
||||
offset: -5% 0
|
||||
}
|
||||
@@ -23,7 +23,7 @@ class AboutScreen(Screen):
|
||||
yield Header(show_clock=True)
|
||||
with ScrollableContainer(id="about_container"):
|
||||
yield Label("[b]关于与版本信息[/b]")
|
||||
|
||||
|
||||
# 获取系统信息
|
||||
textual_version = self._get_textual_version()
|
||||
terminal_info = self._get_terminal_info()
|
||||
@@ -31,7 +31,7 @@ class AboutScreen(Screen):
|
||||
os_version = self._get_os_version()
|
||||
disk_usage = self._get_disk_usage()
|
||||
memory_info = self._get_memory_info()
|
||||
|
||||
|
||||
about_text = f"""
|
||||
# 关于 "潜进"
|
||||
|
||||
@@ -95,36 +95,39 @@ Textual 框架版本: {textual_version}
|
||||
event.stop()
|
||||
if event.button.id == "back_button":
|
||||
self.action_go_back()
|
||||
|
||||
|
||||
def _get_textual_version(self) -> str:
|
||||
"""获取 Textual 框架版本"""
|
||||
try:
|
||||
import textual
|
||||
|
||||
return textual.__version__
|
||||
except (ImportError, AttributeError):
|
||||
except ImportError, AttributeError:
|
||||
return "未知"
|
||||
|
||||
|
||||
def _get_terminal_info(self) -> str:
|
||||
"""获取终端模拟器信息"""
|
||||
terminal = shutil.which("terminal")
|
||||
if terminal:
|
||||
return terminal
|
||||
# 尝试从环境变量获取
|
||||
terminal_env = os.environ.get('TERM_PROGRAM') or os.environ.get('TERM')
|
||||
terminal_env = os.environ.get("TERM_PROGRAM") or os.environ.get("TERM")
|
||||
return terminal_env or "未知"
|
||||
|
||||
|
||||
def _get_python_version(self) -> str:
|
||||
"""获取 Python 解释器版本"""
|
||||
return platform.python_version()
|
||||
|
||||
|
||||
def _get_os_version(self) -> str:
|
||||
"""获取操作系统版本"""
|
||||
try:
|
||||
if platform.system() == "Darwin":
|
||||
# macOS
|
||||
import subprocess
|
||||
result = subprocess.run(['sw_vers', '-productVersion'],
|
||||
capture_output=True, text=True)
|
||||
|
||||
result = subprocess.run(
|
||||
["sw_vers", "-productVersion"], capture_output=True, text=True
|
||||
)
|
||||
return f"macOS {result.stdout.strip()}"
|
||||
elif platform.system() == "Windows":
|
||||
# Windows
|
||||
@@ -133,30 +136,31 @@ Textual 框架版本: {textual_version}
|
||||
# Linux - 尝试获取发行版信息
|
||||
try:
|
||||
import distro
|
||||
|
||||
return f"{distro.name()} {distro.version()}"
|
||||
except (ImportError, AttributeError):
|
||||
except ImportError, AttributeError:
|
||||
return platform.platform()
|
||||
else:
|
||||
return platform.platform()
|
||||
except Exception:
|
||||
return platform.platform()
|
||||
|
||||
|
||||
def _get_disk_usage(self) -> str:
|
||||
"""获取磁盘使用情况"""
|
||||
try:
|
||||
usage = psutil.disk_usage('/')
|
||||
free_gb = usage.free / (1024 ** 3)
|
||||
total_gb = usage.total / (1024 ** 3)
|
||||
usage = psutil.disk_usage("/")
|
||||
free_gb = usage.free / (1024**3)
|
||||
total_gb = usage.total / (1024**3)
|
||||
percent_free = (free_gb / total_gb) * 100
|
||||
return f"{free_gb:.1f} GB ({percent_free:.1f}%)"
|
||||
except Exception:
|
||||
return "未知"
|
||||
|
||||
|
||||
def _get_memory_info(self) -> str:
|
||||
"""获取内存信息"""
|
||||
try:
|
||||
memory = psutil.virtual_memory()
|
||||
total_gb = memory.total / (1024 ** 3)
|
||||
total_gb = memory.total / (1024**3)
|
||||
return f"{total_gb:.1f} GB"
|
||||
except Exception:
|
||||
return "未知"
|
||||
return "未知"
|
||||
|
||||
@@ -35,6 +35,8 @@ class DashboardScreen(Screen):
|
||||
("q", "go_back", "返回"),
|
||||
]
|
||||
|
||||
CSS_PATH = Path(__file__).parent.parent / 'css' / "screens" / "dashboard.tcss"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str | None = None,
|
||||
@@ -42,97 +44,91 @@ class DashboardScreen(Screen):
|
||||
classes: str | None = None,
|
||||
) -> None:
|
||||
super().__init__(name, id, classes)
|
||||
self.repostat = {}
|
||||
self.title2dirname = {}
|
||||
self.title2repo = {}
|
||||
self.dirname2repo = {}
|
||||
self._load_data()
|
||||
self.repolink = {}
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""组合界面组件"""
|
||||
self._load_data()
|
||||
yield Header(show_clock=True)
|
||||
with ScrollableContainer():
|
||||
yield Horizontal(
|
||||
yield Horizontal( # 顶部的状态
|
||||
Vertical(
|
||||
Label(f'欢迎使用 "潜进" 版本 {version.ver}'),
|
||||
Label(f"当前 UNIX 日时间戳: {timer.get_daystamp()}"),
|
||||
Label(
|
||||
f"当前 UNIX 日时间戳: {timer.get_daystamp()}"
|
||||
f"应用时区修正: UTC+{config_var.get()['services']['timer']['timezone_offset'] / 3600}"
|
||||
),
|
||||
Label(f"应用时区修正: UTC+{config_var.get()['services']['timer']['timezone_offset'] / 3600}"),
|
||||
Label(f"全局算法设置: {config_var.get()['interface']['global']['algorithm']}: {algorithms[config_var.get()['interface']['global']['algorithm']].desc}"),
|
||||
classes="column infview",
|
||||
Label(
|
||||
f"默认算法设置: {config_var.get()['interface']['global']['algorithm']}"
|
||||
),
|
||||
classes="left",
|
||||
),
|
||||
Vertical(
|
||||
Label(f"已加载 {len(self.repostat)} 个单元集", classes='dataview'),
|
||||
Label(f"共计 {reduce(lambda x, y: x + y, map(lambda x: x.get('unit_sum'), self.repostat.values()))} 个单元", classes='dataview'),
|
||||
Label(f"已激活 {reduce(lambda x, y: x + y, map(lambda x: x.get('activated_sum'), self.repostat.values()))} 个单元", classes='dataview'),
|
||||
Label(f"已加载 {len(self.repos)} 个单元集"),
|
||||
Label(
|
||||
f"共计 {reduce(lambda x, y: x + y, map(lambda x: x.progress['total'], self.repos))} 个单元"
|
||||
),
|
||||
Label(
|
||||
f"已激活 {reduce(lambda x, y: x + y, map(lambda x: x.progress['touched'], self.repos))} 个单元"
|
||||
),
|
||||
Label(f""),
|
||||
classes="column dataview",
|
||||
classes="right",
|
||||
),
|
||||
id="dashboardtop"
|
||||
id="header",
|
||||
)
|
||||
|
||||
yield ListView(id="repo-list", classes="repo-list-view")
|
||||
yield Label(f'"潜进" 启发式辅助记忆调度器 版本 {version.ver} {version.stage.capitalize()}')
|
||||
yield ListView(id="repo_list", classes="repo-list") # 单元集选择
|
||||
|
||||
yield Label(
|
||||
f'"潜进" 启发式辅助记忆调度器 版本 {version.ver} {version.stage.capitalize()}'
|
||||
) # 版本信息
|
||||
yield Footer()
|
||||
|
||||
def _load_data(self):
|
||||
self.repo_dirs = Repo.probe_valid_repos_in_dir(
|
||||
Path(config_var.get()['global']["paths"]["data"]) / "repo"
|
||||
repo_dirs = Repo.probe_valid_repos_in_dir(
|
||||
Path(config_var.get()["global"]["paths"]["repo"])
|
||||
)
|
||||
for repo_dir in self.repo_dirs:
|
||||
repo = Repo.create_from_repodir(repo_dir)
|
||||
self.repos = list(map(Repo.from_repodir, repo_dirs))
|
||||
for repo in self.repos:
|
||||
self._analyse_repo(repo)
|
||||
|
||||
def _analyse_repo(self, repo: Repo):
|
||||
dirname = repo.source.name # type: ignore
|
||||
title = repo.manifest["title"]
|
||||
is_due = 0
|
||||
unit_sum = len(repo)
|
||||
activated_sum = 0
|
||||
nextdate = float('inf')
|
||||
for i in repo.ident_index:
|
||||
nucleon = pt.Nucleon.create_on_nucleonic_data(
|
||||
nucleonic_data=repo.nucleonic_data_lict.get_itemic_unit(i)
|
||||
)
|
||||
electron = pt.Electron.create_on_electonic_data(
|
||||
electronic_data=repo.electronic_data_lict.get_itemic_unit(i),
|
||||
algo_name=config_var.get()['repo'][repo.manifest['title']]['algorithm']
|
||||
)
|
||||
if electron.is_activated():
|
||||
activated_sum += 1
|
||||
if electron.is_due():
|
||||
is_due = 1
|
||||
nextdate = min(nextdate, electron.nextdate())
|
||||
is_unfinished = unit_sum > activated_sum
|
||||
if is_unfinished:
|
||||
nextdate = min(nextdate, timer.get_daystamp())
|
||||
need_to_study = is_due or is_unfinished
|
||||
prompt = f"{title}\0\n 进度: {activated_sum}/{unit_sum} ({round(activated_sum/unit_sum*100)}%)\n {'需要学习' if need_to_study else '无需操作'}"
|
||||
stat = {
|
||||
"is_due": is_due,
|
||||
"unit_sum": unit_sum,
|
||||
"title": title,
|
||||
"activated_sum": activated_sum,
|
||||
"nextdate": nextdate,
|
||||
"is_unfinished": is_unfinished,
|
||||
"need_to_study": need_to_study,
|
||||
"prompt": prompt,
|
||||
"dirname": dirname,
|
||||
# need_review: 需要/不需要学习
|
||||
# nearest_review_time: 最近下次学习时间
|
||||
# progress: 进度
|
||||
# algotype: 算法类型
|
||||
## initial_time: 起始时间
|
||||
# package: 包名
|
||||
# prompt: 最终呈现信息
|
||||
repo.package = repo.manifest["package"]
|
||||
repo.nearest_review_time = float("inf")
|
||||
repo.progress = {
|
||||
"total": repo.data_length,
|
||||
"touched": 0,
|
||||
}
|
||||
self.repostat[dirname] = stat
|
||||
self.title2dirname[title] = dirname
|
||||
self.title2repo[title] = repo
|
||||
self.dirname2repo[dirname] = repo
|
||||
initial_time = float("inf")
|
||||
for i in range(repo.data_length):
|
||||
e = pt.Electron.from_data(repo.electronic_data_lict[i])
|
||||
n = pt.Nucleon.from_data(repo.nucleonic_data_lict[i])
|
||||
if e.is_activated():
|
||||
repo.algotype = e.algoname
|
||||
repo.progress["touched"] += 1
|
||||
repo.nearest_review_time = min(repo.nearest_review_time, e.nextdate())
|
||||
# initial_time = min(initial_time, e.)
|
||||
repo.need_review = timer.get_daystamp() >= repo.nearest_review_time
|
||||
repo.prompt = f"""{repo.manifest['title']} ({repo.algotype})
|
||||
进度: {repo.progress['touched']}/{repo.progress['total']} ({round(repo.progress['touched']/repo.progress['total']*100, 1)}%)
|
||||
{'需要学习' if repo.need_review else "无需操作"}
|
||||
"""
|
||||
|
||||
def on_mount(self) -> None:
|
||||
"""挂载组件时初始化"""
|
||||
repo_list_widget = self.query_one("#repo-list", ListView)
|
||||
repo_list_widget = self.query_one("#repo_list", ListView)
|
||||
|
||||
# 按下次复习时间排序
|
||||
repodirs = sorted(
|
||||
self.repo_dirs,
|
||||
key=lambda f: self.repostat[f.name]["nextdate"],
|
||||
self.repos,
|
||||
key=lambda r: r.nearest_review_time,
|
||||
reverse=True,
|
||||
)
|
||||
repotitles = map(lambda f: self.repostat[f.name]["title"], repodirs)
|
||||
@@ -141,43 +137,44 @@ class DashboardScreen(Screen):
|
||||
repo_list_widget.append(
|
||||
ListItem(
|
||||
Static(
|
||||
"在 ./data/repo/ 中未找到任何仓库.\n"
|
||||
f"在 {config_var.get()['global']['paths']['repo']} 中未找到任何仓库.\n"
|
||||
"请导入仓库后重启应用, 或者新建空的仓库."
|
||||
)
|
||||
),
|
||||
id="not-found",
|
||||
)
|
||||
)
|
||||
repo_list_widget.disabled = True
|
||||
return
|
||||
|
||||
for repotitle in repotitles:
|
||||
prompt = self.repostat[self.title2dirname[repotitle]]["prompt"]
|
||||
list_item = ListItem(Label(prompt), Button(f"开始学习", flat=True, variant="primary", classes="repo_listitem_btn", id=f"launch_{self.repostat[self.title2dirname[repotitle]]['dirname']}"), classes="repo_listitem")
|
||||
for r in self.repos:
|
||||
self.repolink[str(id(r))] = r # 用于规避 ctype id 对象还原
|
||||
list_item = ListItem(
|
||||
Label(r.prompt),
|
||||
Button(
|
||||
f"开始学习",
|
||||
flat=True,
|
||||
variant="primary",
|
||||
id=f"slaunch_repo_{id(r)}",
|
||||
classes="repo-list-item-shortcut",
|
||||
),
|
||||
classes="repo-list-item",
|
||||
id=f"launch_repo_{id(r)}",
|
||||
)
|
||||
repo_list_widget.append(list_item)
|
||||
|
||||
# if not self.stay_enabled[repodir]:
|
||||
# list_item.disabled = True
|
||||
|
||||
def on_list_view_selected(self, event) -> None:
|
||||
"""处理列表项选择事件"""
|
||||
if not isinstance(event.item, ListItem):
|
||||
return
|
||||
|
||||
selected_label = event.item.query_one(Label)
|
||||
label_text = str(selected_label.render())
|
||||
|
||||
if "未找到任何仓库" in label_text:
|
||||
if "not-found" == event.item.id:
|
||||
return
|
||||
|
||||
# 提取文件名
|
||||
selected_repotitle = label_text.partition("\0")[0].replace("*", "")
|
||||
selected_repo = self.title2repo[label_text.partition("\0")[0].replace("*", "")]
|
||||
# 还原对象
|
||||
selected_repo = self.repolink[event.item.id.lstrip("launch_repo_")]
|
||||
|
||||
# 跳转到准备屏幕
|
||||
self.app.push_screen(
|
||||
PreparationScreen(
|
||||
selected_repo, self.repostat[self.title2dirname[selected_repotitle]]
|
||||
)
|
||||
)
|
||||
self.app.push_screen(PreparationScreen(selected_repo))
|
||||
|
||||
def action_quit_app(self) -> None:
|
||||
"""退出应用程序"""
|
||||
@@ -188,9 +185,10 @@ class DashboardScreen(Screen):
|
||||
self.app.push_screen(NavigatorScreen())
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
logger.debug(f"event.button.id: {event.button.id}")
|
||||
"""处理按钮点击事件"""
|
||||
if str(event.button.id).startswith("launch_"): # type: ignore
|
||||
logger.debug(f"event.button.id: {event.button.id}")
|
||||
if event.button.id.startswith("slaunch_repo_"): # type: ignore
|
||||
from .preparation import launch
|
||||
launch(repo=self.dirname2repo[event.button.id[7:]], app=self.app, scheduled_num=-1) # type: ignore
|
||||
|
||||
launch(repo=self.repolink[event.button.id.lstrip("slaunch_repo_")], app=self.app, scheduled_num=-1) # type: ignore
|
||||
# TODO: 这样启动的记忆实例的状态机无法绑定到 PreparationScreen 中
|
||||
|
||||
@@ -68,7 +68,7 @@ class FavoriteManagerScreen(Screen):
|
||||
if self.favorites:
|
||||
list_view = self.query_one("#favorites-list")
|
||||
for fav in self.favorites:
|
||||
list_view.append(self._create_favorite_item(fav)) # type: ignore
|
||||
list_view.append(self._create_favorite_item(fav)) # type: ignore
|
||||
|
||||
def _encode_favorite_key(self, repo_path: str, ident: str) -> str:
|
||||
"""编码仓库路径和标识符为安全的按钮 ID 部分"""
|
||||
@@ -115,12 +115,12 @@ class FavoriteManagerScreen(Screen):
|
||||
def _get_repo_info(self, repo_path: str, fav: FavoriteItem) -> Optional[dict]:
|
||||
"""获取仓库信息(标题、原子内容预览)"""
|
||||
try:
|
||||
data_repo = Path(config_var.get()['global']["paths"]["data"]) / "repo"
|
||||
data_repo = Path(config_var.get()["global"]["paths"]["data"]) / "repo"
|
||||
repo_dir = data_repo / repo_path
|
||||
if not repo_dir.exists():
|
||||
logger.warning("仓库目录不存在: %s", repo_dir)
|
||||
return None
|
||||
repo = Repo.create_from_repodir(repo_dir)
|
||||
repo = Repo.from_repodir(repo_dir)
|
||||
# 获取原子内容预览
|
||||
content_preview = ""
|
||||
payload = repo.payload
|
||||
@@ -201,4 +201,4 @@ class FavoriteManagerScreen(Screen):
|
||||
|
||||
def action_toggle_dark(self) -> None:
|
||||
"""切换暗黑模式"""
|
||||
self.app.dark = not self.app.dark # type: ignore
|
||||
self.app.dark = not self.app.dark # type: ignore
|
||||
|
||||
@@ -38,7 +38,7 @@ class MemScreen(Screen):
|
||||
("0,1,2,3", "app.push_screen('about')", ""),
|
||||
]
|
||||
|
||||
if config_var.get()['interface']['global']["quick_pass"]:
|
||||
if config_var.get()["interface"]["global"]["quick_pass"]:
|
||||
BINDINGS.append(("k", "quick_pass", "正确应答"))
|
||||
BINDINGS.append(("f", "quick_fail", "错误应答"))
|
||||
rating = reactive(-1)
|
||||
@@ -70,7 +70,6 @@ class MemScreen(Screen):
|
||||
"""更新状态机"""
|
||||
self.procession: Procession = self.phaser.current_procession() # type: ignore
|
||||
self.atom: pt.Atom = self.procession.current_atom # type: ignore
|
||||
|
||||
|
||||
def on_mount(self):
|
||||
self.fission = self.procession.get_fission()
|
||||
@@ -93,7 +92,7 @@ class MemScreen(Screen):
|
||||
if self.repo is not None:
|
||||
fav_status = "已收藏" if self._is_current_atom_favorited() else "未收藏"
|
||||
s += f"收藏: {fav_status}\n"
|
||||
'''if config_var.get().get("debug_topline", 0):
|
||||
"""if config_var.get().get("debug_topline", 0):
|
||||
try:
|
||||
alia = self.fission.get_current_puzzle_inf()["alia"] # type: ignore
|
||||
s += f"谜题: {alia}\n"
|
||||
@@ -113,7 +112,7 @@ class MemScreen(Screen):
|
||||
stat = self.fission.__repr__("simple", "")
|
||||
s += f"{stat}\n"
|
||||
except Exception as e:
|
||||
s = str(e)'''
|
||||
s = str(e)"""
|
||||
s += f"进度: {self.procession.process() + 1}/{self.procession.total_length()}"
|
||||
return s
|
||||
|
||||
@@ -139,9 +138,9 @@ class MemScreen(Screen):
|
||||
i.remove()
|
||||
from heurams.interface.widgets.finished import Finished
|
||||
|
||||
if config_var.get()['interface']['global']["persist_to_file"]:
|
||||
if config_var.get()["interface"]["global"]["persist_to_file"]:
|
||||
self.save_func()
|
||||
container.mount(Finished(is_saved=['interface']['global']["persist_to_file"]))
|
||||
container.mount(Finished(is_saved=["interface"]["global"]["persist_to_file"]))
|
||||
|
||||
def on_button_pressed(self, event):
|
||||
event.stop()
|
||||
@@ -156,7 +155,7 @@ class MemScreen(Screen):
|
||||
from heurams.services.audio_service import play_by_path
|
||||
from heurams.services.hasher import get_md5
|
||||
|
||||
path = Path(config_var.get()['global']["paths"]["data"]) / "cache" / "voice"
|
||||
path = Path(config_var.get()["global"]["paths"]["data"]) / "cache" / "voice"
|
||||
path = path / f"{get_md5(self.atom.registry['nucleon']["tts_text"])}.wav"
|
||||
if path.exists():
|
||||
play_by_path(path)
|
||||
@@ -177,7 +176,6 @@ class MemScreen(Screen):
|
||||
self.forward(new_rating)
|
||||
self.rating = -1
|
||||
|
||||
|
||||
def forward(self, rating):
|
||||
self.update_state()
|
||||
allow_forward = 1 if rating >= 4 else 0
|
||||
@@ -226,7 +224,7 @@ class MemScreen(Screen):
|
||||
return ""
|
||||
# self.repo.source 是 Path 对象,指向仓库目录
|
||||
repo_full_path = self.repo.source
|
||||
data_repo_path = Path(config_var.get()['global']["paths"]["data"]) / "repo"
|
||||
data_repo_path = Path(config_var.get()["global"]["paths"]["data"]) / "repo"
|
||||
try:
|
||||
rel_path = repo_full_path.relative_to(data_repo_path)
|
||||
return str(rel_path)
|
||||
|
||||
@@ -53,7 +53,11 @@ class NavigatorScreen(ModalScreen):
|
||||
)
|
||||
yield Static("按下回车以完成切换\n所有会话将被保存")
|
||||
yield Button(
|
||||
"关闭 (n)", id="close_button", variant="primary", classes="close-button", flat=True
|
||||
"关闭 (n)",
|
||||
id="close_button",
|
||||
variant="primary",
|
||||
classes="close-button",
|
||||
flat=True,
|
||||
)
|
||||
|
||||
def on_mount(self) -> None:
|
||||
|
||||
@@ -13,16 +13,16 @@ import heurams.services.hasher as hasher
|
||||
from heurams.context import *
|
||||
|
||||
# 兼容性缓存路径:优先使用 paths.cache,否则使用 data/cache
|
||||
paths = config_var.get()['global']["paths"]
|
||||
paths = config_var.get()["global"]["paths"]
|
||||
cache_dir = pathlib.Path(paths.get("cache", paths["data"] + "/cache")) / "voice"
|
||||
|
||||
|
||||
def format_size(bytes_num: int) -> str:
|
||||
"""将字节数格式化为人类可读的字符串"""
|
||||
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
|
||||
for unit in ["B", "KB", "MB", "GB", "TB"]:
|
||||
if bytes_num < 1024.0:
|
||||
return f"{bytes_num:.2f} {unit}"
|
||||
bytes_num /= 1024.0 # type: ignore
|
||||
bytes_num /= 1024.0 # type: ignore
|
||||
return f"{bytes_num:.2f} PB"
|
||||
|
||||
|
||||
@@ -54,16 +54,24 @@ class PrecachingScreen(Screen):
|
||||
self.cancel_flag = 0
|
||||
self.desc = desc
|
||||
# 不再需要缓存配置,保留配置读取以兼容
|
||||
self.cache_stats = {"total_size": 0, "file_count": 0, "human_size": "0 B", "cached_units": 0, "total_units": 0, "cache_rate": 0}
|
||||
self.cache_stats = {
|
||||
"total_size": 0,
|
||||
"file_count": 0,
|
||||
"human_size": "0 B",
|
||||
"cached_units": 0,
|
||||
"total_units": 0,
|
||||
"cache_rate": 0,
|
||||
}
|
||||
self._update_cache_stats()
|
||||
|
||||
def _get_total_units(self) -> int:
|
||||
"""获取所有仓库的总单元数"""
|
||||
from heurams.context import config_var
|
||||
from heurams.kernel.repolib import Repo
|
||||
repo_path = pathlib.Path(config_var.get()['global']["paths"]["data"]) / "repo"
|
||||
|
||||
repo_path = pathlib.Path(config_var.get()["global"]["paths"]["data"]) / "repo"
|
||||
repo_dirs = Repo.probe_valid_repos_in_dir(repo_path)
|
||||
repos = map(Repo.create_from_repodir, repo_dirs)
|
||||
repos = map(Repo.from_repodir, repo_dirs)
|
||||
total = 0
|
||||
for repo in repos:
|
||||
try:
|
||||
@@ -86,7 +94,7 @@ class PrecachingScreen(Screen):
|
||||
cached_units += 1
|
||||
total_units = self._get_total_units()
|
||||
cache_rate = (cached_units / total_units * 100) if total_units > 0 else 0
|
||||
|
||||
|
||||
self.cache_stats["total_size"] = total_size
|
||||
self.cache_stats["file_count"] = file_count
|
||||
self.cache_stats["human_size"] = format_size(total_size)
|
||||
@@ -101,11 +109,15 @@ class PrecachingScreen(Screen):
|
||||
with Container():
|
||||
yield Static(
|
||||
f"缓存率: {self.cache_stats.get('cache_rate', 0):.1f}% (已缓存 {self.cache_stats.get('cached_units', 0)} / {self.cache_stats.get('total_units', 0)} 个单元)",
|
||||
classes="cache-usage-text"
|
||||
classes="cache-usage-text",
|
||||
)
|
||||
if self.nucleons:
|
||||
yield Static(f"目标单元归属: [b]{self.desc}[/b]", classes="target-info")
|
||||
yield Static(f"单元数量: {len(self.nucleons)}", classes="target-info")
|
||||
yield Static(
|
||||
f"目标单元归属: [b]{self.desc}[/b]", classes="target-info"
|
||||
)
|
||||
yield Static(
|
||||
f"单元数量: {len(self.nucleons)}", classes="target-info"
|
||||
)
|
||||
else:
|
||||
yield Static("目标: 所有单元", classes="target-info")
|
||||
|
||||
@@ -114,16 +126,26 @@ class PrecachingScreen(Screen):
|
||||
yield ProgressBar(total=100, show_eta=False, id="progress_bar")
|
||||
with Horizontal(classes="button-group"):
|
||||
if not self.is_precaching:
|
||||
yield Button("开始预缓存", id="start_precache", variant="primary")
|
||||
yield Button(
|
||||
"开始预缓存", id="start_precache", variant="primary"
|
||||
)
|
||||
else:
|
||||
yield Button("取消预缓存", id="cancel_precache", variant="error")
|
||||
yield Button(
|
||||
"取消预缓存", id="cancel_precache", variant="error"
|
||||
)
|
||||
yield Button("清空缓存", id="clear_cache", variant="warning")
|
||||
yield Button("返回", id="go_back", variant="default")
|
||||
with Container(classes="cache-info"):
|
||||
yield Static(f"缓存路径: {cache_dir}", classes="cache-path")
|
||||
yield Static(f"文件数: {self.cache_stats['file_count']}", classes="cache-count")
|
||||
yield Static(f"总大小: {self.cache_stats['human_size']}", classes="cache-size")
|
||||
yield Button("刷新", id="refresh_cache_stats", variant="default", flat=True)
|
||||
yield Static(
|
||||
f"文件数: {self.cache_stats['file_count']}", classes="cache-count"
|
||||
)
|
||||
yield Static(
|
||||
f"总大小: {self.cache_stats['human_size']}", classes="cache-size"
|
||||
)
|
||||
yield Button(
|
||||
"刷新", id="refresh_cache_stats", variant="default", flat=True
|
||||
)
|
||||
yield Static("若您离开此界面, 未完成的缓存进程会自动停止.")
|
||||
yield Static('缓存程序支持 "断点续传".')
|
||||
|
||||
@@ -230,9 +252,9 @@ class PrecachingScreen(Screen):
|
||||
from heurams.context import config_var, rootdir, workdir
|
||||
from heurams.kernel.repolib import Repo
|
||||
|
||||
repo_path = pathlib.Path(config_var.get()['global']["paths"]["data"]) / "repo"
|
||||
repo_path = pathlib.Path(config_var.get()["global"]["paths"]["data"]) / "repo"
|
||||
repo_dirs = Repo.probe_valid_repos_in_dir(repo_path)
|
||||
repos = map(Repo.create_from_repodir, repo_dirs)
|
||||
repos = map(Repo.from_repodir, repo_dirs)
|
||||
|
||||
# 计算总项目数
|
||||
self.total = 0
|
||||
@@ -241,7 +263,7 @@ class PrecachingScreen(Screen):
|
||||
try:
|
||||
for i in repo.ident_index:
|
||||
nucleon_list.append(
|
||||
pt.Nucleon.create_on_nucleonic_data(
|
||||
pt.Nucleon.from_data(
|
||||
repo.nucleonic_data_lict.get_itemic_unit(i)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -5,7 +5,16 @@ from textual.containers import ScrollableContainer
|
||||
from textual.reactive import reactive
|
||||
from textual.screen import Screen
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Button, Footer, Header, Label, Markdown, Static, Rule, Sparkline
|
||||
from textual.widgets import (
|
||||
Button,
|
||||
Footer,
|
||||
Header,
|
||||
Label,
|
||||
Markdown,
|
||||
Static,
|
||||
Rule,
|
||||
Sparkline,
|
||||
)
|
||||
|
||||
import heurams.kernel.particles as pt
|
||||
import heurams.services.hasher as hasher
|
||||
@@ -28,20 +37,19 @@ class PreparationScreen(Screen):
|
||||
("0,1,2,3", "app.push_screen('about')", ""),
|
||||
]
|
||||
|
||||
scheduled_num = reactive(config_var.get()['interface']['global']["scheduled_num"])
|
||||
scheduled_num = reactive(config_var.get()["interface"]["global"]["scheduled_num"])
|
||||
|
||||
def __init__(self, repo: Repo, repostat: dict) -> None:
|
||||
def __init__(self, repo: Repo) -> None:
|
||||
super().__init__(name=None, id=None, classes=None)
|
||||
self.repo = repo
|
||||
self.repostat = repostat
|
||||
self.load_data()
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header(show_clock=True)
|
||||
with ScrollableContainer(id="vice_container"):
|
||||
yield Label(f"准备就绪: [b]{self.repostat['title']}[/b]\n")
|
||||
yield Label(f"准备就绪: [b]{self.repo.manifest['title']}[/b]\n")
|
||||
yield Label(
|
||||
f"仓库路径: {config_var.get()['global']['paths']['data']}/repo/[b]{self.repostat['dirname']}[/b]"
|
||||
f"[b]仓库路径: {self.repo.source}[/b]"
|
||||
)
|
||||
yield Label(f"\n单元数量: {len(self.repo)}\n")
|
||||
yield Label(f"最小记忆分组: {self.scheduled_num}\n", id="schnum_label")
|
||||
@@ -62,12 +70,12 @@ class PreparationScreen(Screen):
|
||||
yield Static()
|
||||
yield Sparkline(self.spark_line_arr, summary_function=max)
|
||||
yield Rule()
|
||||
#yield Static(str(self.spark_line_arr))
|
||||
# yield Static(str(self.spark_line_arr))
|
||||
yield Static(f"单元状态预览:\n")
|
||||
for i in self.content.splitlines():
|
||||
yield Static(i, classes="full")
|
||||
yield Footer()
|
||||
|
||||
|
||||
# def watch_scheduled_num(self, old_scheduled_num, new_scheduled_num):
|
||||
# logger.debug("响应", old_scheduled_num, "->", new_scheduled_num)
|
||||
# try:
|
||||
@@ -80,19 +88,21 @@ class PreparationScreen(Screen):
|
||||
content = ""
|
||||
spark_line_arr = []
|
||||
for i in self.repo.ident_index:
|
||||
n = pt.Nucleon.create_on_nucleonic_data(
|
||||
n = pt.Nucleon.from_data(
|
||||
nucleonic_data=self.repo.nucleonic_data_lict.get_itemic_unit(i)
|
||||
)
|
||||
e = pt.Electron.create_on_electonic_data(electronic_data=self.repo.electronic_data_lict.get_itemic_unit(i))
|
||||
e = pt.Electron.from_data(
|
||||
electronic_data=self.repo.electronic_data_lict.get_itemic_unit(i)
|
||||
)
|
||||
statstr = ""
|
||||
|
||||
if e.is_activated():
|
||||
statstr = '[#00ff00]A[/]'
|
||||
statstr = "[#00ff00]A[/]"
|
||||
if e.is_due():
|
||||
statstr = '[#ffff00]R[/]'
|
||||
#statstr += ('[dim]' + str(e.rept(real_rept=True)).zfill(2)+'[/]')
|
||||
statstr = "[#ffff00]R[/]"
|
||||
# statstr += ('[dim]' + str(e.rept(real_rept=True)).zfill(2)+'[/]')
|
||||
else:
|
||||
statstr = '[#ff0000]U[/]'
|
||||
statstr = "[#ff0000]U[/]"
|
||||
spark_line_arr.append(e.rept(real_rept=True))
|
||||
content += f" {statstr} {n['content'].replace('/', '')} \n"
|
||||
self.content = content
|
||||
@@ -107,9 +117,7 @@ class PreparationScreen(Screen):
|
||||
lst = list()
|
||||
for i in self.repo.ident_index:
|
||||
lst.append(
|
||||
pt.Nucleon.create_on_nucleonic_data(
|
||||
self.repo.nucleonic_data_lict.get_itemic_unit(i)
|
||||
)
|
||||
pt.Nucleon.from_data(self.repo.nucleonic_data_lict.get_itemic_unit(i))
|
||||
)
|
||||
precache_screen = PrecachingScreen(
|
||||
nucleons=lst, desc=self.repo.manifest["title"]
|
||||
@@ -128,15 +136,16 @@ class PreparationScreen(Screen):
|
||||
elif event.button.id == "precache_button":
|
||||
self.action_precache()
|
||||
|
||||
|
||||
def launch(repo, app, scheduled_num):
|
||||
if scheduled_num == -1:
|
||||
scheduled_num = config_var.get()['interface']['global']["scheduled_num"]
|
||||
scheduled_num = config_var.get()["interface"]["global"]["scheduled_num"]
|
||||
atoms = list()
|
||||
for i in repo.ident_index:
|
||||
n = pt.Nucleon.create_on_nucleonic_data(
|
||||
n = pt.Nucleon.from_data(
|
||||
nucleonic_data=repo.nucleonic_data_lict.get_itemic_unit(i)
|
||||
)
|
||||
e = pt.Electron.create_on_electonic_data(
|
||||
e = pt.Electron.from_data(
|
||||
electronic_data=repo.electronic_data_lict.get_itemic_unit(i)
|
||||
)
|
||||
a = pt.Atom(n, e, repo.orbitic_data)
|
||||
|
||||
@@ -8,7 +8,19 @@ import os
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import ScrollableContainer, Container, Horizontal, Vertical
|
||||
from textual.screen import Screen
|
||||
from textual.widgets import Button, Footer, Header, Label, ListItem, ListView, Static, Collapsible, Input, Switch, Select
|
||||
from textual.widgets import (
|
||||
Button,
|
||||
Footer,
|
||||
Header,
|
||||
Label,
|
||||
ListItem,
|
||||
ListView,
|
||||
Static,
|
||||
Collapsible,
|
||||
Input,
|
||||
Switch,
|
||||
Select,
|
||||
)
|
||||
from textual.layouts import horizontal
|
||||
|
||||
import heurams.kernel.particles as pt
|
||||
@@ -45,13 +57,15 @@ class SettingScreen(Screen):
|
||||
"""组合界面组件"""
|
||||
yield Header(show_clock=True)
|
||||
with ScrollableContainer():
|
||||
yield Label('[b]设置页面[/b]')
|
||||
yield Label("[b]设置页面[/b]")
|
||||
for i in config_var.get():
|
||||
if i.startswith('_'):
|
||||
if i.startswith("_"):
|
||||
continue
|
||||
a = self._get_subcfg(f'{i}')
|
||||
a = self._get_subcfg(f"{i}")
|
||||
if a:
|
||||
yield Collapsible(*a, title=i + f'\n{config_var.get().get(f"_{i}_desc", "")}')
|
||||
yield Collapsible(
|
||||
*a, title=i + f'\n{config_var.get().get(f"_{i}_desc", "")}'
|
||||
)
|
||||
yield Footer()
|
||||
|
||||
def _get_subcfg(self, parent_epath: str):
|
||||
@@ -60,61 +74,115 @@ class SettingScreen(Screen):
|
||||
if parent.is_dir:
|
||||
lst = list()
|
||||
for i in parent:
|
||||
if i.startswith('_'):
|
||||
if i.startswith("_"):
|
||||
continue
|
||||
a = self._get_subcfg(f"{parent_epath}.{i}")
|
||||
if a:
|
||||
lst.append(Collapsible(*a, title=i + f'\n{parent.get(f"_{i}_desc", "")}'))
|
||||
lst.append(
|
||||
Collapsible(
|
||||
*a, title=i + f'\n{parent.get(f"_{i}_desc", "")}'
|
||||
)
|
||||
)
|
||||
return lst
|
||||
if isinstance(parent, dict) or (isinstance(parent, ConfigDict) and not parent.is_dir):
|
||||
if isinstance(parent, dict) or (
|
||||
isinstance(parent, ConfigDict) and not parent.is_dir
|
||||
):
|
||||
lst = list()
|
||||
for i in parent:
|
||||
if i.startswith('_'):
|
||||
if i.startswith("_"):
|
||||
continue
|
||||
if isinstance(parent[i], dict):
|
||||
a = self._get_subcfg(f"{parent_epath}.{i}")
|
||||
if a:
|
||||
lst.append(Collapsible(*a, title=i + f'\n{parent.get(f"_{i}_desc", "")}'))
|
||||
elif f'_{i}_candidate' in parent: # 选择框模式
|
||||
if isinstance(parent[f'_{i}_candidate'], dict):
|
||||
lst.append(Horizontal(
|
||||
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
|
||||
Select(((f"{j} ({k})", j) for j, k in parent[f'_{i}_candidate'].items()), prompt=f'{parent.get(f"{i}", "")}', id=domize(f"{parent_epath}.{i}")),
|
||||
classes='container'
|
||||
))
|
||||
elif isinstance(parent[f'_{i}_candidate'], list):
|
||||
lst.append(Horizontal(
|
||||
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
|
||||
Select(((j, j) for j in parent[f'_{i}_candidate']), prompt=f'{parent.get(f"{i}", "")}', id=domize(f"{parent_epath}.{i}")),
|
||||
classes='container'
|
||||
))
|
||||
lst.append(
|
||||
Collapsible(
|
||||
*a, title=i + f'\n{parent.get(f"_{i}_desc", "")}'
|
||||
)
|
||||
)
|
||||
elif f"_{i}_candidate" in parent: # 选择框模式
|
||||
if isinstance(parent[f"_{i}_candidate"], dict):
|
||||
lst.append(
|
||||
Horizontal(
|
||||
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
|
||||
Select(
|
||||
(
|
||||
(f"{j} ({k})", j)
|
||||
for j, k in parent[f"_{i}_candidate"].items()
|
||||
),
|
||||
prompt=f'{parent.get(f"{i}", "")}',
|
||||
id=domize(f"{parent_epath}.{i}"),
|
||||
),
|
||||
classes="container",
|
||||
)
|
||||
)
|
||||
elif isinstance(parent[f"_{i}_candidate"], list):
|
||||
lst.append(
|
||||
Horizontal(
|
||||
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
|
||||
Select(
|
||||
((j, j) for j in parent[f"_{i}_candidate"]),
|
||||
prompt=f'{parent.get(f"{i}", "")}',
|
||||
id=domize(f"{parent_epath}.{i}"),
|
||||
),
|
||||
classes="container",
|
||||
)
|
||||
)
|
||||
else:
|
||||
if isinstance(parent[i], float):
|
||||
lst.append(Horizontal(
|
||||
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
|
||||
Input(value=str(parent[i]), placeholder='要求一个浮点数', type='number', id=domize(f"{parent_epath}.{i}")),
|
||||
classes='container'))
|
||||
lst.append(
|
||||
Horizontal(
|
||||
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
|
||||
Input(
|
||||
value=str(parent[i]),
|
||||
placeholder="要求一个浮点数",
|
||||
type="number",
|
||||
id=domize(f"{parent_epath}.{i}"),
|
||||
),
|
||||
classes="container",
|
||||
)
|
||||
)
|
||||
elif isinstance(parent[i], str):
|
||||
lst.append(Horizontal(
|
||||
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
|
||||
Input(value=parent[i], placeholder='要求一个字符串', type='text', id=domize(f"{parent_epath}.{i}")),
|
||||
classes='container'))
|
||||
lst.append(
|
||||
Horizontal(
|
||||
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
|
||||
Input(
|
||||
value=parent[i],
|
||||
placeholder="要求一个字符串",
|
||||
type="text",
|
||||
id=domize(f"{parent_epath}.{i}"),
|
||||
),
|
||||
classes="container",
|
||||
)
|
||||
)
|
||||
elif isinstance(parent[i], bool):
|
||||
lst.append(Horizontal(
|
||||
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
|
||||
Switch(value=parent[i], id=domize(f"{parent_epath}.{i}")),
|
||||
classes='container'))
|
||||
lst.append(
|
||||
Horizontal(
|
||||
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
|
||||
Switch(
|
||||
value=parent[i], id=domize(f"{parent_epath}.{i}")
|
||||
),
|
||||
classes="container",
|
||||
)
|
||||
)
|
||||
elif isinstance(parent[i], int):
|
||||
lst.append(Horizontal(
|
||||
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
|
||||
Input(value=str(parent[i]), placeholder='要求一个整数', type='integer', id=domize(f"{parent_epath}.{i}")),
|
||||
classes='container'))
|
||||
lst.append(
|
||||
Horizontal(
|
||||
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
|
||||
Input(
|
||||
value=str(parent[i]),
|
||||
placeholder="要求一个整数",
|
||||
type="integer",
|
||||
id=domize(f"{parent_epath}.{i}"),
|
||||
),
|
||||
classes="container",
|
||||
)
|
||||
)
|
||||
elif isinstance(parent[i], list):
|
||||
pass
|
||||
else:
|
||||
lst.append(Label('未知类型'))
|
||||
lst.append(Label("未知类型"))
|
||||
return lst
|
||||
return [Label('无子项')]
|
||||
return [Label("无子项")]
|
||||
|
||||
def on_mount(self) -> None:
|
||||
"""挂载组件时初始化"""
|
||||
@@ -133,14 +201,18 @@ class SettingScreen(Screen):
|
||||
"""打开导航器"""
|
||||
self.app.push_screen(NavigatorScreen())
|
||||
|
||||
|
||||
def on_input_changed(self, event: Input.Changed) -> None:
|
||||
widget_id = event.input.id
|
||||
if not widget_id:
|
||||
return
|
||||
eepath = undomize(widget_id)
|
||||
value = event.value
|
||||
epath(config_var.get(), eepath, enable_modify=True, new_value=type(epath(config_var.get(), eepath))(value))
|
||||
epath(
|
||||
config_var.get(),
|
||||
eepath,
|
||||
enable_modify=True,
|
||||
new_value=type(epath(config_var.get(), eepath))(value),
|
||||
)
|
||||
|
||||
def on_switch_changed(self, event: Switch.Changed) -> None:
|
||||
widget_id = event.switch.id
|
||||
@@ -148,7 +220,12 @@ class SettingScreen(Screen):
|
||||
return
|
||||
eepath = undomize(widget_id)
|
||||
value = event.value
|
||||
epath(config_var.get(), eepath, enable_modify=True, new_value=type(epath(config_var.get(), eepath))(value))
|
||||
epath(
|
||||
config_var.get(),
|
||||
eepath,
|
||||
enable_modify=True,
|
||||
new_value=type(epath(config_var.get(), eepath))(value),
|
||||
)
|
||||
|
||||
def on_select_changed(self, event: Select.Changed) -> None:
|
||||
widget_id = event.select.id
|
||||
@@ -156,4 +233,9 @@ class SettingScreen(Screen):
|
||||
return
|
||||
eepath = undomize(widget_id)
|
||||
value = event.value
|
||||
epath(config_var.get(), eepath, enable_modify=True, new_value=type(epath(config_var.get(), eepath))(value))
|
||||
epath(
|
||||
config_var.get(),
|
||||
eepath,
|
||||
enable_modify=True,
|
||||
new_value=type(epath(config_var.get(), eepath))(value),
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ class BasePuzzleWidget(Widget):
|
||||
id: str | None = None,
|
||||
classes: str | None = None,
|
||||
disabled: bool = False,
|
||||
markup: bool = True
|
||||
markup: bool = True,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
*children,
|
||||
@@ -23,7 +23,7 @@ class BasePuzzleWidget(Widget):
|
||||
id=id,
|
||||
classes=classes,
|
||||
disabled=disabled,
|
||||
markup=markup
|
||||
markup=markup,
|
||||
)
|
||||
self.atom = atom
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ class ClozePuzzle(BasePuzzleWidget):
|
||||
if lst:
|
||||
lastone = lst[-1]
|
||||
for i in lst[:-1]:
|
||||
s += (i + ' ')
|
||||
s += i + " "
|
||||
s += f" `{lastone}`"
|
||||
return s
|
||||
|
||||
|
||||
@@ -55,8 +55,8 @@ class MCQPuzzle(BasePuzzleWidget):
|
||||
|
||||
def _load(self):
|
||||
cfg = self.atom.registry["nucleon"]["puzzles"][self.alia]
|
||||
if cfg['mapping'] == {}:
|
||||
self.screen.rating = 5 # type: ignore
|
||||
if cfg["mapping"] == {}:
|
||||
self.screen.rating = 5 # type: ignore
|
||||
self.puzzle = pz.MCQPuzzle(
|
||||
cfg["mapping"], cfg["jammer"], int(cfg["max_riddles_num"]), cfg["prefix"]
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ class Placeholder(Widget):
|
||||
id: str | None = None,
|
||||
classes: str | None = None,
|
||||
disabled: bool = False,
|
||||
markup: bool = True
|
||||
markup: bool = True,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
*children,
|
||||
@@ -19,7 +19,7 @@ class Placeholder(Widget):
|
||||
id=id,
|
||||
classes=classes,
|
||||
disabled=disabled,
|
||||
markup=markup
|
||||
markup=markup,
|
||||
)
|
||||
|
||||
def compose(self):
|
||||
|
||||
@@ -67,7 +67,7 @@ class Recognition(BasePuzzleWidget):
|
||||
f";{delim}": ";",
|
||||
f":{delim}": ":",
|
||||
}
|
||||
|
||||
|
||||
primary = cfg["primary"]
|
||||
|
||||
with Center():
|
||||
@@ -88,7 +88,7 @@ class Recognition(BasePuzzleWidget):
|
||||
for item in cfg["secondary"]:
|
||||
if isinstance(item, list):
|
||||
for j in item:
|
||||
yield Markdown(f"### 笔记: {j}") #TODO ANNOTATION
|
||||
yield Markdown(f"### 笔记: {j}") # TODO ANNOTATION
|
||||
continue
|
||||
if isinstance(item, Dict):
|
||||
total = ""
|
||||
|
||||
Reference in New Issue
Block a user