fix(model3d): preserve Tripo provider integration
This commit is contained in:
parent
658c3345f3
commit
c962fff811
@ -17,7 +17,6 @@ import {
|
|||||||
resolveTripoProviderConfigForTaskId,
|
resolveTripoProviderConfigForTaskId,
|
||||||
} from "@/lib/tripo/server";
|
} from "@/lib/tripo/server";
|
||||||
import type { TripoTaskData } from "@/lib/tripo/types";
|
import type { TripoTaskData } from "@/lib/tripo/types";
|
||||||
import { getConfiguredVip98kTaskStatus } from "@/lib/ai/vip98k-runtime";
|
|
||||||
|
|
||||||
export const runtime = "nodejs";
|
export const runtime = "nodejs";
|
||||||
export const maxDuration = 120;
|
export const maxDuration = 120;
|
||||||
@ -320,111 +319,6 @@ export async function GET(request: Request, { params }: RouteParams) {
|
|||||||
|
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
const preferredProviderId = searchParams.get("providerId");
|
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
|
const config = preferredProviderId
|
||||||
? await getTripoProviderConfig(preferredProviderId)
|
? await getTripoProviderConfig(preferredProviderId)
|
||||||
: await resolveTripoProviderConfigForTaskId(normalizedTaskId);
|
: await resolveTripoProviderConfigForTaskId(normalizedTaskId);
|
||||||
|
|||||||
@ -14,10 +14,6 @@ import {
|
|||||||
resolveTripoProviderConfigForTaskId,
|
resolveTripoProviderConfigForTaskId,
|
||||||
} from "@/lib/tripo/server";
|
} from "@/lib/tripo/server";
|
||||||
import { TripoApiError, tripoRequest } from "@/lib/tripo/client";
|
import { TripoApiError, tripoRequest } from "@/lib/tripo/client";
|
||||||
import {
|
|
||||||
getVip98kRuntimeProvider,
|
|
||||||
submitConfiguredVip98kTask,
|
|
||||||
} from "@/lib/ai/vip98k-runtime";
|
|
||||||
|
|
||||||
export const runtime = "nodejs";
|
export const runtime = "nodejs";
|
||||||
export const maxDuration = 60;
|
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) {
|
export async function POST(request: Request) {
|
||||||
try {
|
try {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
@ -426,73 +415,6 @@ export async function POST(request: Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const preferredProviderId = readOptionalTrimmedText(body.providerId);
|
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 =
|
const dependencyTaskId =
|
||||||
taskType === "refine_model"
|
taskType === "refine_model"
|
||||||
? String(body.draftModelTaskId || "").trim()
|
? String(body.draftModelTaskId || "").trim()
|
||||||
|
|||||||
@ -2,8 +2,6 @@ import { NextResponse } from "next/server";
|
|||||||
import { auth } from "@/app/(auth)/auth";
|
import { auth } from "@/app/(auth)/auth";
|
||||||
import { getTripoProviderConfig } from "@/lib/tripo/server";
|
import { getTripoProviderConfig } from "@/lib/tripo/server";
|
||||||
import { TripoApiError, tripoRequest } from "@/lib/tripo/client";
|
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 runtime = "nodejs";
|
||||||
export const maxDuration = 60;
|
export const maxDuration = 60;
|
||||||
@ -53,26 +51,7 @@ export async function POST(request: Request) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let config: Awaited<ReturnType<typeof getTripoProviderConfig>> | null = null;
|
const config = await getTripoProviderConfig(providerId);
|
||||||
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 uploadPaths = ["/upload/sts", "/upload"];
|
const uploadPaths = ["/upload/sts", "/upload"];
|
||||||
let lastError: unknown = null;
|
let lastError: unknown = null;
|
||||||
|
|||||||
@ -12,7 +12,12 @@ import {
|
|||||||
dotenv.config({ path: ".env.local" });
|
dotenv.config({ path: ".env.local" });
|
||||||
dotenv.config();
|
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 = [
|
const REQUIRED_CAPABILITY_MODELS = [
|
||||||
"nano-banana-2",
|
"nano-banana-2",
|
||||||
"remove-background",
|
"remove-background",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user