Add File
This commit is contained in:
448
src/landppt/services/image/config/image_config.py
Normal file
448
src/landppt/services/image/config/image_config.py
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
"""
|
||||||
|
图片服务配置管理
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Dict, Any, Optional, List
|
||||||
|
from pathlib import Path
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class ImageServiceConfig:
|
||||||
|
"""图片服务配置管理器"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._config = self._load_default_config()
|
||||||
|
self._load_env_config()
|
||||||
|
|
||||||
|
def _load_default_config(self) -> Dict[str, Any]:
|
||||||
|
"""加载默认配置"""
|
||||||
|
return {
|
||||||
|
# DALL-E配置
|
||||||
|
'dalle': {
|
||||||
|
'api_key': '',
|
||||||
|
'api_base': 'https://api.openai.com/v1',
|
||||||
|
'model': 'dall-e-3',
|
||||||
|
'default_size': '1792x1024', # 16:9比例,适合PPT
|
||||||
|
'default_quality': 'standard', # standard, hd
|
||||||
|
'default_style': 'natural', # vivid, natural
|
||||||
|
'rate_limit_requests': 50, # 每分钟请求数
|
||||||
|
'rate_limit_window': 60, # 时间窗口(秒)
|
||||||
|
'timeout': 180 # 请求超时(秒)
|
||||||
|
},
|
||||||
|
|
||||||
|
# Stable Diffusion配置
|
||||||
|
'stable_diffusion': {
|
||||||
|
'api_key': '',
|
||||||
|
'api_base': 'https://api.stability.ai/v1',
|
||||||
|
'engine_id': 'stable-diffusion-xl-1024-v1-0',
|
||||||
|
'default_width': 1024,
|
||||||
|
'default_height': 576, # 16:9比例
|
||||||
|
'default_steps': 30,
|
||||||
|
'default_cfg_scale': 7.0,
|
||||||
|
'default_sampler': 'K_DPM_2_ANCESTRAL',
|
||||||
|
'rate_limit_requests': 150, # 每分钟请求数
|
||||||
|
'rate_limit_window': 60, # 时间窗口(秒)
|
||||||
|
'timeout': 180 # 请求超时(秒)
|
||||||
|
},
|
||||||
|
|
||||||
|
# SiliconFlow配置
|
||||||
|
'siliconflow': {
|
||||||
|
'api_key': '',
|
||||||
|
'api_base': 'https://api.siliconflow.cn/v1',
|
||||||
|
'model': 'Kwai-Kolors/Kolors',
|
||||||
|
'default_size': '1024x1024',
|
||||||
|
'default_batch_size': 1,
|
||||||
|
'default_steps': 20,
|
||||||
|
'default_guidance_scale': 7.5,
|
||||||
|
'rate_limit_requests': 60, # 每分钟请求数
|
||||||
|
'rate_limit_window': 60, # 时间窗口(秒)
|
||||||
|
'timeout': 120 # 请求超时(秒)
|
||||||
|
},
|
||||||
|
|
||||||
|
# Pollinations配置
|
||||||
|
'pollinations': {
|
||||||
|
'api_base': 'https://image.pollinations.ai',
|
||||||
|
'api_token': '', # API token(可选,用于认证和移除logo)
|
||||||
|
'referrer': '', # 推荐人标识符(可选,用于认证)
|
||||||
|
'model': 'flux', # flux, turbo, gptimage
|
||||||
|
'default_width': 1024,
|
||||||
|
'default_height': 1024,
|
||||||
|
'default_enhance': False, # 是否增强提示词
|
||||||
|
'default_safe': False, # 是否启用安全过滤
|
||||||
|
'default_nologo': False, # 是否移除logo(需要注册用户或token)
|
||||||
|
'default_private': False, # 是否私有模式
|
||||||
|
'default_transparent': False, # 是否生成透明背景(仅gptimage模型)
|
||||||
|
'rate_limit_requests': 60, # 每分钟请求数
|
||||||
|
'rate_limit_window': 60, # 时间窗口(秒)
|
||||||
|
'timeout': 300 # 请求超时(秒,增加以适应图片生成时间)
|
||||||
|
},
|
||||||
|
|
||||||
|
# Unsplash配置(网络搜索)
|
||||||
|
'unsplash': {
|
||||||
|
'api_key': '',
|
||||||
|
'api_base': 'https://api.unsplash.com',
|
||||||
|
'per_page': 20,
|
||||||
|
'rate_limit_requests': 50,
|
||||||
|
'rate_limit_window': 3600, # 1小时
|
||||||
|
'timeout': 30
|
||||||
|
},
|
||||||
|
|
||||||
|
# Pixabay配置(网络搜索)- 根据官方API文档
|
||||||
|
'pixabay': {
|
||||||
|
'api_key': '',
|
||||||
|
'api_base': 'https://pixabay.com/api',
|
||||||
|
'per_page': 20, # 默认20,最大200
|
||||||
|
'rate_limit_requests': 100, # 官方文档:100请求/60秒
|
||||||
|
'rate_limit_window': 60, # 官方文档:60秒窗口
|
||||||
|
'timeout': 30,
|
||||||
|
'cache_duration': 86400 # 官方要求:缓存24小时
|
||||||
|
},
|
||||||
|
|
||||||
|
# SearXNG配置(网络搜索)
|
||||||
|
'searxng': {
|
||||||
|
'host': '', # SearXNG实例的主机地址,如 http://209.135.170.113:8888
|
||||||
|
'per_page': 20,
|
||||||
|
'rate_limit_requests': 60, # 每分钟请求限制
|
||||||
|
'rate_limit_window': 60, # 60秒窗口
|
||||||
|
'timeout': 30,
|
||||||
|
'language': 'auto', # 搜索语言
|
||||||
|
'theme': 'simple' # 主题
|
||||||
|
},
|
||||||
|
|
||||||
|
# 缓存配置 - 简化配置,图片永久有效
|
||||||
|
'cache': {
|
||||||
|
'base_dir': 'temp/images_cache',
|
||||||
|
'max_size_gb': 5.0, # 默认最大缓存大小5GB
|
||||||
|
'cleanup_interval_hours': 24 # 默认24小时清理一次(虽然图片永久有效,但保留配置项)
|
||||||
|
},
|
||||||
|
|
||||||
|
# 图片处理配置
|
||||||
|
'processing': {
|
||||||
|
'max_file_size_mb': 50,
|
||||||
|
'supported_formats': ['jpg', 'jpeg', 'png', 'webp', 'gif'],
|
||||||
|
'default_quality': 85,
|
||||||
|
'thumbnail_size': (300, 200),
|
||||||
|
'watermark_enabled': False,
|
||||||
|
'watermark_text': 'LandPPT',
|
||||||
|
'watermark_opacity': 0.3
|
||||||
|
},
|
||||||
|
|
||||||
|
# 智能匹配配置
|
||||||
|
'matching': {
|
||||||
|
'similarity_threshold': 0.3,
|
||||||
|
'max_suggestions': 10,
|
||||||
|
'keyword_weight': 0.4,
|
||||||
|
'tag_weight': 0.3,
|
||||||
|
'usage_weight': 0.2,
|
||||||
|
'freshness_weight': 0.1
|
||||||
|
},
|
||||||
|
|
||||||
|
# PPT适配器配置
|
||||||
|
'ppt_adapter': {
|
||||||
|
'default_size': '1792x1024',
|
||||||
|
'quality_mode': 'standard',
|
||||||
|
'style_preference': 'natural',
|
||||||
|
'enable_negative_prompts': True,
|
||||||
|
'prompt_enhancement': True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _load_env_config(self):
|
||||||
|
"""从环境变量加载配置"""
|
||||||
|
# DALL-E配置
|
||||||
|
if os.getenv('OPENAI_API_KEY'):
|
||||||
|
self._config['dalle']['api_key'] = os.getenv('OPENAI_API_KEY')
|
||||||
|
|
||||||
|
if os.getenv('DALLE_API_BASE'):
|
||||||
|
self._config['dalle']['api_base'] = os.getenv('DALLE_API_BASE')
|
||||||
|
|
||||||
|
# Stable Diffusion配置
|
||||||
|
if os.getenv('STABILITY_API_KEY'):
|
||||||
|
self._config['stable_diffusion']['api_key'] = os.getenv('STABILITY_API_KEY')
|
||||||
|
|
||||||
|
if os.getenv('STABILITY_API_BASE'):
|
||||||
|
self._config['stable_diffusion']['api_base'] = os.getenv('STABILITY_API_BASE')
|
||||||
|
|
||||||
|
# SiliconFlow配置
|
||||||
|
if os.getenv('SILICONFLOW_API_KEY'):
|
||||||
|
self._config['siliconflow']['api_key'] = os.getenv('SILICONFLOW_API_KEY')
|
||||||
|
|
||||||
|
if os.getenv('SILICONFLOW_API_BASE'):
|
||||||
|
self._config['siliconflow']['api_base'] = os.getenv('SILICONFLOW_API_BASE')
|
||||||
|
|
||||||
|
# 从配置服务加载SiliconFlow配置
|
||||||
|
try:
|
||||||
|
from ...config_service import get_config_service
|
||||||
|
config_service = get_config_service()
|
||||||
|
all_config = config_service.get_all_config()
|
||||||
|
|
||||||
|
# 加载SiliconFlow API密钥
|
||||||
|
siliconflow_api_key = all_config.get('siliconflow_api_key')
|
||||||
|
if siliconflow_api_key:
|
||||||
|
self._config['siliconflow']['api_key'] = siliconflow_api_key
|
||||||
|
|
||||||
|
# 加载SiliconFlow生成参数
|
||||||
|
siliconflow_size = all_config.get('siliconflow_image_size')
|
||||||
|
if siliconflow_size:
|
||||||
|
self._config['siliconflow']['default_size'] = siliconflow_size
|
||||||
|
|
||||||
|
siliconflow_steps = all_config.get('siliconflow_steps')
|
||||||
|
if siliconflow_steps:
|
||||||
|
self._config['siliconflow']['default_steps'] = int(siliconflow_steps)
|
||||||
|
|
||||||
|
siliconflow_guidance = all_config.get('siliconflow_guidance_scale')
|
||||||
|
if siliconflow_guidance:
|
||||||
|
self._config['siliconflow']['default_guidance_scale'] = float(siliconflow_guidance)
|
||||||
|
|
||||||
|
# 加载Unsplash配置
|
||||||
|
unsplash_access_key = all_config.get('unsplash_access_key')
|
||||||
|
if unsplash_access_key:
|
||||||
|
self._config['unsplash']['api_key'] = unsplash_access_key
|
||||||
|
|
||||||
|
# 加载Pixabay配置
|
||||||
|
pixabay_api_key = all_config.get('pixabay_api_key')
|
||||||
|
if pixabay_api_key:
|
||||||
|
self._config['pixabay']['api_key'] = pixabay_api_key
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to load config from config service: {e}")
|
||||||
|
|
||||||
|
# Unsplash配置(环境变量)
|
||||||
|
if os.getenv('UNSPLASH_ACCESS_KEY'):
|
||||||
|
self._config['unsplash']['api_key'] = os.getenv('UNSPLASH_ACCESS_KEY')
|
||||||
|
|
||||||
|
# Pixabay配置(环境变量)
|
||||||
|
if os.getenv('PIXABAY_API_KEY'):
|
||||||
|
self._config['pixabay']['api_key'] = os.getenv('PIXABAY_API_KEY')
|
||||||
|
|
||||||
|
# SearXNG配置(环境变量)
|
||||||
|
if os.getenv('SEARXNG_HOST'):
|
||||||
|
self._config['searxng']['host'] = os.getenv('SEARXNG_HOST')
|
||||||
|
|
||||||
|
# Pollinations配置(环境变量)
|
||||||
|
if os.getenv('POLLINATIONS_API_BASE'):
|
||||||
|
self._config['pollinations']['api_base'] = os.getenv('POLLINATIONS_API_BASE')
|
||||||
|
if os.getenv('POLLINATIONS_API_TOKEN'):
|
||||||
|
self._config['pollinations']['api_token'] = os.getenv('POLLINATIONS_API_TOKEN')
|
||||||
|
if os.getenv('POLLINATIONS_REFERRER'):
|
||||||
|
self._config['pollinations']['referrer'] = os.getenv('POLLINATIONS_REFERRER')
|
||||||
|
if os.getenv('POLLINATIONS_MODEL'):
|
||||||
|
self._config['pollinations']['model'] = os.getenv('POLLINATIONS_MODEL')
|
||||||
|
|
||||||
|
# 从配置服务加载Pollinations配置
|
||||||
|
try:
|
||||||
|
from ...config_service import config_service
|
||||||
|
all_config = config_service.get_all_config()
|
||||||
|
|
||||||
|
# 加载Pollinations配置
|
||||||
|
pollinations_token = all_config.get('pollinations_api_token')
|
||||||
|
if pollinations_token:
|
||||||
|
self._config['pollinations']['api_token'] = pollinations_token
|
||||||
|
|
||||||
|
pollinations_referrer = all_config.get('pollinations_referrer')
|
||||||
|
if pollinations_referrer:
|
||||||
|
self._config['pollinations']['referrer'] = pollinations_referrer
|
||||||
|
|
||||||
|
pollinations_model = all_config.get('pollinations_model')
|
||||||
|
if pollinations_model:
|
||||||
|
self._config['pollinations']['model'] = pollinations_model
|
||||||
|
|
||||||
|
pollinations_enhance = all_config.get('pollinations_enhance')
|
||||||
|
if pollinations_enhance is not None:
|
||||||
|
# 处理布尔值或字符串
|
||||||
|
if isinstance(pollinations_enhance, bool):
|
||||||
|
self._config['pollinations']['default_enhance'] = pollinations_enhance
|
||||||
|
else:
|
||||||
|
self._config['pollinations']['default_enhance'] = str(pollinations_enhance).lower() == 'true'
|
||||||
|
|
||||||
|
pollinations_safe = all_config.get('pollinations_safe')
|
||||||
|
if pollinations_safe is not None:
|
||||||
|
if isinstance(pollinations_safe, bool):
|
||||||
|
self._config['pollinations']['default_safe'] = pollinations_safe
|
||||||
|
else:
|
||||||
|
self._config['pollinations']['default_safe'] = str(pollinations_safe).lower() == 'true'
|
||||||
|
|
||||||
|
pollinations_nologo = all_config.get('pollinations_nologo')
|
||||||
|
if pollinations_nologo is not None:
|
||||||
|
if isinstance(pollinations_nologo, bool):
|
||||||
|
self._config['pollinations']['default_nologo'] = pollinations_nologo
|
||||||
|
else:
|
||||||
|
self._config['pollinations']['default_nologo'] = str(pollinations_nologo).lower() == 'true'
|
||||||
|
|
||||||
|
pollinations_private = all_config.get('pollinations_private')
|
||||||
|
if pollinations_private is not None:
|
||||||
|
if isinstance(pollinations_private, bool):
|
||||||
|
self._config['pollinations']['default_private'] = pollinations_private
|
||||||
|
else:
|
||||||
|
self._config['pollinations']['default_private'] = str(pollinations_private).lower() == 'true'
|
||||||
|
|
||||||
|
pollinations_transparent = all_config.get('pollinations_transparent')
|
||||||
|
if pollinations_transparent is not None:
|
||||||
|
if isinstance(pollinations_transparent, bool):
|
||||||
|
self._config['pollinations']['default_transparent'] = pollinations_transparent
|
||||||
|
else:
|
||||||
|
self._config['pollinations']['default_transparent'] = str(pollinations_transparent).lower() == 'true'
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Could not load Pollinations config from config service: {e}")
|
||||||
|
|
||||||
|
# 缓存目录配置
|
||||||
|
if os.getenv('IMAGE_CACHE_DIR'):
|
||||||
|
self._config['cache']['base_dir'] = os.getenv('IMAGE_CACHE_DIR')
|
||||||
|
|
||||||
|
def get_config(self) -> Dict[str, Any]:
|
||||||
|
"""获取完整配置"""
|
||||||
|
return self._config.copy()
|
||||||
|
|
||||||
|
def get_provider_config(self, provider: str) -> Dict[str, Any]:
|
||||||
|
"""获取特定提供者的配置"""
|
||||||
|
return self._config.get(provider, {}).copy()
|
||||||
|
|
||||||
|
def update_config(self, updates: Dict[str, Any]):
|
||||||
|
"""更新配置"""
|
||||||
|
def deep_update(base_dict, update_dict):
|
||||||
|
for key, value in update_dict.items():
|
||||||
|
if key in base_dict and isinstance(base_dict[key], dict) and isinstance(value, dict):
|
||||||
|
deep_update(base_dict[key], value)
|
||||||
|
else:
|
||||||
|
base_dict[key] = value
|
||||||
|
|
||||||
|
deep_update(self._config, updates)
|
||||||
|
|
||||||
|
def is_provider_configured(self, provider: str) -> bool:
|
||||||
|
"""检查提供者是否已配置"""
|
||||||
|
provider_config = self._config.get(provider, {})
|
||||||
|
|
||||||
|
# SearXNG使用host而不是api_key
|
||||||
|
if provider == 'searxng':
|
||||||
|
host = provider_config.get('host', '')
|
||||||
|
return bool(host and host.strip())
|
||||||
|
|
||||||
|
# Pollinations不需要API密钥,只要配置存在就认为已配置
|
||||||
|
if provider == 'pollinations':
|
||||||
|
return True # Pollinations是免费服务,不需要API密钥
|
||||||
|
|
||||||
|
# 其他提供者检查API密钥是否存在
|
||||||
|
api_key = provider_config.get('api_key', '')
|
||||||
|
return bool(api_key and api_key.strip())
|
||||||
|
|
||||||
|
def should_enable_search_provider(self, provider: str) -> bool:
|
||||||
|
"""检查是否应该启用指定的搜索提供者"""
|
||||||
|
# 首先检查API密钥是否配置
|
||||||
|
if not self.is_provider_configured(provider):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 获取默认网络搜索提供商配置
|
||||||
|
try:
|
||||||
|
from ...config_service import get_config_service
|
||||||
|
config_service = get_config_service()
|
||||||
|
all_config = config_service.get_all_config()
|
||||||
|
default_provider = all_config.get('default_network_search_provider', 'unsplash')
|
||||||
|
|
||||||
|
# 如果是默认提供商,则启用
|
||||||
|
return provider.lower() == default_provider.lower()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to get default network search provider config: {e}")
|
||||||
|
# 如果获取配置失败,回退到检查API密钥
|
||||||
|
return self.is_provider_configured(provider)
|
||||||
|
|
||||||
|
def get_configured_providers(self) -> List[str]:
|
||||||
|
"""获取已配置的提供者列表"""
|
||||||
|
providers = []
|
||||||
|
|
||||||
|
for provider in ['dalle', 'stable_diffusion', 'siliconflow', 'pollinations', 'unsplash', 'pixabay', 'searxng']:
|
||||||
|
if self.is_provider_configured(provider):
|
||||||
|
providers.append(provider)
|
||||||
|
|
||||||
|
return providers
|
||||||
|
|
||||||
|
def get_all_config(self) -> Dict[str, Any]:
|
||||||
|
"""获取所有配置"""
|
||||||
|
return self._config
|
||||||
|
|
||||||
|
def validate_config(self) -> Dict[str, List[str]]:
|
||||||
|
"""验证配置并返回错误信息"""
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
# 验证缓存配置
|
||||||
|
cache_config = self._config.get('cache', {})
|
||||||
|
cache_errors = []
|
||||||
|
|
||||||
|
# 检查max_size_gb(如果存在的话)
|
||||||
|
max_size_gb = cache_config.get('max_size_gb')
|
||||||
|
if max_size_gb is not None and max_size_gb <= 0:
|
||||||
|
cache_errors.append("max_size_gb must be greater than 0")
|
||||||
|
|
||||||
|
# 检查cleanup_interval_hours(如果存在的话)
|
||||||
|
cleanup_interval = cache_config.get('cleanup_interval_hours')
|
||||||
|
if cleanup_interval is not None and cleanup_interval <= 0:
|
||||||
|
cache_errors.append("cleanup_interval_hours must be greater than 0")
|
||||||
|
|
||||||
|
# 检查base_dir是否存在
|
||||||
|
if not cache_config.get('base_dir'):
|
||||||
|
cache_errors.append("base_dir is required")
|
||||||
|
|
||||||
|
if cache_errors:
|
||||||
|
errors['cache'] = cache_errors
|
||||||
|
|
||||||
|
# 验证处理配置
|
||||||
|
processing_config = self._config.get('processing', {})
|
||||||
|
processing_errors = []
|
||||||
|
|
||||||
|
if processing_config.get('max_file_size_mb', 0) <= 0:
|
||||||
|
processing_errors.append("max_file_size_mb must be greater than 0")
|
||||||
|
|
||||||
|
if not processing_config.get('supported_formats'):
|
||||||
|
processing_errors.append("supported_formats cannot be empty")
|
||||||
|
|
||||||
|
if processing_errors:
|
||||||
|
errors['processing'] = processing_errors
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
def save_to_file(self, file_path: str):
|
||||||
|
"""保存配置到文件"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
config_path = Path(file_path)
|
||||||
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
with open(config_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(self._config, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
def load_from_file(self, file_path: str):
|
||||||
|
"""从文件加载配置"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
config_path = Path(file_path)
|
||||||
|
if config_path.exists():
|
||||||
|
with open(config_path, 'r', encoding='utf-8') as f:
|
||||||
|
file_config = json.load(f)
|
||||||
|
self.update_config(file_config)
|
||||||
|
|
||||||
|
|
||||||
|
# 创建全局配置实例
|
||||||
|
image_config = ImageServiceConfig()
|
||||||
|
|
||||||
|
|
||||||
|
def get_image_config() -> ImageServiceConfig:
|
||||||
|
"""获取图片服务配置实例"""
|
||||||
|
return image_config
|
||||||
|
|
||||||
|
|
||||||
|
def update_image_config(updates: Dict[str, Any]):
|
||||||
|
"""更新图片服务配置"""
|
||||||
|
image_config.update_config(updates)
|
||||||
|
|
||||||
|
|
||||||
|
def get_provider_config(provider: str) -> Dict[str, Any]:
|
||||||
|
"""获取特定提供者的配置"""
|
||||||
|
return image_config.get_provider_config(provider)
|
||||||
|
|
||||||
|
|
||||||
|
def is_provider_configured(provider: str) -> bool:
|
||||||
|
"""检查提供者是否已配置"""
|
||||||
|
return image_config.is_provider_configured(provider)
|
||||||
Reference in New Issue
Block a user