diff --git a/src/heurams/kernel/algorithms/__init__.py b/src/heurams/kernel/algorithms/__init__.py index 1c0b3bd..364369a 100644 --- a/src/heurams/kernel/algorithms/__init__.py +++ b/src/heurams/kernel/algorithms/__init__.py @@ -4,6 +4,7 @@ __all__ = [ 'SM2Algorithm', ] -registry = { +algorithms = { "SM-2": SM2Algorithm, + "supermemo2": SM2Algorithm, } \ No newline at end of file diff --git a/src/heurams/kernel/algorithms/base.py b/src/heurams/kernel/algorithms/base.py new file mode 100644 index 0000000..95ec9d1 --- /dev/null +++ b/src/heurams/kernel/algorithms/base.py @@ -0,0 +1,33 @@ +import heurams.services.timer as timer +from typing import TypedDict + +class BaseAlgorithm: + algo_name = "BaseAlgorithm" + + class AlgodataDict(TypedDict): + efactor: float + real_rept: int + rept: int + interval: int + last_date: int + next_date: int + is_activated: int + last_modify: float + + defaults = { + 'real_rept': 0, + 'rept': 0, + 'interval': 0, + 'last_date': 0, + 'next_date': 0, + 'is_activated': 0, + 'last_modify': timer.get_timestamp() + } + + @classmethod + def revisor(cls, algodata: dict, feedback: int = 5, is_new_activation: bool = False): + pass + + @classmethod + def is_due(cls, algodata): + return 1 \ No newline at end of file diff --git a/src/heurams/kernel/algorithms/sm2.py b/src/heurams/kernel/algorithms/sm2.py index c5d3d72..0d862ee 100644 --- a/src/heurams/kernel/algorithms/sm2.py +++ b/src/heurams/kernel/algorithms/sm2.py @@ -1,7 +1,8 @@ +from .base import BaseAlgorithm import heurams.services.timer as timer from typing import TypedDict -class SM2Algorithm: +class SM2Algorithm(BaseAlgorithm): algo_name = "SM-2" class AlgodataDict(TypedDict): diff --git a/src/heurams/kernel/particles/__init__.py b/src/heurams/kernel/particles/__init__.py index e474781..09801e1 100644 --- a/src/heurams/kernel/particles/__init__.py +++ b/src/heurams/kernel/particles/__init__.py @@ -2,4 +2,13 @@ from .electron import Electron from .nucleon import Nucleon from .orbital import Orbital from .atom import Atom -from .probe import probe_all, probe_by_filename \ No newline at end of file +from .probe import probe_all, probe_by_filename + +__all__ = [ + "Electron", + "Nucleon", + "Orbital", + "Atom", + "probe_all", + "probe_by_filename", +] \ No newline at end of file diff --git a/src/heurams/kernel/particles/electron.py b/src/heurams/kernel/particles/electron.py index 45ea2de..fb4a741 100644 --- a/src/heurams/kernel/particles/electron.py +++ b/src/heurams/kernel/particles/electron.py @@ -1,11 +1,11 @@ import heurams.services.timer as timer from heurams.context import config_var -from heurams.kernel.algorithms import get_algorithm +from heurams.kernel.algorithms import algorithms class Electron: """电子: 记忆分析元数据及算法""" - def __init__(self, ident: str, algodata: dict = {}, algo: str = "supermemo2"): + def __init__(self, ident: str, algodata: dict = {}, algo_name: str = "supermemo2"): """初始化电子对象 (记忆数据) Args: @@ -15,14 +15,12 @@ class Electron: """ self.algodata = algodata self.ident = ident - self.algo = algo - - algorithm_config = get_algorithm(self.algo) + self.algo = algorithms[algo_name] if self.algo not in self.algodata.keys(): self.algodata[self.algo] = {} if not self.algodata[self.algo]: - self._default_init(algorithm_config['defaults']) + self._default_init(self.algo.defaults) def _default_init(self, defaults: dict): """默认初始化包装""" @@ -41,6 +39,11 @@ class Electron: else: print(f"警告: '{var}' 非已知元数据字段") + def is_due(self): + """是否应该复习 + """ + return self.algo.is_due(self.algodata) + def revisor(self, quality: int = 5, is_new_activation: bool = False): """算法迭代决策机制实现 @@ -48,8 +51,7 @@ class Electron: quality (int): 记忆保留率量化参数 (0-5) is_new_activation (bool): 是否为初次激活 """ - algorithm_config = get_algorithm(self.algo) - algorithm_config['revisor'](self.algodata, quality, is_new_activation) + self.algo.revisor(self.algodata, quality, is_new_activation) def __str__(self): return ( diff --git a/src/heurams/kernel/particles/probe.py b/src/heurams/kernel/particles/probe.py index 756bc2d..5f4015a 100644 --- a/src/heurams/kernel/particles/probe.py +++ b/src/heurams/kernel/particles/probe.py @@ -2,7 +2,7 @@ from heurams.context import config_var import pathlib def probe_by_filename(filename): - """探测指定文件的所有信息""" + """探测指定文件 (无扩展名) 的所有信息""" paths: dict = config_var.get().get("paths") formats = ["toml", "json"] result = {} diff --git a/src/heurams/kernel/puzzles/__init__.py b/src/heurams/kernel/puzzles/__init__.py index 58d0a16..3136177 100644 --- a/src/heurams/kernel/puzzles/__init__.py +++ b/src/heurams/kernel/puzzles/__init__.py @@ -7,13 +7,46 @@ Puzzle 模块 - 谜题生成系统 from .base import BasePuzzle from .cloze import ClozePuzzle from .mcq import MCQPuzzle -from .factory import PuzzleFactory -from .loader import PuzzleLoader __all__ = [ 'BasePuzzle', 'ClozePuzzle', 'MCQPuzzle', - 'PuzzleFactory', - 'PuzzleLoader', -] \ No newline at end of file +] + +puzzles = { + "mcq": MCQPuzzle, + "cloze": ClozePuzzle, + "base": BasePuzzle, +} + +@staticmethod +def create_by_dict(config_dict: dict) -> BasePuzzle: + """ + 根据配置字典创建谜题 + + Args: + config_dict: 配置字典,包含谜题类型和参数 + + Returns: + BasePuzzle: 谜题实例 + + Raises: + ValueError: 当配置无效时抛出 + """ + puzzle_type = config_dict.get('type') + + if puzzle_type == 'cloze': + return puzzles["cloze"]( + text=config_dict['text'], + min_denominator=config_dict.get('min_denominator', 7) + ) + elif puzzle_type == 'mcq': + return puzzles["mcq"]( + mapping=config_dict['mapping'], + jammer=config_dict.get('jammer', []), + max_riddles_num=config_dict.get('max_riddles_num', 2), + prefix=config_dict.get('prefix', '') + ) + else: + raise ValueError(f"未知的谜题类型: {puzzle_type}") \ No newline at end of file diff --git a/src/heurams/kernel/puzzles/factory.py b/src/heurams/kernel/puzzles/factory.py deleted file mode 100644 index 0284b19..0000000 --- a/src/heurams/kernel/puzzles/factory.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -谜题工厂类 - 统一创建各种类型的谜题 -""" - -from typing import Dict, List, Union, Any -from .base import BasePuzzle -from .cloze import ClozePuzzle -from .mcq import MCQPuzzle - -class PuzzleFactory: - """谜题工厂,根据配置创建不同类型的谜题""" - - @staticmethod - def create_cloze(text: str, min_denominator: int = 7) -> ClozePuzzle: - """ - 创建填空题谜题 - - Args: - text: 原始字符串,使用 "/" 分割句子 - min_denominator: 最小概率倒数,控制填空数量 - - Returns: - ClozePuzzle: 填空题实例 - """ - return ClozePuzzle(text, min_denominator) - - @staticmethod - def create_mcq( - mapping: Dict[str, str], - jammer: List[str], - max_riddles_num: int = 2, - prefix: str = "" - ) -> MCQPuzzle: - """ - 创建选择题谜题 - - Args: - mapping: 题目-答案映射字典 - jammer: 干扰项列表 - max_riddles_num: 最大题目数量 - prefix: 题目前缀 - - Returns: - MCQPuzzle: 选择题实例 - """ - return MCQPuzzle(mapping, jammer, max_riddles_num, prefix) - - @staticmethod - def from_config(config: Dict[str, Any]) -> BasePuzzle: - """ - 根据配置字典创建谜题 - - Args: - config: 配置字典,包含谜题类型和参数 - - Returns: - BasePuzzle: 谜题实例 - - Raises: - ValueError: 当配置无效时抛出 - """ - puzzle_type = config.get('type') - - if puzzle_type == 'cloze': - return PuzzleFactory.create_cloze( - text=config['text'], - min_denominator=config.get('min_denominator', 7) - ) - elif puzzle_type == 'mcq': - return PuzzleFactory.create_mcq( - mapping=config['mapping'], - jammer=config.get('jammer', []), - max_riddles_num=config.get('max_riddles_num', 2), - prefix=config.get('prefix', '') - ) - else: - raise ValueError(f"未知的谜题类型: {puzzle_type}") \ No newline at end of file diff --git a/src/heurams/kernel/puzzles/loader.py b/src/heurams/kernel/puzzles/loader.py deleted file mode 100644 index b1beec7..0000000 --- a/src/heurams/kernel/puzzles/loader.py +++ /dev/null @@ -1,154 +0,0 @@ -""" -谜题加载器 - 从多种数据源加载谜题配置 -""" - -import json -import yaml -import re -from typing import Dict, List, Union, Any -from pathlib import Path -from .factory import PuzzleFactory - - -class PuzzleLoader: - """谜题加载器,支持从多种格式加载谜题配置""" - - @staticmethod - def from_json(file_path: Union[str, Path]) -> Dict[str, Any]: - """ - 从JSON文件加载谜题配置 - - Args: - file_path: JSON文件路径 - - Returns: - Dict: 谜题配置字典 - """ - with open(file_path, 'r', encoding='utf-8') as f: - return json.load(f) - - @staticmethod - def from_yaml(file_path: Union[str, Path]) -> Dict[str, Any]: - """ - 从YAML文件加载谜题配置 - - Args: - file_path: YAML文件路径 - - Returns: - Dict: 谜题配置字典 - """ - with open(file_path, 'r', encoding='utf-8') as f: - return yaml.safe_load(f) - - @staticmethod - def from_text(text: str, puzzle_type: str = 'cloze', **kwargs) -> Dict[str, Any]: - """ - 从纯文本字符串创建谜题配置 - - Args: - text: 原始文本 - puzzle_type: 谜题类型 ('cloze' 或 'mcq') - **kwargs: 其他参数 - - Returns: - Dict: 谜题配置字典 - """ - config = {'type': puzzle_type} - - if puzzle_type == 'cloze': - config['text'] = text - config.update(kwargs) - elif puzzle_type == 'mcq': - # 从文本解析选择题配置 - config.update(PuzzleLoader._parse_mcq_from_text(text, **kwargs)) - else: - raise ValueError(f"不支持的谜题类型: {puzzle_type}") - - return config - - @staticmethod - def _parse_mcq_from_text(text: str, **kwargs) -> Dict[str, Any]: - """ - 从文本解析选择题配置 - - Args: - text: 原始文本 - **kwargs: 其他参数 - - Returns: - Dict: 选择题配置 - """ - # 简单的解析逻辑:每行一个题目,格式为 "题目:答案" - mapping = {} - lines = text.strip().split('\n') - - for line in lines: - line = line.strip() - if ':' in line: - question, answer = line.split(':', 1) - mapping[question.strip()] = answer.strip() - - return { - 'mapping': mapping, - 'jammer': kwargs.get('jammer', []), - 'max_riddles_num': kwargs.get('max_riddles_num', 2), - 'prefix': kwargs.get('prefix', '') - } - - @staticmethod - def from_csv(file_path: Union[str, Path], **kwargs) -> Dict[str, Any]: - """ - 从CSV文件加载选择题配置 - - Args: - file_path: CSV文件路径 - **kwargs: 其他参数 - - Returns: - Dict: 选择题配置 - """ - import csv - mapping = {} - - with open(file_path, 'r', encoding='utf-8') as f: - reader = csv.reader(f) - for row in reader: - if len(row) >= 2: - mapping[row[0].strip()] = row[1].strip() - - return { - 'type': 'mcq', - 'mapping': mapping, - 'jammer': kwargs.get('jammer', []), - 'max_riddles_num': kwargs.get('max_riddles_num', 2), - 'prefix': kwargs.get('prefix', '') - } - - @staticmethod - def load_puzzles_from_directory(directory: Union[str, Path]) -> List[Dict[str, Any]]: - """ - 从目录批量加载谜题配置 - - Args: - directory: 目录路径 - - Returns: - List[Dict]: 谜题配置列表 - """ - puzzles = [] - dir_path = Path(directory) - - # 加载JSON文件 - for json_file in dir_path.glob('*.json'): - puzzles.append(PuzzleLoader.from_json(json_file)) - - # 加载YAML文件 - for yaml_file in dir_path.glob('*.yaml'): - puzzles.append(PuzzleLoader.from_yaml(yaml_file)) - - # 加载CSV文件 - for csv_file in dir_path.glob('*.csv'): - puzzles.append(PuzzleLoader.from_csv(csv_file)) - - return puzzles \ No newline at end of file diff --git a/src/heurams/kernel/reactor/persistence.py b/src/heurams/kernel/reactor/persistence.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/heurams/providers/audio/__init__.py b/src/heurams/providers/audio/__init__.py index e69de29..f198ec3 100644 --- a/src/heurams/providers/audio/__init__.py +++ b/src/heurams/providers/audio/__init__.py @@ -0,0 +1,10 @@ +# 音频播放器, 必须基于文件操作 +from .termux_audio import player as termux_player + +__all__ = [ + "termux_player" +] + +players = { + "termux": termux_player +} \ No newline at end of file diff --git a/src/heurams/providers/audio/base.py b/src/heurams/providers/audio/base.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/heurams/providers/audio/system_audio.py b/src/heurams/providers/audio/system_audio.py new file mode 100644 index 0000000..bcef367 --- /dev/null +++ b/src/heurams/providers/audio/system_audio.py @@ -0,0 +1,4 @@ +""" 通用音频适配器 +基于 playsound 库的音频播放器, 在绝大多数 python 环境上提供音频服务 +注意: 在未配置 pulseaudio 的 termux 不可用 +""" diff --git a/src/heurams/providers/audio/system_autio.py b/src/heurams/providers/audio/system_autio.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/heurams/providers/audio/termux_audio.py b/src/heurams/providers/audio/termux_audio.py index e69de29..7b4e5d8 100644 --- a/src/heurams/providers/audio/termux_audio.py +++ b/src/heurams/providers/audio/termux_audio.py @@ -0,0 +1,10 @@ +""" Termux 音频适配 +适配 Termux 的 play-audio 命令, 以在 android 上提供可用的播放体验 +无需配置 pulseaudio +""" + +import os +import pathlib + +def player(path: pathlib.Path): + os.system(f"play-audio {path}") \ No newline at end of file diff --git a/src/heurams/providers/llm/__init__.py b/src/heurams/providers/llm/__init__.py index e69de29..24ce2f3 100644 --- a/src/heurams/providers/llm/__init__.py +++ b/src/heurams/providers/llm/__init__.py @@ -0,0 +1 @@ +# 大语言模型 \ No newline at end of file diff --git a/src/heurams/providers/tts/__init__.py b/src/heurams/providers/tts/__init__.py index e69de29..41a78da 100644 --- a/src/heurams/providers/tts/__init__.py +++ b/src/heurams/providers/tts/__init__.py @@ -0,0 +1,12 @@ +from .base import BaseTTS +from .edge_tts import EdgeTTS + +__all__ = [ + "BaseTTS", + "EdgeTTS", +] + +TTSs = { + "BaseTTS": BaseTTS, + "EdgeTTS": EdgeTTS, +} \ No newline at end of file diff --git a/src/heurams/providers/tts/base.py b/src/heurams/providers/tts/base.py index e69de29..8cf2fd4 100644 --- a/src/heurams/providers/tts/base.py +++ b/src/heurams/providers/tts/base.py @@ -0,0 +1,9 @@ +import pathlib + +class BaseTTS: + name = "BaseTTS" + + @classmethod + def convert(cls, text: str, path: pathlib.Path | str = "") -> pathlib.Path: + """path 是可选参数, 不填则自动返回生成文件路径""" + return path # type: ignore \ No newline at end of file diff --git a/src/heurams/providers/tts/edge_tts.py b/src/heurams/providers/tts/edge_tts.py index e69de29..4556052 100644 --- a/src/heurams/providers/tts/edge_tts.py +++ b/src/heurams/providers/tts/edge_tts.py @@ -0,0 +1,15 @@ +from .base import BaseTTS +import pathlib +import edge_tts + +class EdgeTTS(BaseTTS): + name = "EdgeTTS" + + @classmethod + def convert(cls, text, path: pathlib.Path | str = "") -> pathlib.Path: + communicate = edge_tts.Communicate( + text, + "zh-CN-YunjianNeural", + ) + communicate.save_sync(str(path)) + return path # type: ignore \ No newline at end of file diff --git a/src/heurams/services/version.py b/src/heurams/services/version.py index 689c094..9bfcb67 100644 --- a/src/heurams/services/version.py +++ b/src/heurams/services/version.py @@ -1 +1,3 @@ -# 版本控制集成服务 \ No newline at end of file +# 版本控制集成服务 + +ver = "0.4.0" \ No newline at end of file