fix(model3d): preserve Tripo provider integration
This commit is contained in:
parent
2489ce54f2
commit
1b281a76dc
@ -17,7 +17,6 @@ import {
|
||||
resolveTripoProviderConfigForTaskId,
|
||||
} from "@/lib/tripo/server";
|
||||
import type { TripoTaskData } from "@/lib/tripo/types";
|
||||
import { getConfiguredVip98kTaskStatus } from "@/lib/ai/vip98k-runtime";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const maxDuration = 120;
|
||||
@ -320,111 +319,6 @@ export async function GET(request: Request, { params }: RouteParams) {
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const preferredProviderId = searchParams.get("providerId");
|
||||
if (normalizedTaskId.startsWith("vip98k:")) {
|
||||
const [, encodedProviderId, ...remoteTaskParts] = normalizedTaskId.split(":");
|
||||
const remoteTaskId = remoteTaskParts.join(":");
|
||||
if (!encodedProviderId || !remoteTaskId) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "VIP98K 3D taskId 格式无效" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
const result = await getConfiguredVip98kTaskStatus({
|
||||
taskId: remoteTaskId,
|
||||
preferredProviderId: encodedProviderId,
|
||||
});
|
||||
const modelUrl =
|
||||
result.outputUrls.find((url) =>
|
||||
/\.(?:glb|gltf|fbx|obj|stl|3mf|usdz|ply|zip)(?:[?#].*)?$/i.test(url),
|
||||
) || result.outputUrls[0] || null;
|
||||
const previewImageUrl =
|
||||
result.outputUrls.find((url) =>
|
||||
/\.(?:png|jpe?g|webp|gif)(?:[?#].*)?$/i.test(url),
|
||||
) || null;
|
||||
const stableModelUrl = await archiveTripoModelIfNeeded({
|
||||
taskId: remoteTaskId,
|
||||
cacheKey: `vip98k:${remoteTaskId}:model`,
|
||||
sourceUrl: modelUrl,
|
||||
filenamePrefix: `vip98k-model-${remoteTaskId}`,
|
||||
fallbackExtension: "glb",
|
||||
});
|
||||
const stablePreviewImageUrl = await archiveTripoModelIfNeeded({
|
||||
taskId: remoteTaskId,
|
||||
cacheKey: `vip98k:${remoteTaskId}:preview`,
|
||||
sourceUrl: previewImageUrl,
|
||||
filenamePrefix: `vip98k-preview-${remoteTaskId}`,
|
||||
fallbackExtension: "jpg",
|
||||
});
|
||||
const errorMessage = result.failed ? "VIP98K 3D 任务执行失败" : null;
|
||||
const refundedPoints = result.failed
|
||||
? await refundModel3DPointsForFailedTask({
|
||||
userId: session.user.id,
|
||||
taskId: normalizedTaskId,
|
||||
errorMessage: errorMessage || "VIP98K 3D task failed",
|
||||
})
|
||||
: 0;
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
taskId: normalizedTaskId,
|
||||
type: "model",
|
||||
status: result.failed ? "failed" : result.done ? "success" : result.status || "running",
|
||||
progress: result.done ? 100 : null,
|
||||
output: result.payload,
|
||||
modelUrl: stableModelUrl,
|
||||
previewImageUrl: stablePreviewImageUrl,
|
||||
assetPayload: {
|
||||
providerId: result.providerId,
|
||||
providerName: result.providerName,
|
||||
storageArchivedAt: stableModelUrl || stablePreviewImageUrl ? new Date().toISOString() : null,
|
||||
},
|
||||
errorCode: result.failed ? "VIP98K_TASK_FAILED" : null,
|
||||
errorMessage,
|
||||
refundedPoints,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (normalizedTaskId.startsWith("vip98k-direct:")) {
|
||||
const [, encodedProviderId, encodedUrls] = normalizedTaskId.split(":");
|
||||
if (!encodedProviderId || !encodedUrls) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "VIP98K 3D direct taskId 格式无效" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
const outputUrls = JSON.parse(
|
||||
Buffer.from(encodedUrls, "base64url").toString("utf8"),
|
||||
) as string[];
|
||||
const modelUrl =
|
||||
outputUrls.find((url) =>
|
||||
/\.(?:glb|gltf|fbx|obj|stl|3mf|usdz|ply|zip)(?:[?#].*)?$/i.test(url),
|
||||
) || outputUrls[0] || null;
|
||||
const previewImageUrl =
|
||||
outputUrls.find((url) => /\.(?:png|jpe?g|webp|gif)(?:[?#].*)?$/i.test(url)) ||
|
||||
null;
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
taskId: normalizedTaskId,
|
||||
type: "model",
|
||||
status: "success",
|
||||
progress: 100,
|
||||
output: { urls: outputUrls },
|
||||
modelUrl,
|
||||
previewImageUrl,
|
||||
assetPayload: {
|
||||
providerId: encodedProviderId,
|
||||
providerName: "VIP98K",
|
||||
storageArchivedAt: null,
|
||||
},
|
||||
errorCode: null,
|
||||
errorMessage: null,
|
||||
refundedPoints: 0,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const config = preferredProviderId
|
||||
? await getTripoProviderConfig(preferredProviderId)
|
||||
: await resolveTripoProviderConfigForTaskId(normalizedTaskId);
|
||||
|
||||
@ -14,10 +14,6 @@ import {
|
||||
resolveTripoProviderConfigForTaskId,
|
||||
} from "@/lib/tripo/server";
|
||||
import { TripoApiError, tripoRequest } from "@/lib/tripo/client";
|
||||
import {
|
||||
getVip98kRuntimeProvider,
|
||||
submitConfiguredVip98kTask,
|
||||
} from "@/lib/ai/vip98k-runtime";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const maxDuration = 60;
|
||||
@ -120,13 +116,6 @@ function assignMapped(
|
||||
}
|
||||
}
|
||||
|
||||
const VIP98K_TEXT_TO_3D_MODEL = "tencent/hunyuan3d-pro/text-to-3d";
|
||||
const VIP98K_IMAGE_TO_3D_MODEL = "tencent/hunyuan3d-pro/image-to-3d";
|
||||
|
||||
function encodeVip98kDirectResult(outputUrls: string[]) {
|
||||
return Buffer.from(JSON.stringify(outputUrls), "utf8").toString("base64url");
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const session = await auth();
|
||||
@ -426,73 +415,6 @@ export async function POST(request: Request) {
|
||||
}
|
||||
|
||||
const preferredProviderId = readOptionalTrimmedText(body.providerId);
|
||||
let vip98kProvider: Awaited<ReturnType<typeof getVip98kRuntimeProvider>> | null = null;
|
||||
try {
|
||||
vip98kProvider = await getVip98kRuntimeProvider(preferredProviderId);
|
||||
} catch {
|
||||
vip98kProvider = null;
|
||||
}
|
||||
|
||||
if (vip98kProvider) {
|
||||
if (taskType !== "text_to_model" && taskType !== "image_to_model") {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "VIP98K 当前仅支持文生 3D 和图生 3D;细化、动画和格式转换暂不支持",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const inputFile = body.file && typeof body.file === "object" ? body.file : null;
|
||||
const imageUrl = String(body.imageUrl || inputFile?.url || "").trim();
|
||||
if (taskType === "image_to_model" && !imageUrl) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "VIP98K 图生 3D 缺少可访问的图片地址" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
const vipModelId =
|
||||
taskType === "image_to_model"
|
||||
? VIP98K_IMAGE_TO_3D_MODEL
|
||||
: VIP98K_TEXT_TO_3D_MODEL;
|
||||
const submission = await submitConfiguredVip98kTask({
|
||||
preferredProviderId: vip98kProvider.id,
|
||||
modelId: vipModelId,
|
||||
kind: "model-3d",
|
||||
prompt: String(body.prompt || "Generate a production-ready 3D model"),
|
||||
imageUrls: imageUrl ? [imageUrl] : [],
|
||||
});
|
||||
if (!submission.taskId && submission.outputUrls.length === 0) {
|
||||
throw new Error("VIP98K 3D 任务未返回 taskId 或模型地址");
|
||||
}
|
||||
const taskId = submission.taskId
|
||||
? `vip98k:${vip98kProvider.id}:${submission.taskId}`
|
||||
: `vip98k-direct:${vip98kProvider.id}:${encodeVip98kDirectResult(submission.outputUrls)}`;
|
||||
|
||||
if (pointsCharge.chargeable) {
|
||||
await pointsCharge.consume({
|
||||
note: `3D模型任务消耗积分(${billableTaskLabel} / ${taskId})`,
|
||||
}).catch((error) => {
|
||||
logger.error("VIP98K points consume failed", error, {
|
||||
taskId,
|
||||
userId: session.user.id,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
taskId,
|
||||
status: submission.outputUrls.length > 0 ? "success" : "queued",
|
||||
providerId: vip98kProvider.id,
|
||||
providerName: vip98kProvider.name,
|
||||
raw: submission.payload,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const dependencyTaskId =
|
||||
taskType === "refine_model"
|
||||
? String(body.draftModelTaskId || "").trim()
|
||||
|
||||
@ -2,8 +2,6 @@ import { NextResponse } from "next/server";
|
||||
import { auth } from "@/app/(auth)/auth";
|
||||
import { getTripoProviderConfig } from "@/lib/tripo/server";
|
||||
import { TripoApiError, tripoRequest } from "@/lib/tripo/client";
|
||||
import { getVip98kRuntimeProvider } from "@/lib/ai/vip98k-runtime";
|
||||
import { uploadFile } from "@/lib/storage/service";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const maxDuration = 60;
|
||||
@ -53,26 +51,7 @@ export async function POST(request: Request) {
|
||||
);
|
||||
}
|
||||
|
||||
let config: Awaited<ReturnType<typeof getTripoProviderConfig>> | null = null;
|
||||
try {
|
||||
config = await getTripoProviderConfig(providerId);
|
||||
} catch {
|
||||
const vip98k = await getVip98kRuntimeProvider(providerId);
|
||||
const extension = file.type === "image/png" ? "png" : file.type === "image/webp" ? "webp" : "jpg";
|
||||
const uploaded = await uploadFile(
|
||||
`vip98k-3d-input-${Date.now()}-${crypto.randomUUID()}.${extension}`,
|
||||
await file.arrayBuffer(),
|
||||
);
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
imageToken: null,
|
||||
file: { type: extension, url: uploaded.url },
|
||||
providerId: vip98k.id,
|
||||
providerName: vip98k.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
const config = await getTripoProviderConfig(providerId);
|
||||
|
||||
const uploadPaths = ["/upload/sts", "/upload"];
|
||||
let lastError: unknown = null;
|
||||
|
||||
@ -12,7 +12,12 @@ import {
|
||||
dotenv.config({ path: ".env.local" });
|
||||
dotenv.config();
|
||||
|
||||
const KEEP_PROVIDER_TYPES = new Set(["vip98k", "fal-image", "fal-video"]);
|
||||
const KEEP_PROVIDER_TYPES = new Set([
|
||||
"vip98k",
|
||||
"fal-image",
|
||||
"fal-video",
|
||||
"tripo-3d",
|
||||
]);
|
||||
const REQUIRED_CAPABILITY_MODELS = [
|
||||
"nano-banana-2",
|
||||
"remove-background",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user