443 lines
13 KiB
Rust
443 lines
13 KiB
Rust
use figment::{
|
|
providers::{Env, Format, Toml},
|
|
Figment,
|
|
};
|
|
use serde::Deserialize;
|
|
use std::path::PathBuf;
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
pub struct AppConfig {
|
|
pub server: ServerConfig,
|
|
pub db: DbConfig,
|
|
pub auth: AuthConfig,
|
|
pub proxy: ProxyConfig,
|
|
#[serde(default)]
|
|
pub comfyui: ComfyUIConfig,
|
|
#[serde(default)]
|
|
pub pixelle_video: PixelleVideoConfig,
|
|
#[serde(default)]
|
|
pub payment: PaymentConfig,
|
|
#[serde(default)]
|
|
pub jimeng: JimengConfig,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
pub struct ServerConfig {
|
|
pub host: String,
|
|
pub port: u16,
|
|
pub log_level: String,
|
|
pub cors_origins: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
pub struct DbConfig {
|
|
pub url: String,
|
|
pub max_connections: u32,
|
|
pub ssl: bool,
|
|
pub runtime_db_url: Option<String>,
|
|
pub runtime_db_dialect: Option<String>,
|
|
pub runtime_db_ssl: Option<bool>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
pub struct AuthConfig {
|
|
pub jwt_secret: String,
|
|
pub jwt_expiry_hours: i64,
|
|
pub auth_token: String,
|
|
pub proxy_token: String,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
pub struct ProxyConfig {
|
|
pub upstream_timeout_secs: u64,
|
|
pub first_byte_timeout_secs: u64,
|
|
pub max_retries: u32,
|
|
pub proxy_log_retention_days: u32,
|
|
#[serde(default)]
|
|
pub template_proxy_url: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
pub struct ComfyUIConfig {
|
|
pub enabled: bool,
|
|
#[serde(default)]
|
|
pub backend: ComfyUIBackend,
|
|
#[serde(default)]
|
|
pub money_printer_video_cost: f64,
|
|
#[serde(default)]
|
|
pub digital_human_video_cost: f64,
|
|
#[serde(default = "default_workflow_points_cost")]
|
|
pub workflow_points_cost: f64,
|
|
#[serde(default = "default_comfyui_docker_image")]
|
|
pub docker_image: String,
|
|
#[serde(default = "default_comfyui_container_name")]
|
|
pub container_name: String,
|
|
pub install_dir: Option<PathBuf>,
|
|
pub python_bin: String,
|
|
pub port: u16,
|
|
pub extra_args: Vec<String>,
|
|
pub max_restarts: u32,
|
|
pub external_url: Option<String>,
|
|
pub base_url: Option<String>,
|
|
pub api_key: Option<String>,
|
|
pub agent_model: String,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
pub struct PixelleVideoConfig {
|
|
pub enabled: bool,
|
|
pub api_base_url: Option<String>,
|
|
pub api_key: Option<String>,
|
|
#[serde(default = "default_pixelle_backend_provider")]
|
|
pub backend_provider: String,
|
|
pub frame_template: String,
|
|
pub media_workflow: Option<String>,
|
|
pub tts_workflow: Option<String>,
|
|
pub runninghub_media_workflow: Option<String>,
|
|
pub runninghub_tts_workflow: Option<String>,
|
|
pub mode: String,
|
|
pub n_scenes: i64,
|
|
pub video_fps: i64,
|
|
pub poll_interval_secs: u64,
|
|
pub timeout_secs: u64,
|
|
#[serde(default = "default_pixelle_config_path")]
|
|
pub config_path: String,
|
|
#[serde(default = "default_pixelle_compose_dir")]
|
|
pub compose_dir: String,
|
|
}
|
|
|
|
/// Jimeng (Volcengine Visual / CV) async generation provider.
|
|
///
|
|
/// Credentials are read from config or the `BOOSAPI_JIMENG__*` environment
|
|
/// variables (e.g. `BOOSAPI_JIMENG__ACCESS_KEY`) and never hard-coded. The
|
|
/// provider is gated behind `enabled`; when false the routes short-circuit
|
|
/// with a "capability disabled" response and no upstream calls are made.
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
pub struct JimengConfig {
|
|
/// Master switch. Defaults to false until AK/SK are configured.
|
|
#[serde(default)]
|
|
pub enabled: bool,
|
|
/// Volcengine access key id.
|
|
#[serde(default)]
|
|
pub access_key: String,
|
|
/// Volcengine secret access key.
|
|
#[serde(default)]
|
|
pub secret_key: String,
|
|
/// Optional STS session token (sent as `X-Security-Token` when present).
|
|
#[serde(default)]
|
|
pub session_token: Option<String>,
|
|
/// Signing region.
|
|
#[serde(default = "default_jimeng_region")]
|
|
pub region: String,
|
|
/// API host.
|
|
#[serde(default = "default_jimeng_host")]
|
|
pub host: String,
|
|
/// Poll interval for image tasks (seconds).
|
|
#[serde(default = "default_jimeng_image_poll_interval_secs")]
|
|
pub image_poll_interval_secs: u64,
|
|
/// Overall timeout for image tasks (seconds).
|
|
#[serde(default = "default_jimeng_image_timeout_secs")]
|
|
pub image_timeout_secs: u64,
|
|
/// Poll interval for video tasks (seconds).
|
|
#[serde(default = "default_jimeng_video_poll_interval_secs")]
|
|
pub video_poll_interval_secs: u64,
|
|
/// Overall timeout for video tasks (seconds).
|
|
#[serde(default = "default_jimeng_video_timeout_secs")]
|
|
pub video_timeout_secs: u64,
|
|
/// Cost charged per successful image generation.
|
|
#[serde(default)]
|
|
pub image_cost: f64,
|
|
/// Cost charged per successful video generation.
|
|
#[serde(default)]
|
|
pub video_cost: f64,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
pub struct PaymentConfig {
|
|
pub enabled: bool,
|
|
#[serde(default)]
|
|
pub provider: PaymentProvider,
|
|
pub public_base_url: Option<String>,
|
|
pub product_name_prefix: String,
|
|
pub currency: String,
|
|
pub min_amount_usd: f64,
|
|
pub max_amount_usd: Option<f64>,
|
|
pub daily_limit_usd: Option<f64>,
|
|
pub order_timeout_minutes: i64,
|
|
pub max_pending_orders: i64,
|
|
pub stripe_secret_key: Option<String>,
|
|
pub stripe_webhook_secret: Option<String>,
|
|
pub comfy_customer_token: Option<String>,
|
|
pub comfy_customer_token_endpoint: Option<String>,
|
|
pub comfy_firebase_api_key: Option<String>,
|
|
pub comfy_oauth_client_id: Option<String>,
|
|
pub comfy_oauth_client_secret: Option<String>,
|
|
pub comfy_oauth_refresh_token: Option<String>,
|
|
pub comfy_customer_api_base_url: Option<String>,
|
|
pub comfy_checkout_endpoint: Option<String>,
|
|
pub success_path: String,
|
|
pub cancel_path: String,
|
|
/// When true, confirming a comfy_cloud recharge grants workflow points
|
|
/// (amount_usd * usd_to_points_rate) instead of crediting the USD pool.
|
|
#[serde(default = "default_comfy_recharge_to_points")]
|
|
pub comfy_recharge_to_points: bool,
|
|
/// Points granted per 1 USD when comfy_recharge_to_points is enabled.
|
|
#[serde(default = "default_usd_to_points_rate")]
|
|
pub usd_to_points_rate: f64,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone, Default, PartialEq, Eq)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum PaymentProvider {
|
|
#[default]
|
|
ComfyCloud,
|
|
Stripe,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone, Default)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum ComfyUIBackend {
|
|
#[default]
|
|
Boosapi,
|
|
External,
|
|
Cloud,
|
|
Runninghub,
|
|
Docker,
|
|
}
|
|
|
|
fn default_comfyui_docker_image() -> String {
|
|
"boosapi-comfyui:multi-tenant".to_string()
|
|
}
|
|
|
|
fn default_comfyui_container_name() -> String {
|
|
"boosapi-comfyui-workflow".to_string()
|
|
}
|
|
|
|
fn default_pixelle_backend_provider() -> String {
|
|
"comfy".to_string()
|
|
}
|
|
|
|
fn default_pixelle_config_path() -> String {
|
|
"/opt/Pixelle-Video/config.yaml".to_string()
|
|
}
|
|
|
|
fn default_pixelle_compose_dir() -> String {
|
|
"/opt/Pixelle-Video".to_string()
|
|
}
|
|
|
|
fn default_jimeng_region() -> String {
|
|
"cn-north-1".to_string()
|
|
}
|
|
|
|
fn default_jimeng_host() -> String {
|
|
"visual.volcengineapi.com".to_string()
|
|
}
|
|
|
|
fn default_jimeng_image_poll_interval_secs() -> u64 {
|
|
2
|
|
}
|
|
|
|
fn default_jimeng_image_timeout_secs() -> u64 {
|
|
120
|
|
}
|
|
|
|
fn default_jimeng_video_poll_interval_secs() -> u64 {
|
|
8
|
|
}
|
|
|
|
fn default_jimeng_video_timeout_secs() -> u64 {
|
|
600
|
|
}
|
|
|
|
fn default_workflow_points_cost() -> f64 {
|
|
10.0
|
|
}
|
|
|
|
fn default_comfy_recharge_to_points() -> bool {
|
|
true
|
|
}
|
|
|
|
fn default_usd_to_points_rate() -> f64 {
|
|
100.0
|
|
}
|
|
|
|
impl Default for ComfyUIConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
enabled: false,
|
|
backend: ComfyUIBackend::Boosapi,
|
|
money_printer_video_cost: 0.0,
|
|
digital_human_video_cost: 0.0,
|
|
workflow_points_cost: 10.0,
|
|
docker_image: default_comfyui_docker_image(),
|
|
container_name: default_comfyui_container_name(),
|
|
install_dir: None,
|
|
python_bin: "python3".to_string(),
|
|
port: 8188,
|
|
extra_args: Vec::new(),
|
|
max_restarts: 5,
|
|
external_url: None,
|
|
base_url: Some("http://127.0.0.1:8188".to_string()),
|
|
api_key: None,
|
|
agent_model: "gpt-4o".to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for PixelleVideoConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
enabled: false,
|
|
api_base_url: Some("http://127.0.0.1:8000".to_string()),
|
|
api_key: None,
|
|
backend_provider: default_pixelle_backend_provider(),
|
|
frame_template: "1080x1920/image_default.html".to_string(),
|
|
media_workflow: None,
|
|
tts_workflow: None,
|
|
runninghub_media_workflow: Some("runninghub/video_wan2.1_fusionx.json".to_string()),
|
|
runninghub_tts_workflow: Some("runninghub/tts_edge.json".to_string()),
|
|
mode: "fixed".to_string(),
|
|
n_scenes: 5,
|
|
video_fps: 30,
|
|
poll_interval_secs: 5,
|
|
timeout_secs: 900,
|
|
config_path: default_pixelle_config_path(),
|
|
compose_dir: default_pixelle_compose_dir(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for PaymentConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
enabled: false,
|
|
provider: PaymentProvider::ComfyCloud,
|
|
public_base_url: None,
|
|
product_name_prefix: "BossAI 账户充值".to_string(),
|
|
currency: "usd".to_string(),
|
|
min_amount_usd: 1.0,
|
|
max_amount_usd: Some(100_000.0),
|
|
daily_limit_usd: None,
|
|
order_timeout_minutes: 30,
|
|
max_pending_orders: 3,
|
|
stripe_secret_key: None,
|
|
stripe_webhook_secret: None,
|
|
comfy_customer_token: None,
|
|
comfy_customer_token_endpoint: None,
|
|
comfy_firebase_api_key: None,
|
|
comfy_oauth_client_id: None,
|
|
comfy_oauth_client_secret: None,
|
|
comfy_oauth_refresh_token: None,
|
|
comfy_customer_api_base_url: None,
|
|
comfy_checkout_endpoint: None,
|
|
success_path: "/user/recharge?payment=success&order={ORDER_ID}".to_string(),
|
|
cancel_path: "/user/recharge?payment=cancel&order={ORDER_ID}".to_string(),
|
|
comfy_recharge_to_points: true,
|
|
usd_to_points_rate: 100.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for JimengConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
enabled: false,
|
|
access_key: String::new(),
|
|
secret_key: String::new(),
|
|
session_token: None,
|
|
region: default_jimeng_region(),
|
|
host: default_jimeng_host(),
|
|
image_poll_interval_secs: default_jimeng_image_poll_interval_secs(),
|
|
image_timeout_secs: default_jimeng_image_timeout_secs(),
|
|
video_poll_interval_secs: default_jimeng_video_poll_interval_secs(),
|
|
video_timeout_secs: default_jimeng_video_timeout_secs(),
|
|
image_cost: 0.0,
|
|
video_cost: 0.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for AppConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
server: ServerConfig {
|
|
host: "127.0.0.1".to_string(),
|
|
port: 4000,
|
|
log_level: "info".to_string(),
|
|
cors_origins: vec!["*".to_string()],
|
|
},
|
|
db: DbConfig {
|
|
url: "sqlite::memory:".to_string(),
|
|
max_connections: 1,
|
|
ssl: false,
|
|
runtime_db_url: None,
|
|
runtime_db_dialect: None,
|
|
runtime_db_ssl: None,
|
|
},
|
|
auth: AuthConfig {
|
|
jwt_secret: "test-secret".to_string(),
|
|
jwt_expiry_hours: 72,
|
|
auth_token: "test-admin-token".to_string(),
|
|
proxy_token: "sk-test-proxy".to_string(),
|
|
},
|
|
proxy: ProxyConfig {
|
|
upstream_timeout_secs: 300,
|
|
first_byte_timeout_secs: 60,
|
|
max_retries: 3,
|
|
proxy_log_retention_days: 30,
|
|
template_proxy_url: None,
|
|
},
|
|
comfyui: ComfyUIConfig::default(),
|
|
pixelle_video: PixelleVideoConfig::default(),
|
|
payment: PaymentConfig::default(),
|
|
jimeng: JimengConfig::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ComfyUIConfig {
|
|
pub fn uses_native_backend(&self) -> bool {
|
|
matches!(
|
|
self.backend,
|
|
ComfyUIBackend::External | ComfyUIBackend::Cloud | ComfyUIBackend::Runninghub
|
|
)
|
|
}
|
|
|
|
pub fn uses_cloud_backend(&self) -> bool {
|
|
matches!(self.backend, ComfyUIBackend::Cloud)
|
|
}
|
|
|
|
pub fn uses_runninghub_backend(&self) -> bool {
|
|
matches!(self.backend, ComfyUIBackend::Runninghub)
|
|
}
|
|
|
|
pub fn uses_docker_backend(&self) -> bool {
|
|
matches!(self.backend, ComfyUIBackend::Docker)
|
|
}
|
|
|
|
pub fn target_base_url(&self) -> String {
|
|
self.external_url
|
|
.as_deref()
|
|
.or(self.base_url.as_deref())
|
|
.map(str::trim)
|
|
.filter(|url| !url.is_empty())
|
|
.unwrap_or("http://127.0.0.1:8188")
|
|
.trim_end_matches('/')
|
|
.to_string()
|
|
}
|
|
}
|
|
|
|
impl AppConfig {
|
|
pub fn load() -> anyhow::Result<Self> {
|
|
Self::load_from_file("config.toml")
|
|
}
|
|
|
|
pub fn load_from_file(path: &str) -> anyhow::Result<Self> {
|
|
dotenvy::dotenv().ok();
|
|
Ok(Figment::new()
|
|
.merge(Toml::file(path))
|
|
.merge(Env::prefixed("BOOSAPI_").split("__"))
|
|
.extract()?)
|
|
}
|
|
}
|