diff --git a/server-rs/crates/api-server/src/app.rs b/server-rs/crates/api-server/src/app.rs index f4adedc22..135e54a6b 100644 --- a/server-rs/crates/api-server/src/app.rs +++ b/server-rs/crates/api-server/src/app.rs @@ -5930,6 +5930,20 @@ mod tests { payload["models"]["seedance2.0"]["prices"]["720p"], Value::Number(24.into()) ); + // 公开读模型的 3D 段保持迁移前的旧形状:前端画布按端点读底价、按单表读加价项。 + assert_eq!( + payload["model3d"]["basePrices"]["text-to-model"]["v3.1-20260211"]["noTexture"], + Value::Number(8.into()) + ); + assert_eq!( + payload["model3d"]["basePrices"]["image-to-model"]["v3.1-20260211"]["texture"], + Value::Number(24.into()) + ); + assert_eq!( + payload["model3d"]["addOnPrices"]["quadMesh"], + Value::Number(4.into()) + ); + assert!(payload["model3d"]["textToModelPricing"].is_null()); } #[tokio::test] diff --git a/server-rs/crates/api-server/src/editor_generation_config.rs b/server-rs/crates/api-server/src/editor_generation_config.rs index d3ff52837..67a07fe4a 100644 --- a/server-rs/crates/api-server/src/editor_generation_config.rs +++ b/server-rs/crates/api-server/src/editor_generation_config.rs @@ -8,7 +8,9 @@ use std::{ use serde::{Deserialize, Serialize}; use crate::asset_billing::current_external_generation_billing_price_mud_points; -use crate::tripo3d::pricing::{Model3dPricingConfig, Model3dPricingError, Model3dPricingQuery}; +use crate::tripo3d::pricing::{ + Model3dPricingConfig, Model3dPricingError, Model3dPricingPublicView, Model3dPricingQuery, +}; /// 图片画布编辑器生成类能力的泥点配置。 /// @@ -83,6 +85,16 @@ pub(crate) struct EditorGenerationPricingConfig { pub model3d: Option, } +/// 主站公开读模型的响应体:`models` 原样透出,3D 段投影回迁移前的旧形状 +/// ([`Model3dPricingPublicView`]),让画布 3D 入口不需要随本次迁移改动。 +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct EditorGenerationPricingPublicResponse { + pub models: BTreeMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub model3d: Option, +} + #[derive(Clone, Debug)] pub(crate) struct EditorGenerationPricingStore { current: Arc>, @@ -107,6 +119,15 @@ impl From for EditorGenerationPricingError { } impl EditorGenerationPricingConfig { + /// 公开读模型视图:字段名与嵌套形状保持迁移前一致,见 + /// [`EditorGenerationPricingPublicResponse`]。 + pub(crate) fn into_public_response(self) -> EditorGenerationPricingPublicResponse { + EditorGenerationPricingPublicResponse { + models: self.models, + model3d: self.model3d.map(Model3dPricingConfig::into_public_view), + } + } + /// Tripo 3D 提交查价:缺段或缺键都直接报错,不回退默认值。 /// /// 错误与同文件其它查询统一成 [`EditorGenerationPricingError`],3D 结构化原因收在 diff --git a/server-rs/crates/api-server/src/editor_project.rs b/server-rs/crates/api-server/src/editor_project.rs index 8029cf054..ef0723f7f 100644 --- a/server-rs/crates/api-server/src/editor_project.rs +++ b/server-rs/crates/api-server/src/editor_project.rs @@ -2025,7 +2025,11 @@ pub async fn get_editor_generation_pricing( "message": error.to_string(), })) })?; - Ok(json_success_body(Some(&request_context), pricing)) + // 公开读模型不改形状:内部两段结构在这里投影回旧的 `basePrices` / `addOnPrices`。 + Ok(json_success_body( + Some(&request_context), + pricing.into_public_response(), + )) } pub async fn list_editor_projects( diff --git a/server-rs/crates/api-server/src/tripo3d/pricing.rs b/server-rs/crates/api-server/src/tripo3d/pricing.rs index 8fb422d07..54a674b66 100644 --- a/server-rs/crates/api-server/src/tripo3d/pricing.rs +++ b/server-rs/crates/api-server/src/tripo3d/pricing.rs @@ -11,6 +11,7 @@ use shared_contracts::model3d::common::{ Model3dGeometryQuality, Model3dModelVersion, Model3dTextureQuality, }; use shared_contracts::model3d::wire_str_lossy; +use tracing::warn; /// 生成端点。批量价键与请求入口一一对应,不做 provider 侧的变形。 #[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] @@ -90,6 +91,19 @@ pub(crate) struct Model3dPricingConfig { pub image_to_model_pricing: Model3dEndpointPricing, } +/// 主站公开读模型里的 3D 段。 +/// +/// 形状保持迁移前的 `basePrices`(端点判别)× `addOnPrices` 单表:画布 3D 入口直接读它、 +/// 没有内置兜底,形状一变入口就会判定「定价缺失」。两段内部结构到这份视图的投影是有损的 +/// ——加价项只有一张表,取文生 3D 段,两段不一致时打告警。前端升级到按端点读取加价项之前, +/// 不要在两段之间差异化加价项。 +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct Model3dPricingPublicView { + pub base_prices: BTreeMap>, + pub add_on_prices: BTreeMap, +} + impl Model3dPricingConfig { pub(crate) fn endpoint_pricing(&self, endpoint: Model3dEndpoint) -> &Model3dEndpointPricing { match endpoint { @@ -97,6 +111,32 @@ impl Model3dPricingConfig { Model3dEndpoint::ImageToModel => &self.image_to_model_pricing, } } + + /// 投影成公开读模型的旧形状。 + pub(crate) fn into_public_view(self) -> Model3dPricingPublicView { + let Model3dPricingConfig { + text_to_model_pricing, + image_to_model_pricing, + } = self; + if text_to_model_pricing.add_on_prices != image_to_model_pricing.add_on_prices { + warn!( + "文生 3D 与图生 3D 的加价项不一致,公开读模型只能给出文生 3D 的加价项:前端按端点读取前不要差异化加价项" + ); + } + let mut base_prices = BTreeMap::new(); + base_prices.insert( + Model3dEndpoint::TextToModel, + text_to_model_pricing.version_prices, + ); + base_prices.insert( + Model3dEndpoint::ImageToModel, + image_to_model_pricing.version_prices, + ); + Model3dPricingPublicView { + base_prices, + add_on_prices: text_to_model_pricing.add_on_prices, + } + } } /// 一次提交的定价输入:只携带判定价格需要的字段,全部来自已校验的请求。