You've already forked HeurAMS-Classic
fix: 改进与日志简化
This commit is contained in:
@@ -1,7 +1,4 @@
|
||||
#print("欢迎使用 HeurAMS 及其组件!")
|
||||
|
||||
# 补充日志记录
|
||||
from heurams.services.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
logger.info("欢迎使用 HeurAMS 及其组件!")
|
||||
logger.info("HeurAMS is imported")
|
||||
|
||||
+74
-35
@@ -1,79 +1,118 @@
|
||||
import platform
|
||||
"""命令行入口
|
||||
"""
|
||||
|
||||
import platform
|
||||
import click
|
||||
from heurams.i18n import _, setup_locale
|
||||
from heurams.services.version import ver, stage, codename, codename_cn
|
||||
from heurams.services.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
def _apply_locale(ctx, param, value):
|
||||
"""立即显式应用 locale"""
|
||||
if value:
|
||||
setup_locale(value)
|
||||
return value
|
||||
|
||||
|
||||
class _I18nGroup(click.Group):
|
||||
"""在运行时完成翻译工作的命令组"""
|
||||
|
||||
_option_help_map: dict[str, str] = {
|
||||
"version": "Show the version and exit.",
|
||||
"locale": "Explicitly specify locale (defaults to LANG env).",
|
||||
"host": "Listening address",
|
||||
"port": "Listening port",
|
||||
"reload": "Development mode hot reload",
|
||||
}
|
||||
|
||||
def format_help(self, ctx, formatter):
|
||||
# 重新翻译每个子命令的帮助信息
|
||||
for cmd in self.commands.values():
|
||||
raw = getattr(cmd, "_raw_help", None)
|
||||
if raw is not None:
|
||||
cmd.help = _(raw)
|
||||
# Re-translate every option's help text by name lookup.
|
||||
for param in self.params:
|
||||
if isinstance(param, click.Option) and param.name in self._option_help_map:
|
||||
param.help = _(self._option_help_map[param.name])
|
||||
self.help = _("HeurAMS {ver} - Heuristic Auxiliary Memorizing Scheduler").format(
|
||||
ver=ver
|
||||
)
|
||||
return super().format_help(ctx, formatter)
|
||||
|
||||
|
||||
class _I18nCommand(click.Command):
|
||||
"""在运行时完成翻译工作的命令"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._raw_help = self.help or (self.short_help or "")
|
||||
|
||||
|
||||
@click.group(
|
||||
cls=_I18nGroup,
|
||||
invoke_without_command=True,
|
||||
help=(
|
||||
f"HeurAMS {ver} - 启发式辅助记忆调度器"
|
||||
),
|
||||
context_settings={"help_option_names": ["-h", "--help"]},
|
||||
)
|
||||
@click.version_option(
|
||||
ver, "-v", "--version",
|
||||
prog_name="HeurAMS",
|
||||
message=f"%(prog)s %(version)s {stage} ({codename}/{codename_cn}), {platform.system()}",
|
||||
help=_I18nGroup._option_help_map["version"],
|
||||
)
|
||||
@click.option(
|
||||
"--locale", "-l", default=None,
|
||||
callback=_apply_locale,
|
||||
is_eager=True,
|
||||
expose_value=True,
|
||||
help=_I18nGroup._option_help_map["locale"],
|
||||
)
|
||||
@click.pass_context
|
||||
def cli(ctx):
|
||||
def cli(ctx, locale):
|
||||
if ctx.invoked_subcommand is None:
|
||||
click.echo(cli.get_help(ctx))
|
||||
ctx.exit(0)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@cli.command(cls=_I18nCommand)
|
||||
def tui():
|
||||
"""启动内置基本用户界面 (TUI)"""
|
||||
"""Launch the built-in user interface (TUI)"""
|
||||
import heurams.interface.__main__ as tui_module
|
||||
|
||||
tui_module.main()
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("--host", default="127.0.0.1", help="监听地址")
|
||||
@click.option("--port", default=8821, help="监听端口", type=int)
|
||||
@click.option("--reload", is_flag=True, help="开发模式热重载")
|
||||
@cli.command(cls=_I18nCommand)
|
||||
@click.option("--host", default="127.0.0.1", help=_I18nGroup._option_help_map["host"])
|
||||
@click.option("--port", default=8821, help=_I18nGroup._option_help_map["port"], type=int)
|
||||
@click.option("--reload", is_flag=True, help=_I18nGroup._option_help_map["reload"])
|
||||
def serve(host, port, reload):
|
||||
"""启动 API 服务 (unifront)"""
|
||||
"""Launch the API service (unifront)"""
|
||||
from heurams.unifront.server import create_app
|
||||
|
||||
app = create_app()
|
||||
click.echo(f"unifront API 服务启动: http://{host}:{port}")
|
||||
click.echo(
|
||||
_("unifront API service started: http://{host}:{port}").format(
|
||||
host=host, port=port
|
||||
)
|
||||
)
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(app, host=host, port=port, reload=reload, log_level="info")
|
||||
|
||||
|
||||
def _print_version():
|
||||
click.echo(
|
||||
f"HeurAMS {ver} ({codename}/{codename_cn}), 阶段: {stage}"
|
||||
)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def version():
|
||||
"""输出版本信息"""
|
||||
_print_version()
|
||||
|
||||
|
||||
@cli.command(name="ver", hidden=True)
|
||||
def ver_cmd():
|
||||
"""输出版本信息"""
|
||||
_print_version()
|
||||
|
||||
|
||||
@cli.command(name="help")
|
||||
@cli.command(cls=_I18nCommand, name="help")
|
||||
@click.pass_context
|
||||
def help_cmd(ctx):
|
||||
"""显示此帮助信息"""
|
||||
"""Show this help message"""
|
||||
click.echo(cli.get_help(ctx.parent))
|
||||
|
||||
|
||||
def main():
|
||||
cli()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
logger.info("HeurAMS cmdline entrypoint invoked")
|
||||
main()
|
||||
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
全局上下文管理模块
|
||||
以及基准路径
|
||||
"""全局上下文模块
|
||||
|
||||
初始化并管理基准路径, 程序配置对象, 并提供调试所需上下文管理器
|
||||
"""
|
||||
|
||||
import pathlib
|
||||
@@ -19,13 +19,14 @@ workdir = pathlib.Path.cwd()
|
||||
"""工作目录路径."""
|
||||
|
||||
logger = get_logger(__name__)
|
||||
logger.debug(f"包目录: {rootdir}")
|
||||
logger.debug(f"工作目录: {workdir}")
|
||||
logger.info(f"rootdir: {rootdir}")
|
||||
logger.info(f"workdir: {workdir}")
|
||||
|
||||
default_data = rootdir / "assets" / "data"
|
||||
user_data = workdir / "data"
|
||||
|
||||
if not user_data.exists():
|
||||
logger.info("初始化数据目录: %s", user_data)
|
||||
logger.info("Create a new data directory: %s", user_data)
|
||||
import shutil
|
||||
|
||||
shutil.copytree(default_data, user_data)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
"""国际化支持模块
|
||||
|
||||
基于 GNU gettext, 并使用英文 (en_US) 作为 msgid 的基础语言.
|
||||
"""
|
||||
|
||||
import gettext
|
||||
import os
|
||||
from pathlib import Path
|
||||
import locale
|
||||
from heurams.context import rootdir
|
||||
|
||||
_LOCALE_DIR = rootdir / "locale"
|
||||
|
||||
_DEFAULT_LOCALE = "en_US"
|
||||
_translation = gettext.NullTranslations()
|
||||
|
||||
def setup_locale(_locale: str | None = None) -> None:
|
||||
"""获得翻译项.
|
||||
|
||||
如果没有找到对应的 locale, 就回退到原始 msgid.
|
||||
"""
|
||||
global _translation
|
||||
if _locale is None:
|
||||
_locale = locale.getlocale()[0]
|
||||
try:
|
||||
_translation = gettext.translation(
|
||||
"heurams", localedir=str(_LOCALE_DIR), languages=[_locale], fallback=True
|
||||
)
|
||||
except FileNotFoundError:
|
||||
_translation = gettext.NullTranslations()
|
||||
|
||||
|
||||
def _(message: str) -> str:
|
||||
"""执行翻译的函数"""
|
||||
return _translation.gettext(message)
|
||||
|
||||
# 导入时初始化
|
||||
setup_locale()
|
||||
@@ -2,52 +2,53 @@ from time import sleep, perf_counter
|
||||
|
||||
# import gc
|
||||
# gc.set_threshold(100, 1, 1)
|
||||
print("欢迎使用基本用户界面!")
|
||||
print("加载配置与上下文... ", end="", flush=True)
|
||||
from heurams.i18n import _
|
||||
|
||||
print(_("Welcome to the basic user interface!"))
|
||||
print(_("Loading config and context... "), end="", flush=True)
|
||||
_start_all = perf_counter()
|
||||
_start = _start_all
|
||||
from heurams.context import rootdir, workdir, config_var
|
||||
|
||||
_end = perf_counter()
|
||||
print(f"已完成! (耗时: {round(1000 * (_end - _start))}ms)")
|
||||
print(_("Done! ({time}ms)").format(time=round(1000 * (_end - _start))))
|
||||
|
||||
print("加载用户界面框架... ", end="", flush=True)
|
||||
print(_("Loading UI framework... "), 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)")
|
||||
print(_("Done! ({time}ms)").format(time=round(1000 * (_end - _start))))
|
||||
|
||||
print("加载用户界面布局... ", end="", flush=True)
|
||||
print(_("Loading UI layout... "), end="", flush=True)
|
||||
_start = perf_counter()
|
||||
from .screens.about import AboutScreen
|
||||
from .screens.dashboard import DashboardScreen
|
||||
from .screens.navigator import NavigatorScreen
|
||||
from .screens.precache import PrecachingScreen
|
||||
from .screens.setting import SettingScreen
|
||||
from .screens.synctool import SyncScreen
|
||||
from .screens.about import AboutScreen
|
||||
from . import shim
|
||||
|
||||
_end = perf_counter()
|
||||
print(f"已完成! (耗时: {round(1000 * (_end - _start))}ms)")
|
||||
print(_("Done! ({time}ms)").format(time=round(1000 * (_end - _start))))
|
||||
|
||||
print(f"组件目录: {rootdir}")
|
||||
print(f"工作目录: {workdir}")
|
||||
print(_("Component directory: {path}").format(path=rootdir))
|
||||
print(_("Working directory: {path}").format(path=workdir))
|
||||
_end_all = perf_counter()
|
||||
print(f"前置工作共计耗时: {round(1000 * (_end_all - _start_all))}ms")
|
||||
print(_("Pre-work total: {time}ms").format(time=round(1000 * (_end_all - _start_all))))
|
||||
|
||||
|
||||
class HeurAMSApp(App):
|
||||
TITLE = "潜进"
|
||||
TITLE = "HeurAMS"
|
||||
CSS_PATH = rootdir / "interface" / "css" / "main.tcss"
|
||||
SUB_TITLE = "启发式辅助记忆调度器"
|
||||
SUB_TITLE = _("Heuristic Auxiliary Memorizing Scheduler")
|
||||
BINDINGS = [
|
||||
("q", "go_back", "退出"),
|
||||
("d", "toggle_dark", "主题"),
|
||||
("n", "app.push_screen('navigator')", "导航"),
|
||||
("s", "app.push_screen('setting')", "设置"),
|
||||
("z", "app.push_screen('about')", "关于"),
|
||||
("q", "go_back", _("Quit")),
|
||||
("d", "toggle_dark", _("Theme")),
|
||||
("n", "app.push_screen('navigator')", _("Navigate")),
|
||||
("s", "app.push_screen('setting')", _("Settings")),
|
||||
("z", "app.push_screen('about')", _("About")),
|
||||
]
|
||||
SCREENS = {
|
||||
"dashboard": DashboardScreen,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from heurams.interface import *
|
||||
from heurams.context import config_var
|
||||
from heurams.i18n import _
|
||||
from heurams.services.logger import get_logger
|
||||
import threading
|
||||
import pickle
|
||||
@@ -21,7 +22,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 += _(" [Debug Connected]")
|
||||
first = 0
|
||||
try:
|
||||
# 先尝试 eval
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""关于界面"""
|
||||
"""About screen"""
|
||||
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import ScrollableContainer
|
||||
@@ -8,6 +8,7 @@ from textual import events, on
|
||||
|
||||
import heurams.services.version as version
|
||||
from heurams.context import *
|
||||
from heurams.i18n import _
|
||||
import platform
|
||||
import shutil
|
||||
import os
|
||||
@@ -16,10 +17,10 @@ import sys
|
||||
|
||||
class AboutScreen(Screen):
|
||||
BINDINGS = [
|
||||
("q", "go_back", "返回"),
|
||||
("z", "go_back", "关于"),
|
||||
("q", "go_back", _("Back")),
|
||||
("z", "go_back", _("About")),
|
||||
]
|
||||
SUB_TITLE = "关于"
|
||||
SUB_TITLE = _("About")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -37,55 +38,64 @@ class AboutScreen(Screen):
|
||||
show_clock=config_var.get()["interface"]["global"]["clock_on_header"]
|
||||
)
|
||||
with ScrollableContainer(id="about_container"):
|
||||
yield Label("[b]关于与版本信息[/b]")
|
||||
# 获取系统信息
|
||||
yield Label(_("[b]About & Version Info[/b]"))
|
||||
# Get system info
|
||||
textual_version = self._get_textual_version()
|
||||
terminal_info = self._get_terminal_info()
|
||||
python_version = self._get_python_version()
|
||||
os_version = self._get_os_version()
|
||||
disk_usage = self._get_disk_usage()
|
||||
|
||||
about_text = f"""
|
||||
# 关于 HeurAMS "潜进"
|
||||
about_text = _(
|
||||
"""# About HeurAMS
|
||||
|
||||
主程序库版本: `{version.ver}-python`
|
||||
用户界面分支: `Textual TUI (基本用户界面)`
|
||||
用户界面版本: `{version.ver}`
|
||||
API 版本代号: `{version.codename.capitalize()}`
|
||||
Main library version: `{ver}-python`
|
||||
UI frontend: `Textual TUI (Basic UI)`
|
||||
UI version: `{ver}`
|
||||
API codename: `{codename}`
|
||||
|
||||
> 一个基于启发式算法与认知科学理论的辅助记忆调度器, 旨在帮助用户更高效地进行记忆工作与学习规划.
|
||||
> 一个开放, 优雅, 易于扩展的间隔重复调度器实验平台, 旨在帮助研究者更高效地进行前沿记忆算法的研究.
|
||||
> A heuristic auxiliary memorizing scheduler based on heuristic algorithms and cognitive science theories, designed to help users memorize and plan learning more efficiently.
|
||||
> An open, elegant, and extensible spaced repetition scheduler experimental platform, designed to help researchers conduct investigations, experiments, and research on cutting-edge memory algorithms more efficiently.
|
||||
|
||||
您可在项目主页 https://ams.pluv27.top 获取用户指南, 开发文档与软件更新, 并参与到软件的开发与改进工作.
|
||||
You can visit the project homepage at https://ams.pluv27.top for user guides, development documentation and software updates, and participate in software development and improvement.
|
||||
|
||||
以 GNU Affero 通用公共许可证 (第3版) 开放源代码, 并有一条豁免本机 API 调用的附加条款, 用于其他前端到程序库的接口调用.
|
||||
Open source under the GNU Affero General Public License (version 3), with an additional exemption clause for local API calls, used for other frontend to library interface calls.
|
||||
|
||||
您正使用程序库内置的终端用户界面, 它是第一个全功能前端实现与程序库测试套件, 位于程序库的 interface 子目录.
|
||||
You are using the built-in terminal user interface, which is the first full-featured frontend implementation and library test suite, located in the interface subdirectory of the library.
|
||||
|
||||
开发人员列表:
|
||||
- Wang Zhiyu ([@pluvium27](https://github.com/pluvium27)): 项目发起与主要开发者
|
||||
Developers:
|
||||
- Wang Zhiyu ([@pluvium27](https://github.com/pluvium27)): Project initiator and lead developer
|
||||
|
||||
感谢以下人士与团体, 他们的算法与理论构成了此软件现有算法的基石:
|
||||
Special thanks to the following individuals and groups; their algorithms and theories form the cornerstone of the current software algorithms:
|
||||
|
||||
- [Piotr A. Woźniak](https://supermemo.guru/wiki/Piotr_Wozniak): SM-2 算法与 SM-15 算法理论
|
||||
- [Jarrett Ye](https://github.com/L-M-Sherlock): FSRS 算法与间隔重复理论文献参考
|
||||
- [Kazuaki Tanida](https://github.com/slaypni): SM-15 算法的 CoffeeScript 逆向实现
|
||||
- [Open Spaced Repetition](https://github.com/open-spaced-repetition): FSRS 算法底层实现
|
||||
- [Piotr A. Woźniak](https://supermemo.guru/wiki/Piotr_Wozniak): SM-2 algorithm and SM-15 algorithm theory
|
||||
- [Jarrett Ye](https://github.com/L-M-Sherlock): FSRS algorithm and spaced repetition theory references
|
||||
- [Kazuaki Tanida](https://github.com/slaypni): CoffeeScript reverse implementation of SM-15 algorithm
|
||||
- [Open Spaced Repetition](https://github.com/open-spaced-repetition): FSRS algorithm underlying implementation
|
||||
|
||||
# 运行环境信息
|
||||
# Runtime Environment
|
||||
|
||||
Python 解释器版本: {python_version}
|
||||
Python 解释器路径: {sys.executable}
|
||||
Textual 框架版本: {textual_version}
|
||||
终端模拟器: {terminal_info}
|
||||
操作系统版本: {os_version}
|
||||
存储余量: {disk_usage}
|
||||
Python interpreter version: {python_version}
|
||||
Python interpreter path: {executable}
|
||||
Textual framework version: {textual_version}
|
||||
Terminal emulator: {terminal_info}
|
||||
Operating system version: {os_version}
|
||||
Disk free space: {disk_usage}
|
||||
|
||||
报告问题时, 请复制这些信息到问题描述, 并上传软件日志 `heurams.log` 作为附件, 以协助开发者定位错误
|
||||
"""
|
||||
When reporting issues, please copy this information into the issue description and attach `heurams.log` as an attachment to help developers locate the error."""
|
||||
).format(
|
||||
ver=version.ver,
|
||||
codename=version.codename.capitalize(),
|
||||
python_version=python_version,
|
||||
executable=sys.executable,
|
||||
textual_version=textual_version,
|
||||
terminal_info=terminal_info,
|
||||
os_version=os_version,
|
||||
disk_usage=disk_usage,
|
||||
)
|
||||
yield Markdown(about_text, classes="about-markdown")
|
||||
yield Button(
|
||||
"返回主界面",
|
||||
_("Back to Main"),
|
||||
id="back_button",
|
||||
variant="primary",
|
||||
flat=True,
|
||||
@@ -102,22 +112,20 @@ Textual 框架版本: {textual_version}
|
||||
self.action_go_back()
|
||||
|
||||
def _get_textual_version(self) -> str:
|
||||
"""获取 Textual 框架版本"""
|
||||
try:
|
||||
import textual
|
||||
|
||||
return textual.__version__
|
||||
except (ImportError, AttributeError):
|
||||
return "未知"
|
||||
return _("Unknown")
|
||||
|
||||
def _get_terminal_info(self) -> str:
|
||||
"""获取终端模拟器信息"""
|
||||
terminal = shutil.which("terminal")
|
||||
if terminal:
|
||||
return terminal
|
||||
# 尝试从环境变量获取
|
||||
# Try from environment variables
|
||||
terminal_env = os.environ.get("TERM_PROGRAM") or os.environ.get("TERM")
|
||||
return terminal_env or "未知"
|
||||
return terminal_env or _("Unknown")
|
||||
|
||||
def _get_python_version(self) -> str:
|
||||
"""获取 Python 解释器版本"""
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""仪表盘界面"""
|
||||
"""Dashboard screen"""
|
||||
|
||||
from functools import reduce
|
||||
from pathlib import Path
|
||||
@@ -14,6 +14,7 @@ import heurams.kernel.particles as pt
|
||||
import heurams.services.timer as timer
|
||||
import heurams.services.version as version
|
||||
from heurams.context import *
|
||||
from heurams.i18n import _
|
||||
from heurams.kernel.particles import *
|
||||
from heurams.kernel.repolib import *
|
||||
from heurams.services.logger import get_logger
|
||||
@@ -25,11 +26,11 @@ logger = get_logger(__name__)
|
||||
|
||||
|
||||
class DashboardScreen(Screen):
|
||||
"""主仪表盘屏幕"""
|
||||
"""Main dashboard screen"""
|
||||
|
||||
SUB_TITLE = "仪表盘"
|
||||
SUB_TITLE = _("Dashboard")
|
||||
BINDINGS = [
|
||||
("q", "go_back", "返回"),
|
||||
("q", "go_back", _("Back")),
|
||||
]
|
||||
|
||||
CSS_PATH = rootdir / "interface" / "css" / "screens" / "dashboard.tcss"
|
||||
@@ -46,45 +47,50 @@ class DashboardScreen(Screen):
|
||||
self._load_data()
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""组合界面组件"""
|
||||
"""Compose UI components"""
|
||||
if config_var.get()["interface"]["global"]["show_header"]:
|
||||
yield Header(
|
||||
show_clock=config_var.get()["interface"]["global"]["clock_on_header"]
|
||||
)
|
||||
with ScrollableContainer():
|
||||
yield Horizontal( # 顶部的状态
|
||||
yield Horizontal(
|
||||
Vertical(
|
||||
Label(f"当前日时间戳: {timer.get_daystamp()}"),
|
||||
Label(_("Current daystamp: {ds}").format(ds=timer.get_daystamp())),
|
||||
Label(
|
||||
f"应用时区修正: UTC+{str(config_var.get()['services']['timer']['timezone_offset'] / 3600).removesuffix('.0')}"
|
||||
_("Timezone offset: UTC+{offset}").format(offset=str(config_var.get()['services']['timer']['timezone_offset'] / 3600).removesuffix('.0'))
|
||||
),
|
||||
Label(
|
||||
f"默认算法设置: {config_var.get()['interface']['global']['algorithm']}",
|
||||
_("Default algorithm: {algo}").format(algo=config_var.get()['interface']['global']['algorithm']),
|
||||
),
|
||||
classes="left",
|
||||
),
|
||||
Vertical(
|
||||
Label(f"已加载 {len(self.repos)} 个单元集"),
|
||||
Label(_("Loaded {n} repo(s)").format(n=len(self.repos))),
|
||||
Label(
|
||||
f"共计 {reduce(lambda x, y: x + y, map(lambda x: x.progress['total'], self.repos)) if self.repos else 0} 个单元"
|
||||
_("Total {n} unit(s)").format(n=reduce(lambda x, y: x + y, map(lambda x: x.progress['total'], self.repos)) if self.repos else 0)
|
||||
),
|
||||
Label(
|
||||
f"已激活 {reduce(lambda x, y: x + y, map(lambda x: x.progress['touched'], self.repos)) if self.repos else 0} 个单元"
|
||||
_("Activated {n} unit(s)").format(n=reduce(lambda x, y: x + y, map(lambda x: x.progress['touched'], self.repos)) if self.repos else 0)
|
||||
),
|
||||
Label(f""),
|
||||
classes="right",
|
||||
),
|
||||
id="header",
|
||||
)
|
||||
yield ListView(id="repo_list", classes="repo-list") # 单元集选择
|
||||
yield ListView(id="repo_list", classes="repo-list")
|
||||
from heurams.services.attic import Attic
|
||||
|
||||
a = Attic("ana", {"totaltime": 0, "openpuzzles": 0, "puzzles_err": 0})
|
||||
yield Label(f"版本 {version.ver}-{version.stage}") # 版本信息
|
||||
yield Label(_("Version {ver}-{stage}").format(ver=version.ver, stage=version.stage))
|
||||
yield Label(
|
||||
f"在 {round(a.data['totaltime'], 2)} 秒内处理了 {a.data['openpuzzles']} 个谜题, 正确率{'无法求解' if not a.data['openpuzzles'] else ' ' + str(round(100 * (1 - a.data['puzzles_err']/a.data['openpuzzles']), 2)) + '%'}, 平均速度{'无法求解' if not a.data['totaltime'] else ' ' + str(round(a.data['openpuzzles']/a.data['totaltime'], 2)) + ' 个每秒'}",
|
||||
_("Processed {puzzles} puzzles in {time}s, accuracy {accuracy}, speed {speed} puzzle(s)/s").format(
|
||||
puzzles=a.data['openpuzzles'],
|
||||
time=round(a.data['totaltime'], 2),
|
||||
accuracy=_("N/A") if not a.data['openpuzzles'] else str(round(100 * (1 - a.data['puzzles_err']/a.data['openpuzzles']), 2)) + '%',
|
||||
speed=_("N/A") if not a.data['totaltime'] else str(round(a.data['openpuzzles']/a.data['totaltime'], 2)),
|
||||
),
|
||||
id="analysis",
|
||||
) # 版本信息
|
||||
) # Version info
|
||||
yield Footer()
|
||||
|
||||
@on(events.ScreenResume)
|
||||
@@ -140,9 +146,28 @@ class DashboardScreen(Screen):
|
||||
repo.preview["review"] += 1
|
||||
# initial_time = min(initial_time, e.)
|
||||
repo.need_review = timer.get_daystamp() >= repo.nearest_review_time
|
||||
repo.prompt = f"""{repo.manifest['title']} \\[{repo.config['algorithm']}]
|
||||
[d]进度: {repo.progress['touched']}/{repo.progress['total']} ({round(repo.progress['touched']/repo.progress['total']*100, 1)}%)[/d]
|
||||
[d]{f'需要学习: {repo.preview['review']}R + {repo.preview['new']}U' if repo.need_review else (f"暂未开始: 0R + {repo.preview['new']}U" if not repo.progress['have_activated_ever'] else '无需操作')}[/d]"""
|
||||
repo.prompt = _(
|
||||
"""{title} [{algo}]
|
||||
[d]Progress: {touched}/{total} ({pct}%)[/d]
|
||||
[d]{status}[/d]"""
|
||||
).format(
|
||||
title=repo.manifest["title"],
|
||||
algo=repo.config["algorithm"],
|
||||
touched=repo.progress["touched"],
|
||||
total=repo.progress["total"],
|
||||
pct=round(repo.progress["touched"] / repo.progress["total"] * 100, 1),
|
||||
status=(
|
||||
_("Due: {review}R + {new}U").format(
|
||||
review=repo.preview["review"], new=repo.preview["new"]
|
||||
)
|
||||
if repo.need_review
|
||||
else (
|
||||
_("Not started: 0R + {new}U").format(new=repo.preview["new"])
|
||||
if not repo.progress["have_activated_ever"]
|
||||
else _("Up to date")
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
def on_mount(self) -> None:
|
||||
"""挂载组件时初始化"""
|
||||
@@ -160,8 +185,7 @@ class DashboardScreen(Screen):
|
||||
repo_list_widget.append(
|
||||
ListItem(
|
||||
Static(
|
||||
f"在 {config_var.get()['global']['paths']['repo']} 中未找到任何单元集仓库目录.\n"
|
||||
"请导入单元集后重启应用, 或者新建单元集."
|
||||
_("No repo directories found in {path}.\nPlease import a repo and restart, or create a new one.").format(path=config_var.get()['global']['paths']['repo'])
|
||||
),
|
||||
id="not-found",
|
||||
)
|
||||
@@ -175,7 +199,7 @@ class DashboardScreen(Screen):
|
||||
list_item = ListItem(
|
||||
*[Label(line) for line in r.prompt.splitlines()],
|
||||
Button(
|
||||
f"开始学习",
|
||||
_("Start Learning"),
|
||||
flat=True,
|
||||
variant="primary",
|
||||
id=f"slaunch_repo_{r.manifest['package']}",
|
||||
@@ -210,7 +234,6 @@ class DashboardScreen(Screen):
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
"""处理按钮点击事件"""
|
||||
logger.debug(f"event.button.id: {event.button.id}")
|
||||
if event.button.id.startswith("slaunch_repo_"): # type: ignore
|
||||
from .preparation import launch
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""收藏夹管理器界面"""
|
||||
"""Favorites manager screen"""
|
||||
|
||||
import base64
|
||||
from pathlib import Path
|
||||
@@ -21,6 +21,7 @@ from textual.widgets import (
|
||||
|
||||
from textual import events, on
|
||||
from heurams.context import config_var
|
||||
from heurams.i18n import _
|
||||
from heurams.kernel.repolib import Repo
|
||||
from heurams.services.favorite_service import FavoriteItem, favorite_manager
|
||||
from heurams.services.logger import get_logger
|
||||
@@ -29,12 +30,12 @@ logger = get_logger(__name__)
|
||||
|
||||
|
||||
class FavoriteManagerScreen(Screen):
|
||||
"""收藏夹管理器屏幕"""
|
||||
"""Favorites manager screen"""
|
||||
|
||||
SUB_TITLE = "收藏夹"
|
||||
SUB_TITLE = _("Favorites")
|
||||
|
||||
BINDINGS = [
|
||||
("q", "go_back", "返回"),
|
||||
("q", "go_back", _("Back")),
|
||||
("d", "toggle_dark", ""),
|
||||
]
|
||||
|
||||
@@ -49,12 +50,12 @@ class FavoriteManagerScreen(Screen):
|
||||
self._load_favorites()
|
||||
|
||||
def _load_favorites(self) -> None:
|
||||
"""加载收藏列表"""
|
||||
"""Load favorites list"""
|
||||
self.favorites = favorite_manager.get_all()
|
||||
logger.debug("加载 %d 个收藏项", len(self.favorites))
|
||||
logger.info("Loaded %d favorites", len(self.favorites))
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""组合界面组件"""
|
||||
"""Compose UI components"""
|
||||
|
||||
if config_var.get()["interface"]["global"]["show_header"]:
|
||||
yield Header(
|
||||
@@ -62,10 +63,10 @@ class FavoriteManagerScreen(Screen):
|
||||
)
|
||||
with ScrollableContainer(id="favorites-container"):
|
||||
if not self.favorites:
|
||||
yield Label("暂无收藏", classes="empty-label")
|
||||
yield Static("使用 * 键在记忆界面中添加收藏.")
|
||||
yield Label(_("No favorites"), classes="empty-label")
|
||||
yield Static(_("Press * in the memorization screen to add favorites."))
|
||||
else:
|
||||
yield Label(f"共 {len(self.favorites)} 个收藏项", classes="count-label")
|
||||
yield Label(_("Total {n} favorite(s)").format(n=len(self.favorites)), classes="count-label")
|
||||
yield ListView(id="favorites-list")
|
||||
yield Footer()
|
||||
|
||||
@@ -83,41 +84,41 @@ class FavoriteManagerScreen(Screen):
|
||||
list_view.append(self._create_favorite_item(fav)) # type: ignore
|
||||
|
||||
def _encode_favorite_key(self, repo_path: str, ident: str) -> str:
|
||||
"""编码仓库路径和标识符为安全的按钮 ID 部分"""
|
||||
# 使用 \x00 分隔两部分, 然后进行 base64 编码
|
||||
"""Encode repo path and identifier into a safe button ID part"""
|
||||
# Use \x00 as separator between the two parts, then base64 encode
|
||||
combined = f"{repo_path}\x00{ident}"
|
||||
encoded = base64.urlsafe_b64encode(combined.encode()).decode()
|
||||
# 去掉填充的等号
|
||||
# Strip padding
|
||||
return encoded.rstrip("=")
|
||||
|
||||
def _decode_favorite_key(self, key: str) -> tuple[str, str]:
|
||||
"""解码按钮 ID 部分为仓库路径和标识符"""
|
||||
# 补全等号以使长度是4的倍数
|
||||
"""Decode button ID part back to repo path and identifier"""
|
||||
# Pad to make length multiple of 4
|
||||
padded = key + "=" * ((4 - len(key) % 4) % 4)
|
||||
decoded = base64.urlsafe_b64decode(padded.encode()).decode()
|
||||
repo_path, ident = decoded.split("\x00", 1)
|
||||
return repo_path, ident
|
||||
|
||||
def _create_favorite_item(self, fav: FavoriteItem) -> ListItem:
|
||||
"""创建收藏项列表项"""
|
||||
# 尝试获取仓库信息
|
||||
"""Create a favorite list item"""
|
||||
# Try to get repo info
|
||||
repo_info = self._get_repo_info(fav.repo_path, fav)
|
||||
title = repo_info.get("title", fav.repo_path) if repo_info else fav.repo_path
|
||||
added_time = self._format_time(fav.added)
|
||||
|
||||
# 构建显示文本
|
||||
# Build display text
|
||||
display_text = f"{fav.ident}\n"
|
||||
display_text += f" [d]添加于: {added_time}\n 来自 {title}[/d]"
|
||||
display_text += _(" [d]Added: {time}\n From {title}[/d]").format(time=added_time, title=title)
|
||||
if fav.tags:
|
||||
display_text += f"{', '.join(fav.tags)}"
|
||||
|
||||
# 创建安全的按钮 ID
|
||||
# Create safe button ID
|
||||
button_key = self._encode_favorite_key(fav.repo_path, fav.ident)
|
||||
# 创建列表项, 包含移除按钮
|
||||
# Create list item with remove button
|
||||
container = Horizontal(
|
||||
Label(display_text, classes="favorite-content"),
|
||||
Button(
|
||||
"移除",
|
||||
_("Remove"),
|
||||
id=f"remove-{button_key}",
|
||||
variant="error",
|
||||
flat=True,
|
||||
@@ -128,21 +129,21 @@ class FavoriteManagerScreen(Screen):
|
||||
return ListItem(container)
|
||||
|
||||
def _get_repo_info(self, repo_path: str, fav: FavoriteItem) -> Optional[dict]:
|
||||
"""获取仓库信息(标题、原子内容预览)"""
|
||||
"""Get repo info (title, atom content preview)"""
|
||||
try:
|
||||
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)
|
||||
logger.warning("Repo directory does not exist: %s", repo_dir)
|
||||
return None
|
||||
repo = Repo.from_repodir(repo_dir)
|
||||
# 获取原子内容预览
|
||||
# Get atom content preview
|
||||
content_preview = ""
|
||||
payload = repo.payload
|
||||
# 查找对应 ident 的 payload 条目
|
||||
# Find the payload entry matching ident
|
||||
for ident_key, content in payload:
|
||||
if ident_key == fav.ident:
|
||||
# 截断过长的内容
|
||||
# Truncate long content
|
||||
if isinstance(content, dict) and "content" in content:
|
||||
text = content["content"]
|
||||
else:
|
||||
@@ -157,53 +158,53 @@ class FavoriteManagerScreen(Screen):
|
||||
"content_preview": content_preview,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error("获取仓库信息失败: %s", e)
|
||||
logger.error("Failed to get repo info: %s", e)
|
||||
return None
|
||||
|
||||
def _format_time(self, timestamp: int) -> str:
|
||||
"""格式化时间戳"""
|
||||
"""Format timestamp as datetime string"""
|
||||
from datetime import datetime
|
||||
|
||||
dt = datetime.fromtimestamp(timestamp)
|
||||
return dt.strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
"""处理按钮点击事件"""
|
||||
"""Handle button press event"""
|
||||
button_id = event.button.id
|
||||
if button_id and button_id.startswith("remove-"):
|
||||
# 提取编码后的键
|
||||
key = button_id[7:] # 去掉 "remove-" 前缀
|
||||
# Extract encoded key
|
||||
key = button_id[7:] # Remove "remove-" prefix
|
||||
try:
|
||||
repo_path, ident = self._decode_favorite_key(key)
|
||||
self._remove_favorite(repo_path, ident)
|
||||
except Exception as e:
|
||||
logger.error("解析按钮 ID 失败: %s", e)
|
||||
self.app.notify("操作失败: 无效的按钮标识", severity="error")
|
||||
logger.error("Failed to parse button ID: %s", e)
|
||||
self.app.notify(_("Operation failed: invalid button identifier"), severity="error")
|
||||
|
||||
def _remove_favorite(self, repo_path: str, ident: str) -> None:
|
||||
"""移除收藏项"""
|
||||
"""Remove a favorite item"""
|
||||
if favorite_manager.remove(repo_path, ident):
|
||||
self.app.notify(f"已移除收藏: {ident}", severity="information")
|
||||
# 重新加载列表
|
||||
self.app.notify(_("Removed favorite: {ident}").format(ident=ident), severity="information")
|
||||
# Reload list
|
||||
self._load_favorites()
|
||||
# 刷新界面
|
||||
# Refresh UI
|
||||
self._refresh_list()
|
||||
else:
|
||||
self.app.notify(f"移除失败: {ident}", severity="error")
|
||||
self.app.notify(_("Failed to remove: {ident}").format(ident=ident), severity="error")
|
||||
|
||||
def _refresh_list(self) -> None:
|
||||
"""刷新列表显示"""
|
||||
"""Refresh the list display"""
|
||||
container = self.query_one("#favorites-container")
|
||||
# 清空容器
|
||||
# Clear container
|
||||
for child in container.children:
|
||||
child.remove()
|
||||
# 重新组合
|
||||
# Re-compose
|
||||
if not self.favorites:
|
||||
container.mount(Label("暂无收藏", classes="empty-label"))
|
||||
container.mount(Static("使用 * 键在记忆界面中添加收藏。"))
|
||||
container.mount(Label(_("No favorites"), classes="empty-label"))
|
||||
container.mount(Static(_("Press * in the memorization screen to add favorites.")))
|
||||
else:
|
||||
container.mount(
|
||||
Label(f"共 {len(self.favorites)} 个收藏项", classes="count-label")
|
||||
Label(_("Total {n} favorite(s)").format(n=len(self.favorites)), classes="count-label")
|
||||
)
|
||||
list_view = ListView(id="favorites-list")
|
||||
container.mount(list_view)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""队列式记忆工作界面"""
|
||||
"""Queue-based memorization screen"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
@@ -12,6 +12,7 @@ from textual import events, on
|
||||
|
||||
import heurams.kernel.particles as pt
|
||||
from heurams.context import config_var, rootdir
|
||||
from heurams.i18n import _
|
||||
from heurams.kernel.reactor import *
|
||||
from heurams.services.favorite_service import favorite_manager
|
||||
from heurams.services.logger import get_logger
|
||||
@@ -24,11 +25,11 @@ logger = get_logger(__name__)
|
||||
|
||||
class MemScreen(Screen):
|
||||
BINDINGS = [
|
||||
("q", "go_back_notif", "返回"),
|
||||
("p", "prev", "查看上一个"),
|
||||
("q", "go_back_notif", _("Back")),
|
||||
("p", "prev", _("Previous")),
|
||||
("d", "toggle_dark", ""),
|
||||
("v", "play_voice", "朗读"),
|
||||
("*", "toggle_favorite", "收藏"),
|
||||
("v", "play_voice", _("Read Aloud")),
|
||||
("*", "toggle_favorite", _("Favorite")),
|
||||
("r", "resume_mark"),
|
||||
("Q", "go_back"),
|
||||
("n", "block_prompt"),
|
||||
@@ -36,12 +37,12 @@ class MemScreen(Screen):
|
||||
("z", "block_prompt"),
|
||||
]
|
||||
|
||||
SUB_TITLE = "学习中"
|
||||
SUB_TITLE = _("Learning")
|
||||
CSS_PATH = rootdir / "interface" / "css" / "screens" / "memoqueue.tcss"
|
||||
|
||||
if config_var.get()["interface"]["global"]["quick_pass"]:
|
||||
BINDINGS.append(("k", "quick_pass", "正确应答"))
|
||||
BINDINGS.append(("f", "quick_fail", "错误应答"))
|
||||
BINDINGS.append(("k", "quick_pass", _("Correct")))
|
||||
BINDINGS.append(("f", "quick_fail", _("Incorrect")))
|
||||
|
||||
rating = reactive(-1)
|
||||
|
||||
@@ -80,7 +81,7 @@ class MemScreen(Screen):
|
||||
yield Footer()
|
||||
|
||||
def update_state(self):
|
||||
"""更新状态机"""
|
||||
"""Update state machine"""
|
||||
self.procession: Procession = self.router.current_procession() # type: ignore
|
||||
self.atom: pt.Atom = self.procession.current_atom # type: ignore
|
||||
|
||||
@@ -101,17 +102,17 @@ class MemScreen(Screen):
|
||||
atom=self.atom, alia=puzzle["alia"] # type: ignore
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug(f"调度展开出错: {e}")
|
||||
return Static(f"无法生成谜题 {e}")
|
||||
logger.error(f"Failed to expand puzzle: {e}")
|
||||
return Static(_("Failed to generate puzzle: {e}").format(e=e))
|
||||
|
||||
def _get_progress_text(self):
|
||||
s = ""
|
||||
if self.repo is not None:
|
||||
fav_status = "已收藏" if self._is_current_atom_favorited() else "未收藏"
|
||||
fav_status = _("Favorited") if self._is_current_atom_favorited() else _("Not favorited")
|
||||
s += f"[{fav_status}] "
|
||||
s += f"[{self.procession.process() + 1}/{self.procession.total_length()}] \[{self.procession.route.name}]\n"
|
||||
s += f"[{self.procession.process() + 1}/{self.procession.total_length()}] \\[{self.procession.route.name}]\n"
|
||||
if self.procession.cursor - 1 >= 0:
|
||||
s += f"上一个: [d]{self.procession.atoms[self.procession.cursor - 1]['ident']}[/d]"
|
||||
s += _("Previous: {ident}").format(ident=f"[d]{self.procession.atoms[self.procession.cursor - 1]['ident']}[/d]")
|
||||
return s
|
||||
|
||||
def update_display(self):
|
||||
@@ -120,7 +121,7 @@ class MemScreen(Screen):
|
||||
progress_widget.update(self._get_progress_text()) # type: ignore
|
||||
|
||||
def mount_puzzle(self):
|
||||
"""挂载当前谜题组件"""
|
||||
"""Mount current puzzle widget"""
|
||||
if self.procession.route == RouterState.FINISHED:
|
||||
self.mount_finished_widget()
|
||||
return
|
||||
@@ -130,7 +131,7 @@ class MemScreen(Screen):
|
||||
container.mount(self.puzzle_widget())
|
||||
|
||||
def mount_finished_widget(self):
|
||||
"""挂载已完成组件"""
|
||||
"""Mount finished widget"""
|
||||
a = Attic("ana", {"finished": 0})
|
||||
a.data["finished"] += 1
|
||||
container = self.query_one("#puzzle_container")
|
||||
@@ -153,7 +154,7 @@ class MemScreen(Screen):
|
||||
self.run_worker(self.play_voice, exclusive=True, thread=True)
|
||||
|
||||
def play_voice(self):
|
||||
"""朗读当前内容"""
|
||||
"""Read current content aloud"""
|
||||
from pathlib import Path
|
||||
|
||||
from heurams.services.audio_service import play_by_path
|
||||
@@ -161,7 +162,6 @@ class MemScreen(Screen):
|
||||
|
||||
path = Path(config_var.get()["global"]["paths"]["data"]) / "cache" / "voice"
|
||||
path = path / f"{get_md5(self.atom.registry['nucleon']["tts_text"])}.wav"
|
||||
logger.debug(str(path))
|
||||
if path.exists():
|
||||
play_by_path(path)
|
||||
else:
|
||||
@@ -205,7 +205,7 @@ class MemScreen(Screen):
|
||||
if not self.atom.registry["runtime"]["locked"]:
|
||||
if not self.atom.registry["electron"].is_activated():
|
||||
self.atom.registry["electron"].activate()
|
||||
logger.debug(f"激活原子 {self.atom}")
|
||||
logger.debug(f"Activated atom: {self.atom}")
|
||||
self.atom.lock(1)
|
||||
self.atom.minimize(5)
|
||||
else:
|
||||
@@ -228,7 +228,7 @@ class MemScreen(Screen):
|
||||
self.expander = self.procession.get_expander()
|
||||
|
||||
def action_go_back_notif(self):
|
||||
self.notify("确定吗? 按下大写 Q 以返回")
|
||||
self.notify(_("Are you sure? Press uppercase Q to go back."))
|
||||
|
||||
def action_go_back(self):
|
||||
self.app.pop_screen()
|
||||
@@ -240,44 +240,44 @@ class MemScreen(Screen):
|
||||
self.rating = 3
|
||||
|
||||
def _get_repo_rel_path(self) -> str:
|
||||
"""获取仓库相对路径(相对于 data/repo)"""
|
||||
"""Get repo relative path (relative to data/repo)"""
|
||||
if self.repo is None:
|
||||
return ""
|
||||
# self.repo.source 是 Path 对象, 指向仓库目录
|
||||
# self.repo.source is the Path object pointing to the repo directory
|
||||
repo_full_path = self.repo.source
|
||||
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)
|
||||
except ValueError:
|
||||
# 如果不在 data/repo 下, 则返回完整路径(字符串形式)
|
||||
# If not under data/repo, return the full path as string
|
||||
return str(repo_full_path)
|
||||
|
||||
def _is_current_atom_favorited(self) -> bool:
|
||||
"""检查当前原子是否已收藏"""
|
||||
"""Check if current atom is favorited"""
|
||||
if self.repo is None:
|
||||
return False
|
||||
repo_path = self._get_repo_rel_path()
|
||||
return favorite_manager.has(repo_path, self.atom.ident)
|
||||
|
||||
def action_toggle_favorite(self):
|
||||
"""切换收藏状态"""
|
||||
"""Toggle favorite status"""
|
||||
if self.repo is None:
|
||||
self.app.notify("无法收藏:未关联仓库", severity="error")
|
||||
self.app.notify(_("Cannot favorite: no repo associated"), severity="error")
|
||||
return
|
||||
repo_path = self._get_repo_rel_path()
|
||||
ident = self.atom.ident
|
||||
if favorite_manager.has(repo_path, ident):
|
||||
favorite_manager.remove(repo_path, ident)
|
||||
self.app.notify(f"已取消收藏:{ident}", severity="information")
|
||||
self.app.notify(_("Unfavorited: {ident}").format(ident=ident), severity="information")
|
||||
else:
|
||||
favorite_manager.add(repo_path, ident)
|
||||
self.app.notify(f"已收藏:{ident}", severity="information")
|
||||
# 更新显示(如果需要)
|
||||
self.app.notify(_("Favorited: {ident}").format(ident=ident), severity="information")
|
||||
# Update display if needed
|
||||
self.update_display()
|
||||
|
||||
def action_block_prompt(self):
|
||||
self.app.notify("功能在记忆界面中不可用, 完成或返回后再试", severity="error")
|
||||
self.app.notify(_("This function is not available during memorization. Please finish or go back first."), severity="error")
|
||||
|
||||
def action_resume_mark(self):
|
||||
from heurams.services.attic import Attic
|
||||
@@ -286,4 +286,4 @@ class MemScreen(Screen):
|
||||
a = Attic("ana")
|
||||
l = a.data["last"]
|
||||
a.data["last"] = time.time()
|
||||
self.app.notify(f"时间恢复已修正: {l} -> {a.data['last']}")
|
||||
self.app.notify(_("Time resume corrected: {old} -> {new}").format(old=l, new=a.data['last']))
|
||||
|
||||
@@ -4,6 +4,7 @@ from textual.screen import ModalScreen
|
||||
from textual.widgets import Button, Label, ListItem, ListView, Static
|
||||
|
||||
|
||||
from heurams.i18n import _
|
||||
from heurams.services.logger import get_logger
|
||||
|
||||
from .favmgr import FavoriteManagerScreen
|
||||
@@ -12,36 +13,33 @@ logger = get_logger(__name__)
|
||||
|
||||
|
||||
class NavigatorScreen(ModalScreen):
|
||||
"""导航器模态窗口"""
|
||||
"""Navigator modal screen"""
|
||||
|
||||
BINDINGS = [
|
||||
("q", "go_back", "返回"),
|
||||
("escape", "go_back", "返回"),
|
||||
("n", "go_back", "切换"),
|
||||
("q", "go_back", _("Back")),
|
||||
("escape", "go_back", _("Back")),
|
||||
("n", "go_back", _("Switch")),
|
||||
]
|
||||
|
||||
SCREENS = [
|
||||
("仪表盘", "dashboard"),
|
||||
# ("创建仓库", "repo_creator"),
|
||||
("缓存管理器", "precache_all"),
|
||||
("收藏夹", FavoriteManagerScreen),
|
||||
("设置页面", "setting"),
|
||||
# ("调试日志", "logviewer"),
|
||||
("同步工具", "synctool"),
|
||||
("关于此软件", "about"),
|
||||
# ("仓库编辑器", "repo_editor"),
|
||||
(_("Dashboard"), "dashboard"),
|
||||
(_("Cache Manager"), "precache_all"),
|
||||
(_("Favorites"), FavoriteManagerScreen),
|
||||
(_("Settings Page"), "setting"),
|
||||
(_("Sync Tool"), "synctool"),
|
||||
(_("About"), "about"),
|
||||
]
|
||||
|
||||
OTHERS = [
|
||||
("退出程序", "self.app.exit()"),
|
||||
("项目主页", "webbrowser.open('https://ams.pluv27.top')"),
|
||||
(_("Exit"), "self.app.exit()"),
|
||||
(_("Project Homepage"), "webbrowser.open('https://ams.pluv27.top')"),
|
||||
]
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""组合界面组件"""
|
||||
"""Compose UI components"""
|
||||
with Grid(id="dialog"):
|
||||
yield Label(
|
||||
"[b]请选择要跳转的功能\n或记忆会话实例[/b]\n\n将在此处显示提示",
|
||||
_("[b]Select a function to navigate to\nor a memorization session instance[/b]\n\nTips will be displayed here"),
|
||||
classes="title-label",
|
||||
)
|
||||
yield ListView(
|
||||
@@ -49,9 +47,9 @@ class NavigatorScreen(ModalScreen):
|
||||
id="nav-list",
|
||||
classes="nav-list-view",
|
||||
)
|
||||
yield Static("按下回车以完成切换\n所有会话将被保存")
|
||||
yield Static(_("Press Enter to switch\nAll sessions will be saved"))
|
||||
yield Button(
|
||||
"关闭 (n)",
|
||||
_("Close (n)"),
|
||||
id="close_button",
|
||||
variant="primary",
|
||||
classes="close-button",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""缓存工具界面"""
|
||||
"""Cache tool screen"""
|
||||
|
||||
import pathlib
|
||||
|
||||
@@ -13,14 +13,15 @@ from textual import events, on
|
||||
import heurams.kernel.particles as pt
|
||||
import heurams.services.hasher as hasher
|
||||
from heurams.context import *
|
||||
from heurams.i18n import _
|
||||
|
||||
# 兼容性缓存路径:优先使用 paths.cache, 否则使用 data/cache
|
||||
# Compatibility cache path: prefer paths.cache, otherwise data/cache
|
||||
paths = config_var.get()["global"]["paths"]
|
||||
cache_dir = pathlib.Path(paths.get("cache", paths["data"] + "/cache")) / "voice"
|
||||
|
||||
|
||||
def human_size(bytes_num: int) -> str:
|
||||
"""将字节数格式化为人类可读的字符串"""
|
||||
"""Format byte count as human-readable string"""
|
||||
for unit in ["B", "KB", "MB", "GB", "TB"]:
|
||||
if bytes_num < 1024.0:
|
||||
return f"{bytes_num:.2f} {unit}"
|
||||
@@ -29,18 +30,18 @@ def human_size(bytes_num: int) -> str:
|
||||
|
||||
|
||||
class PrecachingScreen(Screen):
|
||||
"""预缓存音频文件屏幕
|
||||
"""Audio file pre-caching screen
|
||||
|
||||
缓存记忆单元音频文件, 全部(默认) 或部分记忆单元(可选参数传入)
|
||||
Cache memory unit audio files, all (default) or some memory units (optional params)
|
||||
|
||||
Args:
|
||||
nucleons (list): 可选列表, 仅包含 Nucleon 对象
|
||||
desc (list): 可选字符串, 包含对此次调用的文字描述
|
||||
nucleons (list): Optional list containing Nucleon objects only
|
||||
desc (list): Optional string containing description of this call
|
||||
"""
|
||||
|
||||
SUB_TITLE = "缓存管理器"
|
||||
SUB_TITLE = _("Cache Manager")
|
||||
BINDINGS = [
|
||||
("q", "go_back", "返回"),
|
||||
("q", "go_back", _("Back")),
|
||||
]
|
||||
|
||||
def __init__(self, nucleons: list = [], desc: str = ""):
|
||||
@@ -67,7 +68,7 @@ class PrecachingScreen(Screen):
|
||||
self._update_cache_stats()
|
||||
|
||||
def _get_total_units(self) -> int:
|
||||
"""获取所有仓库的总单元数"""
|
||||
"""Get total units across all repos"""
|
||||
from heurams.context import config_var
|
||||
from heurams.kernel.repolib import Repo
|
||||
|
||||
@@ -89,7 +90,7 @@ class PrecachingScreen(Screen):
|
||||
shim.set_term_title(f"{self.app.TITLE} - {self.SUB_TITLE}")
|
||||
|
||||
def _update_cache_stats(self) -> None:
|
||||
"""更新缓存统计信息"""
|
||||
"""Update cache statistics"""
|
||||
total_size = 0
|
||||
file_count = 0
|
||||
cached_units = 0
|
||||
@@ -117,21 +118,25 @@ class PrecachingScreen(Screen):
|
||||
show_clock=config_var.get()["interface"]["global"]["clock_on_header"]
|
||||
)
|
||||
with ScrollableContainer(id="precache_container"):
|
||||
yield Label("[b]音频预缓存[/b]", classes="title-label")
|
||||
yield Label(_("[b]Audio Pre-cache[/b]"), classes="title-label")
|
||||
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)} 个单元)",
|
||||
_("Cache rate: {rate:.1f}% ({cached} / {total} units)").format(
|
||||
rate=self.cache_stats.get('cache_rate', 0),
|
||||
cached=self.cache_stats.get('cached_units', 0),
|
||||
total=self.cache_stats.get('total_units', 0),
|
||||
),
|
||||
classes="cache-usage-text",
|
||||
)
|
||||
if self.nucleons:
|
||||
yield Static(
|
||||
f"目标单元归属: [b]{self.desc}[/b]", classes="target-info"
|
||||
_("Target units from: [b]{desc}[/b]").format(desc=self.desc), classes="target-info"
|
||||
)
|
||||
yield Static(
|
||||
f"单元数量: {len(self.nucleons)}", classes="target-info"
|
||||
_("Unit count: {n}").format(n=len(self.nucleons)), classes="target-info"
|
||||
)
|
||||
else:
|
||||
yield Static("目标: 所有单元", classes="target-info")
|
||||
yield Static(_("Target: all units"), classes="target-info")
|
||||
|
||||
yield Static(id="status", classes="status-info")
|
||||
yield Static(id="current_item", classes="current-item")
|
||||
@@ -139,69 +144,72 @@ class PrecachingScreen(Screen):
|
||||
with Horizontal(classes="button-group"):
|
||||
if not self.is_precaching:
|
||||
yield Button(
|
||||
"开始预缓存", id="start_precache", variant="primary"
|
||||
_("Start Pre-cache"), id="start_precache", variant="primary"
|
||||
)
|
||||
else:
|
||||
yield Button(
|
||||
"取消预缓存", id="cancel_precache", variant="error"
|
||||
_("Cancel Pre-cache"), id="cancel_precache", variant="error"
|
||||
)
|
||||
yield Button("清空缓存", id="clear_cache", variant="warning")
|
||||
yield Button("返回", id="go_back", variant="default")
|
||||
yield Button(_("Clear Cache"), id="clear_cache", variant="warning")
|
||||
yield Button(_("Back"), id="go_back", variant="default")
|
||||
with Container(classes="cache-info"):
|
||||
yield Static(f"缓存路径: {cache_dir}", classes="cache-path")
|
||||
yield Static(_("Cache path: {path}").format(path=cache_dir), classes="cache-path")
|
||||
yield Static(
|
||||
f"文件数: {self.cache_stats['file_count']}", classes="cache-count"
|
||||
_("Files: {n}").format(n=self.cache_stats['file_count']), classes="cache-count"
|
||||
)
|
||||
yield Static(
|
||||
f"总大小: {self.cache_stats['human_size']}", classes="cache-size"
|
||||
_("Total size: {size}").format(size=self.cache_stats['human_size']), classes="cache-size"
|
||||
)
|
||||
yield Button(
|
||||
"刷新", id="refresh_cache_stats", variant="default", flat=True
|
||||
_("Refresh"), id="refresh_cache_stats", variant="default", flat=True
|
||||
)
|
||||
yield Static("若您离开此界面, 未完成的缓存进程会自动停止.")
|
||||
yield Static('缓存程序支持 "断点续传".')
|
||||
yield Static(_("If you leave this screen, ongoing cache processes will stop automatically."))
|
||||
yield Static(_('Cache supports "resume from break".'))
|
||||
|
||||
yield Footer()
|
||||
|
||||
def on_mount(self):
|
||||
"""挂载时初始化状态"""
|
||||
self.update_status("就绪", "等待开始...")
|
||||
"""Initialise state on mount"""
|
||||
self.update_status(_("Ready"), _("Waiting to start..."))
|
||||
self._update_cache_display()
|
||||
|
||||
def update_status(self, status, current_item="", progress=None):
|
||||
"""更新状态显示"""
|
||||
"""Update status display"""
|
||||
status_widget = self.query_one("#status", Static)
|
||||
item_widget = self.query_one("#current_item", Static)
|
||||
progress_bar = self.query_one("#progress_bar", ProgressBar)
|
||||
|
||||
status_widget.update(f"状态: {status}")
|
||||
item_widget.update(f"当前项目: {current_item}" if current_item else "")
|
||||
status_widget.update(_("Status: {s}").format(s=status))
|
||||
item_widget.update(_("Current item: {item}").format(item=current_item) if current_item else "")
|
||||
|
||||
if progress is not None:
|
||||
progress_bar.progress = progress
|
||||
progress_bar.advance(0) # 刷新显示
|
||||
progress_bar.advance(0) # Refresh display
|
||||
|
||||
def _update_cache_display(self) -> None:
|
||||
"""更新缓存信息显示"""
|
||||
# 更新统计信息
|
||||
"""Update cache info display"""
|
||||
# Update stats
|
||||
self._update_cache_stats()
|
||||
# 更新缓存率进度条
|
||||
# 更新缓存大小和文件数显示
|
||||
# Update cache rate progress bar
|
||||
# Update cache size and file count display
|
||||
cache_count_widget = self.query_one(".cache-count", Static)
|
||||
cache_size_widget = self.query_one(".cache-size", Static)
|
||||
cache_usage_text = self.query_one(".cache-usage-text", Static)
|
||||
if cache_count_widget:
|
||||
cache_count_widget.update(f"文件数: {self.cache_stats['file_count']}")
|
||||
cache_count_widget.update(_("Files: {n}").format(n=self.cache_stats['file_count']))
|
||||
if cache_size_widget:
|
||||
cache_size_widget.update(f"总大小: {self.cache_stats['human_size']}")
|
||||
cache_size_widget.update(_("Total size: {size}").format(size=self.cache_stats['human_size']))
|
||||
if cache_usage_text:
|
||||
cache_usage_text.update(
|
||||
f"缓存率: {self.cache_stats.get('cache_rate', 0):.1f}% "
|
||||
f"(已缓存 {self.cache_stats.get('cached_units', 0)} / {self.cache_stats.get('total_units', 0)} 个单元)"
|
||||
_("Cache rate: {rate:.1f}% ({cached} / {total} units)").format(
|
||||
rate=self.cache_stats.get('cache_rate', 0),
|
||||
cached=self.cache_stats.get('cached_units', 0),
|
||||
total=self.cache_stats.get('total_units', 0),
|
||||
)
|
||||
)
|
||||
|
||||
def precache_by_text(self, text: str):
|
||||
"""预缓存单段文本的音频"""
|
||||
"""Pre-cache audio for a single text string"""
|
||||
|
||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
cache_file = cache_dir / f"{hasher.get_md5(text)}.wav"
|
||||
@@ -212,21 +220,21 @@ class PrecachingScreen(Screen):
|
||||
convertor(text, cache_file)
|
||||
return 1
|
||||
except Exception as e:
|
||||
print(f"预缓存失败 '{text}': {e}")
|
||||
print(f"Pre-cache failed '{text}': {e}")
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def precache_by_nucleon(self, nucleon: pt.Nucleon):
|
||||
"""依据 Nucleon 缓存"""
|
||||
"""Cache based on Nucleon"""
|
||||
ret = self.precache_by_text(nucleon["tts_text"])
|
||||
return ret
|
||||
|
||||
def precache_by_list(self, nucleons: list):
|
||||
"""依据 Nucleons 列表缓存"""
|
||||
"""Cache based on Nucleons list"""
|
||||
for idx, nucleon in enumerate(nucleons):
|
||||
# print(f"PROC: {nucleon}")
|
||||
worker = get_current_worker()
|
||||
if worker and worker.is_cancelled: # 函数在worker中执行且已被取消
|
||||
if worker and worker.is_cancelled: # Function running in worker and has been cancelled
|
||||
return False
|
||||
text = nucleon["tts_text"]
|
||||
# self.current_item = text[:30] + "..." if len(text) > 50 else text
|
||||
@@ -236,12 +244,12 @@ class PrecachingScreen(Screen):
|
||||
# print(self.total)
|
||||
progress = int((self.processed / self.total) * 100) if self.total > 0 else 0
|
||||
# print(progress)
|
||||
self.update_status(f"正处理 ({idx + 1}/{len(nucleons)})", text, progress)
|
||||
self.update_status(_("Processing ({i}/{total})").format(i=idx + 1, total=len(nucleons)), text, progress)
|
||||
ret = self.precache_by_nucleon(nucleon)
|
||||
if not ret:
|
||||
self.update_status(
|
||||
"出错",
|
||||
f"处理失败, 跳过: {self.current_item}",
|
||||
_("Error"),
|
||||
_("Failed, skipping: {item}").format(item=self.current_item),
|
||||
)
|
||||
import time
|
||||
|
||||
@@ -286,7 +294,7 @@ class PrecachingScreen(Screen):
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
event.stop()
|
||||
if event.button.id == "start_precache" and not self.is_precaching:
|
||||
# 开始预缓存
|
||||
# Start pre-cache
|
||||
if self.nucleons:
|
||||
self.precache_worker = self.run_worker(
|
||||
self.precache_by_nucleons,
|
||||
@@ -303,32 +311,32 @@ class PrecachingScreen(Screen):
|
||||
)
|
||||
|
||||
elif event.button.id == "cancel_precache" and self.is_precaching:
|
||||
# 取消预缓存
|
||||
# Cancel pre-cache
|
||||
if self.precache_worker:
|
||||
self.precache_worker.cancel()
|
||||
self.is_precaching = False
|
||||
self.processed = 0
|
||||
self.progress = 0
|
||||
self.update_status("已取消", "预缓存操作被用户取消", 0)
|
||||
self.update_status(_("Cancelled"), _("Pre-cache cancelled by user"), 0)
|
||||
|
||||
elif event.button.id == "clear_cache":
|
||||
# 清空缓存
|
||||
# Clear cache
|
||||
try:
|
||||
import shutil
|
||||
|
||||
shutil.rmtree(cache_dir, ignore_errors=True)
|
||||
self.update_status("已清空", "音频缓存已清空", 0)
|
||||
self._update_cache_display() # 更新缓存统计显示
|
||||
self.update_status(_("Cleared"), _("Audio cache cleared"), 0)
|
||||
self._update_cache_display() # Update cache stats display
|
||||
except Exception as e:
|
||||
self.update_status("错误", f"清空缓存失败: {e}")
|
||||
self.update_status(_("Error"), _("Failed to clear cache: {error}").format(error=e))
|
||||
self.cancel_flag = 1
|
||||
self.processed = 0
|
||||
self.progress = 0
|
||||
|
||||
elif event.button.id == "refresh_cache_stats":
|
||||
# 刷新缓存统计信息
|
||||
# Refresh cache stats
|
||||
self._update_cache_display()
|
||||
self.app.notify("缓存信息已刷新", severity="information")
|
||||
self.app.notify(_("Cache info refreshed"), severity="information")
|
||||
elif event.button.id == "go_back":
|
||||
self.action_go_back()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""记忆准备界面"""
|
||||
"""Memorization preparation screen"""
|
||||
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import ScrollableContainer, Horizontal
|
||||
@@ -19,6 +19,7 @@ from textual import events, on
|
||||
import heurams.kernel.particles as pt
|
||||
from heurams.context import *
|
||||
from heurams.context import config_var
|
||||
from heurams.i18n import _
|
||||
from heurams.kernel.repolib import *
|
||||
from heurams.kernel.algorithms import algorithms
|
||||
from heurams.services.logger import get_logger
|
||||
@@ -28,11 +29,11 @@ logger = get_logger(__name__)
|
||||
|
||||
class PreparationScreen(Screen):
|
||||
|
||||
SUB_TITLE = "准备记忆集"
|
||||
SUB_TITLE = _("Prepare Repository")
|
||||
|
||||
BINDINGS = [
|
||||
("q", "go_back", "返回"),
|
||||
("p", "precache", "缓存"),
|
||||
("q", "go_back", _("Back")),
|
||||
("p", "precache", _("Cache")),
|
||||
("d", "toggle_dark", ""),
|
||||
("0,1,2,3", "app.push_screen('about')", ""),
|
||||
]
|
||||
@@ -61,29 +62,40 @@ class PreparationScreen(Screen):
|
||||
)
|
||||
with ScrollableContainer(id="main_container"):
|
||||
yield Markdown(
|
||||
f"**准备就绪**: `{self.repo.manifest['title']}`\n", id="title"
|
||||
_("**Ready**: `{title}`\n").format(title=self.repo.manifest['title']), id="title"
|
||||
)
|
||||
yield Label(f"单元集路径: {self.repo.source}")
|
||||
yield Label(_("Repo path: {path}").format(path=self.repo.source))
|
||||
yield Label(
|
||||
f"学习完成度: {self.repo.progress['touched']}/{len(self.repo)} [d]\\[{round(self.repo.progress['touched']/self.repo.progress['total']*100, 1)}%][/d]"
|
||||
_("Progress: {touched}/{total} [{pct}%]").format(
|
||||
touched=self.repo.progress['touched'],
|
||||
total=len(self.repo),
|
||||
pct=round(self.repo.progress['touched'] / self.repo.progress['total'] * 100, 1)
|
||||
)
|
||||
)
|
||||
yield Label(
|
||||
f"调度算法: {self.repo.config["algorithm"]} {algorithms[self.repo.config["algorithm"]].desc}"
|
||||
_("Scheduling algorithm: {algo} {desc}").format(
|
||||
algo=self.repo.config["algorithm"],
|
||||
desc=algorithms[self.repo.config["algorithm"]].desc
|
||||
)
|
||||
)
|
||||
yield Label(
|
||||
f"学习数量: {self.repo.preview['review'] + self.scheduled_num} = {self.repo.preview['review']} [d][复习][/d] + {self.scheduled_num} [d][新识记][/d]\n",
|
||||
_("Study count: {total} = {review} [d][Review][/d] + {new} [d][New][/d]\n").format(
|
||||
total=self.repo.preview['review'] + self.scheduled_num,
|
||||
review=self.repo.preview['review'],
|
||||
new=self.scheduled_num,
|
||||
),
|
||||
id="schnum_label",
|
||||
)
|
||||
|
||||
yield Horizontal(
|
||||
Button(
|
||||
"开始记忆",
|
||||
_("Start Memorizing"),
|
||||
id="start_memorizing_button",
|
||||
variant="primary",
|
||||
classes="btn",
|
||||
),
|
||||
Button(
|
||||
"管理缓存",
|
||||
_("Manage Cache"),
|
||||
id="precache_button",
|
||||
variant="success",
|
||||
classes="btn",
|
||||
@@ -99,14 +111,6 @@ class PreparationScreen(Screen):
|
||||
yield Static(i, classes="unit-statline")
|
||||
yield Footer()
|
||||
|
||||
# def watch_scheduled_num(self, old_scheduled_num, new_scheduled_num):
|
||||
# logger.debug("响应", old_scheduled_num, "->", new_scheduled_num)
|
||||
# try:
|
||||
# one = self.query_one("#schnum_label")
|
||||
# one.update(f"单次记忆数量: {new_scheduled_num}") # type: ignore
|
||||
# except:
|
||||
# pass
|
||||
|
||||
def load_data(self):
|
||||
self.scheduled_num = self.repo.config["scheduled_num"]
|
||||
content = ""
|
||||
@@ -154,7 +158,6 @@ class PreparationScreen(Screen):
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
event.stop()
|
||||
logger.debug("按下按钮")
|
||||
if event.button.id == "start_memorizing_button":
|
||||
launch(repo=self.repo, app=self.app, scheduled_num=self.scheduled_num)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""设置页面"""
|
||||
"""Settings screen"""
|
||||
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import ScrollableContainer, Horizontal
|
||||
@@ -16,6 +16,7 @@ from textual.widgets import (
|
||||
from textual import events, on
|
||||
|
||||
from heurams.context import *
|
||||
from heurams.i18n import _
|
||||
from heurams.kernel.particles import *
|
||||
from heurams.kernel.repolib import *
|
||||
from heurams.services.logger import get_logger
|
||||
@@ -26,12 +27,12 @@ logger = get_logger(__name__)
|
||||
|
||||
|
||||
class SettingScreen(Screen):
|
||||
"""设置页面屏幕"""
|
||||
"""Settings screen"""
|
||||
|
||||
SUB_TITLE = "设置"
|
||||
SUB_TITLE = _("Settings")
|
||||
BINDINGS = [
|
||||
("q", "go_back", "返回"),
|
||||
("s", "go_back", "设置"),
|
||||
("q", "go_back", _("Back")),
|
||||
("s", "go_back", _("Settings")),
|
||||
]
|
||||
CSS_PATH = rootdir / "interface" / "css" / "screens" / "setting.tcss"
|
||||
|
||||
@@ -50,13 +51,13 @@ class SettingScreen(Screen):
|
||||
shim.set_term_title(f"{self.app.TITLE} - {self.SUB_TITLE}")
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""组合界面组件"""
|
||||
"""Compose UI components"""
|
||||
if config_var.get()["interface"]["global"]["show_header"]:
|
||||
yield Header(
|
||||
show_clock=config_var.get()["interface"]["global"]["clock_on_header"]
|
||||
)
|
||||
with ScrollableContainer():
|
||||
yield Label("[b]设置页面[/b]")
|
||||
yield Label("[b]" + _("Settings") + "[/b]")
|
||||
for i in config_var.get():
|
||||
if i.startswith("_"):
|
||||
continue
|
||||
@@ -67,7 +68,7 @@ class SettingScreen(Screen):
|
||||
title=i + f'\n[d]{config_var.get().get(f"_{i}_desc", "")}[/d]',
|
||||
)
|
||||
yield Label(
|
||||
"退出页面时, 所作的更改会立即保存, 但仍建议重启软件以确保新的配置得到应用",
|
||||
_("Changes are saved immediately when you leave this page, but restart is recommended to ensure the new configuration is applied."),
|
||||
classes="foot",
|
||||
)
|
||||
yield Footer()
|
||||
@@ -138,7 +139,7 @@ class SettingScreen(Screen):
|
||||
Label(i + f'\n[d]{parent.get(f"_{i}_desc", "")}[/d]'),
|
||||
Input(
|
||||
value=str(parent[i]),
|
||||
placeholder="要求一个浮点数",
|
||||
placeholder=_("Requires a float"),
|
||||
type="number",
|
||||
id=domize(f"{parent_epath}.{i}"),
|
||||
),
|
||||
@@ -151,7 +152,7 @@ class SettingScreen(Screen):
|
||||
Label(i + f'\n[d]{parent.get(f"_{i}_desc", "")}[/d]'),
|
||||
Input(
|
||||
value=parent[i],
|
||||
placeholder="要求一个字符串",
|
||||
placeholder=_("Requires a string"),
|
||||
type="text",
|
||||
id=domize(f"{parent_epath}.{i}"),
|
||||
),
|
||||
@@ -176,7 +177,7 @@ class SettingScreen(Screen):
|
||||
Label(i + f'\n[d]{parent.get(f"_{i}_desc", "")}[/d]'),
|
||||
Input(
|
||||
value=str(parent[i]),
|
||||
placeholder="要求一个整数",
|
||||
placeholder=_("Requires an integer"),
|
||||
type="integer",
|
||||
id=domize(f"{parent_epath}.{i}"),
|
||||
),
|
||||
@@ -186,9 +187,9 @@ class SettingScreen(Screen):
|
||||
elif isinstance(parent[i], list):
|
||||
pass
|
||||
else:
|
||||
lst.append(Label("未知类型"))
|
||||
lst.append(Label(_("Unknown type")))
|
||||
return lst
|
||||
return [Label("无子项")]
|
||||
return [Label(_("No sub-items"))]
|
||||
|
||||
def on_mount(self) -> None:
|
||||
"""挂载组件时初始化"""
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""同步工具界面"""
|
||||
"""Sync tool screen"""
|
||||
|
||||
import pathlib
|
||||
import time
|
||||
@@ -12,11 +12,12 @@ from textual.worker import get_current_worker
|
||||
from textual import events, on
|
||||
|
||||
from heurams.context import *
|
||||
from heurams.i18n import _
|
||||
|
||||
|
||||
class SyncScreen(Screen):
|
||||
|
||||
BINDINGS = [("q", "go_back", "返回")]
|
||||
BINDINGS = [("q", "go_back", _("Back"))]
|
||||
|
||||
def __init__(self, nucleons: list = [], desc: str = ""):
|
||||
super().__init__(name=None, id=None, classes=None)
|
||||
@@ -39,67 +40,67 @@ class SyncScreen(Screen):
|
||||
show_clock=config_var.get()["interface"]["global"]["clock_on_header"]
|
||||
)
|
||||
with ScrollableContainer(id="sync_container"):
|
||||
# 标题和连接状态
|
||||
yield Static("同步工具", classes="title")
|
||||
# Title and connection status
|
||||
yield Static(_("Sync Tool"), classes="title")
|
||||
yield Static("", id="status_label", classes="status")
|
||||
|
||||
# 配置信息
|
||||
yield Static(f"同步协议: {config_var.get()['services']['sync']}")
|
||||
yield Static("服务器配置:", classes="section_title")
|
||||
# Config info
|
||||
yield Static(_("Sync protocol: {proto}").format(proto=config_var.get()['services']['sync']))
|
||||
yield Static(_("Server Configuration:"), classes="section_title")
|
||||
with Horizontal(classes="config_info"):
|
||||
yield Static("远程服务器:", classes="config_label")
|
||||
yield Static(_("Remote server:"), classes="config_label")
|
||||
yield Static("", id="server_url", classes="config_value")
|
||||
with Horizontal(classes="config_info"):
|
||||
yield Static("远程路径:", classes="config_label")
|
||||
yield Static(_("Remote path:"), classes="config_label")
|
||||
yield Static("", id="remote_path", classes="config_value")
|
||||
|
||||
with Horizontal(classes="control_buttons"):
|
||||
yield Button("测试连接", id="test_connection", variant="primary")
|
||||
yield Button("开始同步", id="start_sync", variant="success")
|
||||
yield Button("暂停", id="pause_sync", variant="warning", disabled=True)
|
||||
yield Button("取消", id="cancel_sync", variant="error", disabled=True)
|
||||
yield Button(_("Test Connection"), id="test_connection", variant="primary")
|
||||
yield Button(_("Start Sync"), id="start_sync", variant="success")
|
||||
yield Button(_("Pause"), id="pause_sync", variant="warning", disabled=True)
|
||||
yield Button(_("Cancel"), id="cancel_sync", variant="error", disabled=True)
|
||||
|
||||
yield Static("同步进度", classes="section_title")
|
||||
yield Static(_("Sync Progress"), classes="section_title")
|
||||
yield ProgressBar(id="progress_bar", show_percentage=True, total=100)
|
||||
yield Static("", id="progress_label", classes="progress_text")
|
||||
|
||||
yield Static("同步日志", classes="section_title")
|
||||
yield Static(_("Sync Log"), classes="section_title")
|
||||
yield Static("", id="log_output", classes="log_output")
|
||||
|
||||
yield Footer()
|
||||
|
||||
def on_mount(self):
|
||||
"""挂载时初始化状态"""
|
||||
"""Initialise state on mount"""
|
||||
self.update_ui_from_config()
|
||||
self.log_message("同步工具已启动")
|
||||
self.log_message(_("Sync tool started"))
|
||||
|
||||
def update_ui_from_config(self):
|
||||
"""更新 UI 显示配置信息"""
|
||||
"""Update UI with config info"""
|
||||
try:
|
||||
sync_cfg: dict = config_var.get()["providers"]["sync"]["webdav"]
|
||||
# 更新服务器 URL
|
||||
url = sync_cfg.get("url", "未配置")
|
||||
# Update server URL
|
||||
url = sync_cfg.get("url", _("Not configured"))
|
||||
url_widget = self.query_one("#server_url")
|
||||
url_widget.update(url) # type: ignore
|
||||
# 更新远程路径
|
||||
# Update remote path
|
||||
remote_path = sync_cfg.get("remote_path", "/")
|
||||
path_widget = self.query_one("#remote_path")
|
||||
path_widget.update(remote_path) # type: ignore
|
||||
|
||||
# 更新状态标签
|
||||
# Update status label
|
||||
status_widget = self.query_one("#status_label")
|
||||
if self.sync_service and self.sync_service.client:
|
||||
status_widget.update("✅ 同步服务已就绪") # type: ignore
|
||||
status_widget.update(_("✅ Sync service ready")) # type: ignore
|
||||
status_widget.add_class("ready")
|
||||
else:
|
||||
status_widget.update("❌ 同步服务未配置或未启用") # type: ignore
|
||||
status_widget.update(_("❌ Sync service not configured or not enabled")) # type: ignore
|
||||
status_widget.add_class("error")
|
||||
|
||||
except Exception as e:
|
||||
self.log_message(f"更新 UI 失败: {e}", is_error=True)
|
||||
self.log_message(_("Failed to update UI: {error}").format(error=e), is_error=True)
|
||||
|
||||
def update_status(self, status, current_item="", progress=None):
|
||||
"""更新状态显示"""
|
||||
"""Update status display"""
|
||||
try:
|
||||
status_widget = self.query_one("#status_label")
|
||||
status_widget.update(status) # type: ignore
|
||||
@@ -112,28 +113,28 @@ class SyncScreen(Screen):
|
||||
progress_label.update(f"{progress}% - {current_item}" if current_item else f"{progress}%") # type: ignore
|
||||
|
||||
except Exception as e:
|
||||
self.log_message(f"更新状态失败: {e}", is_error=True)
|
||||
self.log_message(_("Failed to update status: {error}").format(error=e), is_error=True)
|
||||
|
||||
def log_message(self, message: str, is_error: bool = False):
|
||||
"""添加日志消息并更新显示"""
|
||||
"""Add log message and update display"""
|
||||
timestamp = time.strftime("%H:%M:%S")
|
||||
prefix = "[ERROR]" if is_error else "[INFO]"
|
||||
log_line = f"{timestamp} {prefix} {message}"
|
||||
|
||||
self.log_messages.append(log_line)
|
||||
# 保持日志行数不超过最大值
|
||||
# Keep log lines under max
|
||||
if len(self.log_messages) > self.max_log_lines:
|
||||
self.log_messages = self.log_messages[-self.max_log_lines :]
|
||||
|
||||
# 更新日志显示
|
||||
# Update log display
|
||||
try:
|
||||
log_widget = self.query_one("#log_output")
|
||||
log_widget.update("\n".join(self.log_messages)) # type: ignore
|
||||
except Exception:
|
||||
pass # 如果组件未就绪, 忽略错误
|
||||
pass # Ignore if widget not ready
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
"""处理按钮点击事件"""
|
||||
"""Handle button press events"""
|
||||
button_id = event.button.id
|
||||
|
||||
if button_id == "test_connection":
|
||||
@@ -148,124 +149,133 @@ class SyncScreen(Screen):
|
||||
event.stop()
|
||||
|
||||
def test_connection(self):
|
||||
"""测试 WebDAV 服务器连接"""
|
||||
"""Test WebDAV server connection"""
|
||||
if not self.sync_service:
|
||||
self.log_message("同步服务未初始化, 请检查配置", is_error=True)
|
||||
self.update_status("❌ 同步服务未初始化")
|
||||
self.log_message(_("Sync service not initialised, please check configuration"), is_error=True)
|
||||
self.update_status(_("❌ Sync service not initialised"))
|
||||
return
|
||||
|
||||
self.log_message("正在测试 WebDAV 连接...")
|
||||
self.update_status("正在测试连接...")
|
||||
self.log_message(_("Testing WebDAV connection..."))
|
||||
self.update_status(_("Testing connection..."))
|
||||
|
||||
try:
|
||||
success = self.sync_service.test_connection()
|
||||
if success:
|
||||
self.log_message("连接测试成功")
|
||||
self.update_status("✅ 连接正常")
|
||||
self.log_message(_("Connection test successful"))
|
||||
self.update_status(_("✅ Connection OK"))
|
||||
else:
|
||||
self.log_message("连接测试失败", is_error=True)
|
||||
self.update_status("❌ 连接失败")
|
||||
self.log_message(_("Connection test failed"), is_error=True)
|
||||
self.update_status(_("❌ Connection failed"))
|
||||
except Exception as e:
|
||||
self.log_message(f"连接测试异常: {e}", is_error=True)
|
||||
self.update_status("❌ 连接异常")
|
||||
self.log_message(_("Connection test error: {error}").format(error=e), is_error=True)
|
||||
self.update_status(_("❌ Connection error"))
|
||||
|
||||
def start_sync(self):
|
||||
"""开始同步"""
|
||||
"""Start syncing"""
|
||||
if not self.sync_service:
|
||||
self.log_message("同步服务未初始化, 无法开始同步", is_error=True)
|
||||
self.log_message(_("Sync service not initialised, cannot start sync"), is_error=True)
|
||||
return
|
||||
|
||||
if self.is_syncing:
|
||||
self.log_message("同步已在进行中", is_error=True)
|
||||
self.log_message(_("Sync already in progress"), is_error=True)
|
||||
return
|
||||
|
||||
self.is_syncing = True
|
||||
self.is_paused = False
|
||||
self.update_button_states()
|
||||
|
||||
self.log_message("开始同步数据...")
|
||||
self.update_status("正在同步...", progress=0)
|
||||
self.log_message(_("Starting data sync..."))
|
||||
self.update_status(_("Syncing..."), progress=0)
|
||||
|
||||
# 启动后台同步任务
|
||||
# Start background sync task
|
||||
self.run_worker(self.perform_sync, thread=True)
|
||||
|
||||
def perform_sync(self):
|
||||
"""执行同步任务(在后台线程中运行)"""
|
||||
"""Execute sync task (runs in background thread)"""
|
||||
worker = get_current_worker()
|
||||
|
||||
try:
|
||||
# 获取需要同步的本地目录
|
||||
# Get local directories to sync
|
||||
from heurams.context import config_var
|
||||
|
||||
config = config_var.get()
|
||||
paths = config.get("paths", {})
|
||||
|
||||
# 同步 nucleon 目录
|
||||
# Sync nucleon directory
|
||||
nucleon_dir = pathlib.Path(paths.get("nucleon_dir", "./data/nucleon"))
|
||||
if nucleon_dir.exists():
|
||||
self.log_message(f"同步 nucleon 目录: {nucleon_dir}")
|
||||
self.update_status(f"同步 nucleon 目录...", progress=10)
|
||||
self.log_message(_("Syncing nucleon directory: {dir}").format(dir=nucleon_dir))
|
||||
self.update_status(_("Syncing nucleon directory..."), progress=10)
|
||||
|
||||
result = self.sync_service.sync_directory(nucleon_dir) # type: ignore
|
||||
if result.get("success"):
|
||||
self.log_message(
|
||||
f"nucleon 同步完成: 上传 {result.get('uploaded', 0)} 个, 下载 {result.get('downloaded', 0)} 个"
|
||||
_("nucleon sync complete: uploaded {up}, downloaded {down}").format(
|
||||
up=result.get('uploaded', 0),
|
||||
down=result.get('downloaded', 0),
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.log_message(
|
||||
f"nucleon 同步失败: {result.get('error', '未知错误')}",
|
||||
_("nucleon sync failed: {err}").format(err=result.get('error', _('Unknown error'))),
|
||||
is_error=True,
|
||||
)
|
||||
|
||||
# 同步 electron 目录
|
||||
# Sync electron directory
|
||||
electron_dir = pathlib.Path(paths.get("electron_dir", "./data/electron"))
|
||||
if electron_dir.exists():
|
||||
self.log_message(f"同步 electron 目录: {electron_dir}")
|
||||
self.update_status(f"同步 electron 目录...", progress=60)
|
||||
self.log_message(_("Syncing electron directory: {dir}").format(dir=electron_dir))
|
||||
self.update_status(_("Syncing electron directory..."), progress=60)
|
||||
|
||||
result = self.sync_service.sync_directory(electron_dir) # type: ignore
|
||||
if result.get("success"):
|
||||
self.log_message(
|
||||
f"electron 同步完成: 上传 {result.get('uploaded', 0)} 个, 下载 {result.get('downloaded', 0)} 个"
|
||||
_("electron sync complete: uploaded {up}, downloaded {down}").format(
|
||||
up=result.get('uploaded', 0),
|
||||
down=result.get('downloaded', 0),
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.log_message(
|
||||
f"electron 同步失败: {result.get('error', '未知错误')}",
|
||||
_("electron sync failed: {err}").format(err=result.get('error', _('Unknown error'))),
|
||||
is_error=True,
|
||||
)
|
||||
|
||||
# 同步 orbital 目录(如果存在)
|
||||
# Sync orbital directory (if exists)
|
||||
orbital_dir = pathlib.Path(paths.get("orbital_dir", "./data/orbital"))
|
||||
if orbital_dir.exists():
|
||||
self.log_message(f"同步 orbital 目录: {orbital_dir}")
|
||||
self.update_status(f"同步 orbital 目录...", progress=80)
|
||||
self.log_message(_("Syncing orbital directory: {dir}").format(dir=orbital_dir))
|
||||
self.update_status(_("Syncing orbital directory..."), progress=80)
|
||||
|
||||
result = self.sync_service.sync_directory(orbital_dir) # type: ignore
|
||||
if result.get("success"):
|
||||
self.log_message(
|
||||
f"orbital 同步完成: 上传 {result.get('uploaded', 0)} 个, 下载 {result.get('downloaded', 0)} 个"
|
||||
_("orbital sync complete: uploaded {up}, downloaded {down}").format(
|
||||
up=result.get('uploaded', 0),
|
||||
down=result.get('downloaded', 0),
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.log_message(
|
||||
f"orbital 同步失败: {result.get('error', '未知错误')}",
|
||||
_("orbital sync failed: {err}").format(err=result.get('error', _('Unknown error'))),
|
||||
is_error=True,
|
||||
)
|
||||
|
||||
# 同步完成
|
||||
self.update_status("同步完成", progress=100)
|
||||
self.log_message("所有目录同步完成")
|
||||
# Sync complete
|
||||
self.update_status(_("Sync complete"), progress=100)
|
||||
self.log_message(_("All directories synced"))
|
||||
|
||||
except Exception as e:
|
||||
self.log_message(f"同步过程中发生错误: {e}", is_error=True)
|
||||
self.update_status("同步失败")
|
||||
self.log_message(_("Error during sync: {error}").format(error=e), is_error=True)
|
||||
self.update_status(_("Sync failed"))
|
||||
finally:
|
||||
# 重置同步状态
|
||||
# Reset sync state
|
||||
self.is_syncing = False
|
||||
self.is_paused = False
|
||||
self.update_button_states() # type: ignore
|
||||
|
||||
def pause_sync(self):
|
||||
"""暂停同步"""
|
||||
"""Pause sync"""
|
||||
if not self.is_syncing:
|
||||
return
|
||||
|
||||
@@ -273,14 +283,14 @@ class SyncScreen(Screen):
|
||||
self.update_button_states()
|
||||
|
||||
if self.is_paused:
|
||||
self.log_message("同步已暂停")
|
||||
self.update_status("同步已暂停")
|
||||
self.log_message(_("Sync paused"))
|
||||
self.update_status(_("Sync paused"))
|
||||
else:
|
||||
self.log_message("同步已恢复")
|
||||
self.update_status("正在同步...")
|
||||
self.log_message(_("Sync resumed"))
|
||||
self.update_status(_("Syncing..."))
|
||||
|
||||
def cancel_sync(self):
|
||||
"""取消同步"""
|
||||
"""Cancel sync"""
|
||||
if not self.is_syncing:
|
||||
return
|
||||
|
||||
@@ -288,11 +298,11 @@ class SyncScreen(Screen):
|
||||
self.is_paused = False
|
||||
self.update_button_states()
|
||||
|
||||
self.log_message("同步已取消")
|
||||
self.update_status("同步已取消")
|
||||
self.log_message(_("Sync cancelled"))
|
||||
self.update_status(_("Sync cancelled"))
|
||||
|
||||
def update_button_states(self):
|
||||
"""更新按钮状态"""
|
||||
"""Update button states"""
|
||||
try:
|
||||
start_button = self.query_one("#start_sync")
|
||||
pause_button = self.query_one("#pause_sync")
|
||||
@@ -302,14 +312,14 @@ class SyncScreen(Screen):
|
||||
start_button.disabled = True
|
||||
pause_button.disabled = False
|
||||
cancel_button.disabled = False
|
||||
pause_button.label = "继续" if self.is_paused else "暂停" # type: ignore
|
||||
pause_button.label = _("Resume") if self.is_paused else _("Pause") # type: ignore
|
||||
else:
|
||||
start_button.disabled = False
|
||||
pause_button.disabled = True
|
||||
cancel_button.disabled = True
|
||||
|
||||
except Exception as e:
|
||||
self.log_message(f"更新按钮状态失败: {e}", is_error=True)
|
||||
self.log_message(_("Failed to update button state: {error}").format(error=e), is_error=True)
|
||||
|
||||
def action_go_back(self):
|
||||
self.app.pop_screen()
|
||||
|
||||
@@ -5,6 +5,7 @@ from textual.widgets import Button, Label, Static
|
||||
|
||||
import heurams.kernel.particles as pt
|
||||
|
||||
from heurams.i18n import _
|
||||
from .base_puzzle_widget import BasePuzzleWidget
|
||||
|
||||
|
||||
@@ -32,49 +33,49 @@ class BasicEvaluation(BasePuzzleWidget):
|
||||
|
||||
class RatingChanged(Message):
|
||||
def __init__(self, rating: int) -> None:
|
||||
self.rating = rating # 评分值 (0-5)
|
||||
self.rating = rating # Rating value (0-5)
|
||||
super().__init__()
|
||||
|
||||
# 反馈映射表
|
||||
# Feedback mapping
|
||||
feedback_mapping = {
|
||||
"feedback_5": {"rating": 5, "text": "完美回想"},
|
||||
"feedback_4": {"rating": 4, "text": "犹豫后正确"},
|
||||
"feedback_3": {"rating": 3, "text": "困难地正确"},
|
||||
"feedback_2": {"rating": 2, "text": "错误但熟悉"},
|
||||
"feedback_1": {"rating": 1, "text": "错误且不熟"},
|
||||
"feedback_0": {"rating": 0, "text": "完全空白"},
|
||||
"feedback_5": {"rating": 5, "text": _("Perfect recall")},
|
||||
"feedback_4": {"rating": 4, "text": _("Correct after hesitation")},
|
||||
"feedback_3": {"rating": 3, "text": _("Correct with difficulty")},
|
||||
"feedback_2": {"rating": 2, "text": _("Wrong but familiar")},
|
||||
"feedback_1": {"rating": 1, "text": _("Wrong and unfamiliar")},
|
||||
"feedback_0": {"rating": 0, "text": _("Complete blank")},
|
||||
}
|
||||
|
||||
def compose(self):
|
||||
# 显示主要内容
|
||||
# Show main content
|
||||
yield Label(self.atom.registry["nucleon"]["content"], id="main")
|
||||
|
||||
# 显示评估说明(可选)
|
||||
yield Static("请评估你对这个内容的记忆程度: ", classes="instruction")
|
||||
# Show instruction (optional)
|
||||
yield Static(_("Evaluate how well you remember this content: "), classes="instruction")
|
||||
|
||||
# 按钮容器
|
||||
# Button container
|
||||
with ScrollableContainer(id="button_container"):
|
||||
btn = {}
|
||||
btn["5"] = Button(
|
||||
"完美回想", variant="success", id="feedback_5", classes="choice"
|
||||
_("Perfect recall"), variant="success", id="feedback_5", classes="choice"
|
||||
)
|
||||
btn["4"] = Button(
|
||||
"犹豫后正确", variant="success", id="feedback_4", classes="choice"
|
||||
_("Correct after hesitation"), variant="success", id="feedback_4", classes="choice"
|
||||
)
|
||||
btn["3"] = Button(
|
||||
"困难地正确", variant="warning", id="feedback_3", classes="choice"
|
||||
_("Correct with difficulty"), variant="warning", id="feedback_3", classes="choice"
|
||||
)
|
||||
btn["2"] = Button(
|
||||
"错误但熟悉", variant="warning", id="feedback_2", classes="choice"
|
||||
_("Wrong but familiar"), variant="warning", id="feedback_2", classes="choice"
|
||||
)
|
||||
btn["1"] = Button(
|
||||
"错误且不熟", variant="error", id="feedback_1", classes="choice"
|
||||
_("Wrong and unfamiliar"), variant="error", id="feedback_1", classes="choice"
|
||||
)
|
||||
btn["0"] = Button(
|
||||
"完全空白", variant="error", id="feedback_0", classes="choice"
|
||||
_("Complete blank"), variant="error", id="feedback_0", classes="choice"
|
||||
)
|
||||
|
||||
# 布局按钮
|
||||
# Layout buttons
|
||||
yield Horizontal(btn["5"], btn["4"])
|
||||
yield Horizontal(btn["3"], btn["2"])
|
||||
yield Horizontal(btn["1"], btn["0"])
|
||||
|
||||
@@ -10,6 +10,7 @@ from textual.events import Key
|
||||
import heurams.kernel.particles as pt
|
||||
import heurams.kernel.puzzles as pz
|
||||
from heurams.services.hasher import hash
|
||||
from heurams.i18n import _
|
||||
from heurams.services.logger import get_logger
|
||||
|
||||
from .base_puzzle_widget import BasePuzzleWidget
|
||||
@@ -79,7 +80,6 @@ class ClozePuzzle(BasePuzzleWidget):
|
||||
c += 1
|
||||
self.hashmap[h] = i
|
||||
btnid = f"sel000-{h}"
|
||||
logger.debug(f"建立按钮 {btnid}")
|
||||
self.btn_shortcuts[f"{c}"] = btnid
|
||||
btns.append(Button(f"{i}", id=f"{btnid}", classes="cloze-option-btn"))
|
||||
for i in range((len(btns) + 1) // 2):
|
||||
@@ -89,7 +89,7 @@ class ClozePuzzle(BasePuzzleWidget):
|
||||
yield btns[i]
|
||||
s.focus()
|
||||
|
||||
yield Button("退格", id="delete")
|
||||
yield Button(_("Backspace"), id="delete")
|
||||
self.btn_shortcuts[f"0"] = "delete"
|
||||
self.btn_shortcuts[f"backspace"] = "delete"
|
||||
self.btn_shortcuts[f"delete"] = "delete"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Button, Label
|
||||
|
||||
from heurams.i18n import _
|
||||
|
||||
|
||||
class Finished(Widget):
|
||||
def __init__(
|
||||
@@ -26,9 +28,9 @@ class Finished(Widget):
|
||||
)
|
||||
|
||||
def compose(self):
|
||||
yield Label("本次记忆进程结束", id="finished_msg")
|
||||
yield Label(f"算法数据{'已保存' if self.is_saved else "未能保存"}")
|
||||
yield Button("返回上一级", flat=True, id="back-to-menu")
|
||||
yield Label(_("This memorization session is finished"), id="finished_msg")
|
||||
yield Label(_("Algorithm data {}").format(_("saved") if self.is_saved else _("not saved")))
|
||||
yield Button(_("Back to Menu"), flat=True, id="back-to-menu")
|
||||
|
||||
def on_button_pressed(self, event):
|
||||
button_id = event.button.id
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 单项选择题
|
||||
# Multiple-choice puzzle
|
||||
from typing import TypedDict
|
||||
|
||||
from textual.containers import ScrollableContainer
|
||||
@@ -8,6 +8,7 @@ from textual.widgets import Button, Label
|
||||
import heurams.kernel.particles as pt
|
||||
import heurams.kernel.puzzles as pz
|
||||
from heurams.services.hasher import hash
|
||||
from heurams.i18n import _
|
||||
from heurams.services.logger import get_logger
|
||||
from textual.events import Key
|
||||
from .base_puzzle_widget import BasePuzzleWidget
|
||||
@@ -65,15 +66,10 @@ class MCQPuzzle(BasePuzzleWidget):
|
||||
|
||||
def compose(self):
|
||||
setting: Setting = self.atom.registry["nucleon"]["puzzles"][self.alia]
|
||||
if len(self.inputlist) > len(self.puzzle.options):
|
||||
logger.debug("ERR IDX")
|
||||
logger.debug(self.inputlist)
|
||||
logger.debug(self.puzzle.options)
|
||||
else:
|
||||
current_options = self.puzzle.options[len(self.inputlist)]
|
||||
yield Label(setting["primary"], id="sentence")
|
||||
yield Label(self.puzzle.wording[len(self.inputlist)], id="puzzle")
|
||||
yield Label(f"当前输入: {self.inputlist}", id="inputpreview")
|
||||
current_options = self.puzzle.options[len(self.inputlist)]
|
||||
yield Label(setting["primary"], id="sentence")
|
||||
yield Label(self.puzzle.wording[len(self.inputlist)], id="puzzle")
|
||||
yield Label(_("Current input: {input}").format(input=self.inputlist), id="inputpreview")
|
||||
|
||||
# 渲染当前问题的选项
|
||||
c = 0
|
||||
@@ -85,21 +81,19 @@ class MCQPuzzle(BasePuzzleWidget):
|
||||
h = str(hash(i))
|
||||
self.hashmap[h] = i
|
||||
btnid = f"sel{str(self.cursor).zfill(3)}-{h}"
|
||||
logger.debug(f"建立按钮 {btnid}")
|
||||
self.btn_shortcuts[f"{c}"] = f"{btnid}"
|
||||
yield Button(f"[{c}] " + i, id=f"{btnid}")
|
||||
s.focus()
|
||||
yield Button("退格", id="delete")
|
||||
yield Button(_("Backspace"), id="delete")
|
||||
|
||||
self.btn_shortcuts["0"] = f"delete"
|
||||
self.btn_shortcuts["delete"] = f"delete"
|
||||
self.btn_shortcuts["backspace"] = f"delete"
|
||||
|
||||
def update_display(self, error=0):
|
||||
# 更新预览标签
|
||||
# Update preview label
|
||||
preview = self.query_one("#inputpreview")
|
||||
preview.update(f"当前输入: {self.inputlist}") # type: ignore
|
||||
logger.debug("已经更新预览标签")
|
||||
preview.update(_("Current input: {input}").format(input=self.inputlist)) # type: ignore
|
||||
# 更新问题标签
|
||||
puzzle_label = self.query_one("#puzzle")
|
||||
current_question_index = len(self.inputlist)
|
||||
@@ -122,7 +116,7 @@ class MCQPuzzle(BasePuzzleWidget):
|
||||
# 选项选择处理
|
||||
answer_text = self.hashmap[button_id[7:]] # type: ignore
|
||||
self.inputlist.append(answer_text)
|
||||
logger.debug(f"{self.inputlist}")
|
||||
logger.debug(f"Input list: {self.inputlist}")
|
||||
# 检查是否完成所有题目
|
||||
if len(self.inputlist) >= len(self.puzzle.answer):
|
||||
is_correct = self.inputlist == self.puzzle.answer
|
||||
@@ -143,7 +137,6 @@ class MCQPuzzle(BasePuzzleWidget):
|
||||
def refresh_buttons(self):
|
||||
"""刷新按钮显示(用于题目切换)"""
|
||||
# 移除所有选项按钮
|
||||
logger.debug("刷新按钮")
|
||||
self.cursor += 1
|
||||
container = self.query_one("#btn-container")
|
||||
buttons_to_remove = [
|
||||
@@ -153,7 +146,6 @@ class MCQPuzzle(BasePuzzleWidget):
|
||||
]
|
||||
container.focus()
|
||||
for button in buttons_to_remove:
|
||||
logger.info(button)
|
||||
container.remove_children("#" + button.id) # type: ignore
|
||||
|
||||
# 添加当前题目的选项按钮
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Button, Label
|
||||
|
||||
from heurams.i18n import _
|
||||
|
||||
|
||||
class Placeholder(Widget):
|
||||
def __init__(
|
||||
@@ -23,8 +25,8 @@ class Placeholder(Widget):
|
||||
)
|
||||
|
||||
def compose(self):
|
||||
yield Label("示例标签", id="testlabel")
|
||||
yield Button("示例按钮", id="testbtn", classes="choice")
|
||||
yield Label(_("Sample Label"), id="testlabel")
|
||||
yield Button(_("Sample Button"), id="testbtn", classes="choice")
|
||||
|
||||
def on_button_pressed(self, event):
|
||||
pass
|
||||
|
||||
@@ -6,6 +6,7 @@ from textual.widget import Widget
|
||||
from textual.widgets import Button, Label, Markdown, Static
|
||||
|
||||
import heurams.kernel.particles as pt
|
||||
from heurams.i18n import _
|
||||
from heurams.services.logger import get_logger
|
||||
|
||||
from .base_puzzle_widget import BasePuzzleWidget
|
||||
@@ -86,7 +87,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(_("### Note: {note}").format(note=j)) # TODO ANNOTATION
|
||||
continue
|
||||
if isinstance(item, Dict):
|
||||
total = ""
|
||||
@@ -97,7 +98,7 @@ class Recognition(BasePuzzleWidget):
|
||||
yield Markdown(item)
|
||||
|
||||
with Center() as c:
|
||||
with Button("我已知晓", id="ok") as b:
|
||||
with Button(_("I know this"), id="ok") as b:
|
||||
b.focus()
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
|
||||
@@ -44,38 +44,21 @@ class BaseAlgorithm:
|
||||
cls, algodata: dict, feedback: int = 5, is_new_activation: bool = False
|
||||
) -> None:
|
||||
"""迭代记忆数据"""
|
||||
logger.debug(
|
||||
"BaseAlgorithm.revisor 被调用, algodata keys: %s, feedback: %d, is_new_activation: %s",
|
||||
list(algodata.keys()) if algodata else [],
|
||||
feedback,
|
||||
is_new_activation,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def is_due(cls, algodata) -> int:
|
||||
"""是否应该复习"""
|
||||
logger.debug(
|
||||
"BaseAlgorithm.is_due 被调用, algodata keys: %s",
|
||||
list(algodata.keys()) if algodata else [],
|
||||
)
|
||||
return 1
|
||||
|
||||
@classmethod
|
||||
def get_rating(cls, algodata) -> str:
|
||||
"""获取评分信息"""
|
||||
logger.debug(
|
||||
"BaseAlgorithm.rate 被调用, algodata keys: %s",
|
||||
list(algodata.keys()) if algodata else [],
|
||||
)
|
||||
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
def nextdate(cls, algodata) -> int:
|
||||
"""获取下一次记忆时间戳"""
|
||||
logger.debug(
|
||||
"BaseAlgorithm.nextdate 被调用, algodata keys: %s",
|
||||
list(algodata.keys()) if algodata else [],
|
||||
)
|
||||
return -1
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -33,7 +33,7 @@ def _get_global_scheduler():
|
||||
with open(_SCHEDULER_STATE_FILE, "r", encoding="utf-8") as f:
|
||||
return Scheduler.from_json(f.read())
|
||||
except Exception:
|
||||
logger.warning("FSRS Scheduler 状态文件加载失败, 创建新实例")
|
||||
logger.warning("No former FSRS Scheduler file founded, creating new instance")
|
||||
return Scheduler()
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ def _save_global_scheduler(scheduler):
|
||||
with open(_SCHEDULER_STATE_FILE, "w", encoding="utf-8") as f:
|
||||
f.write(data)
|
||||
except Exception:
|
||||
logger.exception("FSRS Scheduler 状态保存失败")
|
||||
logger.error("Failed to persist FSRS Scheduler state")
|
||||
|
||||
|
||||
def _feedback_to_rating(feedback: int) -> Rating:
|
||||
@@ -176,14 +176,9 @@ class FSRSAlgorithm(BaseAlgorithm):
|
||||
feedback (int): 0-5 的记忆保留率量化参数
|
||||
is_new_activation: 是否为全新激活(重置为初始状态)
|
||||
"""
|
||||
logger.debug(
|
||||
"FSRS.revisor 开始, feedback: %d, is_new_activation: %s",
|
||||
feedback,
|
||||
is_new_activation,
|
||||
)
|
||||
|
||||
if feedback == -1:
|
||||
logger.debug("feedback 为 -1, 跳过更新")
|
||||
logger.debug("feedback = -1, update skipped")
|
||||
return
|
||||
|
||||
scheduler = _get_global_scheduler()
|
||||
@@ -191,7 +186,7 @@ class FSRSAlgorithm(BaseAlgorithm):
|
||||
|
||||
if is_new_activation:
|
||||
card = Card()
|
||||
logger.debug("新激活, 创建新 Card")
|
||||
logger.debug("New activation, create new Card")
|
||||
else:
|
||||
card = cls._algodata_to_card(algodata)
|
||||
|
||||
@@ -206,7 +201,7 @@ class FSRSAlgorithm(BaseAlgorithm):
|
||||
algodata[cls.algo_name]["rept"] += 1
|
||||
|
||||
logger.debug(
|
||||
"FSRS.revisor 完成: stability=%s, difficulty=%s, state=%s, " "next_date=%d",
|
||||
"FSRS.revisor finished: stability=%s, difficulty=%s, state=%s, " "next_date=%d",
|
||||
card.stability,
|
||||
card.difficulty,
|
||||
card.state,
|
||||
|
||||
@@ -51,7 +51,7 @@ class NSP0Algorithm(BaseAlgorithm):
|
||||
)
|
||||
|
||||
if feedback == -1:
|
||||
logger.debug("feedback 为 -1, 跳过更新")
|
||||
logger.debug("feedback = -1, update skipped")
|
||||
return
|
||||
algodata[cls.algo_name]["interval"] = 1 if feedback <= 3 else float("inf")
|
||||
if not algodata[cls.algo_name]["important"]:
|
||||
@@ -65,7 +65,7 @@ class NSP0Algorithm(BaseAlgorithm):
|
||||
algodata[cls.algo_name]["last_modify"] = timer.get_timestamp()
|
||||
|
||||
logger.debug(
|
||||
"更新日期: last_date=%d, next_date=%d, last_modify=%f",
|
||||
"Update date: last_date=%d, next_date=%d, last_modify=%f",
|
||||
algodata[cls.algo_name]["last_date"],
|
||||
algodata[cls.algo_name]["next_date"],
|
||||
algodata[cls.algo_name]["last_modify"],
|
||||
|
||||
@@ -614,7 +614,7 @@ def _get_global_sm():
|
||||
with open(_GLOBAL_STATE_FILE, "r", encoding="utf-8") as f:
|
||||
return SM.load(json.load(f))
|
||||
except Exception:
|
||||
logger.warning("SM-15M 全局状态文件加载失败, 创建新实例")
|
||||
logger.warning("Failed to load SM-15M global state file, creating new instance")
|
||||
sm = SM()
|
||||
_save_global_sm(sm)
|
||||
return sm
|
||||
@@ -626,7 +626,7 @@ def _save_global_sm(sm):
|
||||
with open(_GLOBAL_STATE_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(sm.data(), f, indent=2)
|
||||
except Exception:
|
||||
logger.exception("SM-15M 全局状态保存失败")
|
||||
logger.error("Failed to save SM-15M global state")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -758,7 +758,7 @@ class SM15MAlgorithm(BaseAlgorithm):
|
||||
cls, algodata: dict, feedback: int = 5, is_new_activation: bool = False
|
||||
):
|
||||
logger.debug(
|
||||
"SM-15M.revisor 开始, feedback=%d, is_new_activation=%s",
|
||||
"SM-15M.revisor, feedback=%d, is_new_activation=%s",
|
||||
feedback,
|
||||
is_new_activation,
|
||||
)
|
||||
@@ -788,7 +788,7 @@ class SM15MAlgorithm(BaseAlgorithm):
|
||||
algodata[cls.algo_name]["rept"] += 1
|
||||
|
||||
logger.debug(
|
||||
"SM-15M.revisor 完成: repetition=%d, of=%.4f, next_date=%d",
|
||||
"SM-15M.revisor: repetition=%d, of=%.4f, next_date=%d",
|
||||
item.repetition,
|
||||
item.of,
|
||||
algodata[cls.algo_name]["next_date"],
|
||||
|
||||
@@ -45,13 +45,13 @@ class SM2Algorithm(BaseAlgorithm):
|
||||
quality (int): 记忆保留率量化参数
|
||||
"""
|
||||
logger.debug(
|
||||
"SM2.revisor 开始, feedback: %d, is_new_activation: %s",
|
||||
"SM2.revisor, feedback: %d, is_new_activation: %s",
|
||||
feedback,
|
||||
is_new_activation,
|
||||
)
|
||||
|
||||
if feedback == -1:
|
||||
logger.debug("feedback 为 -1, 跳过更新")
|
||||
logger.debug("feedback = -1, update skipped")
|
||||
return
|
||||
|
||||
algodata[cls.algo_name]["efactor"] = algodata[cls.algo_name]["efactor"] + (
|
||||
@@ -60,7 +60,7 @@ class SM2Algorithm(BaseAlgorithm):
|
||||
algodata[cls.algo_name]["efactor"] = max(
|
||||
1.3, algodata[cls.algo_name]["efactor"]
|
||||
)
|
||||
logger.debug("更新 efactor: %f", algodata[cls.algo_name]["efactor"])
|
||||
logger.debug("Update efactor: %f", algodata[cls.algo_name]["efactor"])
|
||||
|
||||
if feedback < 3:
|
||||
algodata[cls.algo_name]["rept"] = 0
|
||||
@@ -68,28 +68,28 @@ class SM2Algorithm(BaseAlgorithm):
|
||||
logger.debug("feedback < 3, 重置 rept 和 interval")
|
||||
else:
|
||||
algodata[cls.algo_name]["rept"] += 1
|
||||
logger.debug("递增 rept: %d", algodata[cls.algo_name]["rept"])
|
||||
logger.debug("Increase rept: %d", algodata[cls.algo_name]["rept"])
|
||||
|
||||
algodata[cls.algo_name]["real_rept"] += 1
|
||||
logger.debug("递增 real_rept: %d", algodata[cls.algo_name]["real_rept"])
|
||||
logger.debug("Increase real_rept: %d", algodata[cls.algo_name]["real_rept"])
|
||||
|
||||
if is_new_activation:
|
||||
algodata[cls.algo_name]["rept"] = 0
|
||||
algodata[cls.algo_name]["efactor"] = 2.5
|
||||
logger.debug("新激活, 重置 rept 和 efactor")
|
||||
logger.debug("New activation, reset rept and efactor")
|
||||
|
||||
if algodata[cls.algo_name]["rept"] == 0:
|
||||
algodata[cls.algo_name]["interval"] = 1
|
||||
logger.debug("rept=0, 设置 interval=1")
|
||||
logger.debug("rept=0, set interval=1")
|
||||
elif algodata[cls.algo_name]["rept"] == 1:
|
||||
algodata[cls.algo_name]["interval"] = 6
|
||||
logger.debug("rept=1, 设置 interval=6")
|
||||
logger.debug("rept=1, set interval=6")
|
||||
else:
|
||||
algodata[cls.algo_name]["interval"] = round(
|
||||
algodata[cls.algo_name]["interval"] * algodata[cls.algo_name]["efactor"]
|
||||
)
|
||||
logger.debug(
|
||||
"rept>1, 计算 interval: %d", algodata[cls.algo_name]["interval"]
|
||||
"rept>1, providing interval: %d", algodata[cls.algo_name]["interval"]
|
||||
)
|
||||
|
||||
algodata[cls.algo_name]["last_date"] = timer.get_daystamp()
|
||||
@@ -99,7 +99,7 @@ class SM2Algorithm(BaseAlgorithm):
|
||||
algodata[cls.algo_name]["last_modify"] = timer.get_timestamp()
|
||||
|
||||
logger.debug(
|
||||
"更新日期: last_date=%d, next_date=%d, last_modify=%f",
|
||||
"Update date: last_date=%d, next_date=%d, last_modify=%f",
|
||||
algodata[cls.algo_name]["last_date"],
|
||||
algodata[cls.algo_name]["next_date"],
|
||||
algodata[cls.algo_name]["last_modify"],
|
||||
|
||||
@@ -63,7 +63,7 @@ class Atom:
|
||||
)
|
||||
|
||||
def lock(self, locked=-1):
|
||||
logger.debug(f"锁定参数 {locked}")
|
||||
logger.debug(f"Lock atom: {locked}")
|
||||
"""锁定, 效果等同于 self.registry['runtime']['locked'] = locked 或者返回是否锁定"""
|
||||
if locked == 1:
|
||||
self.registry["runtime"]["locked"] = True
|
||||
@@ -80,13 +80,13 @@ class Atom:
|
||||
PuzzleWidget 的 handler 除了测试, 严禁直接执行 Electron 的 revisor 函数, 否则造成逻辑混乱
|
||||
"""
|
||||
if self.registry["runtime"]["locked"]:
|
||||
logger.debug(f"允许总评分: {self.registry['runtime']['min_rate']}")
|
||||
logger.debug(f"Rating allowed: {self.registry['runtime']['min_rate']}")
|
||||
self.registry["electron"].revisor(
|
||||
self.registry["runtime"]["min_rate"],
|
||||
is_new_activation=self.registry["runtime"]["new_activation"],
|
||||
)
|
||||
else:
|
||||
logger.debug("禁止总评分")
|
||||
logger.debug("Rating disallowed")
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.registry[key]
|
||||
|
||||
@@ -8,9 +8,7 @@ class BasePuzzle:
|
||||
"""谜题基类"""
|
||||
|
||||
def refresh(self):
|
||||
logger.debug("BasePuzzle.refresh 被调用(未实现)")
|
||||
raise NotImplementedError("谜题对象未实现 refresh 方法")
|
||||
raise NotImplementedError("Method refresh not implemented")
|
||||
|
||||
def __str__(self):
|
||||
logger.debug("BasePuzzle.__str__ 被调用")
|
||||
return f"谜题: {type(self).__name__}"
|
||||
return f"Puzzle: {type(self).__name__}"
|
||||
|
||||
@@ -27,20 +27,20 @@ class ClozePuzzle(BasePuzzle):
|
||||
self.wording = "填空题 - 尚未刷新谜题"
|
||||
self.answer = ["填空题 - 尚未刷新谜题"]
|
||||
self.delimiter = delimiter
|
||||
logger.debug("ClozePuzzle 初始化完成")
|
||||
logger.debug("ClozePuzzle inited")
|
||||
|
||||
def refresh(self): # 刷新谜题
|
||||
logger.debug("ClozePuzzle.refresh 开始")
|
||||
logger.debug("ClozePuzzle.refresh")
|
||||
placeholder = "___SLASH___"
|
||||
tmp_text = self.text.replace(self.delimiter, placeholder)
|
||||
words = tmp_text.split(placeholder)
|
||||
if not words:
|
||||
logger.warning("ClozePuzzle.refresh: 无单词可处理")
|
||||
logger.warning("ClozePuzzle.refresh: nothing to proceed")
|
||||
return
|
||||
words = [word for word in words if word]
|
||||
logger.debug("ClozePuzzle.refresh: 分割出 %d 个单词", len(words))
|
||||
logger.debug("ClozePuzzle.refresh: %d words splited", len(words))
|
||||
num_blanks = min(max(1, len(words) // self.min_denominator), len(words))
|
||||
logger.debug("ClozePuzzle.refresh: 需要生成 %d 个填空", num_blanks)
|
||||
logger.debug("ClozePuzzle.refresh: %d blank required", num_blanks)
|
||||
indices_to_blank = random.sample(range(len(words)), num_blanks)
|
||||
indices_to_blank.sort()
|
||||
blanked_words = list(words)
|
||||
@@ -50,8 +50,7 @@ class ClozePuzzle(BasePuzzle):
|
||||
answer.append(words[index])
|
||||
self.answer = answer
|
||||
self.wording = "".join(blanked_words)
|
||||
logger.debug("ClozePuzzle.refresh 完成, 生成 %d 个填空", len(answer))
|
||||
logger.debug("ClozePuzzle.refresh, %d blanks generated", len(answer))
|
||||
|
||||
def __str__(self):
|
||||
logger.debug("ClozePuzzle.__str__ 被调用")
|
||||
return f"{self.wording}\n{str(self.answer)}"
|
||||
|
||||
@@ -65,8 +65,8 @@ class MCQPuzzle(BasePuzzle):
|
||||
jammer: 传入的干扰项列表
|
||||
"""
|
||||
# 合并正确答案和传入的干扰项, 并去重
|
||||
logger.debug(f"答案映射: {self.mapping}, {type(self.mapping)}")
|
||||
logger.debug(f"干扰项: {jammer}, {type(jammer)}")
|
||||
logger.debug(f"Answer table: {self.mapping}, {type(self.mapping)}")
|
||||
logger.debug(f"Jammers: {jammer}, {type(jammer)}")
|
||||
unique_jammers = set(jammer + list(self.mapping.values()))
|
||||
self.jammer = list(unique_jammers)
|
||||
|
||||
@@ -94,7 +94,7 @@ class MCQPuzzle(BasePuzzle):
|
||||
Raises:
|
||||
ValueError: 当mapping为空时不会抛出异常, 但会设置空谜题状态
|
||||
"""
|
||||
logger.debug("MCQPuzzle.refresh 开始, mapping size=%d", len(self.mapping))
|
||||
logger.debug("MCQPuzzle.refresh, mapping size=%d", len(self.mapping))
|
||||
if not self.mapping:
|
||||
self._set_empty_puzzle()
|
||||
return
|
||||
|
||||
@@ -11,8 +11,7 @@ class RecognitionPuzzle(BasePuzzle):
|
||||
"""识别占位符"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
logger.debug("RecognitionPuzzle.__init__")
|
||||
super().__init__()
|
||||
|
||||
def refresh(self):
|
||||
logger.debug("RecognitionPuzzle.refresh(空实现)")
|
||||
pass
|
||||
|
||||
@@ -48,7 +48,7 @@ class Expander(Machine):
|
||||
self.puzzles_inf = list()
|
||||
self.min_ratings = []
|
||||
for item, possibility in orbital_schedule: # type: ignore
|
||||
logger.debug(f"开始处理: {item}")
|
||||
logger.debug(f"Process: {item}")
|
||||
|
||||
puzzle = puz.puzzles[orbital_puzzles[item]["__origin__"]]
|
||||
|
||||
|
||||
@@ -14,12 +14,6 @@ class Procession(Machine):
|
||||
"""队列: 标识单次记忆流程"""
|
||||
|
||||
def __init__(self, atoms: list, route_state: RouterState, name_: str = ""):
|
||||
logger.debug(
|
||||
"Procession.__init__: 原子数量=%d, route=%s, name='%s'",
|
||||
len(atoms),
|
||||
route_state.value,
|
||||
name_,
|
||||
)
|
||||
self.current_atom: pt.Atom | None
|
||||
self.atoms = atoms
|
||||
self.current_atom = atoms[0] if atoms else None
|
||||
@@ -52,65 +46,48 @@ class Procession(Machine):
|
||||
initial=ProcessionState.ACTIVE.value,
|
||||
)
|
||||
|
||||
logger.debug("Procession 初始化完成, 队列长度=%d", len(self.atoms))
|
||||
|
||||
def on_active(self):
|
||||
"""进入active状态时的回调"""
|
||||
logger.debug("Procession 进入 active 状态")
|
||||
pass
|
||||
|
||||
def on_finished(self):
|
||||
"""进入FINISHED状态时的回调"""
|
||||
logger.debug("Procession 进入 FINISHED 状态")
|
||||
pass
|
||||
|
||||
def forward(self, step=1):
|
||||
"""将记忆原子指针向前移动并依情况更新原子(返回 1)或完成队列(返回 0)"""
|
||||
logger.debug("Procession.forward: step=%d, 当前 cursor=%d", step, self.cursor)
|
||||
self.cursor += step
|
||||
if self.cursor >= len(self.atoms):
|
||||
if self.state != ProcessionState.FINISHED.value:
|
||||
self.finish() # 触发状态转换
|
||||
logger.debug("Procession 已完成")
|
||||
else:
|
||||
if self.state != ProcessionState.ACTIVE.value:
|
||||
self.restart() # 确保在active状态
|
||||
self.current_atom = self.atoms[self.cursor]
|
||||
logger.debug("cursor 更新为: %d", self.cursor)
|
||||
logger.debug(
|
||||
"当前原子更新为: %s",
|
||||
self.current_atom.ident if self.current_atom else "None",
|
||||
)
|
||||
|
||||
def append(self, atom=None):
|
||||
"""追加(回忆失败的)原子(默认为当前原子)到队列末端"""
|
||||
if atom is None:
|
||||
atom = self.current_atom
|
||||
logger.debug("Procession.append: atom=%s", atom.ident if atom else "None")
|
||||
|
||||
if not self.atoms or self.atoms[-1] != atom or len(self) <= 1:
|
||||
self.atoms.append(atom)
|
||||
logger.debug("原子已追加到队列, 新队列长度=%d", len(self.atoms))
|
||||
else:
|
||||
logger.debug("原子未追加(重复或队列长度<=1)")
|
||||
|
||||
def __len__(self):
|
||||
if not self.atoms:
|
||||
return 0
|
||||
length = len(self.atoms) - self.cursor
|
||||
logger.debug("Procession.__len__: 剩余长度=%d", length)
|
||||
return length
|
||||
|
||||
def process(self):
|
||||
logger.debug("Procession.process: cursor=%d", self.cursor)
|
||||
return self.cursor
|
||||
|
||||
def total_length(self):
|
||||
total = len(self.atoms)
|
||||
logger.debug("Procession.total_length: %d", total)
|
||||
return total
|
||||
|
||||
def is_empty(self):
|
||||
empty = len(self.atoms) == 0
|
||||
logger.debug("Procession.is_empty: %s", empty)
|
||||
return empty
|
||||
|
||||
def get_expander(self):
|
||||
|
||||
@@ -14,7 +14,7 @@ class Router(Machine):
|
||||
"""全局调度阶段路由器"""
|
||||
|
||||
def __init__(self, atoms: list[pt.Atom]) -> None:
|
||||
logger.debug(f"Router.__init__: 原子数量={len(atoms)}")
|
||||
logger.debug(f"number of atoms={len(atoms)}")
|
||||
|
||||
self.atoms = atoms
|
||||
new_atoms = list()
|
||||
@@ -26,7 +26,7 @@ class Router(Machine):
|
||||
else:
|
||||
old_atoms.append(i)
|
||||
|
||||
logger.debug(f"新原子数量={len(new_atoms)}, 旧原子数量={len(old_atoms)}")
|
||||
logger.debug(f"number of new atoms={len(new_atoms)}, number of old atoms={len(old_atoms)}")
|
||||
|
||||
self.processions = list()
|
||||
"""路由中的所有队列"""
|
||||
@@ -35,17 +35,14 @@ class Router(Machine):
|
||||
self.processions.append(
|
||||
Procession(old_atoms, RouterState.QUICK_REVIEW, "初始复习")
|
||||
)
|
||||
logger.debug("创建初始复习 Procession")
|
||||
|
||||
if len(new_atoms):
|
||||
self.processions.append(
|
||||
Procession(new_atoms, RouterState.RECOGNITION, "新记忆")
|
||||
)
|
||||
logger.debug("创建新记忆 Procession")
|
||||
|
||||
self.processions.append(Procession(atoms, RouterState.FINAL_REVIEW, "总体复习"))
|
||||
logger.debug("创建总体复习 Procession")
|
||||
logger.debug("Router 初始化完成, processions 数量=%d", len(self.processions))
|
||||
logger.debug("Router inited, number of processions =%d", len(self.processions))
|
||||
|
||||
# 设置transitions状态机
|
||||
states = [
|
||||
@@ -91,29 +88,27 @@ class Router(Machine):
|
||||
|
||||
def on_unsure(self):
|
||||
"""进入UNSURE状态时的回调"""
|
||||
logger.debug("Router 进入 UNSURE 状态")
|
||||
pass
|
||||
|
||||
def on_quick_review(self):
|
||||
"""进入QUICK_REVIEW状态时的回调"""
|
||||
logger.debug("Router 进入 QUICK_REVIEW 状态")
|
||||
pass
|
||||
|
||||
def on_recognition(self):
|
||||
"""进入RECOGNITION状态时的回调"""
|
||||
logger.debug("Router 进入 RECOGNITION 状态")
|
||||
pass
|
||||
|
||||
def on_final_review(self):
|
||||
"""进入FINAL_REVIEW状态时的回调"""
|
||||
logger.debug("Router 进入 FINAL_REVIEW 状态")
|
||||
pass
|
||||
|
||||
def on_finished(self):
|
||||
"""进入FINISHED状态时的回调"""
|
||||
for i in self.atoms:
|
||||
i.lock(1)
|
||||
i.revise()
|
||||
logger.debug("Router 进入 FINISHED 状态")
|
||||
|
||||
def current_procession(self):
|
||||
logger.debug("Router.current_procession 被调用")
|
||||
for i in self.processions:
|
||||
i: Procession
|
||||
if i.state != ProcessionState.FINISHED.value:
|
||||
@@ -125,12 +120,10 @@ class Router(Machine):
|
||||
elif i.route == RouterState.FINAL_REVIEW:
|
||||
self.to_final_review()
|
||||
|
||||
logger.debug("找到未完成的 Procession: route=%s", i.route)
|
||||
return i
|
||||
|
||||
# 所有Procession都已完成
|
||||
self.to_finished()
|
||||
logger.debug("所有 Procession 已完成, 状态设置为 FINISHED")
|
||||
return Procession([AtomPlaceholder()], RouterState.FINISHED)
|
||||
|
||||
def __repr__(self, style="pipe", ends="\n"):
|
||||
|
||||
@@ -20,7 +20,4 @@ class ProcessionState(Enum):
|
||||
|
||||
class ExpanderState(Enum):
|
||||
EXAMMODE = "exammode"
|
||||
RETRONLY = "retronly"
|
||||
|
||||
|
||||
logger.debug("状态枚举定义已加载")
|
||||
RETRONLY = "retronly"
|
||||
@@ -0,0 +1,3 @@
|
||||
[General]
|
||||
LangCode=zh_CN
|
||||
TargetLangCode=zh_CN
|
||||
Binary file not shown.
@@ -0,0 +1,670 @@
|
||||
# Japanese translations for HeurAMS.
|
||||
# Copyright (C) 2026 Wang Zhiyu
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: heurams 0.5.1\n"
|
||||
"Language: ja\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
|
||||
msgid "Welcome to the basic user interface!"
|
||||
msgstr "基本ユーザーインターフェースへようこそ!"
|
||||
|
||||
msgid "Loading config and context... "
|
||||
msgstr "設定とコンテキストを読み込み中… "
|
||||
|
||||
msgid "Done! ({time}ms)"
|
||||
msgstr "完了!({time}ms)"
|
||||
|
||||
msgid "Loading UI framework... "
|
||||
msgstr "UI フレームワークを読み込み中… "
|
||||
|
||||
msgid "Loading UI layout... "
|
||||
msgstr "UI レイアウトを読み込み中… "
|
||||
|
||||
msgid "Component directory: {path}"
|
||||
msgstr "コンポーネントディレクトリ: {path}"
|
||||
|
||||
msgid "Working directory: {path}"
|
||||
msgstr "作業ディレクトリ: {path}"
|
||||
|
||||
msgid "Pre-work total: {time}ms"
|
||||
msgstr "前処理合計: {time}ms"
|
||||
|
||||
msgid "Heuristic Auxiliary Memorizing Scheduler"
|
||||
msgstr "ヒューリスティック補助記憶スケジューラ"
|
||||
|
||||
msgid "Quit"
|
||||
msgstr "終了"
|
||||
|
||||
msgid "Theme"
|
||||
msgstr "テーマ"
|
||||
|
||||
msgid "Navigate"
|
||||
msgstr "ナビゲート"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "設定"
|
||||
|
||||
msgid "About"
|
||||
msgstr "このソフトについて"
|
||||
|
||||
msgid "HeurAMS {ver} - Heuristic Auxiliary Memorizing Scheduler"
|
||||
msgstr "HeurAMS {ver} - ヒューリスティック補助記憶スケジューラ"
|
||||
|
||||
msgid "Listening address"
|
||||
msgstr "リッスンアドレス"
|
||||
|
||||
msgid "Listening port"
|
||||
msgstr "リッスンポート"
|
||||
|
||||
msgid "Development mode hot reload"
|
||||
msgstr "開発モードのホットリロード"
|
||||
|
||||
msgid "Show this help message"
|
||||
msgstr "このヘルプを表示"
|
||||
|
||||
msgid "unifront API service started: http://{host}:{port}"
|
||||
msgstr "unifront API サービス開始: http://{host}:{port}"
|
||||
|
||||
msgid "Show the version and exit."
|
||||
msgstr "バージョンを表示して終了。"
|
||||
|
||||
msgid "Explicitly specify locale (defaults to LANG env)."
|
||||
msgstr "UI ロケール(例: en_US, ja)。デフォルトは LANG 環境変数。"
|
||||
|
||||
msgid "Launch the built-in user interface (TUI)"
|
||||
msgstr "内蔵 UI(TUI)を起動"
|
||||
|
||||
msgid "Launch the API service (unifront)"
|
||||
msgstr "API サービス(unifront)を起動"
|
||||
|
||||
msgid "Dashboard"
|
||||
msgstr "ダッシュボード"
|
||||
|
||||
msgid "Back"
|
||||
msgstr "戻る"
|
||||
|
||||
msgid "Current daystamp: {ds}"
|
||||
msgstr "現在の日付スタンプ: {ds}"
|
||||
|
||||
msgid "Timezone offset: UTC+{offset}"
|
||||
msgstr "タイムゾーンオフセット: UTC+{offset}"
|
||||
|
||||
msgid "Default algorithm: {algo}"
|
||||
msgstr "デフォルトアルゴリズム: {algo}"
|
||||
|
||||
msgid "Loaded {n} repo(s)"
|
||||
msgstr "リポジトリ {n} 個を読み込み"
|
||||
|
||||
msgid "Total {n} unit(s)"
|
||||
msgstr "ユニット合計 {n}"
|
||||
|
||||
msgid "Activated {n} unit(s)"
|
||||
msgstr "アクティブ {n} ユニット"
|
||||
|
||||
msgid "Version {ver}-{stage}"
|
||||
msgstr "バージョン {ver}-{stage}"
|
||||
|
||||
msgid "Processed {puzzles} puzzles in {time}s, accuracy {accuracy}, speed {speed} puzzle(s)/s"
|
||||
msgstr "{time} 秒で {puzzles} 問処理, 正解率 {accuracy}, 速度 {speed} 問/秒"
|
||||
|
||||
msgid "N/A"
|
||||
msgstr "N/A"
|
||||
|
||||
msgid ""
|
||||
"No repo directories found in {path}.\n"
|
||||
"Please import a repo and restart, or create a new one."
|
||||
msgstr ""
|
||||
"{path} にリポジトリが見つかりません。\n"
|
||||
"リポジトリをインポートして再起動するか、新規作成してください。"
|
||||
|
||||
msgid "Start Learning"
|
||||
msgstr "学習開始"
|
||||
|
||||
msgid ""
|
||||
"{title} [{algo}]\n"
|
||||
" [d]Progress: {touched}/{total} ({pct}%)[/d]\n"
|
||||
" [d]{status}[/d]"
|
||||
msgstr ""
|
||||
"{title} [{algo}]\n"
|
||||
" [d]進捗: {touched}/{total} ({pct}%)[/d]\n"
|
||||
" [d]{status}[/d]"
|
||||
|
||||
msgid "Due: {review}R + {new}U"
|
||||
msgstr "期限: {review}R + {new}U"
|
||||
|
||||
msgid "Not started: 0R + {new}U"
|
||||
msgstr "未開始: 0R + {new}U"
|
||||
|
||||
msgid "Up to date"
|
||||
msgstr "最新"
|
||||
|
||||
msgid "[b]About & Version Info[/b]"
|
||||
msgstr "[b]バージョン情報[/b]"
|
||||
|
||||
msgid ""
|
||||
"# About HeurAMS\n"
|
||||
"\n"
|
||||
"Main library version: `{ver}-python` \n"
|
||||
"UI frontend: `Textual TUI (Basic UI)` \n"
|
||||
"UI version: `{ver}` \n"
|
||||
"API codename: `{codename}` \n"
|
||||
"\n"
|
||||
"> A heuristic auxiliary memorizing scheduler based on heuristic algorithms and cognitive science theories, designed to help users memorize and plan learning more efficiently. \n"
|
||||
"> An open, elegant, and extensible spaced repetition scheduler experimental platform, designed to help researchers conduct investigations, experiments, and research on cutting-edge memory algorithms more efficiently. \n"
|
||||
"\n"
|
||||
"You can visit the project homepage at https://ams.pluv27.top for user guides, development documentation and software updates, and participate in software development and improvement. \n"
|
||||
"\n"
|
||||
"Open source under the GNU Affero General Public License (version 3), with an additional exemption clause for local API calls, used for other frontend to library interface calls. \n"
|
||||
"\n"
|
||||
"You are using the built-in terminal user interface, which is the first full-featured frontend implementation and library test suite, located in the interface subdirectory of the library. \n"
|
||||
"\n"
|
||||
"Developers: \n"
|
||||
"- Wang Zhiyu ([@pluvium27](https://github.com/pluvium27)): Project initiator and lead developer \n"
|
||||
"\n"
|
||||
"Special thanks to the following individuals and groups; their algorithms and theories form the cornerstone of the current software algorithms: \n"
|
||||
"\n"
|
||||
"- [Piotr A. Woźniak](https://supermemo.guru/wiki/Piotr_Wozniak): SM-2 algorithm and SM-15 algorithm theory \n"
|
||||
"- [Jarrett Ye](https://github.com/L-M-Sherlock): FSRS algorithm and spaced repetition theory references \n"
|
||||
"- [Kazuaki Tanida](https://github.com/slaypni): CoffeeScript reverse implementation of SM-15 algorithm \n"
|
||||
"- [Open Spaced Repetition](https://github.com/open-spaced-repetition): FSRS algorithm underlying implementation \n"
|
||||
"\n"
|
||||
"# Runtime Environment\n"
|
||||
"\n"
|
||||
"Python interpreter version: {python_version} \n"
|
||||
"Python interpreter path: {executable} \n"
|
||||
"Textual framework version: {textual_version} \n"
|
||||
"Terminal emulator: {terminal_info} \n"
|
||||
"Operating system version: {os_version} \n"
|
||||
"Disk free space: {disk_usage} \n"
|
||||
"\n"
|
||||
"When reporting issues, please copy this information into the issue description and attach `heurams.log` as an attachment to help developers locate the error."
|
||||
msgstr ""
|
||||
"# HeurAMS について\n"
|
||||
"\n"
|
||||
"メインライブラリバージョン: `{ver}-python` \n"
|
||||
"UI フロントエンド: `Textual TUI(基本 UI)` \n"
|
||||
"UI バージョン: `{ver}` \n"
|
||||
"API コードネーム: `{codename}` \n"
|
||||
"\n"
|
||||
"> ヒューリスティックアルゴリズムと認知科学理論に基づく補助記憶スケジューラ。 \n"
|
||||
"> オープンで拡張可能な間隔反復実験プラットフォーム。 \n"
|
||||
"\n"
|
||||
"プロジェクトHP: https://ams.pluv27.top \n"
|
||||
"\n"
|
||||
"GNU AGPL-3.0 で公開。ローカル API 呼び出しの例外条項付き。 \n"
|
||||
"\n"
|
||||
"開発者: \n"
|
||||
"- Wang Zhiyu ([@pluvium27](https://github.com/pluvium27)) \n"
|
||||
"\n"
|
||||
"謝辞: \n"
|
||||
"- [Piotr A. Woźniak](https://supermemo.guru/wiki/Piotr_Wozniak): SM-2, SM-15 \n"
|
||||
"- [Jarrett Ye](https://github.com/L-M-Sherlock): FSRS \n"
|
||||
"- [Kazuaki Tanida](https://github.com/slaypni): SM-15 CoffeeScript 実装 \n"
|
||||
"- [Open Spaced Repetition](https://github.com/open-spaced-repetition): FSRS 実装 \n"
|
||||
"\n"
|
||||
"# 実行環境\n"
|
||||
"\n"
|
||||
"Python: {python_version} \n"
|
||||
"Python パス: {executable} \n"
|
||||
"Textual: {textual_version} \n"
|
||||
"端末: {terminal_info} \n"
|
||||
"OS: {os_version} \n"
|
||||
"空き容量: {disk_usage} \n"
|
||||
"\n"
|
||||
"問題報告時はこの情報を添付してください。"
|
||||
|
||||
msgid "Back to Main"
|
||||
msgstr "メインに戻る"
|
||||
|
||||
msgid "Unknown"
|
||||
msgstr "不明"
|
||||
|
||||
msgid "Requires a float"
|
||||
msgstr "浮動小数点数が必要"
|
||||
|
||||
msgid "Requires a string"
|
||||
msgstr "文字列が必要"
|
||||
|
||||
msgid "Requires an integer"
|
||||
msgstr "整数が必要"
|
||||
|
||||
msgid "Unknown type"
|
||||
msgstr "不明な型"
|
||||
|
||||
msgid "No sub-items"
|
||||
msgstr "サブ項目なし"
|
||||
|
||||
msgid "Changes are saved immediately when you leave this page, but restart is recommended to ensure the new configuration is applied."
|
||||
msgstr "このページを離れると変更は即保存されますが、確実に適用するには再起動を推奨します。"
|
||||
|
||||
msgid "Previous"
|
||||
msgstr "前へ"
|
||||
|
||||
msgid "Read Aloud"
|
||||
msgstr "読み上げ"
|
||||
|
||||
msgid "Favorite"
|
||||
msgstr "お気に入り"
|
||||
|
||||
msgid "Learning"
|
||||
msgstr "学習中"
|
||||
|
||||
msgid "Correct"
|
||||
msgstr "正解"
|
||||
|
||||
msgid "Incorrect"
|
||||
msgstr "不正解"
|
||||
|
||||
msgid "Failed to generate puzzle: {e}"
|
||||
msgstr "パズル生成失敗: {e}"
|
||||
|
||||
msgid "Favorited"
|
||||
msgstr "お気に入り済み"
|
||||
|
||||
msgid "Not favorited"
|
||||
msgstr "未お気に入り"
|
||||
|
||||
msgid "Previous: {ident}"
|
||||
msgstr "前: {ident}"
|
||||
|
||||
msgid "Are you sure? Press uppercase Q to go back."
|
||||
msgstr "よろしいですか? 大文字 Q で戻ります。"
|
||||
|
||||
msgid "Cannot favorite: no repo associated"
|
||||
msgstr "お気に入り不可: リポジトリが未関連"
|
||||
|
||||
msgid "Unfavorited: {ident}"
|
||||
msgstr "お気に入り解除: {ident}"
|
||||
|
||||
msgid "Favorited: {ident}"
|
||||
msgstr "お気に入り登録: {ident}"
|
||||
|
||||
msgid "This function is not available during memorization. Please finish or go back first."
|
||||
msgstr "この機能は記憶中は使用できません。終了または戻ってからお試しください。"
|
||||
|
||||
msgid "Time resume corrected: {old} -> {new}"
|
||||
msgstr "時間再開補正: {old} -> {new}"
|
||||
|
||||
msgid " [Debug Connected]"
|
||||
msgstr " [デバッグ接続済]"
|
||||
|
||||
msgid "Favorites"
|
||||
msgstr "お気に入り"
|
||||
|
||||
msgid "No favorites"
|
||||
msgstr "お気に入りなし"
|
||||
|
||||
msgid "Press * in the memorization screen to add favorites."
|
||||
msgstr "記憶画面で * キーを押すとお気に入りに追加。"
|
||||
|
||||
msgid "Total {n} favorite(s)"
|
||||
msgstr "お気に入り合計 {n}"
|
||||
|
||||
msgid ""
|
||||
" [d]Added: {time}\n"
|
||||
" From {title}[/d]"
|
||||
msgstr ""
|
||||
" [d]追加: {time}\n"
|
||||
" 出典: {title}[/d]"
|
||||
|
||||
msgid "Remove"
|
||||
msgstr "削除"
|
||||
|
||||
msgid "Operation failed: invalid button identifier"
|
||||
msgstr "操作失敗: 無効なボタンID"
|
||||
|
||||
msgid "Removed favorite: {ident}"
|
||||
msgstr "お気に入り削除: {ident}"
|
||||
|
||||
msgid "Failed to remove: {ident}"
|
||||
msgstr "削除失敗: {ident}"
|
||||
|
||||
msgid "Switch"
|
||||
msgstr "切替"
|
||||
|
||||
msgid "Cache Manager"
|
||||
msgstr "キャッシュ管理"
|
||||
|
||||
msgid "Settings Page"
|
||||
msgstr "設定ページ"
|
||||
|
||||
msgid "Sync Tool"
|
||||
msgstr "同期ツール"
|
||||
|
||||
msgid "Exit"
|
||||
msgstr "終了"
|
||||
|
||||
msgid "Project Homepage"
|
||||
msgstr "プロジェクトHP"
|
||||
|
||||
msgid ""
|
||||
"[b]Select a function to navigate to\n"
|
||||
"or a memorization session instance[/b]\n"
|
||||
"\n"
|
||||
"Tips will be displayed here"
|
||||
msgstr ""
|
||||
"[b]移動先の機能または\n"
|
||||
"記憶セッションを選択[/b]\n"
|
||||
"\n"
|
||||
"ヒントがここに表示されます"
|
||||
|
||||
msgid ""
|
||||
"Press Enter to switch\n"
|
||||
"All sessions will be saved"
|
||||
msgstr ""
|
||||
"Enter で切替\n"
|
||||
"全セッションは保存されます"
|
||||
|
||||
msgid "Close (n)"
|
||||
msgstr "閉じる (n)"
|
||||
|
||||
msgid "Prepare Repository"
|
||||
msgstr "リポジトリ準備"
|
||||
|
||||
msgid "Cache"
|
||||
msgstr "キャッシュ"
|
||||
|
||||
msgid ""
|
||||
"**Ready**: `{title}`\n"
|
||||
""
|
||||
msgstr ""
|
||||
"**準備完了**: `{title}`\n"
|
||||
""
|
||||
|
||||
msgid "Repo path: {path}"
|
||||
msgstr "リポジトリパス: {path}"
|
||||
|
||||
msgid "Progress: {touched}/{total} [{pct}%]"
|
||||
msgstr "進捗: {touched}/{total} [{pct}%]"
|
||||
|
||||
msgid "Scheduling algorithm: {algo} {desc}"
|
||||
msgstr "スケジュールアルゴリズム: {algo} {desc}"
|
||||
|
||||
msgid ""
|
||||
"Study count: {total} = {review} [d][Review][/d] + {new} [d][New][/d]\n"
|
||||
""
|
||||
msgstr ""
|
||||
"学習数: {total} = {review} [d][復習][/d] + {new} [d][新規][/d]\n"
|
||||
""
|
||||
|
||||
msgid "Start Memorizing"
|
||||
msgstr "記憶開始"
|
||||
|
||||
msgid "Manage Cache"
|
||||
msgstr "キャッシュ管理"
|
||||
|
||||
msgid "Sync protocol: {proto}"
|
||||
msgstr "同期プロトコル: {proto}"
|
||||
|
||||
msgid "Server Configuration:"
|
||||
msgstr "サーバー設定:"
|
||||
|
||||
msgid "Remote server:"
|
||||
msgstr "リモートサーバー:"
|
||||
|
||||
msgid "Remote path:"
|
||||
msgstr "リモートパス:"
|
||||
|
||||
msgid "Test Connection"
|
||||
msgstr "接続テスト"
|
||||
|
||||
msgid "Start Sync"
|
||||
msgstr "同期開始"
|
||||
|
||||
msgid "Pause"
|
||||
msgstr "一時停止"
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr "キャンセル"
|
||||
|
||||
msgid "Sync Progress"
|
||||
msgstr "同期進捗"
|
||||
|
||||
msgid "Sync Log"
|
||||
msgstr "同期ログ"
|
||||
|
||||
msgid "Not configured"
|
||||
msgstr "未設定"
|
||||
|
||||
msgid "Sync service ready"
|
||||
msgstr "同期サービス準備完了"
|
||||
|
||||
msgid "Sync service not configured or not enabled"
|
||||
msgstr "同期サービスが未設定または無効"
|
||||
|
||||
msgid "Failed to update UI: {error}"
|
||||
msgstr "UI 更新失敗: {error}"
|
||||
|
||||
msgid "Failed to update status: {error}"
|
||||
msgstr "ステータス更新失敗: {error}"
|
||||
|
||||
msgid "Sync service not initialised, please check configuration"
|
||||
msgstr "同期サービスが未初期化。設定を確認してください。"
|
||||
|
||||
msgid "Sync service not initialised"
|
||||
msgstr "同期サービス未初期化"
|
||||
|
||||
msgid "Testing WebDAV connection..."
|
||||
msgstr "WebDAV 接続テスト中…"
|
||||
|
||||
msgid "Testing connection..."
|
||||
msgstr "接続テスト中…"
|
||||
|
||||
msgid "Connection test successful"
|
||||
msgstr "接続テスト成功"
|
||||
|
||||
msgid "Connection OK"
|
||||
msgstr "接続正常"
|
||||
|
||||
msgid "Connection test failed"
|
||||
msgstr "接続テスト失敗"
|
||||
|
||||
msgid "Connection failed"
|
||||
msgstr "接続失敗"
|
||||
|
||||
msgid "Connection test error: {error}"
|
||||
msgstr "接続テストエラー: {error}"
|
||||
|
||||
msgid "Connection error"
|
||||
msgstr "接続エラー"
|
||||
|
||||
msgid "Sync service not initialised, cannot start sync"
|
||||
msgstr "同期サービス未初期化のため開始不可"
|
||||
|
||||
msgid "Sync already in progress"
|
||||
msgstr "同期は既に実行中"
|
||||
|
||||
msgid "Starting data sync..."
|
||||
msgstr "データ同期開始…"
|
||||
|
||||
msgid "Syncing..."
|
||||
msgstr "同期中…"
|
||||
|
||||
msgid "Syncing nucleon directory: {dir}"
|
||||
msgstr "nucleon ディレクトリ同期中: {dir}"
|
||||
|
||||
msgid "Syncing electron directory: {dir}"
|
||||
msgstr "electron ディレクトリ同期中: {dir}"
|
||||
|
||||
msgid "Syncing orbital directory: {dir}"
|
||||
msgstr "orbital ディレクトリ同期中: {dir}"
|
||||
|
||||
msgid "nucleon sync complete: uploaded {up}, downloaded {down}"
|
||||
msgstr "nucleon 同期完了: アップロード {up}, ダウンロード {down}"
|
||||
|
||||
msgid "electron sync complete: uploaded {up}, downloaded {down}"
|
||||
msgstr "electron 同期完了: アップロード {up}, ダウンロード {down}"
|
||||
|
||||
msgid "orbital sync complete: uploaded {up}, downloaded {down}"
|
||||
msgstr "orbital 同期完了: アップロード {up}, ダウンロード {down}"
|
||||
|
||||
msgid "Sync complete"
|
||||
msgstr "同期完了"
|
||||
|
||||
msgid "All directories synced"
|
||||
msgstr "全ディレクトリ同期完了"
|
||||
|
||||
msgid "Error during sync: {error}"
|
||||
msgstr "同期エラー: {error}"
|
||||
|
||||
msgid "Sync failed"
|
||||
msgstr "同期失敗"
|
||||
|
||||
msgid "Sync paused"
|
||||
msgstr "同期一時停止"
|
||||
|
||||
msgid "Sync resumed"
|
||||
msgstr "同期再開"
|
||||
|
||||
msgid "Sync cancelled"
|
||||
msgstr "同期キャンセル"
|
||||
|
||||
msgid "Resume"
|
||||
msgstr "再開"
|
||||
|
||||
msgid "Failed to update button state: {error}"
|
||||
msgstr "ボタン状態更新失敗: {error}"
|
||||
|
||||
msgid "Unknown error"
|
||||
msgstr "不明なエラー"
|
||||
|
||||
msgid "[b]Audio Pre-cache[/b]"
|
||||
msgstr "[b]音声プリキャッシュ[/b]"
|
||||
|
||||
msgid "Cache rate: {rate:.1f}% ({cached} / {total} units)"
|
||||
msgstr "キャッシュ率: {rate:.1f}% ({cached} / {total} ユニット)"
|
||||
|
||||
msgid "Target units from: [b]{desc}[/b]"
|
||||
msgstr "対象ユニット: [b]{desc}[/b]"
|
||||
|
||||
msgid "Unit count: {n}"
|
||||
msgstr "ユニット数: {n}"
|
||||
|
||||
msgid "Target: all units"
|
||||
msgstr "対象: 全ユニット"
|
||||
|
||||
msgid "Start Pre-cache"
|
||||
msgstr "プリキャッシュ開始"
|
||||
|
||||
msgid "Cancel Pre-cache"
|
||||
msgstr "プリキャッシュ中止"
|
||||
|
||||
msgid "Clear Cache"
|
||||
msgstr "キャッシュ消去"
|
||||
|
||||
msgid "Cache path: {path}"
|
||||
msgstr "キャッシュパス: {path}"
|
||||
|
||||
msgid "Files: {n}"
|
||||
msgstr "ファイル数: {n}"
|
||||
|
||||
msgid "Total size: {size}"
|
||||
msgstr "合計サイズ: {size}"
|
||||
|
||||
msgid "Refresh"
|
||||
msgstr "更新"
|
||||
|
||||
msgid "If you leave this screen, ongoing cache processes will stop automatically."
|
||||
msgstr "この画面を離れるとキャッシュ処理は自動停止します。"
|
||||
|
||||
msgid "Cache supports \"resume from break\"."
|
||||
msgstr "中断再開に対応しています。"
|
||||
|
||||
msgid "Ready"
|
||||
msgstr "準備完了"
|
||||
|
||||
msgid "Waiting to start..."
|
||||
msgstr "開始待機中…"
|
||||
|
||||
msgid "Status: {s}"
|
||||
msgstr "ステータス: {s}"
|
||||
|
||||
msgid "Current item: {item}"
|
||||
msgstr "現在の項目: {item}"
|
||||
|
||||
msgid "Processing ({i}/{total})"
|
||||
msgstr "処理中 ({i}/{total})"
|
||||
|
||||
msgid "Error"
|
||||
msgstr "エラー"
|
||||
|
||||
msgid "Failed, skipping: {item}"
|
||||
msgstr "失敗、スキップ: {item}"
|
||||
|
||||
msgid "Cancelled"
|
||||
msgstr "キャンセル済"
|
||||
|
||||
msgid "Pre-cache cancelled by user"
|
||||
msgstr "ユーザーがプリキャッシュをキャンセル"
|
||||
|
||||
msgid "Cleared"
|
||||
msgstr "消去済"
|
||||
|
||||
msgid "Audio cache cleared"
|
||||
msgstr "音声キャッシュ消去済"
|
||||
|
||||
msgid "Failed to clear cache: {error}"
|
||||
msgstr "キャッシュ消去失敗: {error}"
|
||||
|
||||
msgid "Cache info refreshed"
|
||||
msgstr "キャッシュ情報更新完了"
|
||||
|
||||
msgid "This memorization session is finished"
|
||||
msgstr "記憶セッション終了"
|
||||
|
||||
msgid "Algorithm data {}"
|
||||
msgstr "アルゴリズムデータ{}"
|
||||
|
||||
msgid "saved"
|
||||
msgstr "保存済"
|
||||
|
||||
msgid "not saved"
|
||||
msgstr "未保存"
|
||||
|
||||
msgid "Back to Menu"
|
||||
msgstr "メニューに戻る"
|
||||
|
||||
msgid "Perfect recall"
|
||||
msgstr "完全に想起"
|
||||
|
||||
msgid "Correct after hesitation"
|
||||
msgstr "ためらい後正解"
|
||||
|
||||
msgid "Correct with difficulty"
|
||||
msgstr "困難だが正解"
|
||||
|
||||
msgid "Wrong but familiar"
|
||||
msgstr "間違えたが見覚えあり"
|
||||
|
||||
msgid "Wrong and unfamiliar"
|
||||
msgstr "間違えて見覚えなし"
|
||||
|
||||
msgid "Complete blank"
|
||||
msgstr "まったく想起不可"
|
||||
|
||||
msgid "Evaluate how well you remember this content: "
|
||||
msgstr "この内容の記憶程度を評価: "
|
||||
|
||||
msgid "### Note: {note}"
|
||||
msgstr "### メモ: {note}"
|
||||
|
||||
msgid "I know this"
|
||||
msgstr "分かっています"
|
||||
|
||||
msgid "Current input: {input}"
|
||||
msgstr "現在の入力: {input}"
|
||||
|
||||
msgid "Backspace"
|
||||
msgstr "BS"
|
||||
|
||||
msgid "Sample Label"
|
||||
msgstr "サンプルラベル"
|
||||
|
||||
msgid "Sample Button"
|
||||
msgstr "サンプルボタン"
|
||||
Binary file not shown.
@@ -0,0 +1,669 @@
|
||||
# Russian translations for HeurAMS.
|
||||
# Copyright (C) 2026 Wang Zhiyu
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: heurams 0.5.1\n"
|
||||
"Language: ru\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
|
||||
msgid "Welcome to the basic user interface!"
|
||||
msgstr "Добро пожаловать в интерфейс!"
|
||||
|
||||
msgid "Loading config and context... "
|
||||
msgstr "Загрузка конфигурации… "
|
||||
|
||||
msgid "Done! ({time}ms)"
|
||||
msgstr "Готово! ({time}ms)"
|
||||
|
||||
msgid "Loading UI framework... "
|
||||
msgstr "Загрузка UI… "
|
||||
|
||||
msgid "Loading UI layout... "
|
||||
msgstr "Загрузка разметки… "
|
||||
|
||||
msgid "Component directory: {path}"
|
||||
msgstr "Директория: {path}"
|
||||
|
||||
msgid "Working directory: {path}"
|
||||
msgstr "Рабочая директория: {path}"
|
||||
|
||||
msgid "Pre-work total: {time}ms"
|
||||
msgstr "Всего: {time}ms"
|
||||
|
||||
msgid "Heuristic Auxiliary Memorizing Scheduler"
|
||||
msgstr "Эвристический планировщик запоминания"
|
||||
|
||||
msgid "Quit"
|
||||
msgstr "Выход"
|
||||
|
||||
msgid "Theme"
|
||||
msgstr "Тема"
|
||||
|
||||
msgid "Navigate"
|
||||
msgstr "Навигация"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Настройки"
|
||||
|
||||
msgid "About"
|
||||
msgstr "О программе"
|
||||
|
||||
msgid "HeurAMS {ver} - Heuristic Auxiliary Memorizing Scheduler"
|
||||
msgstr "HeurAMS {ver} - Эвристический планировщик"
|
||||
|
||||
msgid "Listening address"
|
||||
msgstr "Адрес"
|
||||
|
||||
msgid "Listening port"
|
||||
msgstr "Порт"
|
||||
|
||||
msgid "Development mode hot reload"
|
||||
msgstr "Горячая перезагрузка (dev)"
|
||||
|
||||
msgid "Show this help message"
|
||||
msgstr "Показать справку"
|
||||
|
||||
msgid "unifront API service started: http://{host}:{port}"
|
||||
msgstr "API запущен: http://{host}:{port}"
|
||||
|
||||
msgid "Show the version and exit."
|
||||
msgstr "Показать версию и выйти."
|
||||
|
||||
msgid "Explicitly specify locale (defaults to LANG env)."
|
||||
msgstr "Локаль (напр. en_US, ru). По умолчанию из LANG."
|
||||
|
||||
msgid "Launch the built-in user interface (TUI)"
|
||||
msgstr "Запустить TUI"
|
||||
|
||||
msgid "Launch the API service (unifront)"
|
||||
msgstr "Запустить API"
|
||||
|
||||
msgid "Dashboard"
|
||||
msgstr "Панель"
|
||||
|
||||
msgid "Back"
|
||||
msgstr "Назад"
|
||||
|
||||
msgid "Current daystamp: {ds}"
|
||||
msgstr "Дата: {ds}"
|
||||
|
||||
msgid "Timezone offset: UTC+{offset}"
|
||||
msgstr "Смещение: UTC+{offset}"
|
||||
|
||||
msgid "Default algorithm: {algo}"
|
||||
msgstr "Алгоритм: {algo}"
|
||||
|
||||
msgid "Loaded {n} repo(s)"
|
||||
msgstr "Загружено: {n}"
|
||||
|
||||
msgid "Total {n} unit(s)"
|
||||
msgstr "Всего单元: {n}"
|
||||
|
||||
msgid "Activated {n} unit(s)"
|
||||
msgstr "Активно: {n}"
|
||||
|
||||
msgid "Version {ver}-{stage}"
|
||||
msgstr "Версия {ver}-{stage}"
|
||||
|
||||
msgid "Processed {puzzles} puzzles in {time}s, accuracy {accuracy}, speed {speed} puzzle(s)/s"
|
||||
msgstr "Обработано {puzzles} задач за {time}с, точность {accuracy}, скорость {speed} задач/с"
|
||||
|
||||
msgid "N/A"
|
||||
msgstr "Н/Д"
|
||||
|
||||
msgid ""
|
||||
"No repo directories found in {path}.\n"
|
||||
"Please import a repo and restart, or create a new one."
|
||||
msgstr ""
|
||||
"Репозитории не найдены в {path}.\n"
|
||||
"Импортируйте или создайте новый."
|
||||
|
||||
msgid "Start Learning"
|
||||
msgstr "Начать"
|
||||
|
||||
msgid ""
|
||||
"{title} [{algo}]\n"
|
||||
" [d]Progress: {touched}/{total} ({pct}%)[/d]\n"
|
||||
" [d]{status}[/d]"
|
||||
msgstr ""
|
||||
"{title} [{algo}]\n"
|
||||
" [d]Прогресс: {touched}/{total} ({pct}%)[/d]\n"
|
||||
" [d]{status}[/d]"
|
||||
|
||||
msgid "Due: {review}R + {new}U"
|
||||
msgstr "К сроку: {review}R + {new}U"
|
||||
|
||||
msgid "Not started: 0R + {new}U"
|
||||
msgstr "Не начато: 0R + {new}U"
|
||||
|
||||
msgid "Up to date"
|
||||
msgstr "Актуально"
|
||||
|
||||
msgid "[b]About & Version Info[/b]"
|
||||
msgstr "[b]О программе[/b]"
|
||||
|
||||
msgid ""
|
||||
"# About HeurAMS\n"
|
||||
"\n"
|
||||
"Main library version: `{ver}-python` \n"
|
||||
"UI frontend: `Textual TUI (Basic UI)` \n"
|
||||
"UI version: `{ver}` \n"
|
||||
"API codename: `{codename}` \n"
|
||||
"\n"
|
||||
"> A heuristic auxiliary memorizing scheduler based on heuristic algorithms and cognitive science theories, designed to help users memorize and plan learning more efficiently. \n"
|
||||
"> An open, elegant, and extensible spaced repetition scheduler experimental platform, designed to help researchers conduct investigations, experiments, and research on cutting-edge memory algorithms more efficiently. \n"
|
||||
"\n"
|
||||
"You can visit the project homepage at https://ams.pluv27.top for user guides, development documentation and software updates, and participate in software development and improvement. \n"
|
||||
"\n"
|
||||
"Open source under the GNU Affero General Public License (version 3), with an additional exemption clause for local API calls, used for other frontend to library interface calls. \n"
|
||||
"\n"
|
||||
"You are using the built-in terminal user interface, which is the first full-featured frontend implementation and library test suite, located in the interface subdirectory of the library. \n"
|
||||
"\n"
|
||||
"Developers: \n"
|
||||
"- Wang Zhiyu ([@pluvium27](https://github.com/pluvium27)): Project initiator and lead developer \n"
|
||||
"\n"
|
||||
"Special thanks to the following individuals and groups; their algorithms and theories form the cornerstone of the current software algorithms: \n"
|
||||
"\n"
|
||||
"- [Piotr A. Woźniak](https://supermemo.guru/wiki/Piotr_Wozniak): SM-2 algorithm and SM-15 algorithm theory \n"
|
||||
"- [Jarrett Ye](https://github.com/L-M-Sherlock): FSRS algorithm and spaced repetition theory references \n"
|
||||
"- [Kazuaki Tanida](https://github.com/slaypni): CoffeeScript reverse implementation of SM-15 algorithm \n"
|
||||
"- [Open Spaced Repetition](https://github.com/open-spaced-repetition): FSRS algorithm underlying implementation \n"
|
||||
"\n"
|
||||
"# Runtime Environment\n"
|
||||
"\n"
|
||||
"Python interpreter version: {python_version} \n"
|
||||
"Python interpreter path: {executable} \n"
|
||||
"Textual framework version: {textual_version} \n"
|
||||
"Terminal emulator: {terminal_info} \n"
|
||||
"Operating system version: {os_version} \n"
|
||||
"Disk free space: {disk_usage} \n"
|
||||
"\n"
|
||||
"When reporting issues, please copy this information into the issue description and attach `heurams.log` as an attachment to help developers locate the error."
|
||||
msgstr ""
|
||||
"# О HeurAMS\n"
|
||||
"\n"
|
||||
"Версия: `{ver}-python` \n"
|
||||
"UI: `Textual TUI` \n"
|
||||
"Версия UI: `{ver}` \n"
|
||||
"Кодовое имя: `{codename}` \n"
|
||||
"\n"
|
||||
"> Эвристический планировщик запоминания на основе когнитивных наук. \n"
|
||||
"\n"
|
||||
"Сайт: https://ams.pluv27.top \n"
|
||||
"\n"
|
||||
"AGPL-3.0 с исключением для локальных API. \n"
|
||||
"\n"
|
||||
"Разработчик: \n"
|
||||
"- Wang Zhiyu ([@pluvium27](https://github.com/pluvium27)) \n"
|
||||
"\n"
|
||||
"Благодарности: \n"
|
||||
"- [Piotr A. Woźniak](https://supermemo.guru/wiki/Piotr_Wozniak): SM-2, SM-15 \n"
|
||||
"- [Jarrett Ye](https://github.com/L-M-Sherlock): FSRS \n"
|
||||
"- [Kazuaki Tanida](https://github.com/slaypni): SM-15 CoffeeScript \n"
|
||||
"- [Open Spaced Repetition](https://github.com/open-spaced-repetition): FSRS \n"
|
||||
"\n"
|
||||
"# Окружение\n"
|
||||
"\n"
|
||||
"Python: {python_version} \n"
|
||||
"Путь: {executable} \n"
|
||||
"Textual: {textual_version} \n"
|
||||
"Терминал: {terminal_info} \n"
|
||||
"ОС: {os_version} \n"
|
||||
"Диск: {disk_usage} \n"
|
||||
"\n"
|
||||
"Прикрепите эту информацию к баг-репорту."
|
||||
|
||||
msgid "Back to Main"
|
||||
msgstr "На главную"
|
||||
|
||||
msgid "Unknown"
|
||||
msgstr "Неизв."
|
||||
|
||||
msgid "Requires a float"
|
||||
msgstr "Нужно число с точкой"
|
||||
|
||||
msgid "Requires a string"
|
||||
msgstr "Нужна строка"
|
||||
|
||||
msgid "Requires an integer"
|
||||
msgstr "Нужно целое"
|
||||
|
||||
msgid "Unknown type"
|
||||
msgstr "Неизв. тип"
|
||||
|
||||
msgid "No sub-items"
|
||||
msgstr "Нет подпунктов"
|
||||
|
||||
msgid "Changes are saved immediately when you leave this page, but restart is recommended to ensure the new configuration is applied."
|
||||
msgstr "Изменения сохраняются сразу. Рекомендуется перезапуск."
|
||||
|
||||
msgid "Previous"
|
||||
msgstr "Назад"
|
||||
|
||||
msgid "Read Aloud"
|
||||
msgstr "Озвучить"
|
||||
|
||||
msgid "Favorite"
|
||||
msgstr "Избранное"
|
||||
|
||||
msgid "Learning"
|
||||
msgstr "Обучение"
|
||||
|
||||
msgid "Correct"
|
||||
msgstr "Верно"
|
||||
|
||||
msgid "Incorrect"
|
||||
msgstr "Неверно"
|
||||
|
||||
msgid "Failed to generate puzzle: {e}"
|
||||
msgstr "Ошибка генерации: {e}"
|
||||
|
||||
msgid "Favorited"
|
||||
msgstr "В избранном"
|
||||
|
||||
msgid "Not favorited"
|
||||
msgstr "Не в избранном"
|
||||
|
||||
msgid "Previous: {ident}"
|
||||
msgstr "Пред.: {ident}"
|
||||
|
||||
msgid "Are you sure? Press uppercase Q to go back."
|
||||
msgstr "Уверены? Нажмите Q для возврата."
|
||||
|
||||
msgid "Cannot favorite: no repo associated"
|
||||
msgstr "Ошибка: репозиторий не связан"
|
||||
|
||||
msgid "Unfavorited: {ident}"
|
||||
msgstr "Удалено: {ident}"
|
||||
|
||||
msgid "Favorited: {ident}"
|
||||
msgstr "Добавлено: {ident}"
|
||||
|
||||
msgid "This function is not available during memorization. Please finish or go back first."
|
||||
msgstr "Функция недоступна во время запоминания."
|
||||
|
||||
msgid "Time resume corrected: {old} -> {new}"
|
||||
msgstr "Время скорректировано: {old} -> {new}"
|
||||
|
||||
msgid " [Debug Connected]"
|
||||
msgstr " [Отладка]"
|
||||
|
||||
msgid "Favorites"
|
||||
msgstr "Избранное"
|
||||
|
||||
msgid "No favorites"
|
||||
msgstr "Нет избранного"
|
||||
|
||||
msgid "Press * in the memorization screen to add favorites."
|
||||
msgstr "Нажмите * для добавления в избранное."
|
||||
|
||||
msgid "Total {n} favorite(s)"
|
||||
msgstr "Избранного: {n}"
|
||||
|
||||
msgid ""
|
||||
" [d]Added: {time}\n"
|
||||
" From {title}[/d]"
|
||||
msgstr ""
|
||||
" [d]Добавлено: {time}\n"
|
||||
" Из: {title}[/d]"
|
||||
|
||||
msgid "Remove"
|
||||
msgstr "Удалить"
|
||||
|
||||
msgid "Operation failed: invalid button identifier"
|
||||
msgstr "Ошибка: неверный ID кнопки"
|
||||
|
||||
msgid "Removed favorite: {ident}"
|
||||
msgstr "Удалено: {ident}"
|
||||
|
||||
msgid "Failed to remove: {ident}"
|
||||
msgstr "Ошибка удаления: {ident}"
|
||||
|
||||
msgid "Switch"
|
||||
msgstr "Переключить"
|
||||
|
||||
msgid "Cache Manager"
|
||||
msgstr "Кеш"
|
||||
|
||||
msgid "Settings Page"
|
||||
msgstr "Настройки"
|
||||
|
||||
msgid "Sync Tool"
|
||||
msgstr "Синхр."
|
||||
|
||||
msgid "Exit"
|
||||
msgstr "Выход"
|
||||
|
||||
msgid "Project Homepage"
|
||||
msgstr "Сайт проекта"
|
||||
|
||||
msgid ""
|
||||
"[b]Select a function to navigate to\n"
|
||||
"or a memorization session instance[/b]\n"
|
||||
"\n"
|
||||
"Tips will be displayed here"
|
||||
msgstr ""
|
||||
"[b]Выберите функцию\n"
|
||||
"или сеанс[/b]\n"
|
||||
"\n"
|
||||
"Подсказки здесь"
|
||||
|
||||
msgid ""
|
||||
"Press Enter to switch\n"
|
||||
"All sessions will be saved"
|
||||
msgstr ""
|
||||
"Enter для переключения\n"
|
||||
"Сеансы будут сохранены"
|
||||
|
||||
msgid "Close (n)"
|
||||
msgstr "Закрыть (n)"
|
||||
|
||||
msgid "Prepare Repository"
|
||||
msgstr "Подготовка"
|
||||
|
||||
msgid "Cache"
|
||||
msgstr "Кеш"
|
||||
|
||||
msgid ""
|
||||
"**Ready**: `{title}`\n"
|
||||
""
|
||||
msgstr ""
|
||||
"**Готово**: `{title}`\n"
|
||||
""
|
||||
|
||||
msgid "Repo path: {path}"
|
||||
msgstr "Путь: {path}"
|
||||
|
||||
msgid "Progress: {touched}/{total} [{pct}%]"
|
||||
msgstr "Прогресс: {touched}/{total} [{pct}%]"
|
||||
|
||||
msgid "Scheduling algorithm: {algo} {desc}"
|
||||
msgstr "Алгоритм: {algo} {desc}"
|
||||
|
||||
msgid ""
|
||||
"Study count: {total} = {review} [d][Review][/d] + {new} [d][New][/d]\n"
|
||||
""
|
||||
msgstr ""
|
||||
"Всего: {total} = {review} [d][Повтор][/d] + {new} [d][Новое][/d]\n"
|
||||
""
|
||||
|
||||
msgid "Start Memorizing"
|
||||
msgstr "Начать"
|
||||
|
||||
msgid "Manage Cache"
|
||||
msgstr "Кеш"
|
||||
|
||||
msgid "Sync protocol: {proto}"
|
||||
msgstr "Протокол: {proto}"
|
||||
|
||||
msgid "Server Configuration:"
|
||||
msgstr "Сервер:"
|
||||
|
||||
msgid "Remote server:"
|
||||
msgstr "Сервер:"
|
||||
|
||||
msgid "Remote path:"
|
||||
msgstr "Путь:"
|
||||
|
||||
msgid "Test Connection"
|
||||
msgstr "Тест"
|
||||
|
||||
msgid "Start Sync"
|
||||
msgstr "Синхр."
|
||||
|
||||
msgid "Pause"
|
||||
msgstr "Пауза"
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr "Отмена"
|
||||
|
||||
msgid "Sync Progress"
|
||||
msgstr "Прогресс"
|
||||
|
||||
msgid "Sync Log"
|
||||
msgstr "Лог"
|
||||
|
||||
msgid "Not configured"
|
||||
msgstr "Не настроено"
|
||||
|
||||
msgid "Sync service ready"
|
||||
msgstr "Синхр. готова"
|
||||
|
||||
msgid "Sync service not configured or not enabled"
|
||||
msgstr "Синхр. не настроена"
|
||||
|
||||
msgid "Failed to update UI: {error}"
|
||||
msgstr "Ошибка UI: {error}"
|
||||
|
||||
msgid "Failed to update status: {error}"
|
||||
msgstr "Ошибка статуса: {error}"
|
||||
|
||||
msgid "Sync service not initialised, please check configuration"
|
||||
msgstr "Синхр. не инициализирована"
|
||||
|
||||
msgid "Sync service not initialised"
|
||||
msgstr "Не инициализирована"
|
||||
|
||||
msgid "Testing WebDAV connection..."
|
||||
msgstr "Тест WebDAV…"
|
||||
|
||||
msgid "Testing connection..."
|
||||
msgstr "Тест соединения…"
|
||||
|
||||
msgid "Connection test successful"
|
||||
msgstr "Тест OK"
|
||||
|
||||
msgid "Connection OK"
|
||||
msgstr "Соединение OK"
|
||||
|
||||
msgid "Connection test failed"
|
||||
msgstr "Тест не удался"
|
||||
|
||||
msgid "Connection failed"
|
||||
msgstr "Ошибка соединения"
|
||||
|
||||
msgid "Connection test error: {error}"
|
||||
msgstr "Ошибка теста: {error}"
|
||||
|
||||
msgid "Connection error"
|
||||
msgstr "Ошибка соединения"
|
||||
|
||||
msgid "Sync service not initialised, cannot start sync"
|
||||
msgstr "Синхр. не иниц., запуск невозможен"
|
||||
|
||||
msgid "Sync already in progress"
|
||||
msgstr "Синхр. уже выполняется"
|
||||
|
||||
msgid "Starting data sync..."
|
||||
msgstr "Запуск синхр…"
|
||||
|
||||
msgid "Syncing..."
|
||||
msgstr "Синхр…"
|
||||
|
||||
msgid "Syncing nucleon directory: {dir}"
|
||||
msgstr "Синхр. nucleon: {dir}"
|
||||
|
||||
msgid "Syncing electron directory: {dir}"
|
||||
msgstr "Синхр. electron: {dir}"
|
||||
|
||||
msgid "Syncing orbital directory: {dir}"
|
||||
msgstr "Синхр. orbital: {dir}"
|
||||
|
||||
msgid "nucleon sync complete: uploaded {up}, downloaded {down}"
|
||||
msgstr "nucleon: загружено {up}, скачано {down}"
|
||||
|
||||
msgid "electron sync complete: uploaded {up}, downloaded {down}"
|
||||
msgstr "electron: загружено {up}, скачано {down}"
|
||||
|
||||
msgid "orbital sync complete: uploaded {up}, downloaded {down}"
|
||||
msgstr "orbital: загружено {up}, скачано {down}"
|
||||
|
||||
msgid "Sync complete"
|
||||
msgstr "Синхр. завершена"
|
||||
|
||||
msgid "All directories synced"
|
||||
msgstr "Всё синхронизировано"
|
||||
|
||||
msgid "Error during sync: {error}"
|
||||
msgstr "Ошибка синхр.: {error}"
|
||||
|
||||
msgid "Sync failed"
|
||||
msgstr "Синхр. не удалась"
|
||||
|
||||
msgid "Sync paused"
|
||||
msgstr "Пауза"
|
||||
|
||||
msgid "Sync resumed"
|
||||
msgstr "Продолжено"
|
||||
|
||||
msgid "Sync cancelled"
|
||||
msgstr "Отменено"
|
||||
|
||||
msgid "Resume"
|
||||
msgstr "Продолжить"
|
||||
|
||||
msgid "Failed to update button state: {error}"
|
||||
msgstr "Ошибка кнопки: {error}"
|
||||
|
||||
msgid "Unknown error"
|
||||
msgstr "Неизв. ошибка"
|
||||
|
||||
msgid "[b]Audio Pre-cache[/b]"
|
||||
msgstr "[b]Аудиокеш[/b]"
|
||||
|
||||
msgid "Cache rate: {rate:.1f}% ({cached} / {total} units)"
|
||||
msgstr "Кеш: {rate:.1f}% ({cached} / {total})"
|
||||
|
||||
msgid "Target units from: [b]{desc}[/b]"
|
||||
msgstr "Цель: [b]{desc}[/b]"
|
||||
|
||||
msgid "Unit count: {n}"
|
||||
msgstr "Единиц: {n}"
|
||||
|
||||
msgid "Target: all units"
|
||||
msgstr "Цель: всё"
|
||||
|
||||
msgid "Start Pre-cache"
|
||||
msgstr "Начать"
|
||||
|
||||
msgid "Cancel Pre-cache"
|
||||
msgstr "Отмена"
|
||||
|
||||
msgid "Clear Cache"
|
||||
msgstr "Очистить"
|
||||
|
||||
msgid "Cache path: {path}"
|
||||
msgstr "Путь кеша: {path}"
|
||||
|
||||
msgid "Files: {n}"
|
||||
msgstr "Файлов: {n}"
|
||||
|
||||
msgid "Total size: {size}"
|
||||
msgstr "Размер: {size}"
|
||||
|
||||
msgid "Refresh"
|
||||
msgstr "Обновить"
|
||||
|
||||
msgid "If you leave this screen, ongoing cache processes will stop automatically."
|
||||
msgstr "При выходе кеширование остановится."
|
||||
|
||||
msgid "Cache supports \"resume from break\"."
|
||||
msgstr "Поддерживается возобновление."
|
||||
|
||||
msgid "Ready"
|
||||
msgstr "Готово"
|
||||
|
||||
msgid "Waiting to start..."
|
||||
msgstr "Ожидание…"
|
||||
|
||||
msgid "Status: {s}"
|
||||
msgstr "Статус: {s}"
|
||||
|
||||
msgid "Current item: {item}"
|
||||
msgstr "Элемент: {item}"
|
||||
|
||||
msgid "Processing ({i}/{total})"
|
||||
msgstr "Обработка ({i}/{total})"
|
||||
|
||||
msgid "Error"
|
||||
msgstr "Ошибка"
|
||||
|
||||
msgid "Failed, skipping: {item}"
|
||||
msgstr "Ошибка, пропуск: {item}"
|
||||
|
||||
msgid "Cancelled"
|
||||
msgstr "Отменено"
|
||||
|
||||
msgid "Pre-cache cancelled by user"
|
||||
msgstr "Отменено пользователем"
|
||||
|
||||
msgid "Cleared"
|
||||
msgstr "Очищено"
|
||||
|
||||
msgid "Audio cache cleared"
|
||||
msgstr "Кеш очищен"
|
||||
|
||||
msgid "Failed to clear cache: {error}"
|
||||
msgstr "Ошибка очистки: {error}"
|
||||
|
||||
msgid "Cache info refreshed"
|
||||
msgstr "Инфо обновлено"
|
||||
|
||||
msgid "This memorization session is finished"
|
||||
msgstr "Сеанс завершен"
|
||||
|
||||
msgid "Algorithm data {}"
|
||||
msgstr "Данные алгоритма{}"
|
||||
|
||||
msgid "saved"
|
||||
msgstr "сохранено"
|
||||
|
||||
msgid "not saved"
|
||||
msgstr "не сохранено"
|
||||
|
||||
msgid "Back to Menu"
|
||||
msgstr "В меню"
|
||||
|
||||
msgid "Perfect recall"
|
||||
msgstr "Идеально"
|
||||
|
||||
msgid "Correct after hesitation"
|
||||
msgstr "Верно после паузы"
|
||||
|
||||
msgid "Correct with difficulty"
|
||||
msgstr "Верно с трудом"
|
||||
|
||||
msgid "Wrong but familiar"
|
||||
msgstr "Неверно, но знакомо"
|
||||
|
||||
msgid "Wrong and unfamiliar"
|
||||
msgstr "Неверно и незнакомо"
|
||||
|
||||
msgid "Complete blank"
|
||||
msgstr "Полный провал"
|
||||
|
||||
msgid "Evaluate how well you remember this content: "
|
||||
msgstr "Оцените запоминание: "
|
||||
|
||||
msgid "### Note: {note}"
|
||||
msgstr "### Заметка: {note}"
|
||||
|
||||
msgid "I know this"
|
||||
msgstr "Знаю"
|
||||
|
||||
msgid "Current input: {input}"
|
||||
msgstr "Ввод: {input}"
|
||||
|
||||
msgid "Backspace"
|
||||
msgstr "Забой"
|
||||
|
||||
msgid "Sample Label"
|
||||
msgstr "Метка"
|
||||
|
||||
msgid "Sample Button"
|
||||
msgstr "Кнопка"
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!DOCTYPE martif PUBLIC 'ISO 12200:1999A//DTD MARTIF core (DXFcdV04)//EN' 'TBXcdv04.dtd'>
|
||||
<martif type="TBX" xml:lang="en_US">
|
||||
<text>
|
||||
<body/>
|
||||
</text>
|
||||
</martif>
|
||||
Binary file not shown.
@@ -0,0 +1,679 @@
|
||||
# Chinese (Simplified) Language Pack for HeurAMS.
|
||||
# Copyright (C) 2026 Wang Zhiyu
|
||||
# This file is distributed under the same license as the HeurAMS project.
|
||||
# Wang Zhiyu <pluvium27@outlook.com>, 2026.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: heurams 0.5.1\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/pluvium27/HeurAMS/issues\n"
|
||||
"POT-Creation-Date: 2026-05-21 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-05-21 00:00+0000\n"
|
||||
"Last-Translator: Wang Zhiyu <pluvium27@outlook.com>\n"
|
||||
"Language-Team: Chinese (Simplified) <pluvium27@outlook.com>\n"
|
||||
"Language: zh_CN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
msgid "Welcome to the basic user interface!"
|
||||
msgstr "欢迎使用基本用户界面!"
|
||||
|
||||
msgid "Loading config and context... "
|
||||
msgstr "加载配置与上下文... "
|
||||
|
||||
msgid "Done! ({time}ms)"
|
||||
msgstr "已完成! (耗时: {time}ms)"
|
||||
|
||||
msgid "Loading UI framework... "
|
||||
msgstr "加载用户界面框架... "
|
||||
|
||||
msgid "Loading UI layout... "
|
||||
msgstr "加载用户界面布局... "
|
||||
|
||||
msgid "Component directory: {path}"
|
||||
msgstr "组件目录: {path}"
|
||||
|
||||
msgid "Working directory: {path}"
|
||||
msgstr "工作目录: {path}"
|
||||
|
||||
msgid "Pre-work total: {time}ms"
|
||||
msgstr "前置工作共计耗时: {time}ms"
|
||||
|
||||
msgid "Heuristic Auxiliary Memorizing Scheduler"
|
||||
msgstr "启发式辅助记忆调度器"
|
||||
|
||||
msgid "Quit"
|
||||
msgstr "退出"
|
||||
|
||||
msgid "Theme"
|
||||
msgstr "主题"
|
||||
|
||||
msgid "Navigate"
|
||||
msgstr "导航"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "设置"
|
||||
|
||||
msgid "About"
|
||||
msgstr "关于"
|
||||
|
||||
msgid "HeurAMS {ver} - Heuristic Auxiliary Memorizing Scheduler"
|
||||
msgstr "HeurAMS {ver} - 启发式辅助记忆调度器"
|
||||
|
||||
msgid "Listening address"
|
||||
msgstr "监听地址"
|
||||
|
||||
msgid "Listening port"
|
||||
msgstr "监听端口"
|
||||
|
||||
msgid "Development mode hot reload"
|
||||
msgstr "开发模式热重载"
|
||||
|
||||
msgid "Show this help message"
|
||||
msgstr "显示此帮助信息"
|
||||
|
||||
msgid "unifront API service started: http://{host}:{port}"
|
||||
msgstr "unifront API 服务启动: http://{host}:{port}"
|
||||
|
||||
msgid "Show the version and exit."
|
||||
msgstr "显示版本并退出."
|
||||
|
||||
msgid "Explicitly specify locale (defaults to LANG env)."
|
||||
msgstr "显式指定语言 (默认使用 LANG 环境变量)."
|
||||
|
||||
msgid "Launch the built-in user interface (TUI)"
|
||||
msgstr "启动内置基本用户界面 (TUI)"
|
||||
|
||||
msgid "Launch the API service (unifront)"
|
||||
msgstr "启动 API 服务 (unifront)"
|
||||
|
||||
msgid "Dashboard"
|
||||
msgstr "仪表盘"
|
||||
|
||||
msgid "Back"
|
||||
msgstr "返回"
|
||||
|
||||
msgid "Current daystamp: {ds}"
|
||||
msgstr "当前日时间戳: {ds}"
|
||||
|
||||
msgid "Timezone offset: UTC+{offset}"
|
||||
msgstr "应用时区修正: UTC+{offset}"
|
||||
|
||||
msgid "Default algorithm: {algo}"
|
||||
msgstr "默认算法设置: {algo}"
|
||||
|
||||
msgid "Loaded {n} repo(s)"
|
||||
msgstr "已加载 {n} 个单元集"
|
||||
|
||||
msgid "Total {n} unit(s)"
|
||||
msgstr "共计 {n} 个单元"
|
||||
|
||||
msgid "Activated {n} unit(s)"
|
||||
msgstr "已激活 {n} 个单元"
|
||||
|
||||
msgid "Version {ver}-{stage}"
|
||||
msgstr "版本 {ver}-{stage}"
|
||||
|
||||
msgid "Processed {puzzles} puzzles in {time}s, accuracy {accuracy}, speed {speed} puzzle(s)/s"
|
||||
msgstr "在 {time} 秒内处理了 {puzzles} 个谜题, 正确率 {accuracy}, 平均速度 {speed} 个/秒"
|
||||
|
||||
msgid "N/A"
|
||||
msgstr "无法求解"
|
||||
|
||||
msgid ""
|
||||
"No repo directories found in {path}.\n"
|
||||
"Please import a repo and restart, or create a new one."
|
||||
msgstr ""
|
||||
"在 {path} 中未找到任何单元集仓库目录.\n"
|
||||
"请导入单元集后重启应用, 或者新建单元集."
|
||||
|
||||
msgid "Start Learning"
|
||||
msgstr "开始学习"
|
||||
|
||||
msgid ""
|
||||
"{title} [{algo}]\n"
|
||||
" [d]Progress: {touched}/{total} ({pct}%)[/d]\n"
|
||||
" [d]{status}[/d]"
|
||||
msgstr ""
|
||||
"{title} [{algo}]\n"
|
||||
" [d]进度: {touched}/{total} ({pct}%)[/d]\n"
|
||||
" [d]{status}[/d]"
|
||||
|
||||
msgid "Due: {review}R + {new}U"
|
||||
msgstr "需要学习: {review}R + {new}U"
|
||||
|
||||
msgid "Not started: 0R + {new}U"
|
||||
msgstr "暂未开始: 0R + {new}U"
|
||||
|
||||
msgid "Up to date"
|
||||
msgstr "无需操作"
|
||||
|
||||
msgid "[b]About & Version Info[/b]"
|
||||
msgstr "[b]关于与版本信息[/b]"
|
||||
|
||||
msgid ""
|
||||
"# About HeurAMS\n"
|
||||
"\n"
|
||||
"Main library version: `{ver}-python` \n"
|
||||
"UI frontend: `Textual TUI (Basic UI)` \n"
|
||||
"UI version: `{ver}` \n"
|
||||
"API codename: `{codename}` \n"
|
||||
"\n"
|
||||
"> A heuristic auxiliary memorizing scheduler based on heuristic algorithms and cognitive science theories, designed to help users memorize and plan learning more efficiently. \n"
|
||||
"> An open, elegant, and extensible spaced repetition scheduler experimental platform, designed to help researchers conduct investigations, experiments, and research on cutting-edge memory algorithms more efficiently. \n"
|
||||
"\n"
|
||||
"You can visit the project homepage at https://ams.pluv27.top for user guides, development documentation and software updates, and participate in software development and improvement. \n"
|
||||
"\n"
|
||||
"Open source under the GNU Affero General Public License (version 3), with an additional exemption clause for local API calls, used for other frontend to library interface calls. \n"
|
||||
"\n"
|
||||
"You are using the built-in terminal user interface, which is the first full-featured frontend implementation and library test suite, located in the interface subdirectory of the library. \n"
|
||||
"\n"
|
||||
"Developers: \n"
|
||||
"- Wang Zhiyu ([@pluvium27](https://github.com/pluvium27)): Project initiator and lead developer \n"
|
||||
"\n"
|
||||
"Special thanks to the following individuals and groups; their algorithms and theories form the cornerstone of the current software algorithms: \n"
|
||||
"\n"
|
||||
"- [Piotr A. Woźniak](https://supermemo.guru/wiki/Piotr_Wozniak): SM-2 algorithm and SM-15 algorithm theory \n"
|
||||
"- [Jarrett Ye](https://github.com/L-M-Sherlock): FSRS algorithm and spaced repetition theory references \n"
|
||||
"- [Kazuaki Tanida](https://github.com/slaypni): CoffeeScript reverse implementation of SM-15 algorithm \n"
|
||||
"- [Open Spaced Repetition](https://github.com/open-spaced-repetition): FSRS algorithm underlying implementation \n"
|
||||
"\n"
|
||||
"# Runtime Environment\n"
|
||||
"\n"
|
||||
"Python interpreter version: {python_version} \n"
|
||||
"Python interpreter path: {executable} \n"
|
||||
"Textual framework version: {textual_version} \n"
|
||||
"Terminal emulator: {terminal_info} \n"
|
||||
"Operating system version: {os_version} \n"
|
||||
"Disk free space: {disk_usage} \n"
|
||||
"\n"
|
||||
"When reporting issues, please copy this information into the issue description and attach `heurams.log` as an attachment to help developers locate the error."
|
||||
msgstr ""
|
||||
"# 关于 HeurAMS\n"
|
||||
"\n"
|
||||
"主程序库版本: `{ver}-python` \n"
|
||||
"用户界面分支: `Textual TUI (基本用户界面)` \n"
|
||||
"用户界面版本: `{ver}` \n"
|
||||
"API 版本代号: `{codename}` \n"
|
||||
"\n"
|
||||
"> 一个基于启发式算法与认知科学理论的辅助记忆调度器, 旨在帮助用户更高效地进行记忆工作与学习规划. \n"
|
||||
"> 一个开放, 优雅, 易于扩展的间隔重复调度器实验平台, 旨在帮助研究者更高效地进行前沿记忆算法的研究. \n"
|
||||
"\n"
|
||||
"您可在项目主页 https://ams.pluv27.top 获取用户指南, 开发文档与软件更新, 并参与到软件的开发与改进工作. \n"
|
||||
"\n"
|
||||
"以 GNU Affero 通用公共许可证 (第3版) 开放源代码, 并有一条豁免本机 API 调用的附加条款, 用于其他前端到程序库的接口调用. \n"
|
||||
"\n"
|
||||
"您正使用程序库内置的终端用户界面, 它是第一个全功能前端实现与程序库测试套件, 位于程序库的 interface 子目录. \n"
|
||||
"\n"
|
||||
"开发人员列表: \n"
|
||||
"- Wang Zhiyu ([@pluvium27](https://github.com/pluvium27)): 项目发起与主要开发者 \n"
|
||||
"\n"
|
||||
"感谢以下人士与团体, 他们的算法与理论构成了此软件现有算法的基石: \n"
|
||||
"\n"
|
||||
"- [Piotr A. Woźniak](https://supermemo.guru/wiki/Piotr_Wozniak): SM-2 算法与 SM-15 算法理论 \n"
|
||||
"- [Jarrett Ye](https://github.com/L-M-Sherlock): FSRS 算法与间隔重复理论文献参考 \n"
|
||||
"- [Kazuaki Tanida](https://github.com/slaypni): SM-15 算法的 CoffeeScript 逆向实现 \n"
|
||||
"- [Open Spaced Repetition](https://github.com/open-spaced-repetition): FSRS 算法底层实现 \n"
|
||||
"\n"
|
||||
"# 运行环境信息\n"
|
||||
"\n"
|
||||
"Python 解释器版本: {python_version} \n"
|
||||
"Python 解释器路径: {executable} \n"
|
||||
"Textual 框架版本: {textual_version} \n"
|
||||
"终端模拟器: {terminal_info} \n"
|
||||
"操作系统版本: {os_version} \n"
|
||||
"存储余量: {disk_usage} \n"
|
||||
"\n"
|
||||
"报告问题时, 请复制这些信息到问题描述, 并上传软件日志 `heurams.log` 作为附件, 以协助开发者定位错误."
|
||||
|
||||
msgid "Back to Main"
|
||||
msgstr "返回主界面"
|
||||
|
||||
msgid "Unknown"
|
||||
msgstr "未知"
|
||||
|
||||
msgid "Requires a float"
|
||||
msgstr "要求一个浮点数"
|
||||
|
||||
msgid "Requires a string"
|
||||
msgstr "要求一个字符串"
|
||||
|
||||
msgid "Requires an integer"
|
||||
msgstr "要求一个整数"
|
||||
|
||||
msgid "Unknown type"
|
||||
msgstr "未知类型"
|
||||
|
||||
msgid "No sub-items"
|
||||
msgstr "无子项"
|
||||
|
||||
msgid "Changes are saved immediately when you leave this page, but restart is recommended to ensure the new configuration is applied."
|
||||
msgstr "退出页面时, 所作的更改会立即保存, 但仍建议重启软件以确保新的配置得到应用."
|
||||
|
||||
msgid "Previous"
|
||||
msgstr "查看上一个"
|
||||
|
||||
msgid "Read Aloud"
|
||||
msgstr "朗读"
|
||||
|
||||
msgid "Favorite"
|
||||
msgstr "收藏"
|
||||
|
||||
msgid "Learning"
|
||||
msgstr "学习中"
|
||||
|
||||
msgid "Correct"
|
||||
msgstr "正确应答"
|
||||
|
||||
msgid "Incorrect"
|
||||
msgstr "错误应答"
|
||||
|
||||
msgid "Failed to generate puzzle: {e}"
|
||||
msgstr "无法生成谜题: {e}"
|
||||
|
||||
msgid "Favorited"
|
||||
msgstr "已收藏"
|
||||
|
||||
msgid "Not favorited"
|
||||
msgstr "未收藏"
|
||||
|
||||
msgid "Previous: {ident}"
|
||||
msgstr "上一个: {ident}"
|
||||
|
||||
msgid "Are you sure? Press uppercase Q to go back."
|
||||
msgstr "确定吗? 按下大写 Q 以返回."
|
||||
|
||||
msgid "Cannot favorite: no repo associated"
|
||||
msgstr "无法收藏: 未关联仓库"
|
||||
|
||||
msgid "Unfavorited: {ident}"
|
||||
msgstr "已取消收藏: {ident}"
|
||||
|
||||
msgid "Favorited: {ident}"
|
||||
msgstr "已收藏: {ident}"
|
||||
|
||||
msgid "This function is not available during memorization. Please finish or go back first."
|
||||
msgstr "功能在记忆界面中不可用, 完成或返回后再试."
|
||||
|
||||
msgid "Time resume corrected: {old} -> {new}"
|
||||
msgstr "时间恢复已修正: {old} -> {new}"
|
||||
|
||||
msgid " [Debug Connected]"
|
||||
msgstr " [调试已连接]"
|
||||
|
||||
msgid "Favorites"
|
||||
msgstr "收藏夹"
|
||||
|
||||
msgid "No favorites"
|
||||
msgstr "暂无收藏"
|
||||
|
||||
msgid "Press * in the memorization screen to add favorites."
|
||||
msgstr "使用 * 键在记忆界面中添加收藏."
|
||||
|
||||
msgid "Total {n} favorite(s)"
|
||||
msgstr "共 {n} 个收藏项"
|
||||
|
||||
msgid ""
|
||||
" [d]Added: {time}\n"
|
||||
" From {title}[/d]"
|
||||
msgstr ""
|
||||
" [d]添加于: {time}\n"
|
||||
" 来自 {title}[/d]"
|
||||
|
||||
msgid "Remove"
|
||||
msgstr "移除"
|
||||
|
||||
msgid "Operation failed: invalid button identifier"
|
||||
msgstr "操作失败: 无效的按钮标识"
|
||||
|
||||
msgid "Removed favorite: {ident}"
|
||||
msgstr "已移除收藏: {ident}"
|
||||
|
||||
msgid "Failed to remove: {ident}"
|
||||
msgstr "移除失败: {ident}"
|
||||
|
||||
msgid "Switch"
|
||||
msgstr "切换"
|
||||
|
||||
msgid "Cache Manager"
|
||||
msgstr "缓存管理器"
|
||||
|
||||
msgid "Settings Page"
|
||||
msgstr "设置页面"
|
||||
|
||||
msgid "Sync Tool"
|
||||
msgstr "同步工具"
|
||||
|
||||
msgid "Exit"
|
||||
msgstr "退出程序"
|
||||
|
||||
msgid "Project Homepage"
|
||||
msgstr "项目主页"
|
||||
|
||||
msgid ""
|
||||
"[b]Select a function to navigate to\n"
|
||||
"or a memorization session instance[/b]\n"
|
||||
"\n"
|
||||
"Tips will be displayed here"
|
||||
msgstr ""
|
||||
"[b]请选择要跳转的功能\n"
|
||||
"或记忆会话实例[/b]\n"
|
||||
"\n"
|
||||
"将在此处显示提示"
|
||||
|
||||
msgid ""
|
||||
"Press Enter to switch\n"
|
||||
"All sessions will be saved"
|
||||
msgstr ""
|
||||
"按下回车以完成切换\n"
|
||||
"所有会话将被保存"
|
||||
|
||||
msgid "Close (n)"
|
||||
msgstr "关闭 (n)"
|
||||
|
||||
msgid "Prepare Repository"
|
||||
msgstr "准备记忆集"
|
||||
|
||||
msgid "Cache"
|
||||
msgstr "缓存"
|
||||
|
||||
msgid ""
|
||||
"**Ready**: `{title}`\n"
|
||||
""
|
||||
msgstr ""
|
||||
"**准备就绪**: `{title}`\n"
|
||||
""
|
||||
|
||||
msgid "Repo path: {path}"
|
||||
msgstr "单元集路径: {path}"
|
||||
|
||||
msgid "Progress: {touched}/{total} [{pct}%]"
|
||||
msgstr "学习完成度: {touched}/{total} [{pct}%]"
|
||||
|
||||
msgid "Scheduling algorithm: {algo} {desc}"
|
||||
msgstr "调度算法: {algo} {desc}"
|
||||
|
||||
msgid ""
|
||||
"Study count: {total} = {review} [d][Review][/d] + {new} [d][New][/d]\n"
|
||||
""
|
||||
msgstr ""
|
||||
"学习数量: {total} = {review} [d][复习][/d] + {new} [d][新识记][/d]\n"
|
||||
""
|
||||
|
||||
msgid "Start Memorizing"
|
||||
msgstr "开始记忆"
|
||||
|
||||
msgid "Manage Cache"
|
||||
msgstr "管理缓存"
|
||||
|
||||
msgid "Sync protocol: {proto}"
|
||||
msgstr "同步协议: {proto}"
|
||||
|
||||
msgid "Server Configuration:"
|
||||
msgstr "服务器配置:"
|
||||
|
||||
msgid "Remote server:"
|
||||
msgstr "远程服务器:"
|
||||
|
||||
msgid "Remote path:"
|
||||
msgstr "远程路径:"
|
||||
|
||||
msgid "Test Connection"
|
||||
msgstr "测试连接"
|
||||
|
||||
msgid "Start Sync"
|
||||
msgstr "开始同步"
|
||||
|
||||
msgid "Pause"
|
||||
msgstr "暂停"
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr "取消"
|
||||
|
||||
msgid "Sync Progress"
|
||||
msgstr "同步进度"
|
||||
|
||||
msgid "Sync Log"
|
||||
msgstr "同步日志"
|
||||
|
||||
msgid "Not configured"
|
||||
msgstr "未配置"
|
||||
|
||||
msgid "Sync service ready"
|
||||
msgstr "同步服务已就绪"
|
||||
|
||||
msgid "Sync service not configured or not enabled"
|
||||
msgstr "同步服务未配置或未启用"
|
||||
|
||||
msgid "Failed to update UI: {error}"
|
||||
msgstr "更新 UI 失败: {error}"
|
||||
|
||||
msgid "Failed to update status: {error}"
|
||||
msgstr "更新状态失败: {error}"
|
||||
|
||||
msgid "Sync service not initialised, please check configuration"
|
||||
msgstr "同步服务未初始化, 请检查配置"
|
||||
|
||||
msgid "Sync service not initialised"
|
||||
msgstr "同步服务未初始化"
|
||||
|
||||
msgid "Testing WebDAV connection..."
|
||||
msgstr "正在测试 WebDAV 连接..."
|
||||
|
||||
msgid "Testing connection..."
|
||||
msgstr "正在测试连接..."
|
||||
|
||||
msgid "Connection test successful"
|
||||
msgstr "连接测试成功"
|
||||
|
||||
msgid "Connection OK"
|
||||
msgstr "连接正常"
|
||||
|
||||
msgid "Connection test failed"
|
||||
msgstr "连接测试失败"
|
||||
|
||||
msgid "Connection failed"
|
||||
msgstr "连接失败"
|
||||
|
||||
msgid "Connection test error: {error}"
|
||||
msgstr "连接测试异常: {error}"
|
||||
|
||||
msgid "Connection error"
|
||||
msgstr "连接异常"
|
||||
|
||||
msgid "Sync service not initialised, cannot start sync"
|
||||
msgstr "同步服务未初始化, 无法开始同步"
|
||||
|
||||
msgid "Sync already in progress"
|
||||
msgstr "同步已在进行中"
|
||||
|
||||
msgid "Starting data sync..."
|
||||
msgstr "开始同步数据..."
|
||||
|
||||
msgid "Syncing..."
|
||||
msgstr "正在同步..."
|
||||
|
||||
msgid "Syncing nucleon directory: {dir}"
|
||||
msgstr "同步 nucleon 目录: {dir}"
|
||||
|
||||
msgid "Syncing electron directory: {dir}"
|
||||
msgstr "同步 electron 目录: {dir}"
|
||||
|
||||
msgid "Syncing orbital directory: {dir}"
|
||||
msgstr "同步 orbital 目录: {dir}"
|
||||
|
||||
msgid "nucleon sync complete: uploaded {up}, downloaded {down}"
|
||||
msgstr "nucleon 同步完成: 上传 {up} 个, 下载 {down} 个"
|
||||
|
||||
msgid "electron sync complete: uploaded {up}, downloaded {down}"
|
||||
msgstr "electron 同步完成: 上传 {up} 个, 下载 {down} 个"
|
||||
|
||||
msgid "orbital sync complete: uploaded {up}, downloaded {down}"
|
||||
msgstr "orbital 同步完成: 上传 {up} 个, 下载 {down} 个"
|
||||
|
||||
msgid "Sync complete"
|
||||
msgstr "同步完成"
|
||||
|
||||
msgid "All directories synced"
|
||||
msgstr "所有目录同步完成"
|
||||
|
||||
msgid "Error during sync: {error}"
|
||||
msgstr "同步过程中发生错误: {error}"
|
||||
|
||||
msgid "Sync failed"
|
||||
msgstr "同步失败"
|
||||
|
||||
msgid "Sync paused"
|
||||
msgstr "同步已暂停"
|
||||
|
||||
msgid "Sync resumed"
|
||||
msgstr "同步已恢复"
|
||||
|
||||
msgid "Sync cancelled"
|
||||
msgstr "同步已取消"
|
||||
|
||||
msgid "Resume"
|
||||
msgstr "继续"
|
||||
|
||||
msgid "Failed to update button state: {error}"
|
||||
msgstr "更新按钮状态失败: {error}"
|
||||
|
||||
msgid "Unknown error"
|
||||
msgstr "未知错误"
|
||||
|
||||
msgid "[b]Audio Pre-cache[/b]"
|
||||
msgstr "[b]音频预缓存[/b]"
|
||||
|
||||
msgid "Cache rate: {rate:.1f}% ({cached} / {total} units)"
|
||||
msgstr "缓存率: {rate:.1f}% (已缓存 {cached} / {total} 个单元)"
|
||||
|
||||
msgid "Target units from: [b]{desc}[/b]"
|
||||
msgstr "目标单元归属: [b]{desc}[/b]"
|
||||
|
||||
msgid "Unit count: {n}"
|
||||
msgstr "单元数量: {n}"
|
||||
|
||||
msgid "Target: all units"
|
||||
msgstr "目标: 所有单元"
|
||||
|
||||
msgid "Start Pre-cache"
|
||||
msgstr "开始预缓存"
|
||||
|
||||
msgid "Cancel Pre-cache"
|
||||
msgstr "取消预缓存"
|
||||
|
||||
msgid "Clear Cache"
|
||||
msgstr "清空缓存"
|
||||
|
||||
msgid "Cache path: {path}"
|
||||
msgstr "缓存路径: {path}"
|
||||
|
||||
msgid "Files: {n}"
|
||||
msgstr "文件数: {n}"
|
||||
|
||||
msgid "Total size: {size}"
|
||||
msgstr "总大小: {size}"
|
||||
|
||||
msgid "Refresh"
|
||||
msgstr "刷新"
|
||||
|
||||
msgid "If you leave this screen, ongoing cache processes will stop automatically."
|
||||
msgstr "若您离开此界面, 未完成的缓存进程会自动停止."
|
||||
|
||||
msgid "Cache supports \"resume from break\"."
|
||||
msgstr "缓存程序支持 [断点续传]."
|
||||
|
||||
msgid "Ready"
|
||||
msgstr "就绪"
|
||||
|
||||
msgid "Waiting to start..."
|
||||
msgstr "等待开始..."
|
||||
|
||||
msgid "Status: {s}"
|
||||
msgstr "状态: {s}"
|
||||
|
||||
msgid "Current item: {item}"
|
||||
msgstr "当前项目: {item}"
|
||||
|
||||
msgid "Processing ({i}/{total})"
|
||||
msgstr "正处理 ({i}/{total})"
|
||||
|
||||
msgid "Error"
|
||||
msgstr "出错"
|
||||
|
||||
msgid "Failed, skipping: {item}"
|
||||
msgstr "处理失败, 已跳过: {item}"
|
||||
|
||||
msgid "Cancelled"
|
||||
msgstr "已取消"
|
||||
|
||||
msgid "Pre-cache cancelled by user"
|
||||
msgstr "预缓存操作被用户取消"
|
||||
|
||||
msgid "Cleared"
|
||||
msgstr "已清空"
|
||||
|
||||
msgid "Audio cache cleared"
|
||||
msgstr "音频缓存已清空"
|
||||
|
||||
msgid "Failed to clear cache: {error}"
|
||||
msgstr "清空缓存失败: {error}"
|
||||
|
||||
msgid "Cache info refreshed"
|
||||
msgstr "缓存信息已刷新"
|
||||
|
||||
msgid "This memorization session is finished"
|
||||
msgstr "本次记忆进程结束"
|
||||
|
||||
msgid "Algorithm data {}"
|
||||
msgstr "算法数据{}"
|
||||
|
||||
msgid "saved"
|
||||
msgstr "已保存"
|
||||
|
||||
msgid "not saved"
|
||||
msgstr "未能保存"
|
||||
|
||||
msgid "Back to Menu"
|
||||
msgstr "返回上一级"
|
||||
|
||||
msgid "Perfect recall"
|
||||
msgstr "完美回想"
|
||||
|
||||
msgid "Correct after hesitation"
|
||||
msgstr "犹豫后正确"
|
||||
|
||||
msgid "Correct with difficulty"
|
||||
msgstr "困难地正确"
|
||||
|
||||
msgid "Wrong but familiar"
|
||||
msgstr "错误但熟悉"
|
||||
|
||||
msgid "Wrong and unfamiliar"
|
||||
msgstr "错误且不熟"
|
||||
|
||||
msgid "Complete blank"
|
||||
msgstr "完全空白"
|
||||
|
||||
msgid "Evaluate how well you remember this content: "
|
||||
msgstr "请评估你对这个内容的记忆程度: "
|
||||
|
||||
msgid "### Note: {note}"
|
||||
msgstr "### 笔记: {note}"
|
||||
|
||||
msgid "I know this"
|
||||
msgstr "我已知晓"
|
||||
|
||||
msgid "Current input: {input}"
|
||||
msgstr "当前输入: {input}"
|
||||
|
||||
msgid "Backspace"
|
||||
msgstr "退格"
|
||||
|
||||
msgid "Sample Label"
|
||||
msgstr "示例标签"
|
||||
|
||||
msgid "Sample Button"
|
||||
msgstr "示例按钮"
|
||||
@@ -11,4 +11,4 @@ __all__ = [
|
||||
]
|
||||
|
||||
providers = {"termux": termux_audio, "playsound": playsound_audio}
|
||||
logger.debug("音频 providers 已注册: %s", list(providers.keys()))
|
||||
logger.debug("Audio providers registered: %s", list(providers.keys()))
|
||||
|
||||
@@ -11,10 +11,10 @@ logger = get_logger(__name__)
|
||||
|
||||
|
||||
def play_by_path(path: pathlib.Path):
|
||||
logger.debug("playsound_audio.play_by_path: 开始播放 %s", path)
|
||||
logger.debug("playsound_audio.play_by_path: playing %s", path)
|
||||
try:
|
||||
import playsound3
|
||||
playsound3.playsound(str(path))
|
||||
logger.debug("播放完成: %s", path)
|
||||
logger.debug("Audio playing finished: %s", path)
|
||||
except Exception as e:
|
||||
logger.error("播放失败: %s, 错误: %s", path, e)
|
||||
logger.error("Failed to play: %s, error: %s", path, e)
|
||||
|
||||
@@ -13,9 +13,9 @@ logger = get_logger(__name__)
|
||||
# from .protocol import PlayFunctionProtocol
|
||||
|
||||
def play_by_path(path: pathlib.Path):
|
||||
logger.debug("termux_audio.play_by_path: 开始播放 %s", path)
|
||||
logger.debug("termux_audio.play_by_path: playing %s", path)
|
||||
try:
|
||||
os.system(f"play-audio {path.resolve()}")
|
||||
logger.debug("播放命令已执行: %s", path)
|
||||
logger.debug("Play audio: %s", path)
|
||||
except Exception as e:
|
||||
logger.error("播放失败: %s, 错误: %s", path, e)
|
||||
logger.error("Failed to play audio: %s, error: %s", path, e)
|
||||
|
||||
@@ -16,4 +16,4 @@ providers = {
|
||||
"openai": OpenAILLM,
|
||||
}
|
||||
|
||||
logger.debug("LLM providers 已注册: %s", list(providers.keys()))
|
||||
logger.debug("LLM providers registered: %s", list(providers.keys()))
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
"""LLM 提供者基类"""
|
||||
|
||||
import asyncio
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from heurams.services.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class BaseLLM:
|
||||
"""LLM 提供者基类"""
|
||||
|
||||
name = "BaseLLM"
|
||||
|
||||
def __init__(self, config: Dict[str, Any]):
|
||||
"""初始化 LLM 提供者
|
||||
|
||||
Args:
|
||||
config: 提供者配置字典
|
||||
"""
|
||||
self.config = config
|
||||
logger.debug("BaseLLM 初始化完成")
|
||||
|
||||
async def chat(self, messages: List[Dict[str, str]], **kwargs) -> str:
|
||||
"""发送聊天消息并获取响应
|
||||
|
||||
Args:
|
||||
messages: 消息列表, 每个消息为 {"role": "user"|"assistant"|"system", "content": "消息内容"}
|
||||
**kwargs: 其他参数, 如 temperature, max_tokens 等
|
||||
|
||||
Returns:
|
||||
模型返回的文本响应
|
||||
"""
|
||||
logger.debug("BaseLLM.chat: messages=%d, kwargs=%s", len(messages), kwargs)
|
||||
logger.warning("BaseLLM.chat 是基类方法, 未实现具体功能")
|
||||
await asyncio.sleep(0) # 避免未使用异步的警告
|
||||
return "BaseLLM 未实现具体功能"
|
||||
|
||||
async def chat_stream(self, messages: List[Dict[str, str]], **kwargs):
|
||||
"""流式聊天(可选实现)
|
||||
|
||||
Args:
|
||||
messages: 消息列表
|
||||
**kwargs: 其他参数
|
||||
|
||||
Yields:
|
||||
流式响应的文本块
|
||||
"""
|
||||
logger.debug(
|
||||
"BaseLLM.chat_stream: messages=%d, kwargs=%s", len(messages), kwargs
|
||||
)
|
||||
logger.warning("BaseLLM.chat_stream 是基类方法, 未实现具体功能")
|
||||
await asyncio.sleep(0)
|
||||
yield "BaseLLM 未实现流式功能"
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
"""OpenAI 兼容 LLM 提供者"""
|
||||
|
||||
from typing import Any, AsyncGenerator, Dict, List
|
||||
|
||||
from heurams.services.logger import get_logger
|
||||
|
||||
from .base import BaseLLM
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class OpenAILLM(BaseLLM):
|
||||
"""OpenAI 兼容 LLM 提供者"""
|
||||
|
||||
name = "OpenAI"
|
||||
|
||||
def __init__(self, config: Dict[str, Any]):
|
||||
super().__init__(config)
|
||||
self.api_key = config.get("key", "")
|
||||
self.base_url = config.get("url", "https://api.openai.com/v1")
|
||||
self._client = None
|
||||
logger.debug("OpenAILLM 初始化完成: base_url=%s", self.base_url)
|
||||
|
||||
def _get_client(self):
|
||||
"""获取 OpenAI 客户端(延迟导入)"""
|
||||
if self._client is None:
|
||||
try:
|
||||
from openai import AsyncOpenAI
|
||||
except ImportError:
|
||||
logger.error("未安装 openai 库, 请运行: pip install openai")
|
||||
raise ImportError("未安装 openai 库, 请运行: pip install openai")
|
||||
|
||||
self._client = AsyncOpenAI(
|
||||
api_key=self.api_key if self.api_key else None,
|
||||
base_url=self.base_url if self.base_url else None,
|
||||
)
|
||||
return self._client
|
||||
|
||||
async def chat(self, messages: List[Dict[str, str]], **kwargs) -> str:
|
||||
"""发送聊天消息并获取响应"""
|
||||
logger.debug("OpenAILLM.chat: messages=%d", len(messages))
|
||||
|
||||
client = self._get_client()
|
||||
|
||||
# 默认参数
|
||||
default_kwargs = {
|
||||
"model": kwargs.get("model", "gpt-3.5-turbo"),
|
||||
"temperature": kwargs.get("temperature", 0.7),
|
||||
"max_tokens": kwargs.get("max_tokens", 1000),
|
||||
}
|
||||
|
||||
# 合并参数, 优先使用传入的 kwargs
|
||||
request_kwargs = {**default_kwargs, **kwargs}
|
||||
request_kwargs["messages"] = messages
|
||||
|
||||
try:
|
||||
response = await client.chat.completions.create(**request_kwargs)
|
||||
content = response.choices[0].message.content
|
||||
logger.debug(
|
||||
"OpenAILLM.chat 成功: response length=%d",
|
||||
len(content) if content else 0,
|
||||
)
|
||||
return content or ""
|
||||
except Exception as e:
|
||||
logger.error("OpenAILLM.chat 失败: %s", e)
|
||||
raise
|
||||
|
||||
async def chat_stream(
|
||||
self, messages: List[Dict[str, str]], **kwargs
|
||||
) -> AsyncGenerator[str, None]:
|
||||
"""流式聊天"""
|
||||
logger.debug("OpenAILLM.chat_stream: messages=%d", len(messages))
|
||||
|
||||
client = self._get_client()
|
||||
|
||||
# 默认参数
|
||||
default_kwargs = {
|
||||
"model": kwargs.get("model", "gpt-3.5-turbo"),
|
||||
"temperature": kwargs.get("temperature", 0.7),
|
||||
"max_tokens": kwargs.get("max_tokens", 1000),
|
||||
"stream": True,
|
||||
}
|
||||
|
||||
# 合并参数
|
||||
request_kwargs = {**default_kwargs, **kwargs}
|
||||
request_kwargs["messages"] = messages
|
||||
|
||||
try:
|
||||
stream = await client.chat.completions.create(**request_kwargs)
|
||||
async for chunk in stream:
|
||||
if chunk.choices[0].delta.content:
|
||||
yield chunk.choices[0].delta.content
|
||||
except Exception as e:
|
||||
logger.error("OpenAILLM.chat_stream 失败: %s", e)
|
||||
raise
|
||||
@@ -15,4 +15,4 @@ providers = {
|
||||
"edgetts": EdgeTTS,
|
||||
}
|
||||
|
||||
logger.debug("TTS providers 已注册: %s", list(providers.keys()))
|
||||
logger.debug("TTS providers registered: %s", list(providers.keys()))
|
||||
|
||||
@@ -12,5 +12,5 @@ class BaseTTS:
|
||||
def convert(cls, text: str, path: pathlib.Path | str = "") -> pathlib.Path:
|
||||
"""path 是可选参数, 不填则自动返回生成文件路径"""
|
||||
logger.debug("BaseTTS.convert: text length=%d, path=%s", len(text), path)
|
||||
logger.warning("BaseTTS.convert 是基类方法, 未实现具体功能")
|
||||
logger.warning("BaseTTS.convert is not a functional implementation")
|
||||
return path # type: ignore
|
||||
|
||||
@@ -21,10 +21,10 @@ class EdgeTTS(BaseTTS):
|
||||
text,
|
||||
config_var.get()["providers"]["tts"]["edgetts"]["voice"],
|
||||
)
|
||||
logger.debug("EdgeTTS 通信对象创建成功, 正在保存音频")
|
||||
logger.debug("EdgeTTS object created, saving audio")
|
||||
communicate.save_sync(str(path))
|
||||
logger.debug("EdgeTTS 音频已保存到: %s", path)
|
||||
logger.debug("EdgeTTS audio saved as %s", path)
|
||||
return path # type: ignore
|
||||
except Exception as e:
|
||||
logger.error("EdgeTTS.convert 失败: %s", e)
|
||||
logger.error("EdgeTTS.convert failed: %s", e)
|
||||
raise
|
||||
|
||||
@@ -11,7 +11,7 @@ logger = get_logger(__name__)
|
||||
play_by_path: Callable = prov[
|
||||
config_var.get()["services"]["audio"]["provider"]
|
||||
].play_by_path
|
||||
logger.debug(
|
||||
"音频服务初始化完成, 使用 Provider: %s",
|
||||
logger.info(
|
||||
"TTS Service inited, using provider %s",
|
||||
config_var.get()["services"]["audio"]["provider"],
|
||||
)
|
||||
|
||||
@@ -84,7 +84,7 @@ class ConfigDict(UserDict):
|
||||
if i.suffix == ".toml":
|
||||
self.data[i.stem] = i
|
||||
else:
|
||||
logger.debug(f"配置目录中有无效的文件 {i.stem}") # what's up bro
|
||||
logger.error(f"Illegal file detected in config: {i.stem}") # what's up bro
|
||||
|
||||
def persist(self):
|
||||
if self.is_dir:
|
||||
@@ -92,7 +92,7 @@ class ConfigDict(UserDict):
|
||||
j = self[i]
|
||||
if isinstance(j, ConfigDict):
|
||||
j.persist()
|
||||
logger.debug("完成配置持久化")
|
||||
logger.info("Data persisted")
|
||||
return
|
||||
|
||||
with open(self.path, "w+") as f:
|
||||
|
||||
@@ -19,14 +19,12 @@ def epath(
|
||||
path = path.lstrip(".")
|
||||
target = dct
|
||||
keys = path.split(".")
|
||||
logger.debug(f"处理 EPATH {path}, {new_value}")
|
||||
logger.debug(f"Proceeding EPATH {path}, {new_value}")
|
||||
for idx, i in enumerate(keys):
|
||||
is_last = idx == len(keys) - 1
|
||||
|
||||
# 处理字典键
|
||||
logger.debug(
|
||||
f"处理 {i}, {(isinstance(target, dict) or isinstance(target, ConfigDict))} {i in target}"
|
||||
)
|
||||
logger.debug(f"Proceeding in detail: {i}, {(isinstance(target, dict) or isinstance(target, ConfigDict))} {i in target}")
|
||||
|
||||
if is_last and enable_modify:
|
||||
# 最后一次循环执行修改
|
||||
|
||||
@@ -73,9 +73,9 @@ class FavoriteManager:
|
||||
with open(self._file_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
self._favorites = [FavoriteItem.from_dict(item) for item in data]
|
||||
logger.debug("收藏列表加载成功, 共 %d 项", len(self._favorites))
|
||||
logger.info("Finished loading favlist, %d items in total", len(self._favorites))
|
||||
except Exception as e:
|
||||
logger.error("加载收藏列表失败: %s", e)
|
||||
logger.error("Filed to load favlist: %s", e)
|
||||
self._favorites = []
|
||||
else:
|
||||
self._favorites = []
|
||||
@@ -86,9 +86,9 @@ class FavoriteManager:
|
||||
data = [item.to_dict() for item in self._favorites]
|
||||
with open(self._file_path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
logger.debug("收藏列表保存成功, 共 %d 项", len(self._favorites))
|
||||
logger.info("Finished saving favlist, %d items in total", len(self._favorites))
|
||||
except Exception as e:
|
||||
logger.error("保存收藏列表失败: %s", e)
|
||||
logger.error("Failed to save favlist: %s", e)
|
||||
|
||||
def add(self, repo_path: str, ident: str, tags: List[str] | None = None) -> bool:
|
||||
"""添加收藏
|
||||
@@ -103,7 +103,7 @@ class FavoriteManager:
|
||||
# 检查是否已存在
|
||||
for item in self._favorites:
|
||||
if item.repo_path == repo_path and item.ident == ident:
|
||||
logger.debug("收藏已存在: %s/%s", repo_path, ident)
|
||||
logger.info("Favitem already exists: %s/%s", repo_path, ident)
|
||||
return False
|
||||
item = FavoriteItem(
|
||||
repo_path=repo_path,
|
||||
@@ -113,7 +113,7 @@ class FavoriteManager:
|
||||
)
|
||||
self._favorites.append(item)
|
||||
self.save()
|
||||
logger.info("添加收藏: %s/%s", repo_path, ident)
|
||||
logger.info("Add favitem: %s/%s", repo_path, ident)
|
||||
return True
|
||||
|
||||
def remove(self, repo_path: str, ident: str) -> bool:
|
||||
@@ -126,9 +126,9 @@ class FavoriteManager:
|
||||
if item.repo_path == repo_path and item.ident == ident:
|
||||
del self._favorites[idx]
|
||||
self.save()
|
||||
logger.info("移除收藏: %s/%s", repo_path, ident)
|
||||
logger.info("Remove favitem: %s/%s", repo_path, ident)
|
||||
return True
|
||||
logger.debug("收藏不存在: %s/%s", repo_path, ident)
|
||||
logger.error("Non-existed favitem: %s/%s", repo_path, ident)
|
||||
return False
|
||||
|
||||
def has(self, repo_path: str, ident: str) -> bool:
|
||||
@@ -150,7 +150,7 @@ class FavoriteManager:
|
||||
"""清空收藏列表"""
|
||||
self._favorites = []
|
||||
self.save()
|
||||
logger.info("清空收藏列表")
|
||||
logger.info("Clear favlist")
|
||||
|
||||
def count(self) -> int:
|
||||
"""收藏总数"""
|
||||
|
||||
@@ -6,15 +6,11 @@ logger = get_logger(__name__)
|
||||
|
||||
|
||||
def get_md5(text):
|
||||
logger.debug(f"计算MD5哈希, 输入`{text}`")
|
||||
logger.debug(f"MD5 hash input`{text}`")
|
||||
result = hashlib.md5(text.encode("utf-8")).hexdigest()
|
||||
logger.debug("哈希结果: %s...", result[:8])
|
||||
logger.debug("Providing MD5 hash: %s...", result[:8])
|
||||
return result
|
||||
|
||||
|
||||
def hash(text):
|
||||
# logger.debug(f"计算MD5-时间复合哈希, 输入`{text}`")
|
||||
# result = hashlib.md5(f"{text}{random.randint(0,1000)}".encode("utf-8")).hexdigest()
|
||||
# logger.debug("哈希结果: %s...", result[:8])
|
||||
# return result
|
||||
return get_md5(text)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
HeurAMS 日志服务模块
|
||||
基于Python标准logging库, 提供统一的日志记录功能
|
||||
"""日志服务模块
|
||||
|
||||
基于 logging 库, 提供统一日志记录功能
|
||||
"""
|
||||
|
||||
import logging
|
||||
@@ -8,26 +8,24 @@ import logging.handlers
|
||||
import pathlib
|
||||
from typing import Optional, Union
|
||||
|
||||
# 默认配置
|
||||
DEFAULT_LOG_LEVEL = logging.DEBUG
|
||||
DEFAULT_LOG_FILE = pathlib.Path("heurams.log")
|
||||
DEFAULT_LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
DEFAULT_LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d:%(funcName)s] - %(message)s"
|
||||
DEFAULT_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
# 全局logger缓存
|
||||
_loggers = {}
|
||||
|
||||
|
||||
def setup_logging(
|
||||
log_file: Union[str, pathlib.Path] = DEFAULT_LOG_FILE,
|
||||
log_level: int = DEFAULT_LOG_LEVEL,
|
||||
log_format: str = DEFAULT_LOG_FORMAT,
|
||||
date_format: str = DEFAULT_DATE_FORMAT,
|
||||
max_bytes: int = 10 * 1024 * 1024, # 10MB
|
||||
max_bytes: int = 16 * 1024 * 1024, # 16MB
|
||||
backup_count: int = 5,
|
||||
) -> None:
|
||||
"""
|
||||
配置全局日志系统
|
||||
设置全局日志服务
|
||||
|
||||
Args:
|
||||
log_file: 日志文件路径
|
||||
@@ -44,7 +42,7 @@ def setup_logging(
|
||||
# 创建formatter
|
||||
formatter = logging.Formatter(log_format, date_format)
|
||||
|
||||
# 创建文件handler(使用RotatingFileHandler防止日志过大)
|
||||
# 创建文件 handler (RotatingFileHandler)
|
||||
file_handler = logging.handlers.RotatingFileHandler(
|
||||
filename=log_path,
|
||||
maxBytes=max_bytes,
|
||||
@@ -54,7 +52,6 @@ def setup_logging(
|
||||
file_handler.setFormatter(formatter)
|
||||
file_handler.setLevel(log_level)
|
||||
|
||||
# 配置root logger - 设置为 WARNING 级别(只记录重要信息)
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(logging.WARNING) # 这里改为 WARNING
|
||||
|
||||
@@ -62,40 +59,28 @@ def setup_logging(
|
||||
for handler in root_logger.handlers[:]:
|
||||
root_logger.removeHandler(handler)
|
||||
|
||||
# 创建自己的应用logger(单独设置DEBUG级别)
|
||||
# 创建 heurams logger 并单独设置 DEBUG 级别
|
||||
app_logger = logging.getLogger("heurams")
|
||||
app_logger.setLevel(log_level) # 保持DEBUG级别
|
||||
app_logger.addHandler(file_handler)
|
||||
|
||||
# 禁止传播到root logger, 避免双重记录
|
||||
# 禁止传播到 root logger, 避免双重记录
|
||||
app_logger.propagate = False
|
||||
|
||||
# 设置第三方库的日志级别为WARNING, 避免调试信息干扰
|
||||
third_party_loggers = [
|
||||
"markdown_it",
|
||||
"markdown_it.rules_block",
|
||||
"markdown_it.rules_core",
|
||||
"markdown_it.rules_inline",
|
||||
"asyncio",
|
||||
]
|
||||
|
||||
for logger_name in third_party_loggers:
|
||||
logging.getLogger(logger_name).setLevel(logging.WARNING)
|
||||
|
||||
# 记录日志系统初始化
|
||||
app_logger.info("日志系统已初始化, 日志文件: %s", log_path)
|
||||
app_logger.debug("HeurAMS logger inited, path: %s", log_path.resolve())
|
||||
|
||||
|
||||
def get_logger(name: Optional[str] = None) -> logging.Logger:
|
||||
"""
|
||||
获取指定名称的logger
|
||||
获取指定名称的 logger
|
||||
|
||||
Args:
|
||||
name: logger名称, 通常使用模块名(__name__)
|
||||
如果为None, 返回root logger
|
||||
如果为None, 返回 root logger
|
||||
|
||||
Returns:
|
||||
logging.Logger实例
|
||||
logging.Logger 实例
|
||||
"""
|
||||
if name is None:
|
||||
return logging.getLogger()
|
||||
@@ -106,49 +91,12 @@ def get_logger(name: Optional[str] = None) -> logging.Logger:
|
||||
else:
|
||||
logger_name = name
|
||||
|
||||
# 缓存logger以提高性能
|
||||
# 缓存 logger 以提高性能, 以模块为单位的单例
|
||||
if logger_name not in _loggers:
|
||||
logger = logging.getLogger(logger_name)
|
||||
_loggers[logger_name] = logger
|
||||
|
||||
return _loggers[logger_name]
|
||||
|
||||
|
||||
# 便捷函数
|
||||
def debug(msg: str, *args, **kwargs) -> None:
|
||||
"""DEBUG级别日志"""
|
||||
get_logger().debug(msg, *args, **kwargs)
|
||||
|
||||
|
||||
def info(msg: str, *args, **kwargs) -> None:
|
||||
"""INFO级别日志"""
|
||||
get_logger().info(msg, *args, **kwargs)
|
||||
|
||||
|
||||
def warning(msg: str, *args, **kwargs) -> None:
|
||||
"""WARNING级别日志"""
|
||||
get_logger().warning(msg, *args, **kwargs)
|
||||
|
||||
|
||||
def error(msg: str, *args, **kwargs) -> None:
|
||||
"""ERROR级别日志"""
|
||||
get_logger().error(msg, *args, **kwargs)
|
||||
|
||||
|
||||
def critical(msg: str, *args, **kwargs) -> None:
|
||||
"""CRITICAL级别日志"""
|
||||
get_logger().critical(msg, *args, **kwargs)
|
||||
|
||||
|
||||
def exception(msg: str, *args, **kwargs) -> None:
|
||||
"""记录异常信息 (ERROR级别)"""
|
||||
get_logger().exception(msg, *args, **kwargs)
|
||||
|
||||
|
||||
# 初始化日志系统(硬编码配置)
|
||||
setup_logging()
|
||||
|
||||
|
||||
# 模块级别的logger实例
|
||||
logger = get_logger(__name__)
|
||||
logger.info("HeurAMS日志服务模块已加载")
|
||||
# 初始化日志系统
|
||||
setup_logging()
|
||||
@@ -1,4 +1,5 @@
|
||||
# 时间服务
|
||||
"""时间服务
|
||||
"""
|
||||
import datetime
|
||||
import time
|
||||
|
||||
@@ -7,32 +8,40 @@ from heurams.services.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
daystamp_override = config_var.get()["services"]["timer"]["daystamp_override"]
|
||||
timestamp_override = config_var.get()["services"]["timer"]["timestamp_override"]
|
||||
last_daystamp = 0
|
||||
last_timestamp = 0
|
||||
|
||||
def get_daystamp() -> int:
|
||||
"""获取当前日戳(以天为单位的整数时间戳)"""
|
||||
time_override = config_var.get()["services"]["timer"]["daystamp_override"]
|
||||
if time_override != -1:
|
||||
logger.debug("使用覆盖的日戳: %d", time_override)
|
||||
return int(time_override)
|
||||
if daystamp_override != -1:
|
||||
logger.debug("Daystamp overrode: %d", daystamp_override)
|
||||
return int(daystamp_override)
|
||||
|
||||
result = int(
|
||||
(time.time() + config_var.get()["services"]["timer"]["timezone_offset"])
|
||||
// (24 * 3600)
|
||||
)
|
||||
logger.debug("计算日戳: %d", result)
|
||||
global last_daystamp
|
||||
if last_daystamp != result: # 用于避免日志泛洪
|
||||
logger.debug("Providing new daystamp: %d", result)
|
||||
last_daystamp = result
|
||||
return result
|
||||
|
||||
|
||||
def get_timestamp() -> float:
|
||||
"""获取 UNIX 时间戳"""
|
||||
# 搞这个函数的原因是要支持可复现操作
|
||||
time_override = config_var.get()["services"]["timer"]["timestamp_override"]
|
||||
if time_override != -1:
|
||||
logger.debug("使用覆盖的时间戳: %f", time_override)
|
||||
return float(time_override)
|
||||
if timestamp_override != -1:
|
||||
logger.debug("Timestamp overrode: %f", timestamp_override)
|
||||
return float(timestamp_override)
|
||||
|
||||
result = time.time()
|
||||
logger.debug("获取当前时间戳: %f", result)
|
||||
global last_timestamp
|
||||
if last_timestamp != result:
|
||||
logger.debug("Providing new timestamp: %d", result)
|
||||
last_timestamp = result
|
||||
return result
|
||||
|
||||
|
||||
@@ -49,9 +58,9 @@ def daystamp_to_datetime(daystamp: int) -> datetime.datetime:
|
||||
|
||||
|
||||
def datetime_to_daystamp(dt: datetime.datetime) -> int:
|
||||
"""将 datetime 转换为日戳(从 1970-01-01 起的天数)
|
||||
"""将 datetime 转换为日戳 (从 1970-01-01 起的天数)
|
||||
|
||||
接受带时区或 naive 的 datetime(naive 视为 UTC)。
|
||||
接受带时区或 naive 的 datetime (naive 视为 UTC)
|
||||
"""
|
||||
epoch = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
|
||||
if dt.tzinfo is None:
|
||||
@@ -61,5 +70,5 @@ def datetime_to_daystamp(dt: datetime.datetime) -> int:
|
||||
|
||||
|
||||
def get_now_datetime() -> datetime.datetime:
|
||||
"""获取当前时间的 UTC datetime(遵守时间覆盖)"""
|
||||
"""获取当前时间的 UTC datetime (遵守时间覆盖)"""
|
||||
return datetime.datetime.fromtimestamp(get_timestamp(), tz=datetime.timezone.utc)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# 文本转语音服务
|
||||
"""文本转语音服务
|
||||
"""
|
||||
from typing import Callable
|
||||
|
||||
from heurams.context import config_var
|
||||
@@ -8,7 +9,7 @@ from heurams.services.logger import get_logger
|
||||
logger = get_logger(__name__)
|
||||
|
||||
convertor: Callable = prov[config_var.get()["services"]["tts"]["provider"]].convert
|
||||
logger.debug(
|
||||
"TTS 服务初始化完成, 使用 provider: %s",
|
||||
logger.info(
|
||||
"TTS Service inited, using provider: %s",
|
||||
config_var.get()["services"]["tts"]["provider"],
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# 版本控制集成服务
|
||||
"""版本服务
|
||||
"""
|
||||
from heurams.services.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
@@ -6,6 +7,4 @@ logger = get_logger(__name__)
|
||||
ver = "0.5.1"
|
||||
stage = "stable"
|
||||
codename = "fulcrum"
|
||||
codename_cn = "支点"
|
||||
|
||||
logger.info("HeurAMS 版本: %s (%s), 阶段: %s", ver, codename, stage)
|
||||
codename_cn = "支点"
|
||||
@@ -1,53 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>HeurAMS 潜进</title>
|
||||
<title>HeurAMS</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- 顶栏 -->
|
||||
<!-- Top bar -->
|
||||
<nav id="navbar">
|
||||
<button id="nav-toggle" onclick="toggleSidebar()">≡</button>
|
||||
<span id="nav-title">仪表盘</span>
|
||||
<span id="nav-title">Dashboard</span>
|
||||
<span id="nav-version"></span>
|
||||
<button id="theme-btn" onclick="toggleTheme()" class="btn-icon">☼</button>
|
||||
</nav>
|
||||
|
||||
<!-- 主体 -->
|
||||
<!-- Main -->
|
||||
<div id="main-wrap">
|
||||
|
||||
<!-- 侧边栏 -->
|
||||
<!-- Sidebar -->
|
||||
<aside id="sidebar">
|
||||
<ul class="sidenav">
|
||||
<li class="active" id="nav-lobby"><a href="#" onclick="switchView('lobby')">仪表盘</a></li>
|
||||
<li id="nav-favs"><a href="#" onclick="switchView('favs')">收藏夹</a></li>
|
||||
<li id="nav-cache"><a href="#" onclick="switchView('cache')">缓存管理</a></li>
|
||||
<li id="nav-settings"><a href="#" onclick="switchView('settings')">设置</a></li>
|
||||
<li id="nav-about"><a href="#" onclick="switchView('about')">关于</a></li>
|
||||
<li class="active" id="nav-lobby"><a href="#" onclick="switchView('lobby')">Dashboard</a></li>
|
||||
<li id="nav-favs"><a href="#" onclick="switchView('favs')">Favorites</a></li>
|
||||
<li id="nav-cache"><a href="#" onclick="switchView('cache')">Cache</a></li>
|
||||
<li id="nav-settings"><a href="#" onclick="switchView('settings')">Settings</a></li>
|
||||
<li id="nav-about"><a href="#" onclick="switchView('about')">About</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<!-- 内容区 -->
|
||||
<!-- Content area -->
|
||||
<main id="main">
|
||||
|
||||
<!-- 错误 -->
|
||||
<!-- Error -->
|
||||
<div id="error-box"></div>
|
||||
|
||||
<!-- ===== 仪表盘 ===== -->
|
||||
<!-- ===== Dashboard ===== -->
|
||||
<div id="view-lobby" class="view">
|
||||
<div class="stat-row" id="lobby-stats"></div>
|
||||
<div id="lobby-analysis" class="dim sm tc" style="margin-bottom:12px;"></div>
|
||||
<p id="loading-lobby" class="dim tc">加载仓库...</p>
|
||||
<p id="empty-lobby" class="dim tc hidden">没有找到仓库</p>
|
||||
<p id="loading-lobby" class="dim tc">Loading repositories...</p>
|
||||
<p id="empty-lobby" class="dim tc hidden">No repositories found</p>
|
||||
<div id="repo-list"></div>
|
||||
</div>
|
||||
|
||||
<!-- ===== 仓库详情 ===== -->
|
||||
<!-- ===== Repo details ===== -->
|
||||
<div id="view-prep" class="view hidden">
|
||||
<p id="loading-prep" class="dim tc">加载仓库数据...</p>
|
||||
<p id="loading-prep" class="dim tc">Loading repo data...</p>
|
||||
<div id="prep-content" class="hidden">
|
||||
<h2 id="prep-title"></h2>
|
||||
<p class="dim" id="prep-meta"></p>
|
||||
@@ -58,66 +58,66 @@
|
||||
<p class="dim sm" id="prep-pct"></p>
|
||||
|
||||
<div class="h-group">
|
||||
<label>每次复习量:</label>
|
||||
<label>Study count per session:</label>
|
||||
<input type="number" id="prep-num" value="10" min="1" max="200">
|
||||
<button class="btn pri" onclick="startReviewFromPrep()">开始记忆</button>
|
||||
<button class="btn pri" onclick="startReviewFromPrep()">Start</button>
|
||||
</div>
|
||||
|
||||
<div id="prep-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== 复习 ===== -->
|
||||
<!-- ===== Review ===== -->
|
||||
<div id="view-review" class="view hidden">
|
||||
<div class="h-group sb">
|
||||
<div class="h-group">
|
||||
<strong id="review-repo"></strong>
|
||||
<span id="review-phase" class="tag"></span>
|
||||
<button id="fav-btn" class="btn-link" onclick="toggleFav()">☆</button>
|
||||
<button id="tts-btn" class="btn-link" onclick="playTTS()" title="朗读">▶</button>
|
||||
<button id="tts-btn" class="btn-link" onclick="playTTS()" title="Read Aloud">▶</button>
|
||||
</div>
|
||||
<span id="review-status" class="tag">断开</span>
|
||||
<span id="review-status" class="tag">Disconnected</span>
|
||||
</div>
|
||||
<div class="bar-wrap sm"><div class="bar-fill" id="review-bar"></div></div>
|
||||
<p class="dim sm tc" id="review-pos"></p>
|
||||
|
||||
<div id="screen-start" class="empty-state">
|
||||
<h3>准备就绪</h3>
|
||||
<p class="dim">开始复习</p>
|
||||
<button class="btn pri" onclick="startReview()">开始复习</button>
|
||||
<h3>Ready</h3>
|
||||
<p class="dim">Start review</p>
|
||||
<button class="btn pri" onclick="startReview()">Start Review</button>
|
||||
</div>
|
||||
|
||||
<div id="screen-puzzle" class="hidden">
|
||||
<div id="puzzle-card"></div>
|
||||
<div class="tc">
|
||||
<p class="dim sm">自评记忆程度</p>
|
||||
<p class="dim sm">Self-assessment</p>
|
||||
<div id="rating-group"></div>
|
||||
<div class="h-group jc">
|
||||
<button class="btn sm suc" onclick="quickPass()">正确 (5)</button>
|
||||
<button class="btn sm err" onclick="quickFail()">错误 (2)</button>
|
||||
<button class="btn sm suc" onclick="quickPass()">Correct (5)</button>
|
||||
<button class="btn sm err" onclick="quickFail()">Incorrect (2)</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="screen-finished" class="hidden empty-state">
|
||||
<p class="ok-icon">✓</p>
|
||||
<h3>本次记忆进程结束</h3>
|
||||
<h3>Session complete</h3>
|
||||
<p class="dim" id="finish-info"></p>
|
||||
<p class="dim sm" id="finish-saved"></p>
|
||||
<button class="btn pri" onclick="goBack()" style="margin-top:16px;">返回</button>
|
||||
<button class="btn pri" onclick="goBack()" style="margin-top:16px;">Back</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== 收藏夹 ===== -->
|
||||
<!-- ===== Favorites ===== -->
|
||||
<div id="view-favs" class="view hidden">
|
||||
<p id="loading-favs" class="dim tc">加载收藏...</p>
|
||||
<p id="empty-favs" class="dim tc hidden">暂无收藏</p>
|
||||
<p id="loading-favs" class="dim tc">Loading favorites...</p>
|
||||
<p id="empty-favs" class="dim tc hidden">No favorites</p>
|
||||
<div id="favs-list"></div>
|
||||
</div>
|
||||
|
||||
<!-- ===== 缓存管理 ===== -->
|
||||
<!-- ===== Cache ===== -->
|
||||
<div id="view-cache" class="view hidden">
|
||||
<p id="loading-cache" class="dim tc">加载缓存信息...</p>
|
||||
<p id="loading-cache" class="dim tc">Loading cache info...</p>
|
||||
<div id="cache-content" class="hidden">
|
||||
<div class="cache-stats">
|
||||
<div class="stat-row" id="cache-stats-cards"></div>
|
||||
@@ -126,36 +126,36 @@
|
||||
<p class="dim sm" id="cache-path"></p>
|
||||
</div>
|
||||
<div class="h-group mt">
|
||||
<button class="btn pri" id="cache-precache-btn" onclick="startPrecache()">生成缓存</button>
|
||||
<button class="btn" id="cache-refresh-btn" onclick="loadCache()">刷新</button>
|
||||
<button class="btn err" id="cache-clear-btn" onclick="clearCache()">清空缓存</button>
|
||||
<button class="btn pri" id="cache-precache-btn" onclick="startPrecache()">Generate Cache</button>
|
||||
<button class="btn" id="cache-refresh-btn" onclick="loadCache()">Refresh</button>
|
||||
<button class="btn err" id="cache-clear-btn" onclick="clearCache()">Clear Cache</button>
|
||||
</div>
|
||||
<p id="cache-msg" class="dim sm mt"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== 设置 ===== -->
|
||||
<!-- ===== Settings ===== -->
|
||||
<div id="view-settings" class="view hidden">
|
||||
<p id="loading-settings" class="dim tc">加载设置...</p>
|
||||
<p id="loading-settings" class="dim tc">Loading settings...</p>
|
||||
<div id="settings-content" class="hidden"></div>
|
||||
</div>
|
||||
|
||||
<!-- ===== 关于 ===== -->
|
||||
<!-- ===== About ===== -->
|
||||
<div id="view-about" class="view hidden">
|
||||
<div class="about-section">
|
||||
<h2>HeurAMS 潜进</h2>
|
||||
<h2>HeurAMS</h2>
|
||||
<p class="dim" id="about-version"></p>
|
||||
<p class="dim sm" id="about-codename"></p>
|
||||
<p class="mt">一个基于启发式算法与认知科学理论的辅助记忆调度器。</p>
|
||||
<p class="dim sm mt">以 GNU AGPL-3.0 许可证开放源代码,并含本机 API 调用豁免条款。</p>
|
||||
<p class="mt">A heuristic auxiliary memorizing scheduler based on cognitive science theories.</p>
|
||||
<p class="dim sm mt">Licensed under GNU AGPL-3.0 with local API call exemption.</p>
|
||||
</div>
|
||||
<div class="about-section">
|
||||
<h3>开发人员</h3>
|
||||
<h3>Developers</h3>
|
||||
<p class="dim sm">Wang Zhiyu (<a href="https://github.com/pluvium27" target="_blank">@pluvium27</a>)</p>
|
||||
<p class="dim sm mt">项目发起与主要开发者</p>
|
||||
<p class="dim sm mt">Project initiator and lead developer</p>
|
||||
</div>
|
||||
<div class="about-section">
|
||||
<h3>运行环境</h3>
|
||||
<h3>Runtime Environment</h3>
|
||||
<table class="info-table" id="about-env"></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user