feat: persist story generation states and cache audio
Some checks failed
Build and Push Docker Images / changes (push) Has been cancelled
Build and Push Docker Images / build-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (push) Has been cancelled
Build and Push Docker Images / build-admin-frontend (push) Has been cancelled
Some checks failed
Build and Push Docker Images / changes (push) Has been cancelled
Build and Push Docker Images / build-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (push) Has been cancelled
Build and Push Docker Images / build-admin-frontend (push) Has been cancelled
This commit is contained in:
@@ -1,130 +1,134 @@
|
||||
from pydantic import Field, model_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""应用全局配置"""
|
||||
|
||||
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
|
||||
|
||||
# 应用基础配置
|
||||
app_name: str = "DreamWeaver"
|
||||
debug: bool = False
|
||||
secret_key: str = Field(..., description="JWT 签名密钥")
|
||||
base_url: str = Field("http://localhost:8000", description="后端对外回调地址")
|
||||
|
||||
# 数据库
|
||||
database_url: str = Field(..., description="SQLAlchemy async URL")
|
||||
|
||||
# OAuth - GitHub
|
||||
github_client_id: str = ""
|
||||
github_client_secret: str = ""
|
||||
|
||||
# OAuth - Google
|
||||
google_client_id: str = ""
|
||||
google_client_secret: str = ""
|
||||
|
||||
# AI Capability Keys
|
||||
text_api_key: str = ""
|
||||
tts_api_base: str = ""
|
||||
tts_api_key: str = ""
|
||||
image_api_key: str = ""
|
||||
|
||||
# Additional Provider API Keys
|
||||
openai_api_key: str = ""
|
||||
elevenlabs_api_key: str = ""
|
||||
cqtai_api_key: str = ""
|
||||
minimax_api_key: str = ""
|
||||
minimax_group_id: str = ""
|
||||
antigravity_api_key: str = ""
|
||||
antigravity_api_base: str = ""
|
||||
|
||||
# AI Model Configuration
|
||||
text_model: str = "gemini-2.0-flash"
|
||||
openai_model: str = "gpt-4o-mini"
|
||||
tts_model: str = ""
|
||||
image_model: str = "nano-banana-pro"
|
||||
tts_minimax_model: str = "speech-2.6-turbo"
|
||||
tts_elevenlabs_model: str = "eleven_multilingual_v2"
|
||||
tts_edge_voice: str = "zh-CN-XiaoxiaoNeural"
|
||||
antigravity_model: str = "gemini-3-pro-image"
|
||||
|
||||
from pydantic import Field, model_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""应用全局配置"""
|
||||
|
||||
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
|
||||
|
||||
# 应用基础配置
|
||||
app_name: str = "DreamWeaver"
|
||||
debug: bool = False
|
||||
secret_key: str = Field(..., description="JWT 签名密钥")
|
||||
base_url: str = Field("http://localhost:8000", description="后端对外回调地址")
|
||||
|
||||
# 数据库
|
||||
database_url: str = Field(..., description="SQLAlchemy async URL")
|
||||
|
||||
# OAuth - GitHub
|
||||
github_client_id: str = ""
|
||||
github_client_secret: str = ""
|
||||
|
||||
# OAuth - Google
|
||||
google_client_id: str = ""
|
||||
google_client_secret: str = ""
|
||||
|
||||
# AI Capability Keys
|
||||
text_api_key: str = ""
|
||||
tts_api_base: str = ""
|
||||
tts_api_key: str = ""
|
||||
image_api_key: str = ""
|
||||
|
||||
# Additional Provider API Keys
|
||||
openai_api_key: str = ""
|
||||
elevenlabs_api_key: str = ""
|
||||
cqtai_api_key: str = ""
|
||||
minimax_api_key: str = ""
|
||||
minimax_group_id: str = ""
|
||||
antigravity_api_key: str = ""
|
||||
antigravity_api_base: str = ""
|
||||
|
||||
# AI Model Configuration
|
||||
text_model: str = "gemini-2.0-flash"
|
||||
openai_model: str = "gpt-4o-mini"
|
||||
tts_model: str = ""
|
||||
image_model: str = "nano-banana-pro"
|
||||
tts_minimax_model: str = "speech-2.6-turbo"
|
||||
tts_elevenlabs_model: str = "eleven_multilingual_v2"
|
||||
tts_edge_voice: str = "zh-CN-XiaoxiaoNeural"
|
||||
antigravity_model: str = "gemini-3-pro-image"
|
||||
|
||||
# Provider routing (ordered lists)
|
||||
text_providers: list[str] = Field(default_factory=lambda: ["gemini"])
|
||||
image_providers: list[str] = Field(default_factory=lambda: ["cqtai"])
|
||||
tts_providers: list[str] = Field(default_factory=lambda: ["minimax", "elevenlabs", "edge_tts"])
|
||||
story_audio_cache_dir: str = Field(
|
||||
"storage/audio",
|
||||
description="Directory for cached story audio files",
|
||||
)
|
||||
|
||||
# Celery (Redis)
|
||||
celery_broker_url: str = Field("redis://localhost:6379/0")
|
||||
celery_result_backend: str = Field("redis://localhost:6379/0")
|
||||
|
||||
# Generic Redis
|
||||
redis_url: str = Field("redis://localhost:6379/0", description="Redis connection URL")
|
||||
redis_sentinel_enabled: bool = Field(False, description="Whether to enable Redis Sentinel")
|
||||
redis_sentinel_nodes: str = Field(
|
||||
"",
|
||||
description="Comma-separated Redis Sentinel nodes, e.g. host1:26379,host2:26379",
|
||||
)
|
||||
redis_sentinel_master_name: str = Field("mymaster", description="Redis Sentinel master name")
|
||||
redis_sentinel_password: str = Field("", description="Password for Redis Sentinel (optional)")
|
||||
redis_sentinel_db: int = Field(0, description="Redis DB index when using Sentinel")
|
||||
redis_sentinel_socket_timeout: float = Field(
|
||||
0.5,
|
||||
description="Socket timeout in seconds for Sentinel clients",
|
||||
)
|
||||
|
||||
# Admin console
|
||||
enable_admin_console: bool = False
|
||||
admin_username: str = "admin"
|
||||
admin_password: str = "admin123" # 建议通过环境变量覆盖
|
||||
|
||||
# CORS
|
||||
cors_origins: list[str] = Field(default_factory=lambda: ["http://localhost:5173"])
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _require_core_settings(self) -> "Settings": # type: ignore[override]
|
||||
missing = []
|
||||
if not self.secret_key or self.secret_key == "change-me-in-production":
|
||||
missing.append("SECRET_KEY")
|
||||
if not self.database_url:
|
||||
missing.append("DATABASE_URL")
|
||||
if self.redis_sentinel_enabled and not self.redis_sentinel_nodes.strip():
|
||||
missing.append("REDIS_SENTINEL_NODES")
|
||||
if missing:
|
||||
raise ValueError(f"Missing required settings: {', '.join(missing)}")
|
||||
return self
|
||||
|
||||
@property
|
||||
def redis_sentinel_hosts(self) -> list[tuple[str, int]]:
|
||||
"""Parse Redis Sentinel nodes into (host, port) tuples."""
|
||||
nodes = []
|
||||
raw = self.redis_sentinel_nodes.strip()
|
||||
if not raw:
|
||||
return nodes
|
||||
|
||||
for item in raw.split(","):
|
||||
value = item.strip()
|
||||
if not value:
|
||||
continue
|
||||
if ":" not in value:
|
||||
raise ValueError(f"Invalid sentinel node format: {value}")
|
||||
host, port_text = value.rsplit(":", 1)
|
||||
if not host:
|
||||
raise ValueError(f"Invalid sentinel node host: {value}")
|
||||
try:
|
||||
port = int(port_text)
|
||||
except ValueError as exc:
|
||||
raise ValueError(f"Invalid sentinel node port: {value}") from exc
|
||||
nodes.append((host, port))
|
||||
return nodes
|
||||
|
||||
@property
|
||||
def redis_sentinel_urls(self) -> list[str]:
|
||||
"""Build Celery-compatible Sentinel URLs with DB index."""
|
||||
return [
|
||||
f"sentinel://{host}:{port}/{self.redis_sentinel_db}"
|
||||
for host, port in self.redis_sentinel_hosts
|
||||
]
|
||||
|
||||
|
||||
settings = Settings()
|
||||
celery_result_backend: str = Field("redis://localhost:6379/0")
|
||||
|
||||
# Generic Redis
|
||||
redis_url: str = Field("redis://localhost:6379/0", description="Redis connection URL")
|
||||
redis_sentinel_enabled: bool = Field(False, description="Whether to enable Redis Sentinel")
|
||||
redis_sentinel_nodes: str = Field(
|
||||
"",
|
||||
description="Comma-separated Redis Sentinel nodes, e.g. host1:26379,host2:26379",
|
||||
)
|
||||
redis_sentinel_master_name: str = Field("mymaster", description="Redis Sentinel master name")
|
||||
redis_sentinel_password: str = Field("", description="Password for Redis Sentinel (optional)")
|
||||
redis_sentinel_db: int = Field(0, description="Redis DB index when using Sentinel")
|
||||
redis_sentinel_socket_timeout: float = Field(
|
||||
0.5,
|
||||
description="Socket timeout in seconds for Sentinel clients",
|
||||
)
|
||||
|
||||
# Admin console
|
||||
enable_admin_console: bool = False
|
||||
admin_username: str = "admin"
|
||||
admin_password: str = "admin123" # 建议通过环境变量覆盖
|
||||
|
||||
# CORS
|
||||
cors_origins: list[str] = Field(default_factory=lambda: ["http://localhost:5173"])
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _require_core_settings(self) -> "Settings": # type: ignore[override]
|
||||
missing = []
|
||||
if not self.secret_key or self.secret_key == "change-me-in-production":
|
||||
missing.append("SECRET_KEY")
|
||||
if not self.database_url:
|
||||
missing.append("DATABASE_URL")
|
||||
if self.redis_sentinel_enabled and not self.redis_sentinel_nodes.strip():
|
||||
missing.append("REDIS_SENTINEL_NODES")
|
||||
if missing:
|
||||
raise ValueError(f"Missing required settings: {', '.join(missing)}")
|
||||
return self
|
||||
|
||||
@property
|
||||
def redis_sentinel_hosts(self) -> list[tuple[str, int]]:
|
||||
"""Parse Redis Sentinel nodes into (host, port) tuples."""
|
||||
nodes = []
|
||||
raw = self.redis_sentinel_nodes.strip()
|
||||
if not raw:
|
||||
return nodes
|
||||
|
||||
for item in raw.split(","):
|
||||
value = item.strip()
|
||||
if not value:
|
||||
continue
|
||||
if ":" not in value:
|
||||
raise ValueError(f"Invalid sentinel node format: {value}")
|
||||
host, port_text = value.rsplit(":", 1)
|
||||
if not host:
|
||||
raise ValueError(f"Invalid sentinel node host: {value}")
|
||||
try:
|
||||
port = int(port_text)
|
||||
except ValueError as exc:
|
||||
raise ValueError(f"Invalid sentinel node port: {value}") from exc
|
||||
nodes.append((host, port))
|
||||
return nodes
|
||||
|
||||
@property
|
||||
def redis_sentinel_urls(self) -> list[str]:
|
||||
"""Build Celery-compatible Sentinel URLs with DB index."""
|
||||
return [
|
||||
f"sentinel://{host}:{port}/{self.redis_sentinel_db}"
|
||||
for host, port in self.redis_sentinel_hosts
|
||||
]
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
Reference in New Issue
Block a user