fix: add 3d model feature visibility and refunds
This commit is contained in:
parent
b64a512dfb
commit
434d4d0d8d
@ -17386,7 +17386,8 @@ export function AdminSettingsClient() {
|
||||
<div className="space-y-1.5">
|
||||
<CardTitle className="text-xl">3D / 世界模型显示名映射</CardTitle>
|
||||
<CardDescription>
|
||||
配置 Tripo3D 与 World Labs 的前台展示名称。用户侧只会看到这里配置的模型名,服务端会自动映射到真实模型 ID。
|
||||
配置 Tripo3D 与 World Labs 的前台展示名称,并控制 3D
|
||||
生成附加选项是否显示。用户侧只会看到这里开放的模型和能力。
|
||||
</CardDescription>
|
||||
</div>
|
||||
<LocalAiSectionToggleButton
|
||||
@ -17404,6 +17405,66 @@ export function AdminSettingsClient() {
|
||||
前端提交任务时只会发送系统内部别名,真实模型 ID 只在服务端使用。你可以在这里自由改“展示名”和“真实模型 ID”的对应关系,避免用户看到上游模型名。
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 rounded-xl border p-4">
|
||||
<div className="space-y-1">
|
||||
<div className="text-base font-medium">
|
||||
3D 生成附加选项显隐
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
关闭后,用户侧 3D 生成面板会隐藏对应开关;服务端也会忽略该附加选项,避免隐藏功能继续产生额外扣分。
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-3 md:grid-cols-3">
|
||||
{(
|
||||
[
|
||||
{
|
||||
key: "pbr",
|
||||
label: "PBR 材质",
|
||||
description: "控制 3D 模型生成里的 PBR 材质开关。",
|
||||
},
|
||||
{
|
||||
key: "quad",
|
||||
label: "四边面优化",
|
||||
description: "控制 3D 模型生成里的四边面优化开关。",
|
||||
},
|
||||
{
|
||||
key: "smartLowPoly",
|
||||
label: "智能低模",
|
||||
description: "控制 3D 模型生成里的智能低模开关。",
|
||||
},
|
||||
] as const
|
||||
).map((item) => (
|
||||
<div
|
||||
className="flex items-center justify-between gap-3 rounded-xl border bg-muted/10 px-3 py-3"
|
||||
key={item.key}
|
||||
>
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-sm font-medium">{item.label}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{item.description}
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
checked={
|
||||
threeDModelAliasConfig.featureVisibility?.[
|
||||
item.key
|
||||
] !== false
|
||||
}
|
||||
onCheckedChange={(checked) =>
|
||||
setThreeDModelAliasConfig((prev) => ({
|
||||
...prev,
|
||||
featureVisibility: {
|
||||
...prev.featureVisibility,
|
||||
[item.key]: checked,
|
||||
},
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 xl:grid-cols-2">
|
||||
<div className="space-y-4 rounded-xl border p-4">
|
||||
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { and, eq, sql } from "drizzle-orm";
|
||||
import { auth } from "@/app/(auth)/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { refundUserPoints } from "@/lib/db/queries";
|
||||
import { pointsLedger } from "@/lib/db/schema";
|
||||
import {
|
||||
TripoApiError,
|
||||
pickTaskModelUrl,
|
||||
@ -216,7 +220,87 @@ interface RouteParams {
|
||||
params: Promise<{ taskId: string }>;
|
||||
}
|
||||
|
||||
function isFailedTripoStatus(status?: string | null) {
|
||||
const normalized = String(status || "").trim().toLowerCase();
|
||||
return (
|
||||
normalized === "failed" ||
|
||||
normalized === "error" ||
|
||||
normalized === "cancelled" ||
|
||||
normalized === "canceled"
|
||||
);
|
||||
}
|
||||
|
||||
function getModel3DTaskRefundNote(taskId: string) {
|
||||
return `3D模型任务失败退回积分(${taskId})`;
|
||||
}
|
||||
|
||||
async function refundModel3DPointsForFailedTask(params: {
|
||||
userId: string;
|
||||
taskId: string;
|
||||
errorMessage?: string | null;
|
||||
}) {
|
||||
const chargeLedgers = await db
|
||||
.select({
|
||||
id: pointsLedger.id,
|
||||
amount: pointsLedger.amount,
|
||||
modelId: pointsLedger.modelId,
|
||||
})
|
||||
.from(pointsLedger)
|
||||
.where(
|
||||
and(
|
||||
eq(pointsLedger.userId, params.userId),
|
||||
eq(pointsLedger.reason, "model3d_task"),
|
||||
sql`${pointsLedger.note} LIKE ${`3D模型任务消耗积分(% / ${params.taskId})`}`,
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
const chargeLedger = chargeLedgers[0];
|
||||
const refundAmount = Math.abs(Number(chargeLedger?.amount || 0));
|
||||
|
||||
if (!chargeLedger || refundAmount <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const refundNote = getModel3DTaskRefundNote(params.taskId);
|
||||
const existingRefund = await db
|
||||
.select({ id: pointsLedger.id })
|
||||
.from(pointsLedger)
|
||||
.where(
|
||||
and(
|
||||
eq(pointsLedger.userId, params.userId),
|
||||
eq(pointsLedger.reason, "model3d_task_refund"),
|
||||
eq(pointsLedger.note, refundNote),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (existingRefund[0]) {
|
||||
return refundAmount;
|
||||
}
|
||||
|
||||
const refundResult = await refundUserPoints({
|
||||
userId: params.userId,
|
||||
amount: refundAmount,
|
||||
reason: "model3d_task_refund",
|
||||
modelId: chargeLedger.modelId || "tripo-3d",
|
||||
note: refundNote,
|
||||
});
|
||||
|
||||
if (!refundResult.ok) {
|
||||
console.error("[Points] Failed to refund failed 3D model task:", {
|
||||
taskId: params.taskId,
|
||||
userId: params.userId,
|
||||
errorMessage: params.errorMessage,
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
||||
return refundAmount;
|
||||
}
|
||||
|
||||
export async function GET(request: Request, { params }: RouteParams) {
|
||||
let refundContext: { userId: string; taskId: string } | null = null;
|
||||
|
||||
try {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) {
|
||||
@ -228,6 +312,7 @@ export async function GET(request: Request, { params }: RouteParams) {
|
||||
if (!normalizedTaskId) {
|
||||
return NextResponse.json({ success: false, error: "缺少 taskId" }, { status: 400 });
|
||||
}
|
||||
refundContext = { userId: session.user.id, taskId: normalizedTaskId };
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const preferredProviderId = searchParams.get("providerId");
|
||||
@ -241,6 +326,18 @@ export async function GET(request: Request, { params }: RouteParams) {
|
||||
|
||||
const output =
|
||||
result.data?.output && typeof result.data.output === "object" ? result.data.output : {};
|
||||
const taskStatus = String(result.data.status || "");
|
||||
const errorMessage = String(result.data.error_msg || "").trim();
|
||||
const refundedPoints = isFailedTripoStatus(taskStatus)
|
||||
? await refundModel3DPointsForFailedTask({
|
||||
userId: session.user.id,
|
||||
taskId: normalizedTaskId,
|
||||
errorMessage,
|
||||
}).catch((refundError) => {
|
||||
console.error("[Points] Failed to handle 3D task refund:", refundError);
|
||||
return 0;
|
||||
})
|
||||
: 0;
|
||||
const modelUrl = pickTaskModelUrl(output as Record<string, unknown>);
|
||||
const previewImageUrl = pickTaskPreviewImageUrl(output as Record<string, unknown>);
|
||||
const stableModelUrl = await archiveTripoModelIfNeeded({
|
||||
@ -302,11 +399,28 @@ export async function GET(request: Request, { params }: RouteParams) {
|
||||
},
|
||||
errorCode: result.data.error_code ?? null,
|
||||
errorMessage: result.data.error_msg ?? null,
|
||||
refundedPoints,
|
||||
},
|
||||
traceId: result.traceId,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof TripoApiError) {
|
||||
const shouldRefundMissingTask =
|
||||
error.code === 2001 || /task\s+is\s+not\s+found/i.test(error.message);
|
||||
const refundedPoints =
|
||||
shouldRefundMissingTask && refundContext
|
||||
? await refundModel3DPointsForFailedTask({
|
||||
...refundContext,
|
||||
errorMessage: error.message,
|
||||
}).catch((refundError) => {
|
||||
console.error(
|
||||
"[Points] Failed to refund missing 3D task:",
|
||||
refundError
|
||||
);
|
||||
return 0;
|
||||
})
|
||||
: 0;
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
@ -314,6 +428,7 @@ export async function GET(request: Request, { params }: RouteParams) {
|
||||
code: error.code,
|
||||
suggestion: error.suggestion,
|
||||
traceId: error.traceId,
|
||||
refundedPoints,
|
||||
},
|
||||
{ status: error.httpStatus || 502 }
|
||||
);
|
||||
|
||||
@ -336,14 +336,42 @@ export async function POST(request: Request) {
|
||||
animateInPlace: "animate_in_place",
|
||||
});
|
||||
|
||||
const featureVisibility = modelAliasConfig.featureVisibility;
|
||||
const effectivePbr = featureVisibility.pbr !== false && body.pbr === true;
|
||||
const effectiveQuad = featureVisibility.quad !== false && body.quad === true;
|
||||
const effectiveSmartLowPoly =
|
||||
featureVisibility.smartLowPoly !== false && body.smartLowPoly === true;
|
||||
|
||||
if (featureVisibility.pbr === false) {
|
||||
delete payload.pbr;
|
||||
}
|
||||
if (featureVisibility.quad === false) {
|
||||
delete payload.quad;
|
||||
}
|
||||
if (featureVisibility.smartLowPoly === false) {
|
||||
delete payload.smart_low_poly;
|
||||
}
|
||||
|
||||
if (
|
||||
taskType === "text_to_model" ||
|
||||
taskType === "image_to_model" ||
|
||||
taskType === "multiview_to_model"
|
||||
) {
|
||||
const enabledTripoModels = modelAliasConfig.tripoModels.filter(
|
||||
(entry) => entry.enabled !== false,
|
||||
);
|
||||
if (enabledTripoModels.length === 0) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "当前没有启用的 3D 生成模型",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
payload.model_version = resolveThreeDModelRealId(
|
||||
readOptionalTrimmedText(body.modelVersion),
|
||||
modelAliasConfig.tripoModels,
|
||||
enabledTripoModels,
|
||||
);
|
||||
}
|
||||
|
||||
@ -354,9 +382,9 @@ export async function POST(request: Request) {
|
||||
const pointsConfig = await getPointsSystemConfig();
|
||||
const unitCost = get3DTaskCreditCost(pointsConfig, {
|
||||
taskType,
|
||||
pbr: body.pbr,
|
||||
quad: body.quad,
|
||||
smartLowPoly: body.smartLowPoly,
|
||||
pbr: effectivePbr,
|
||||
quad: effectiveQuad,
|
||||
smartLowPoly: effectiveSmartLowPoly,
|
||||
});
|
||||
const pointsCharge = await prepareFeaturePointsCharge({
|
||||
userId: session.user.id,
|
||||
|
||||
@ -23,6 +23,8 @@ const MEMBERSHIP_USER_OVERRIDES_KEY = "membership_user_plan_overrides";
|
||||
function mapLedgerReasonLabel(reason: string) {
|
||||
if (reason === "image_generation") return "图片生成";
|
||||
if (reason === "model3d_generation") return "3D模型生成";
|
||||
if (reason === "model3d_task") return "3D模型任务";
|
||||
if (reason === "model3d_task_refund") return "3D模型任务失败退回";
|
||||
if (reason === "world3d_generation") return "3D世界生成";
|
||||
if (reason === "video_generation") return "视频生成";
|
||||
if (reason === "enhance_pro_generation") return "图像增强 Pro";
|
||||
|
||||
@ -824,6 +824,9 @@ export default function CanvasModel3DPage() {
|
||||
const isHighVip = membershipSummary?.isHighVip === true;
|
||||
const publicModelAliasConfig =
|
||||
modelAliasConfigResponse?.data || DEFAULT_PUBLIC_THREED_MODEL_ALIAS_CONFIG;
|
||||
const modelFeatureVisibility =
|
||||
publicModelAliasConfig.featureVisibility ||
|
||||
DEFAULT_PUBLIC_THREED_MODEL_ALIAS_CONFIG.featureVisibility;
|
||||
const selectedBatchImageCount = useMemo(
|
||||
() => batchImageSlots.filter((slot) => Boolean(slot.file)).length,
|
||||
[batchImageSlots]
|
||||
@ -833,11 +836,21 @@ export default function CanvasModel3DPage() {
|
||||
() =>
|
||||
get3DTaskCreditCost(pointsSystemConfig, {
|
||||
taskType: currentModelTaskType,
|
||||
pbr,
|
||||
quad,
|
||||
smartLowPoly,
|
||||
pbr: modelFeatureVisibility.pbr !== false && pbr,
|
||||
quad: modelFeatureVisibility.quad !== false && quad,
|
||||
smartLowPoly:
|
||||
modelFeatureVisibility.smartLowPoly !== false && smartLowPoly,
|
||||
}),
|
||||
[currentModelTaskType, pbr, pointsSystemConfig, quad, smartLowPoly]
|
||||
[
|
||||
currentModelTaskType,
|
||||
modelFeatureVisibility.pbr,
|
||||
modelFeatureVisibility.quad,
|
||||
modelFeatureVisibility.smartLowPoly,
|
||||
pbr,
|
||||
pointsSystemConfig,
|
||||
quad,
|
||||
smartLowPoly,
|
||||
]
|
||||
);
|
||||
const currentModelEstimatedCost =
|
||||
mode === "batch" && selectedBatchImageCount > 0
|
||||
@ -941,23 +954,17 @@ export default function CanvasModel3DPage() {
|
||||
siteLogoDark && siteLogoDark.trim() !== "" ? siteLogoDark : siteLogo;
|
||||
const tripoModelOptions = useMemo(
|
||||
() => {
|
||||
const enabledEntries = publicModelAliasConfig.tripoModels.filter(
|
||||
return publicModelAliasConfig.tripoModels.filter(
|
||||
(item) => item.enabled !== false,
|
||||
);
|
||||
return enabledEntries.length > 0
|
||||
? enabledEntries
|
||||
: publicModelAliasConfig.tripoModels;
|
||||
},
|
||||
[publicModelAliasConfig.tripoModels],
|
||||
);
|
||||
const worldModelOptions = useMemo(
|
||||
() => {
|
||||
const enabledEntries = publicModelAliasConfig.worldModels.filter(
|
||||
return publicModelAliasConfig.worldModels.filter(
|
||||
(item) => item.enabled !== false,
|
||||
);
|
||||
return enabledEntries.length > 0
|
||||
? enabledEntries
|
||||
: publicModelAliasConfig.worldModels;
|
||||
},
|
||||
[publicModelAliasConfig.worldModels],
|
||||
);
|
||||
@ -1529,11 +1536,12 @@ export default function CanvasModel3DPage() {
|
||||
modelVersion,
|
||||
faceLimit,
|
||||
texture,
|
||||
pbr,
|
||||
pbr: modelFeatureVisibility.pbr !== false && pbr,
|
||||
autoSize,
|
||||
quad,
|
||||
quad: modelFeatureVisibility.quad !== false && quad,
|
||||
generateParts,
|
||||
smartLowPoly,
|
||||
smartLowPoly:
|
||||
modelFeatureVisibility.smartLowPoly !== false && smartLowPoly,
|
||||
geometryQuality,
|
||||
textureQuality,
|
||||
modelSeed: modelSeed.trim() ? Number(modelSeed) : undefined,
|
||||
@ -3028,47 +3036,57 @@ export default function CanvasModel3DPage() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-3 rounded-lg border border-white/10 bg-zinc-950 p-3">
|
||||
<div className="flex items-center justify-between text-xs text-zinc-400">
|
||||
<span className="flex items-center gap-2">
|
||||
PBR 材质
|
||||
<CostPill label={pbrExtraCostLabel} />
|
||||
</span>
|
||||
<Switch
|
||||
checked={pbr}
|
||||
className="data-[state=checked]:bg-yellow-500"
|
||||
onCheckedChange={setPbr}
|
||||
/>
|
||||
{modelFeatureVisibility.pbr !== false ||
|
||||
modelFeatureVisibility.quad !== false ||
|
||||
modelFeatureVisibility.smartLowPoly !== false ? (
|
||||
<div className="space-y-3 rounded-lg border border-white/10 bg-zinc-950 p-3">
|
||||
{modelFeatureVisibility.pbr !== false ? (
|
||||
<div className="flex items-center justify-between text-xs text-zinc-400">
|
||||
<span className="flex items-center gap-2">
|
||||
PBR 材质
|
||||
<CostPill label={pbrExtraCostLabel} />
|
||||
</span>
|
||||
<Switch
|
||||
checked={pbr}
|
||||
className="data-[state=checked]:bg-yellow-500"
|
||||
onCheckedChange={setPbr}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{modelFeatureVisibility.quad !== false ? (
|
||||
<div className="flex items-center justify-between text-xs text-zinc-400">
|
||||
<span className="flex items-center gap-2">
|
||||
四边面优化
|
||||
<CostPill label={quadExtraCostLabel} />
|
||||
</span>
|
||||
<Switch
|
||||
checked={quad}
|
||||
className="data-[state=checked]:bg-yellow-500"
|
||||
onCheckedChange={setQuad}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{modelFeatureVisibility.smartLowPoly !== false ? (
|
||||
<div className="flex items-center justify-between text-xs text-zinc-400">
|
||||
<span className="flex items-center gap-2">
|
||||
智能低模
|
||||
<CostPill label={smartLowPolyExtraCostLabel} />
|
||||
</span>
|
||||
<Switch
|
||||
checked={smartLowPoly}
|
||||
className="data-[state=checked]:bg-yellow-500"
|
||||
onCheckedChange={setSmartLowPoly}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs text-zinc-400">
|
||||
<span className="flex items-center gap-2">
|
||||
四边面优化
|
||||
<CostPill label={quadExtraCostLabel} />
|
||||
</span>
|
||||
<Switch
|
||||
checked={quad}
|
||||
className="data-[state=checked]:bg-yellow-500"
|
||||
onCheckedChange={setQuad}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs text-zinc-400">
|
||||
<span className="flex items-center gap-2">
|
||||
智能低模
|
||||
<CostPill label={smartLowPolyExtraCostLabel} />
|
||||
</span>
|
||||
<Switch
|
||||
checked={smartLowPoly}
|
||||
className="data-[state=checked]:bg-yellow-500"
|
||||
onCheckedChange={setSmartLowPoly}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
|
||||
<Button
|
||||
className="w-full bg-yellow-500 text-black hover:bg-yellow-400"
|
||||
disabled={isSubmitting}
|
||||
disabled={isSubmitting || tripoModelOptions.length === 0}
|
||||
onClick={handleGenerate}
|
||||
size="lg"
|
||||
>
|
||||
@ -3079,7 +3097,9 @@ export default function CanvasModel3DPage() {
|
||||
)}
|
||||
{isSubmitting
|
||||
? "生成中..."
|
||||
: `开始生成 · ${modelFeatureCostLabel}`}
|
||||
: tripoModelOptions.length === 0
|
||||
? "暂无可用模型"
|
||||
: `开始生成 · ${modelFeatureCostLabel}`}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -9,9 +9,16 @@ export type ThreeDModelAliasEntry = {
|
||||
isDefault: boolean;
|
||||
};
|
||||
|
||||
export type ThreeDModelFeatureVisibilityConfig = {
|
||||
pbr: boolean;
|
||||
quad: boolean;
|
||||
smartLowPoly: boolean;
|
||||
};
|
||||
|
||||
export type ThreeDModelAliasConfig = {
|
||||
tripoModels: ThreeDModelAliasEntry[];
|
||||
worldModels: ThreeDModelAliasEntry[];
|
||||
featureVisibility: ThreeDModelFeatureVisibilityConfig;
|
||||
};
|
||||
|
||||
export type PublicThreeDModelAliasEntry = Omit<
|
||||
@ -24,6 +31,7 @@ export type PublicThreeDModelAliasEntry = Omit<
|
||||
export type PublicThreeDModelAliasConfig = {
|
||||
tripoModels: PublicThreeDModelAliasEntry[];
|
||||
worldModels: PublicThreeDModelAliasEntry[];
|
||||
featureVisibility: ThreeDModelFeatureVisibilityConfig;
|
||||
};
|
||||
|
||||
const DEFAULT_TRIPO_MODEL_ALIASES: ThreeDModelAliasEntry[] = [
|
||||
@ -112,6 +120,12 @@ const DEFAULT_WORLD_MODEL_ALIASES: ThreeDModelAliasEntry[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const DEFAULT_THREED_MODEL_FEATURE_VISIBILITY: ThreeDModelFeatureVisibilityConfig = {
|
||||
pbr: true,
|
||||
quad: true,
|
||||
smartLowPoly: true,
|
||||
};
|
||||
|
||||
function sanitizeText(value: unknown) {
|
||||
return String(value || "").trim();
|
||||
}
|
||||
@ -196,10 +210,35 @@ function normalizeEntryList(
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeFeatureVisibility(
|
||||
value: unknown,
|
||||
): ThreeDModelFeatureVisibilityConfig {
|
||||
const raw =
|
||||
value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Partial<ThreeDModelFeatureVisibilityConfig>)
|
||||
: {};
|
||||
|
||||
return {
|
||||
pbr:
|
||||
typeof raw.pbr === "boolean"
|
||||
? raw.pbr
|
||||
: DEFAULT_THREED_MODEL_FEATURE_VISIBILITY.pbr,
|
||||
quad:
|
||||
typeof raw.quad === "boolean"
|
||||
? raw.quad
|
||||
: DEFAULT_THREED_MODEL_FEATURE_VISIBILITY.quad,
|
||||
smartLowPoly:
|
||||
typeof raw.smartLowPoly === "boolean"
|
||||
? raw.smartLowPoly
|
||||
: DEFAULT_THREED_MODEL_FEATURE_VISIBILITY.smartLowPoly,
|
||||
};
|
||||
}
|
||||
|
||||
export function defaultThreeDModelAliasConfig(): ThreeDModelAliasConfig {
|
||||
return {
|
||||
tripoModels: cloneEntries(DEFAULT_TRIPO_MODEL_ALIASES),
|
||||
worldModels: cloneEntries(DEFAULT_WORLD_MODEL_ALIASES),
|
||||
featureVisibility: { ...DEFAULT_THREED_MODEL_FEATURE_VISIBILITY },
|
||||
};
|
||||
}
|
||||
|
||||
@ -222,6 +261,7 @@ export function normalizeThreeDModelAliasConfig(
|
||||
DEFAULT_WORLD_MODEL_ALIASES,
|
||||
"world-model",
|
||||
),
|
||||
featureVisibility: normalizeFeatureVisibility(raw.featureVisibility),
|
||||
};
|
||||
}
|
||||
|
||||
@ -236,6 +276,7 @@ export function toPublicThreeDModelAliasConfig(
|
||||
...entry,
|
||||
billingModelId: realModelId,
|
||||
})),
|
||||
featureVisibility: normalizeFeatureVisibility(config.featureVisibility),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user