diff --git a/data/config/_.toml b/data/config/_.toml new file mode 100644 index 0000000..741ab92 --- /dev/null +++ b/data/config/_.toml @@ -0,0 +1,6 @@ +_services_desc = '服务模块(services)设置' +_providers_desc = '驱动模块(providers)设置' +_repo_desc = '单元集独立设置' +_interface_desc = '基本用户界面设置' +_default_desc = '默认设置' +_global_desc = '底层设置' diff --git a/data/config/global.toml b/data/config/global.toml index 74fc3ed..257649b 100644 --- a/data/config/global.toml +++ b/data/config/global.toml @@ -1,7 +1,13 @@ enable_built_in_interface = true +_enable_built_in_interface_desc = "启用内置基本用户界面\n(当且仅当 HeurAMS 作为程序库时禁用, 以跳过用户界面逻辑)" +_paths_desc = "用户数据路径定义" [paths] data = "./data" +_data_desc = "用户数据根目录" cache = "./data/cache" +_cache_desc = "缓存根目录\n(如音频缓存在 voice 子目录)" config = "./data/config" +_config_desc = "配置文件根目录" repo = "./data/repo" +_repo_desc = "记忆单元集根目录" diff --git a/data/config/interface/_.toml b/data/config/interface/_.toml new file mode 100644 index 0000000..84f18c8 --- /dev/null +++ b/data/config/interface/_.toml @@ -0,0 +1,4 @@ +_global_desc = "用户界面通用设置" +_widgets_desc = "各组件设置" +_screens_desc = "各界面设置" +_puzzles_desc = "谜题生成器设置" diff --git a/data/config/interface/global.toml b/data/config/interface/global.toml index ddf073e..e6e60fd 100644 --- a/data/config/interface/global.toml +++ b/data/config/interface/global.toml @@ -1,6 +1,17 @@ persist_to_file = true +_persist_to_file_desc = "[调试] 将记忆更改保存到文件" quick_pass = true +_quick_pass_desc = "[调试] 启用快速应答功能(跳过测验)" auto_pass = false +_auto_pass_desc = "[调试] 自动通过测试模式" scheduled_num = 420 +_scheduled_num_desc = "默认记忆单元数量(可被单元集设置覆盖)" algorithm = "NSP-0" -_algorithm_candidate = [ "SM-2", "SM-15M", "FSRS", "NSP-0", "None",] +_algorithm_desc = "默认记忆调度算法(可被单元集设置覆盖)" + +[_algorithm_candidate] +NSP-0 = "筛选用非间隔重复调度器" +none = "不设置默认调度器" +SM-2 = "第二代 SuperMemo 简单间隔重复调度器" +SM-15M = "第15代 SuperMemo 复杂间隔重复调度器 (不稳定且逆向工程)" +FSRS = "先进开放间隔重复调度器" diff --git a/data/config/interface/puzzles/_.toml b/data/config/interface/puzzles/_.toml new file mode 100644 index 0000000..5164f75 --- /dev/null +++ b/data/config/interface/puzzles/_.toml @@ -0,0 +1,2 @@ +_cloze_desc = "填空题" +_mcq_desc = "选择题" diff --git a/data/config/interface/puzzles/cloze.toml b/data/config/interface/puzzles/cloze.toml index 74f917b..d467db1 100644 --- a/data/config/interface/puzzles/cloze.toml +++ b/data/config/interface/puzzles/cloze.toml @@ -1 +1,2 @@ min_denominator = 3 +_min_denominator_desc = "设空比例系数的倒数" diff --git a/data/config/interface/puzzles/mcq.toml b/data/config/interface/puzzles/mcq.toml index b72b3dd..f0fe9ca 100644 --- a/data/config/interface/puzzles/mcq.toml +++ b/data/config/interface/puzzles/mcq.toml @@ -1 +1,2 @@ max_riddles_num = 2 +_max_riddles_num_desc = "单次生成的最大谜题数量" diff --git a/data/config/interface/screens/memoqueue.toml b/data/config/interface/screens/memoqueue.toml deleted file mode 100644 index cf80dfb..0000000 --- a/data/config/interface/screens/memoqueue.toml +++ /dev/null @@ -1 +0,0 @@ -autovoice = true diff --git a/data/config/interface/widgets/_.toml b/data/config/interface/widgets/_.toml new file mode 100644 index 0000000..5a8d43a --- /dev/null +++ b/data/config/interface/widgets/_.toml @@ -0,0 +1 @@ +_recognition_desc = "用于 '辨识' 组件的设置" diff --git a/data/config/interface/widgets/recognition.toml b/data/config/interface/widgets/recognition.toml index cf80dfb..4fa9d77 100644 --- a/data/config/interface/widgets/recognition.toml +++ b/data/config/interface/widgets/recognition.toml @@ -1 +1,2 @@ autovoice = true +_autovoice_desc = "自动语音播放" diff --git a/data/config/providers/_.toml b/data/config/providers/_.toml new file mode 100644 index 0000000..a592309 --- /dev/null +++ b/data/config/providers/_.toml @@ -0,0 +1 @@ +_tts_desc = '文本转语音驱动' diff --git a/data/config/providers/tts/_.toml b/data/config/providers/tts/_.toml new file mode 100644 index 0000000..8ba734b --- /dev/null +++ b/data/config/providers/tts/_.toml @@ -0,0 +1 @@ +_edgetts_desc = "微软文本转语音驱动" diff --git a/data/config/providers/tts/edgetts.toml b/data/config/providers/tts/edgetts.toml index 1ada64f..a88ee6e 100644 --- a/data/config/providers/tts/edgetts.toml +++ b/data/config/providers/tts/edgetts.toml @@ -1,4 +1,5 @@ voice = "zh-CN-XiaoxiaoNeural" +_voice_desc = "音色" [_voice_candidate] zh-CN-XiaoxiaoNeural = "晓晓: 中文温柔女声" diff --git a/data/config/services/_.toml b/data/config/services/_.toml new file mode 100644 index 0000000..38ebb9f --- /dev/null +++ b/data/config/services/_.toml @@ -0,0 +1,5 @@ +_audio_desc = '音频服务' +_llm_desc = '语言模型服务' +_sync_desc = '数据同步服务' +_timer_desc = '时间服务' +_tts_desc = '文本转语音服务' diff --git a/data/config/services/audio.toml b/data/config/services/audio.toml index d1722d7..ac42f77 100644 --- a/data/config/services/audio.toml +++ b/data/config/services/audio.toml @@ -1,4 +1,5 @@ provider = "playsound" +_provider_desc = "音频驱动类型" [_provider_candidate] playsound = "python 跨平台音频系统" diff --git a/data/config/services/llm.toml b/data/config/services/llm.toml index 5e9e971..6f748c0 100644 --- a/data/config/services/llm.toml +++ b/data/config/services/llm.toml @@ -1,4 +1,5 @@ provider = "openai" +_provider_desc = "模型接口类型" [_provider_candidate] openai = "OpenAI 风格 API, 同时支持与其相容的模型服务 (如 deepseek)" diff --git a/data/config/services/sync.toml b/data/config/services/sync.toml index da9c57c..037a247 100644 --- a/data/config/services/sync.toml +++ b/data/config/services/sync.toml @@ -1,4 +1,5 @@ provider = "webdav" +_provider_desc = "同步服务驱动类型" [_provider_candidate] webdav = "WebDAV 兼容网络文件系统 (包括 webdavs)" diff --git a/data/config/services/timer.toml b/data/config/services/timer.toml index 69d414d..6fe395a 100644 --- a/data/config/services/timer.toml +++ b/data/config/services/timer.toml @@ -1,3 +1,6 @@ daystamp_override = -1 +_daystamp_override_desc = "[调试] 覆写 UNIX 日时间戳, 单位为日\n(设为 -1 禁用)" timestamp_override = -1 +_timestamp_override_desc = "[调试] 覆写 UNIX 时间戳, 单位为秒\n(设为 -1 禁用)" timezone_offset = 28800 +_timezone_offset_desc = "时区偏移设置, 用于取消跨天时区误差, 单位为秒\n(如 28800 为 UTC+8.0, 中国标准时间)" diff --git a/data/config/services/tts.toml b/data/config/services/tts.toml index d2bf261..89b3273 100644 --- a/data/config/services/tts.toml +++ b/data/config/services/tts.toml @@ -1,4 +1,5 @@ provider = "edgetts" +_provider_desc = "文本转语音驱动类型" [_provider_candidate] edgetts = "微软神经网络语音合成, 依赖微软网络服务" diff --git a/data/config/test.toml b/data/config/test.toml deleted file mode 100644 index f41c16b..0000000 --- a/data/config/test.toml +++ /dev/null @@ -1,4 +0,0 @@ -[aa.bb] -str = "sq" -boolean = true -int = 123 diff --git a/src/heurams/interface/css/main.tcss b/src/heurams/interface/css/main.tcss index ac93bce..911f205 100644 --- a/src/heurams/interface/css/main.tcss +++ b/src/heurams/interface/css/main.tcss @@ -39,7 +39,10 @@ NavigatorScreen { margin-top: 1; align: center middle; } - +.container { + height: auto; + padding: 0 0 1 0; +} #message-input { width: 1fr; margin-right: 1; diff --git a/src/heurams/interface/screens/setting.py b/src/heurams/interface/screens/setting.py index 53aebac..cdf3c6a 100644 --- a/src/heurams/interface/screens/setting.py +++ b/src/heurams/interface/screens/setting.py @@ -8,7 +8,7 @@ import os from textual.app import ComposeResult from textual.containers import ScrollableContainer, Container, Horizontal, Vertical from textual.screen import Screen -from textual.widgets import Button, Footer, Header, Label, ListItem, ListView, Static, Collapsible, Input, Switch +from textual.widgets import Button, Footer, Header, Label, ListItem, ListView, Static, Collapsible, Input, Switch, Select from textual.layouts import horizontal import heurams.kernel.particles as pt @@ -45,9 +45,13 @@ class SettingScreen(Screen): """组合界面组件""" yield Header(show_clock=True) with ScrollableContainer(): - yield Label('设置页面') + yield Label('[b]设置页面[/b]') for i in config_var.get(): - yield Collapsible(*self._get_subcfg(f'{i}'), title=i) + if i.startswith('_'): + continue + a = self._get_subcfg(f'{i}') + if a: + yield Collapsible(*a, title=i + f'\n{config_var.get().get(f"_{i}_desc", "")}') yield Footer() def _get_subcfg(self, parent_epath: str): @@ -56,7 +60,11 @@ class SettingScreen(Screen): if parent.is_dir: lst = list() for i in parent: - lst.append(Collapsible(*self._get_subcfg(f"{parent_epath}.{i}"), title=i)) + if i.startswith('_'): + continue + a = self._get_subcfg(f"{parent_epath}.{i}") + if a: + lst.append(Collapsible(*a, title=i + f'\n{parent.get(f"_{i}_desc", "")}')) return lst if isinstance(parent, dict) or (isinstance(parent, ConfigDict) and not parent.is_dir): lst = list() @@ -64,32 +72,47 @@ class SettingScreen(Screen): if i.startswith('_'): continue if isinstance(parent[i], dict): - lst.append(Collapsible(*self._get_subcfg(f"{parent_epath}.{i}"), title=i)) - elif isinstance(parent[i], float): - lst.extend([ - Label(i), - Input(value=str(parent[i]), placeholder='要求一个浮点数', type='number', id=domize(f"{parent_epath}.{i}")) - ]) - elif isinstance(parent[i], str): - lst.extend([ - Label(i), - Input(value=parent[i], placeholder='要求一个字符串', type='text', id=domize(f"{parent_epath}.{i}")) - ]) - elif isinstance(parent[i], bool): - lst.extend([ - Label(i), - Switch(value=str(parent[i]), id=domize(f"{parent_epath}.{i}")) - ]) - elif isinstance(parent[i], int): - lst.extend([ - Label(i), - Input(value=str(parent[i]), placeholder='要求一个整数', type='integer', id=domize(f"{parent_epath}.{i}")) - ]) - elif isinstance(parent[i], list): - pass + a = self._get_subcfg(f"{parent_epath}.{i}") + if a: + lst.append(Collapsible(*a, title=i + f'\n{parent.get(f"_{i}_desc", "")}')) + elif f'_{i}_candidate' in parent: + if isinstance(parent[f'_{i}_candidate'], dict): + lst.append(Horizontal( + Label(i + f'\n{parent.get(f"_{i}_desc", "")}'), + Select((f"{j} ({k})", j) for j, k in parent[f'_{i}_candidate'].items()), + classes='container' + )) + elif isinstance(parent[f'_{i}_candidate'], list): + lst.append(Horizontal( + Label(i + f'\n{parent.get(f"_{i}_desc", "")}'), + Select((j, j) for j in parent[f'_{i}_candidate']), + classes='container' + )) else: - lst.append(Label('未知类型')) - + if isinstance(parent[i], float): + lst.append(Horizontal( + Label(i + f'\n{parent.get(f"_{i}_desc", "")}'), + Input(value=str(parent[i]), placeholder='要求一个浮点数', type='number', id=domize(f"{parent_epath}.{i}")), + classes='container')) + elif isinstance(parent[i], str): + lst.append(Horizontal( + Label(i + f'\n{parent.get(f"_{i}_desc", "")}'), + Input(value=parent[i], placeholder='要求一个字符串', type='text', id=domize(f"{parent_epath}.{i}")), + classes='container')) + elif isinstance(parent[i], bool): + lst.append(Horizontal( + Label(i + f'\n{parent.get(f"_{i}_desc", "")}'), + Switch(value=str(parent[i]), id=domize(f"{parent_epath}.{i}")), + classes='container')) + elif isinstance(parent[i], int): + lst.append(Horizontal( + Label(i + f'\n{parent.get(f"_{i}_desc", "")}'), + Input(value=str(parent[i]), placeholder='要求一个整数', type='integer', id=domize(f"{parent_epath}.{i}")), + classes='container')) + elif isinstance(parent[i], list): + pass + else: + lst.append(Label('未知类型')) return lst return [Label('无子项')] @@ -97,6 +120,10 @@ class SettingScreen(Screen): """挂载组件时初始化""" pass + def action_go_back(self) -> None: + """返回上一屏幕""" + self.app.pop_screen() + def action_quit_app(self) -> None: """退出应用程序""" self.app.exit() diff --git a/src/heurams/services/config.py b/src/heurams/services/config.py index c88b727..7119fc2 100644 --- a/src/heurams/services/config.py +++ b/src/heurams/services/config.py @@ -56,6 +56,11 @@ class ConfigDict(UserDict): # 舒服了 def update_index(self): # 如果有人没事干在config里面创建指向config的符号链接 这玩意会崩溃 但是不要修复: 需要这个符号链接特性 for i in self.path.iterdir(): + if i.name.startswith('_'): + if i.name == '_.toml' and not i.is_dir(): + with open(self.path/'_.toml', 'r+') as f: + self.data.update(dict(toml.load(f))) + continue if i.is_dir(): self.data[i.name] = i else: