Files
cmbot/src/services/config_service.py
T
adminandClaude Opus 4.8 572c2cf6dd feat(ai-outfit): drop product_id from prompt UI + default template
Match the doc change (8a4ebe6): remove the 插入货号 button (keep only
插入标题) and drop 货号 {product_id} from DEFAULT_OUTFIT_PROMPT. product_id
still drives Excel column B and the output filename, not the AI prompt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 09:17:28 +08:00

141 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import json
import logging
logger = logging.getLogger(__name__)
DEFAULT_CONFIG = {
"output_format": "PNG", # PNG or JPG
"output_quality": 95, # JPG quality 1-95
"output_dir": "", # empty = use app output/ dir
"last_garment_dir": "",
"last_print_dir": "",
"last_template": "", # name of the last-selected template
"last_batch_mode": "full_combo", # BatchMode value of the last-used mode
"update_source": "", # HTTP(S) manifest source (empty = no update check)
"update_user": "", # HTTP Basic Auth user (empty = anonymous)
"update_pass": "", # HTTP Basic Auth password (empty = anonymous)
# AI 穿搭(docs/11 §11):上次路径与批量设置并入 app_config(密钥不在此,见 ai_models.json)
"outfit_excel": "",
"outfit_output_dir": "",
"outfit_model": "", # last-selected model name
"outfit_concurrency": 1,
"outfit_request_interval": 2.0,
"outfit_task_cooldown": 1.0,
"outfit_retry_count": 2,
"outfit_resolution": "1K",
"outfit_quality": "均衡",
"outfit_retry_failed": False,
}
_CONFIG_FILENAME = "app_config.json"
_AI_MODELS_FILENAME = "ai_models.json"
_OUTFIT_PROMPT_FILENAME = "outfit_prompt.txt"
# Default outfit prompt (docs/11 §7). Persisted to outfit_prompt.txt on first save.
DEFAULT_OUTFIT_PROMPT = (
"为商品「{title}」生成人物上身实穿效果图:真人模特正面穿着这件衣服,"
"完整保留款式、版型、颜色与印花图案,自然光、纯色棚拍背景,"
"电商主图风格,不加文字与促销标签。"
)
def load_config():
"""
从 JSON 文件加载应用配置。
返回默认配置与文件配置的合并字典。如果配置文件不存在、损坏或无法读取,
函数不会抛出异常,而是记录日志并返回默认配置。
Returns:
dict: 合并后的配置字典
"""
from services.file_service import get_config_path
config_file = get_config_path(_CONFIG_FILENAME)
if not config_file.exists():
logger.info("Config file not found, using defaults: %s", config_file)
return dict(DEFAULT_CONFIG)
try:
with open(str(config_file), encoding="utf-8") as f:
data = json.load(f)
if not isinstance(data, dict):
raise ValueError("Config root is not a JSON object")
merged = dict(DEFAULT_CONFIG)
merged.update(data)
logger.info("Config loaded from %s", config_file)
return merged
except (json.JSONDecodeError, ValueError) as exc:
logger.warning("Config file damaged (%s), using defaults: %s", exc, config_file)
return dict(DEFAULT_CONFIG)
except OSError as exc:
logger.warning("Config file unreadable (%s), using defaults: %s", exc, config_file)
return dict(DEFAULT_CONFIG)
def save_config(data):
"""Save config dict to JSON. Logs error on failure, does not raise."""
from services.file_service import get_config_path
config_file = get_config_path(_CONFIG_FILENAME)
try:
config_file.parent.mkdir(parents=True, exist_ok=True)
with open(str(config_file), "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
logger.info("Config saved to %s", config_file)
except OSError as exc:
logger.error("Failed to save config to %s: %s", config_file, exc)
def load_ai_models():
"""Load AI model configs from ai_models.json.
Returns a list of dicts (each with a ``name`` plus AiModelConfig fields).
Missing/corrupt file → empty list (the UI then prompts the admin to add one).
Keys live only in ~/.cmbot and are never committed (docs/11 §11).
"""
from services.file_service import get_config_path
models_file = get_config_path(_AI_MODELS_FILENAME)
if not models_file.exists():
logger.info("AI models file not found: %s", models_file)
return []
try:
with open(str(models_file), encoding="utf-8-sig") as f:
data = json.load(f)
except (json.JSONDecodeError, ValueError, OSError) as exc:
logger.warning("AI models file unreadable (%s): %s", exc, models_file)
return []
models = data.get("models") if isinstance(data, dict) else data
if not isinstance(models, list):
logger.warning("AI models file has no model list: %s", models_file)
return []
return [m for m in models if isinstance(m, dict)]
def load_outfit_prompt():
"""Return the saved outfit prompt template, or the built-in default."""
from services.file_service import get_config_path
prompt_file = get_config_path(_OUTFIT_PROMPT_FILENAME)
if not prompt_file.exists():
return DEFAULT_OUTFIT_PROMPT
try:
with open(str(prompt_file), encoding="utf-8-sig") as f:
return f.read()
except OSError as exc:
logger.warning("Outfit prompt unreadable (%s): %s", exc, prompt_file)
return DEFAULT_OUTFIT_PROMPT
def save_outfit_prompt(text):
"""Persist the outfit prompt template (utf-8, no BOM). Does not raise."""
from services.file_service import get_config_path
prompt_file = get_config_path(_OUTFIT_PROMPT_FILENAME)
try:
prompt_file.parent.mkdir(parents=True, exist_ok=True)
with open(str(prompt_file), "w", encoding="utf-8") as f:
f.write(text)
logger.info("Outfit prompt saved to %s", prompt_file)
except OSError as exc:
logger.error("Failed to save outfit prompt to %s: %s", prompt_file, exc)