diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..e40d4eb --- /dev/null +++ b/utils.py @@ -0,0 +1,105 @@ +""" +ParamHub 工具函数 +""" +import json +import base64 +from pathlib import Path +from datetime import datetime +from werkzeug.utils import secure_filename + +from config import ( + ALLOWED_EXTENSIONS, DEFAULT_CONFIG, CONFIG_FILE, IMAGES_DIR, DATA_DIR +) + + +def allowed_file(filename: str) -> bool: + """检查文件扩展名是否允许""" + return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + + +def load_data(file_path: Path): + """加载JSON数据文件""" + if file_path.exists(): + return json.loads(file_path.read_text(encoding='utf-8')) + return [] + + +def save_data(file_path: Path, data): + """保存JSON数据到文件""" + file_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding='utf-8') + + +def load_config() -> dict: + """加载网站配置,合并默认值""" + if CONFIG_FILE.exists(): + loaded = json.loads(CONFIG_FILE.read_text(encoding='utf-8')) + result = DEFAULT_CONFIG.copy() + result.update(loaded) + return result + return DEFAULT_CONFIG.copy() + + +def save_config(config: dict): + """保存网站配置""" + CONFIG_FILE.write_text(json.dumps(config, ensure_ascii=False, indent=2), encoding='utf-8') + + +def get_items_file(category_id: str) -> Path: + """获取动态分类的数据文件路径""" + return DATA_DIR / f'items_{category_id}.json' + + +def get_llm_config() -> dict: + """从 config.json 动态读取 LLM 配置""" + config = load_config() + return { + 'base_url': config.get('llm_base_url', DEFAULT_CONFIG['llm_base_url']), + 'api_key': config.get('llm_api_key', DEFAULT_CONFIG['llm_api_key']), + 'model': config.get('llm_model', DEFAULT_CONFIG['llm_model']), + 'vision_model': config.get('llm_vision_model', DEFAULT_CONFIG['llm_vision_model']), + } + + +def parse_date_to_timestamp(date_str: str) -> float | None: + """将日期字符串转换为 Unix 时间戳""" + if not date_str: + return None + for fmt in ['%Y-%m-%d', '%Y-%m-%d %H:%M:%S', '%Y/%m/%d']: + try: + return datetime.strptime(date_str, fmt).timestamp() + except (ValueError, TypeError): + pass + return None + + +def safe_sort_key(x: dict, key: str): + """排序时安全获取字段值,缺失时返回合理默认值""" + val = x.get(key) + if val is None: + if key in ['parameters', 'context_length', 'mmlu', 'views']: + return 0 + elif key in ['publish_date', 'created_at', 'updated_at']: + return '' + return '' + return val + + +def read_image_base64(img_src: str) -> str | None: + """将图片路径/base64/URL转换为base64 data URI""" + import base64 as b64 + if img_src.startswith('data:'): + return img_src + if img_src.startswith('http'): + return img_src # URL 直接传给 LLM + # 本地路径 + try: + img_path = IMAGES_DIR / img_src.replace('/static/uploads/', '') + if img_path.exists(): + with open(img_path, 'rb') as f: + img_data = b64.b64encode(f.read()).decode() + ext = img_path.suffix.lower().lstrip('.') + mime = f'image/{"jpeg" if ext == "jpg" else ext}' + return f'data:{mime};base64,{img_data}' + except Exception: + pass + return None