113 lines
3.6 KiB
TypeScript
113 lines
3.6 KiB
TypeScript
import { and, desc, eq } from "drizzle-orm";
|
|
import { notFound } from "next/navigation";
|
|
import { WorldSharePage } from "@/components/canvas/world3d/WorldSharePage";
|
|
import { db } from "@/lib/db";
|
|
import { model3dAsset } from "@/lib/db/schema";
|
|
import { pickPreferredInlineWorldLabsSplatUrl } from "@/lib/worldlabs/splat-utils";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
function readAssetPayload(
|
|
value: unknown
|
|
): {
|
|
displayName?: string | null;
|
|
model?: string | null;
|
|
caption?: string | null;
|
|
worldUrl?: string | null;
|
|
thumbnailUrl?: string | null;
|
|
panoUrl?: string | null;
|
|
meshUrl?: string | null;
|
|
spzUrls?: Record<string, string> | null;
|
|
semanticsMetadata?: Record<string, unknown> | null;
|
|
} | null {
|
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
? (value as {
|
|
displayName?: string | null;
|
|
model?: string | null;
|
|
caption?: string | null;
|
|
worldUrl?: string | null;
|
|
thumbnailUrl?: string | null;
|
|
panoUrl?: string | null;
|
|
meshUrl?: string | null;
|
|
spzUrls?: Record<string, string> | null;
|
|
semanticsMetadata?: Record<string, unknown> | null;
|
|
})
|
|
: null;
|
|
}
|
|
|
|
export default async function SharedWorldPage({
|
|
params,
|
|
}: {
|
|
params: Promise<{ token: string }>;
|
|
}) {
|
|
const { token } = await params;
|
|
const shareToken = String(token || "").trim();
|
|
if (!shareToken) {
|
|
notFound();
|
|
}
|
|
|
|
const assets = await db
|
|
.select({
|
|
taskId: model3dAsset.taskId,
|
|
prompt: model3dAsset.prompt,
|
|
modelVersion: model3dAsset.modelVersion,
|
|
previewImageUrl: model3dAsset.previewImageUrl,
|
|
modelUrl: model3dAsset.modelUrl,
|
|
assetPayload: model3dAsset.assetPayload,
|
|
sharedAt: model3dAsset.sharedAt,
|
|
updatedAt: model3dAsset.updatedAt,
|
|
})
|
|
.from(model3dAsset)
|
|
.where(
|
|
and(
|
|
eq(model3dAsset.shareEnabled, true),
|
|
eq(model3dAsset.shareToken, shareToken),
|
|
eq(model3dAsset.provider, "worldlabs-3d"),
|
|
eq(model3dAsset.status, "success")
|
|
)
|
|
)
|
|
.orderBy(desc(model3dAsset.sharedAt), desc(model3dAsset.updatedAt))
|
|
.limit(30);
|
|
|
|
if (assets.length === 0) {
|
|
notFound();
|
|
}
|
|
|
|
const sharePath = `/worlds/share/${shareToken}`;
|
|
const worlds = assets.map((asset) => {
|
|
const assetPayload = readAssetPayload(asset.assetPayload);
|
|
|
|
return {
|
|
id: asset.taskId,
|
|
caption: assetPayload?.caption || null,
|
|
imageUrl: assetPayload?.panoUrl || assetPayload?.thumbnailUrl || asset.previewImageUrl || null,
|
|
meshUrl: asset.modelUrl || assetPayload?.meshUrl || null,
|
|
model: assetPayload?.model || asset.modelVersion || null,
|
|
previewImageUrl: asset.previewImageUrl || assetPayload?.thumbnailUrl || null,
|
|
prompt: asset.prompt || null,
|
|
semanticsMetadata: assetPayload?.semanticsMetadata || null,
|
|
splatUrl: pickPreferredInlineWorldLabsSplatUrl(assetPayload?.spzUrls),
|
|
title: assetPayload?.displayName || asset.prompt || `World ${asset.taskId.slice(0, 8)}`,
|
|
worldUrl: assetPayload?.worldUrl || null,
|
|
};
|
|
});
|
|
const primaryWorld = worlds[0];
|
|
|
|
return (
|
|
<WorldSharePage
|
|
caption={primaryWorld?.caption || null}
|
|
imageUrl={primaryWorld?.imageUrl || null}
|
|
meshUrl={primaryWorld?.meshUrl || null}
|
|
model={primaryWorld?.model || null}
|
|
previewImageUrl={primaryWorld?.previewImageUrl || null}
|
|
prompt={primaryWorld?.prompt || null}
|
|
semanticsMetadata={primaryWorld?.semanticsMetadata || null}
|
|
shareUrl={sharePath}
|
|
splatUrl={primaryWorld?.splatUrl || null}
|
|
title={primaryWorld?.title || "3D 世界分享"}
|
|
worldUrl={primaryWorld?.worldUrl || null}
|
|
worlds={worlds}
|
|
/>
|
|
);
|
|
}
|