feat(interface): 增加智能单元集排序

This commit is contained in:
2025-12-21 23:42:02 +08:00
parent 0fb421412e
commit 87cefedb61
2 changed files with 149 additions and 81 deletions

View File

@@ -4,8 +4,15 @@ import pathlib
from textual.app import ComposeResult from textual.app import ComposeResult
from textual.containers import ScrollableContainer from textual.containers import ScrollableContainer
from textual.screen import Screen from textual.screen import Screen
from textual.widgets import (Button, Footer, Header, Label, ListItem, ListView, from textual.widgets import (
Static) Button,
Footer,
Header,
Label,
ListItem,
ListView,
Static,
)
import heurams.services.timer as timer import heurams.services.timer as timer
import heurams.services.version as version import heurams.services.version as version
@@ -20,129 +27,190 @@ logger = get_logger(__name__)
class DashboardScreen(Screen): class DashboardScreen(Screen):
"""主仪表盘屏幕"""
SUB_TITLE = "仪表盘" SUB_TITLE = "仪表盘"
def __init__(
self,
name: str | None = None,
id: str | None = None,
classes: str | None = None,
) -> None:
super().__init__(name, id, classes)
self.nextdates = {}
self.texts = {}
self.stay_enabled = {}
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
"""组合界面组件"""
yield Header(show_clock=True) yield Header(show_clock=True)
yield ScrollableContainer( yield ScrollableContainer(
Label(f'欢迎使用 "潜进" 启发式辅助记忆调度器', classes="title-label"), Label('欢迎使用 "潜进" 启发式辅助记忆调度器', classes="title-label"),
Label(f"当前 UNIX 日时间戳: {timer.get_daystamp()}"), Label(f"当前 UNIX 日时间戳: {timer.get_daystamp()}"),
Label(f'时区修正: UTC+{config_var.get()["timezone_offset"] / 3600}'), Label(f'时区修正: UTC+{config_var.get()["timezone_offset"] / 3600}'),
Label(f"使用算法: {config_var.get()['algorithm']['default']}"), Label(f"使用算法: {config_var.get()['algorithm']['default']}"),
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( Label(
f'"潜进" 启发式辅助记忆调度器 | 版本 {version.ver} {version.codename.capitalize()} 2025' f'"潜进" 启发式辅助记忆调度器 | 版本 {version.ver} '
f'{version.codename.capitalize()} 2025'
), ),
) )
yield Footer() yield Footer()
def item_desc_generator(self, filename) -> dict: def analyser(self, filename: str) -> dict:
"""简单分析以生成项目项显示文本 """分析文件状态以生成显示文本
Args:
filename: 要分析的文件名
Returns: Returns:
dict: 以数字为列表, 分别呈现单行字符串 dict: 包含显示文本的字典,键为行号
""" """
res = dict() from heurams.kernel.particles.loader import load_electron, load_nucleon
result = {}
filestem = pathlib.Path(filename).stem filestem = pathlib.Path(filename).stem
res[0] = f"{filename}\0"
import heurams.kernel.particles as pt # 构建电子文件路径
from heurams.kernel.particles.loader import load_electron electron_dir = config_var.get()["paths"]["electron_dir"]
electron_file_path = pathlib.Path(electron_dir) / f"{filestem}.json"
electron_file_path = pathlib.Path(config_var.get()["paths"]["electron_dir"]) / (
filestem + ".json"
)
logger.debug(f"电子文件路径: {electron_file_path}") logger.debug(f"电子文件路径: {electron_file_path}")
if electron_file_path.exists(): # 未找到则创建电子文件 (json) # 确保电子文件存在
pass if not electron_file_path.exists():
else:
electron_file_path.touch() electron_file_path.touch()
with open(electron_file_path, "w") as f: electron_file_path.write_text("{}")
f.write("{}")
electron_dict = load_electron(path=electron_file_path) # TODO: 取消硬编码扩展名 # 加载电子数据
logger.debug(electron_dict) electron_dict = load_electron(path=electron_file_path)
logger.debug(f"电子数据: {electron_dict}")
# 分析电子状态
is_due = 0 is_due = 0
is_activated = 0 is_activated = 0
nextdate = 0x3F3F3F3F nextdate = 0x3F3F3F3F
for i in electron_dict.values():
i: pt.Electron for electron in electron_dict.values():
logger.debug(i, i.is_due()) logger.debug(f"{electron}, 是否到期: {electron.is_due()}")
if i.is_due():
if electron.is_due():
is_due = 1 is_due = 1
if i.is_activated(): if electron.is_activated():
is_activated = 1 is_activated = 1
nextdate = min(nextdate, i.nextdate()) nextdate = min(nextdate, electron.nextdate())
res[1] = f"下一次复习: {nextdate}\n"
res[1] += f"{"需要复习" if is_due else "当前无需复习"}" # 检查是否需要更多复习
nucleon_dir = config_var.get()["paths"]["nucleon_dir"]
nucleon_path = pathlib.Path(nucleon_dir) / f"{filestem}.toml"
nucleon_count = len(load_nucleon(nucleon_path))
electron_count = len(electron_dict)
is_more = not (electron_count >= nucleon_count)
logger.debug(f"是否需要更多复习: {is_more}")
# 更新状态
self.nextdates[filename] = nextdate
self.stay_enabled[filename] = (is_due or is_more)
# 构建返回结果
result[0] = f"{filename}\0"
if not is_activated: if not is_activated:
res[1] = " 尚未激活" result[1] = " 尚未激活"
return res
def on_mount(self) -> None:
union_list_widget = self.query_one("#union-list", ListView)
probe = probe_all(0)
if len(probe["nucleon"]):
for file in probe["nucleon"]:
text = self.item_desc_generator(file)
union_list_widget.append(
ListItem(
Label(text[0] + "\n" + text[1]),
)
)
else: else:
status_text = "需要复习" if is_due else "当前无需复习"
result[1] = f"下一次复习: {nextdate}\n{status_text}"
return result
def on_mount(self) -> None:
"""挂载组件时初始化"""
union_list_widget = self.query_one("#union-list", ListView)
probe = probe_all(0)
# 分析所有文件
for file in probe["nucleon"]:
self.texts[file] = self.analyser(file)
# 按下次复习时间排序
nucleon_files = sorted(
probe["nucleon"],
key=lambda f: self.nextdates[f],
reverse=True,
)
# 填充列表
if not probe["nucleon"]:
union_list_widget.append( union_list_widget.append(
ListItem( ListItem(
Static( Static(
"在 ./nucleon/ 中未找到任何内容源数据文件.\n请放置文件后重启应用.\n或者新建空的单元集." "在 ./nucleon/ 中未找到任何内容源数据文件\n"
"请放置文件后重启应用,或者新建空的单元集。"
) )
) )
) )
union_list_widget.disabled = True union_list_widget.disabled = True
return
for file in nucleon_files:
text = self.texts[file]
list_item = ListItem(
Label(f"{text[0]}\n{text[1]}")
)
union_list_widget.append(list_item)
if not self.stay_enabled[file]:
list_item.disabled = True
def on_list_view_selected(self, event) -> None: def on_list_view_selected(self, event) -> None:
"""处理列表项选择事件"""
if not isinstance(event.item, ListItem): if not isinstance(event.item, ListItem):
return return
selected_label = event.item.query_one(Label) selected_label = event.item.query_one(Label)
if "未找到任何 .toml 文件" in str(selected_label.renderable): # type: ignore label_text = str(selected_label.renderable)
if "未找到任何 .toml 文件" in label_text:
return return
# 提取文件名
selected_filename = pathlib.Path( selected_filename = pathlib.Path(
str(selected_label.renderable) label_text.partition("\0")[0].replace("*", "")
.partition("\0")[0] # 文件名末尾截断, 保留文件名
.replace("*", "")
) # 去除markdown加粗
nucleon_file_path = (
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" # 构建文件路径
nucleon_dir = config_var.get()["paths"]["nucleon_dir"]
electron_dir = config_var.get()["paths"]["electron_dir"]
nucleon_file_path = pathlib.Path(nucleon_dir) / selected_filename
electron_file_path = pathlib.Path(electron_dir) / f"{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": """处理按钮点击事件"""
# 切换到创建单元 button_id = event.button.id
if button_id == "new_nucleon_button":
from .nucreator import NucleonCreatorScreen from .nucreator import NucleonCreatorScreen
new_screen = NucleonCreatorScreen()
newscr = NucleonCreatorScreen() self.app.push_screen(new_screen)
self.app.push_screen(newscr)
elif event.button.id == "precache_all_button": elif 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":
from .about import AboutScreen elif button_id == "about_button":
about_screen = AboutScreen() about_screen = AboutScreen()
self.app.push_screen(about_screen) self.app.push_screen(about_screen)
def action_quit_app(self) -> None: def action_quit_app(self) -> None:
self.app.exit() """退出应用程序"""
self.app.exit()

View File

@@ -89,7 +89,7 @@ class TestDashboardScreenUnit(unittest.TestCase):
screen = DashboardScreen() screen = DashboardScreen()
# 模拟一个文件名 # 模拟一个文件名
filename = "test.toml" filename = "test.toml"
result = screen.item_desc_generator(filename) result = screen.analyser(filename)
self.assertIsInstance(result, dict) self.assertIsInstance(result, dict)
self.assertIn(0, result) self.assertIn(0, result)
self.assertIn(1, result) self.assertIn(1, result)