diff --git a/backend/packages/app/src/windup_app/server/orchestrator/render3d_assets.py b/backend/packages/app/src/windup_app/server/orchestrator/render3d_assets.py index da9f998d..78d48125 100644 --- a/backend/packages/app/src/windup_app/server/orchestrator/render3d_assets.py +++ b/backend/packages/app/src/windup_app/server/orchestrator/render3d_assets.py @@ -44,7 +44,6 @@ from windup_ai_engine.ports import ProgressPort from windup_framework.providers.render3d.tencent import ( - CREDIT_PRICE_CNY, CREDITS, RIG_CREDITS, ) @@ -69,7 +68,6 @@ MODEL3D_CREDITS = CREDITS["Normal"] AUTORIG_CREDITS = RIG_CREDITS BUILD_CREDITS = MODEL3D_CREDITS + AUTORIG_CREDITS -BUILD_CNY = round(BUILD_CREDITS * CREDIT_PRICE_CNY, 2) class Render3DAssetState(str, Enum): @@ -303,7 +301,7 @@ def ensure(self, outfit_key: str, master: bytes, progress: ProgressPort) -> byte if not self._may_build_assets: raise SpendNotAuthorized( f"造型 {outfit_key!r} 的 3D 资产未就绪,而本实例未获准建(建一次 " - f"{BUILD_CREDITS} 积分,约 ¥{BUILD_CNY}:图生 3D {MODEL3D_CREDITS} + " + f"{BUILD_CREDITS} 积分:图生 3D {MODEL3D_CREDITS} + " f"绑骨 {AUTORIG_CREDITS})。要现建请显式授权花钱," "或先把资产备好,或改走 video_i2v。" ) diff --git a/backend/packages/app/src/windup_app/server/orchestrator/render3d_service.py b/backend/packages/app/src/windup_app/server/orchestrator/render3d_service.py index 94ea86ee..5d405e11 100644 --- a/backend/packages/app/src/windup_app/server/orchestrator/render3d_service.py +++ b/backend/packages/app/src/windup_app/server/orchestrator/render3d_service.py @@ -31,7 +31,6 @@ from windup_app.server.orchestrator._fetch import FetchNotAllowed, fetch_own_media from windup_app.server.orchestrator.render3d_assets import ( AUTORIG_CREDITS, - BUILD_CNY, BUILD_CREDITS, MODEL3D_CREDITS, RAW_KEY_PREFIX, @@ -183,7 +182,7 @@ def build(self, outfit_key: str, master_url: str) -> dict: if not self._builder.may_build_assets: raise SpendNotAuthorized( f"本部署未开启建 3D 资产(需 WINDUP_RENDER3D_ALLOW_SPEND)。建一次 " - f"{BUILD_CREDITS} 积分,约 ¥{BUILD_CNY}。" + f"{BUILD_CREDITS} 积分。" ) state = self._builder.state(outfit_key) if state is not Render3DAssetState.ABSENT: diff --git a/backend/packages/framework/src/windup_framework/providers/render3d/tencent.py b/backend/packages/framework/src/windup_framework/providers/render3d/tencent.py index 32dc217c..fe290825 100644 --- a/backend/packages/framework/src/windup_framework/providers/render3d/tencent.py +++ b/backend/packages/framework/src/windup_framework/providers/render3d/tencent.py @@ -43,7 +43,7 @@ __all__ = [ "TencentModel3DProvider", "TencentAutoRigProvider", "TencentCosModelUploader", - "SpendNotAuthorizedError", "CREDITS", "CREDIT_PRICE_CNY", "PRESET_MOTIONS", + "SpendNotAuthorizedError", "CREDITS", "PRESET_MOTIONS", "RIG_CREDITS", "MAX_IMAGE_BYTES", "VIEW_TYPES", ] @@ -54,7 +54,6 @@ CREDITS = {"Normal": 20, "LowPoly": 25, "Geometry": 15, "Sketch": 25} RIG_CREDITS = 10 -CREDIT_PRICE_CNY = 0.12 # 后付费单价;预付费 0.09–0.1 MAX_IMAGE_BYTES = 6 * 10**6 # ImageBase64 上限 VIEW_TYPES = ("back", "left", "right") # 正面走主参数,不在这里 @@ -266,10 +265,10 @@ def __init__( self._poll = poll_interval self._max_min = max_min - def quote(self, n_views: int = 1) -> tuple[int, float]: - """返回 (积分, 预估元)。PBR、多视图各 +10 积分。纯计算,可在提交前随便调。""" + def quote(self, n_views: int = 1) -> int: + """返回本次要花的积分。PBR、多视图各 +10。纯计算,花钱之前就该问得出花多少。""" credits = CREDITS[self._type] + (10 if self._pbr else 0) + (10 if n_views > 1 else 0) - return credits, round(credits * CREDIT_PRICE_CNY, 2) + return credits def build_params(self, master: bytes, extra_views: Mapping[str, bytes] | None = None) -> dict: @@ -305,10 +304,10 @@ def image_to_3d(self, master: bytes, *, want: str = "GLB", if want not in MODEL_FORMATS: raise ArtifactFormatError(f"want 只能是 {MODEL_FORMATS},收到 {want!r}") params = self.build_params(master, extra_views) - credits, cny = self.quote(1 + len(extra_views or {})) + credits = self.quote(1 + len(extra_views or {})) if not self._allow_spend: raise SpendNotAuthorizedError( - f"图生 3D 会消耗 {credits} 积分(后付费约 ¥{cny})。" + f"图生 3D 会消耗 {credits} 积分。" "确认要花这笔钱后,用 TencentModel3DProvider(..., allow_spend=True) 构造。") if self._ask_format: @@ -392,8 +391,8 @@ def __init__( def preset_motions(self) -> Mapping[str, PresetMotion]: return PRESET_MOTIONS - def quote(self) -> tuple[int, float]: - return RIG_CREDITS, round(RIG_CREDITS * CREDIT_PRICE_CNY, 2) + def quote(self) -> int: + return RIG_CREDITS def resolve_motion(self, motion: str | int | None) -> PresetMotion | None: """动作名 / 编号 → :class:`PresetMotion`。名字不认识就抛,不猜编号。""" @@ -424,10 +423,10 @@ def rig(self, model: bytes, *, want: str = "GLB", # 入口预检在**上传与提交之前**:三条硬约束违反了接口不报错,只默默出错结果。 check_model(model) - credits, cny = self.quote() + credits = self.quote() if not self._allow_spend: raise SpendNotAuthorizedError( - f"绑骨会消耗 {credits} 积分(后付费约 ¥{cny})。" + f"绑骨会消耗 {credits} 积分。" "确认要花这笔钱后,用 TencentAutoRigProvider(..., allow_spend=True) 构造。") url = self._upload(model, src_fmt) diff --git a/backend/tests/test_render3d_tencent.py b/backend/tests/test_render3d_tencent.py index 060c4629..932c1a35 100644 --- a/backend/tests/test_render3d_tencent.py +++ b/backend/tests/test_render3d_tencent.py @@ -111,7 +111,10 @@ def test_model3d_refuses_to_spend_by_default(cloud): p = TencentModel3DProvider(CREDS) with pytest.raises(SpendNotAuthorizedError) as e: p.image_to_3d(b"\x89PNG fake") - assert "20 积分" in str(e.value) and "¥2.4" in str(e.value) # 报价必须写在异常里 + # 报价写在异常里,但只给积分 —— 人民币是我们付给供应商的成本,不是用户的价钱, + # 而本仓是公开仓。同一条判据见 test_render3d_asset_endpoints 里的出参断言。 + assert "20 积分" in str(e.value) + assert "¥" not in str(e.value), "人民币成本不出现在任何对外文案里" assert fake.calls == [] # 一个请求都没发出去 @@ -142,11 +145,12 @@ def test_unknown_motion_name_dies_before_the_spend_gate(cloud): def test_quotes_are_pure_computation(): - assert TencentModel3DProvider(CREDS).quote() == (20, 2.4) - assert TencentModel3DProvider(CREDS, generate_type="Geometry").quote() == (15, 1.8) - assert TencentModel3DProvider(CREDS, enable_pbr=True).quote() == (30, 3.6) - assert TencentModel3DProvider(CREDS).quote(n_views=3) == (30, 3.6) - assert TencentAutoRigProvider(Uploader(), CREDS).quote() == (10, 1.2) + """报价只出积分。换算人民币要用供应商单价,那个数不该留在公开仓里。""" + assert TencentModel3DProvider(CREDS).quote() == 20 + assert TencentModel3DProvider(CREDS, generate_type="Geometry").quote() == 15 + assert TencentModel3DProvider(CREDS, enable_pbr=True).quote() == 30 + assert TencentModel3DProvider(CREDS).quote(n_views=3) == 30 + assert TencentAutoRigProvider(Uploader(), CREDS).quote() == 10 def test_unknown_generate_type_dies_at_construction():