diff --git a/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DExperience.tsx b/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DExperience.tsx index 0b673db92d..624bf1bf21 100644 --- a/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DExperience.tsx +++ b/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DExperience.tsx @@ -152,15 +152,17 @@ export const ImageTo3DExperience = ({ ); useEffect(() => { - if (variant === "world" && pendingExternalImage) { + // Read from store directly to avoid Strict Mode double-execution adding the image twice + const pending = useImageTo3DWorldStore.getState().pendingExternalImage; + if (variant === "world" && pending) { setWorldImages((prev) => { if (prev.length >= MAX_WORLD_IMAGES) return prev; return [ ...prev, { id: generateId(), - preview: pendingExternalImage.url, - mediaToken: pendingExternalImage.mediaToken, + preview: pending.url, + mediaToken: pending.mediaToken, name: "Library Image", isUploading: false, }, @@ -400,9 +402,6 @@ export const ImageTo3DExperience = ({ if ("error_type" in result) { throw new Error(result.error_message || result.error_type); } - - setWorldImages([]); - setWorldPrompt(""); } else { const snapshotPrompt = prompt.trim(); const snapshotPreview = uploadedPreview || undefined; @@ -735,7 +734,7 @@ export const ImageTo3DExperience = ({ key={img.id} className={twMerge( "glass group relative aspect-square overflow-hidden rounded-lg border-2 border-white/30 transition-all", - hasResults ? "w-10" : "w-14", + hasResults ? "w-10" : "w-auto", img.isUploading ? "cursor-default" : "cursor-pointer hover:cursor-zoom-in hover:border-white/80", diff --git a/frontend/apps/artcraft/app/src/components/signaled/TopBar/TaskQueue.tsx b/frontend/apps/artcraft/app/src/components/signaled/TopBar/TaskQueue.tsx index 5d0028454c..8b447a3f23 100644 --- a/frontend/apps/artcraft/app/src/components/signaled/TopBar/TaskQueue.tsx +++ b/frontend/apps/artcraft/app/src/components/signaled/TopBar/TaskQueue.tsx @@ -412,13 +412,21 @@ export const TaskQueue = () => { ? getProviderDisplayName(String(t.provider).toLowerCase()) : undefined; const taskTypeStr = t.task_type ? String(t.task_type).toLowerCase() : ""; + const modelTypeStr = t.model_type ? String(t.model_type).toLowerCase() : ""; + const isSplatModel = + taskTypeStr.includes("gaussian") || + modelTypeStr.includes("marble") || + modelTypeStr.includes("worldlabs"); const is3DModel = taskTypeStr.includes("3d") || taskTypeStr.includes("object") || - taskTypeStr.includes("dimensional"); + taskTypeStr.includes("dimensional") || + isSplatModel; let kind = undefined; - if (taskTypeStr.includes("image") && is3DModel) { + if (isSplatModel) { + kind = "3D World"; + } else if (taskTypeStr.includes("image") && is3DModel) { kind = "Image to 3D"; } else if (is3DModel) { kind = "3D Model"; diff --git a/frontend/apps/artcraft/app/src/pages/PageAngles/Angles.tsx b/frontend/apps/artcraft/app/src/pages/PageAngles/Angles.tsx index af65fca99f..25644c25bd 100644 --- a/frontend/apps/artcraft/app/src/pages/PageAngles/Angles.tsx +++ b/frontend/apps/artcraft/app/src/pages/PageAngles/Angles.tsx @@ -26,7 +26,7 @@ import { OrbitSphere, snapToNearest } from "./OrbitSphere"; import { twMerge } from "tailwind-merge"; import { LoadingSpinner } from "@storyteller/ui-loading-spinner"; import { SliderV2 } from "@storyteller/ui-sliderv2"; -import { Switch } from "@headlessui/react"; +// import { Switch } from "@headlessui/react"; import { EnqueueEditImage } from "@storyteller/tauri-api"; import { listen } from "@tauri-apps/api/event"; import { @@ -41,10 +41,7 @@ import { useCostBreakdownModalStore, } from "@storyteller/ui-pricing-modal"; import { HelpMenuButton } from "@storyteller/ui-help-menu"; -import { - HistoryStack, - ImageBundle, -} from "../PageEdit/HistoryStack"; +import { HistoryStack, ImageBundle } from "../PageEdit/HistoryStack"; import type { BaseSelectorImage } from "../PageEdit/BaseImageSelector"; const ANGLES_MODEL_PAGE = ModelPage.Angles; @@ -84,9 +81,9 @@ export const Angles = () => { const sourceMediaToken = useAnglesStore((s) => s.sourceMediaToken); const imageDimensions = useAnglesStore((s) => s.imageDimensions); const angleConfig = useAnglesStore((s) => s.angleConfig); - const generateFromBestAngles = useAnglesStore( - (s) => s.generateFromBestAngles, - ); + // const generateFromBestAngles = useAnglesStore( + // (s) => s.generateFromBestAngles, + // ); const generatedAngles = useAnglesStore((s) => s.generatedAngles); const activeAngleId = useAnglesStore((s) => s.activeAngleId); const isProcessing = useAnglesStore((s) => s.isProcessing); @@ -98,9 +95,9 @@ export const Angles = () => { const setRotation = useAnglesStore((s) => s.setRotation); const setTilt = useAnglesStore((s) => s.setTilt); const setZoom = useAnglesStore((s) => s.setZoom); - const setGenerateFromBestAngles = useAnglesStore( - (s) => s.setGenerateFromBestAngles, - ); + // const setGenerateFromBestAngles = useAnglesStore( + // (s) => s.setGenerateFromBestAngles, + // ); const setActiveAngle = useAnglesStore((s) => s.setActiveAngle); const setIsLoadingImage = useAnglesStore((s) => s.setIsLoadingImage); const resetSource = useAnglesStore((s) => s.resetSource); @@ -160,9 +157,7 @@ export const Angles = () => { // Resolve pending placeholder const resolveId = event.maybe_frontend_subscriber_id ?? state.pendingSubscriberId; - setPendingGenerations((prev) => - prev.filter((p) => p.id !== resolveId), - ); + setPendingGenerations((prev) => prev.filter((p) => p.id !== resolveId)); }); return () => { @@ -469,21 +464,18 @@ export const Angles = () => { [setActiveAngle], ); - const handleHistoryImageRemove = useCallback( - (image: BaseSelectorImage) => { - setHistoryBundles((prev) => - prev - .map((bundle) => ({ - ...bundle, - images: bundle.images.filter( - (img) => img.mediaToken !== image.mediaToken, - ), - })) - .filter((bundle) => bundle.images.length > 0), - ); - }, - [], - ); + const handleHistoryImageRemove = useCallback((image: BaseSelectorImage) => { + setHistoryBundles((prev) => + prev + .map((bundle) => ({ + ...bundle, + images: bundle.images.filter( + (img) => img.mediaToken !== image.mediaToken, + ), + })) + .filter((bundle) => bundle.images.length > 0), + ); + }, []); const handleHistoryClear = useCallback(() => { setHistoryBundles([]); @@ -738,7 +730,7 @@ export const Angles = () => { {/* Toggle + Generate */}
- + {/*
Best 12 angles @@ -761,11 +753,10 @@ export const Angles = () => { />
-
+
*/} { onClick={handleOverlayClick} >
diff --git a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx index 4a63306235..8919a42d04 100644 --- a/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx +++ b/frontend/apps/artcraft/app/src/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx @@ -186,7 +186,7 @@ export const ControlPanelSceneObject = () => { as="div" show={isShowing.value} className={twMerge( - "glass absolute bottom-0 right-0 mb-4 mr-4 flex h-fit w-56 origin-bottom-right flex-col gap-2 rounded-lg border border-ui-panel-border p-3.5 text-white shadow-lg", + "glass absolute bottom-16 right-0 mb-4 mr-4 flex h-fit w-56 origin-bottom-right flex-col gap-2 rounded-lg border border-ui-panel-border p-3.5 text-white shadow-lg", )} enter="transition-opacity duration-150" enterFrom="opacity-0" diff --git a/frontend/libs/api-enums/src/lib/tauri/tasks/task_type.ts b/frontend/libs/api-enums/src/lib/tauri/tasks/task_type.ts index 4dc337f80b..93d73d0b27 100644 --- a/frontend/libs/api-enums/src/lib/tauri/tasks/task_type.ts +++ b/frontend/libs/api-enums/src/lib/tauri/tasks/task_type.ts @@ -6,5 +6,6 @@ export enum TaskType { ImageInpaintEdit = "image_inpaint_edit", VideoGeneration = "video_generation", ObjectGeneration = "object_generation", + SplatGeneration = "gaussian_generation", BackgroundRemoval = "background_removal", } diff --git a/frontend/libs/model-list/src/lib/classes/metadata/ModelMapping.ts b/frontend/libs/model-list/src/lib/classes/metadata/ModelMapping.ts index 0f7a04a95c..f02c7ca1f8 100644 --- a/frontend/libs/model-list/src/lib/classes/metadata/ModelMapping.ts +++ b/frontend/libs/model-list/src/lib/classes/metadata/ModelMapping.ts @@ -51,10 +51,14 @@ export const MODEL_TYPE_TO_CREATOR: Record = { hunyuan_3d: ModelCreator.Tencent, hunyuan_3d_2p0: ModelCreator.Tencent, hunyuan_3d_2p1: ModelCreator.Tencent, + hunyuan_3d_2: ModelCreator.Tencent, + hunyuan_3d_3: ModelCreator.Tencent, // Hunyuan — dot-normalized aliases hunyuan_3d_2_0: ModelCreator.Tencent, hunyuan_3d_2_1: ModelCreator.Tencent, worldlabs_gaussian: ModelCreator.WorldLabs, + marble_0p1_mini: ModelCreator.WorldLabs, + marble_0p1_plus: ModelCreator.WorldLabs, grok_image: ModelCreator.Grok, grok_video: ModelCreator.Grok, midjourney: ModelCreator.Midjourney, @@ -195,14 +199,18 @@ export const getModelDisplayName = (modelType: string): string => { // Hunyuan (Tencent) — p-style IDs hunyuan_3d: "Hunyuan 3D", + hunyuan_3d_2: "Hunyuan 3D 2.0", hunyuan_3d_2p0: "Hunyuan 3D 2.0", hunyuan_3d_2p1: "Hunyuan 3D 2.1", + hunyuan_3d_3: "Hunyuan 3D 3.0", // Hunyuan — dot-normalized aliases hunyuan_3d_2_0: "Hunyuan 3D 2.0", hunyuan_3d_2_1: "Hunyuan 3D 2.1", // World Labs worldlabs_gaussian: "World Labs Marble", + marble_0p1_mini: "Marble Mini", + marble_0p1_plus: "Marble Plus", // Catch-all bucket for Midjourney. midjourney: "Midjourney", diff --git a/frontend/libs/tauri-events/src/lib/events/generation/common.ts b/frontend/libs/tauri-events/src/lib/events/generation/common.ts index 26da645bfa..d936772c35 100644 --- a/frontend/libs/tauri-events/src/lib/events/generation/common.ts +++ b/frontend/libs/tauri-events/src/lib/events/generation/common.ts @@ -15,4 +15,6 @@ export enum GenerationAction { GenerateVideo = "generate_video", RemoveBackground = "remove_background", ImageTo3d = "image_to_3d", + GenerateGaussian = "generate_gaussian", + ImageInpaintEdit = "image_inpaint_edit", } diff --git a/frontend/libs/tauri-events/src/lib/events/generation/useGenerationCompleteEvent.ts b/frontend/libs/tauri-events/src/lib/events/generation/useGenerationCompleteEvent.ts index 6b398a94ab..cf5a83e663 100644 --- a/frontend/libs/tauri-events/src/lib/events/generation/useGenerationCompleteEvent.ts +++ b/frontend/libs/tauri-events/src/lib/events/generation/useGenerationCompleteEvent.ts @@ -57,7 +57,9 @@ const makeMessage = (event: GenerationCompleteEvent) => { case GenerationAction.RemoveBackground: return "Background removal complete!"; case GenerationAction.ImageTo3d: - return "Image to 3D complete!"; + return "3D model generation complete!"; + case GenerationAction.GenerateGaussian: + return "3D world generation complete!"; default: return "Generation complete!"; } diff --git a/frontend/libs/tauri-events/src/lib/events/generation/useGenerationEnqueueFailureEvent.ts b/frontend/libs/tauri-events/src/lib/events/generation/useGenerationEnqueueFailureEvent.ts index 0f7c5d1691..1d4df602cb 100644 --- a/frontend/libs/tauri-events/src/lib/events/generation/useGenerationEnqueueFailureEvent.ts +++ b/frontend/libs/tauri-events/src/lib/events/generation/useGenerationEnqueueFailureEvent.ts @@ -58,7 +58,9 @@ const makeMessage = (event: GenerationEnqueueFailureEvent) => { case GenerationAction.RemoveBackground: return "Couldn't enqueue background removal!"; case GenerationAction.ImageTo3d: - return "Couldn't enqueue image to 3D!"; + return "Couldn't enqueue 3D model generation!"; + case GenerationAction.GenerateGaussian: + return "Couldn't enqueue 3D world generation!"; default: return "Couldn't enqueue generation!"; } diff --git a/frontend/libs/tauri-events/src/lib/events/generation/useGenerationEnqueueSuccessEvent.ts b/frontend/libs/tauri-events/src/lib/events/generation/useGenerationEnqueueSuccessEvent.ts index 8662f87d93..cf15548666 100644 --- a/frontend/libs/tauri-events/src/lib/events/generation/useGenerationEnqueueSuccessEvent.ts +++ b/frontend/libs/tauri-events/src/lib/events/generation/useGenerationEnqueueSuccessEvent.ts @@ -54,7 +54,9 @@ const makeMessage = (event: GenerationEnqueueSuccessEvent) => { case GenerationAction.RemoveBackground: return "Background removal enqueued!"; case GenerationAction.ImageTo3d: - return "Image to 3D enqueued!"; + return "3D model generation enqueued!"; + case GenerationAction.GenerateGaussian: + return "3D world generation enqueued!"; default: return "Generation enqueued!"; } diff --git a/frontend/libs/tauri-events/src/lib/events/generation/useGenerationFailedEvent.ts b/frontend/libs/tauri-events/src/lib/events/generation/useGenerationFailedEvent.ts index edd103117b..6ee6127f0b 100644 --- a/frontend/libs/tauri-events/src/lib/events/generation/useGenerationFailedEvent.ts +++ b/frontend/libs/tauri-events/src/lib/events/generation/useGenerationFailedEvent.ts @@ -58,7 +58,9 @@ const makeMessage = (event: GenerationFailedEvent) => { case GenerationAction.RemoveBackground: return "Background removal failed!"; case GenerationAction.ImageTo3d: - return "Image to 3D failed!"; + return "3D model generation failed!"; + case GenerationAction.GenerateGaussian: + return "3D world generation failed!"; default: return "Generation failed!"; }