diff --git a/components/canvas/chat-panel/OpenClawInteractiveModelViewer.tsx b/components/canvas/chat-panel/OpenClawInteractiveModelViewer.tsx index 4d7b93ff3..ab172b030 100644 --- a/components/canvas/chat-panel/OpenClawInteractiveModelViewer.tsx +++ b/components/canvas/chat-panel/OpenClawInteractiveModelViewer.tsx @@ -1,13 +1,15 @@ "use client"; /* eslint-disable @next/next/no-img-element */ -import { Environment, OrbitControls, useGLTF } from "@react-three/drei"; +import { OrbitControls, useGLTF } from "@react-three/drei"; import { Canvas, useFrame, useThree } from "@react-three/fiber"; import { AlertCircle } from "lucide-react"; import React, { Suspense, useEffect, useMemo, useRef } from "react"; import * as THREE from "three"; import type { Mesh, Object3D } from "three"; import type { OrbitControls as OrbitControlsImpl } from "three-stdlib"; +import { LocalStudioEnvironment } from "@/components/canvas/model3d/LocalStudioEnvironment"; +import { ModelLoadingFallback } from "@/components/canvas/model3d/ModelLoadingFallback"; import type { OpenClawModelInteractionManifest, OpenClawModelInteractionPart, @@ -483,7 +485,6 @@ function ModelInteractionScene({ position={fitTransform.position} scale={fitTransform.scale} /> - -

3D 交互预览加载失败,已显示静态预览

+

{previewImageUrl ? "3D 交互预览加载失败,可查看静态预览或下载模型" : "3D 交互预览加载失败,请退出预览后重试或下载模型"}

} @@ -572,7 +573,8 @@ export function OpenClawInteractiveModelViewer({ shadows style={{ background }} > - + + }> + 正在准备 3D 预览… ); } @@ -658,6 +659,11 @@ export function OpenClawModel3DPreview({ messageId, artifactIndex: interactionManifestIndex, }); + const timeout = setTimeout(() => { + controller.abort(); + setManifest(null); + setManifestError("交互清单加载超时"); + }, 15000); setManifestError(null); fetch(manifestUrl, { signal: controller.signal }) .then(async (response) => { @@ -676,8 +682,12 @@ export function OpenClawModel3DPreview({ setManifestError( error instanceof Error ? error.message : "交互清单读取失败" ); - }); - return () => controller.abort(); + }) + .finally(() => clearTimeout(timeout)); + return () => { + clearTimeout(timeout); + controller.abort(); + }; }, [interactionManifestIndex, chatId, isActive, manifest, messageId]); useEffect(() => { @@ -1082,6 +1092,7 @@ export function OpenClawModel3DPreview({ ) : ( { + const room = new RoomEnvironment(); + const generator = new PMREMGenerator(gl); + const previous = scene.environment; + let target: ReturnType | undefined; + try { + target = generator.fromScene(room, 0.04); + scene.environment = target.texture; + } catch { + // Direct lights still render the model on devices unable to build a PMREM. + } finally { + room.dispose(); + generator.dispose(); + } + return () => { + if (scene.environment === target?.texture) scene.environment = previous; + target?.dispose(); + }; + }, [gl, scene]); + return null; +} diff --git a/components/canvas/model3d/Model3DViewer.tsx b/components/canvas/model3d/Model3DViewer.tsx index ff459f2c2..c52e29455 100644 --- a/components/canvas/model3d/Model3DViewer.tsx +++ b/components/canvas/model3d/Model3DViewer.tsx @@ -16,6 +16,8 @@ import type { Mesh, Object3D } from "three"; import { KTX2Loader } from "three-stdlib"; import { Button } from "@/components/ui/button"; import { buildThreeAssetUrl } from "@/lib/canvas/three-asset-url"; +import { LocalStudioEnvironment } from "./LocalStudioEnvironment"; +import { ModelLoadingFallback } from "./ModelLoadingFallback"; export type Model3DLayer = { id: string; @@ -55,6 +57,7 @@ type Model3DViewerProps = { modelUrl?: string | null; modelFormat?: "glb" | "gltf" | null; autoFitModel?: boolean; + localStudioEnvironment?: boolean; previewImageUrl?: string | null; emptyStateTitle?: string; settings: Model3DViewerSettings; @@ -721,6 +724,7 @@ export function Model3DViewer({ modelUrl, modelFormat, autoFitModel = false, + localStudioEnvironment = false, previewImageUrl, emptyStateTitle = "准备生成 3D 资产", settings, @@ -789,7 +793,7 @@ export function Model3DViewer({ ) : null}
-

3D 预览加载失败,已切换为静态预览

+

{previewImageUrl ? "3D 预览加载失败,可查看静态预览或下载模型" : "3D 预览加载失败,请重试或下载模型"}