From fa4776bb2124279fcfc52ecf37b06911c59634d1 Mon Sep 17 00:00:00 2001 From: BFlat Date: Sun, 17 May 2026 12:39:53 +0700 Subject: [PATCH] webapp: image ref in promptbox click to preview (#1532) feature: add high-res image preview modal to prompt box - Added click-to-preview functionality for reference and end frame images in `ImagePromptRow`. - Updated `RefImage` interface to include `fullUrl` for fetching high-resolution library images. - Populated `fullUrl` for library selections in `create-image` and `create-video` pages. - Configured the preview modal to tightly wrap the image so the close button anchors correctly to the corner. - Updated the image uploading indicator to use the standardized `spinner-third` icon. --- .../components/prompt-box/ImagePromptRow.tsx | 48 ++++++++++++++++--- .../src/components/prompt-box/types.ts | 6 ++- .../src/pages/create-image/create-image.tsx | 3 +- .../src/pages/create-video/create-video.tsx | 8 ++-- .../apps/artcraft-webapp/tsconfig.app.json | 21 +++++--- frontend/apps/artcraft-webapp/tsconfig.json | 21 +++++--- .../app/src/components/reusable/index.ts | 2 - .../libs/components/pagescene/tsconfig.json | 15 +++--- .../components/pagescene/tsconfig.lib.json | 15 +++--- 9 files changed, 95 insertions(+), 44 deletions(-) diff --git a/frontend/apps/artcraft-webapp/src/components/prompt-box/ImagePromptRow.tsx b/frontend/apps/artcraft-webapp/src/components/prompt-box/ImagePromptRow.tsx index c874cb1b29..92c8276cd4 100644 --- a/frontend/apps/artcraft-webapp/src/components/prompt-box/ImagePromptRow.tsx +++ b/frontend/apps/artcraft-webapp/src/components/prompt-box/ImagePromptRow.tsx @@ -9,13 +9,13 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faImages, faPlus, - faSpinner, faSpinnerThird, faXmark, } from "@fortawesome/pro-solid-svg-icons"; import { faImage } from "@fortawesome/pro-regular-svg-icons"; import { Button } from "@storyteller/ui-button"; import { Tooltip } from "@storyteller/ui-tooltip"; +import { Modal } from "@storyteller/ui-modal"; import { twMerge } from "tailwind-merge"; import { UploaderStates } from "@storyteller/common"; import { @@ -75,6 +75,7 @@ export const ImagePromptRow = ({ const [uploadingImages, setUploadingImages] = useState< { id: string; file: File }[] >([]); + const [previewImage, setPreviewImage] = useState(null); const referenceImagesRef = useRef(referenceImages); referenceImagesRef.current = referenceImages; @@ -273,6 +274,7 @@ export const ImagePromptRow = ({ image={image} allowReorder={allowReorder} onRemove={handleRemoveReference} + onPreview={(img) => setPreviewImage(img)} /> ))} @@ -285,6 +287,7 @@ export const ImagePromptRow = ({ key={image.id} image={image} onRemove={handleRemoveReference} + onPreview={(img) => setPreviewImage(img)} /> )) )} @@ -371,14 +374,20 @@ export const ImagePromptRow = ({
{endFrameImage ? ( -
+
setPreviewImage(endFrameImage)} + > End frame
) : onPickEndFrameFromLibrary ? ( @@ -445,6 +454,25 @@ export const ImagePromptRow = ({
)}
+ + setPreviewImage(null)} + className="w-fit h-fit max-w-none bg-transparent border-0 p-0 shadow-none overflow-visible" + backdropClassName="!bg-black/80" + showClose={true} + closeOnOutsideClick={true} + > + {previewImage && ( +
+ Preview +
+ )} +
); }; @@ -454,11 +482,16 @@ export const ImagePromptRow = ({ const ImageThumbnail = ({ image, onRemove, + onPreview, }: { image: RefImage; onRemove: (id: string) => void; + onPreview?: (image: RefImage) => void; }) => ( -
+
onPreview?.(image)} + > Reference void; + onPreview?: (image: RefImage) => void; }) => { const { attributes, @@ -505,6 +540,7 @@ const SortableImage = ({ ref={setNodeRef} style={style} {...(allowReorder ? { ...attributes, ...listeners } : {})} + onClick={() => onPreview?.(image)} className={twMerge( "group glass relative aspect-square w-10 sm:w-14 overflow-hidden rounded-lg border-2 border-white/30 transition-opacity", allowReorder @@ -546,7 +582,7 @@ const UploadingThumbnail = ({ file }: { file: File }) => { />
- +
); diff --git a/frontend/apps/artcraft-webapp/src/components/prompt-box/types.ts b/frontend/apps/artcraft-webapp/src/components/prompt-box/types.ts index 38818bf473..21f0f1dac5 100644 --- a/frontend/apps/artcraft-webapp/src/components/prompt-box/types.ts +++ b/frontend/apps/artcraft-webapp/src/components/prompt-box/types.ts @@ -1,8 +1,10 @@ export interface RefImage { id: string; url: string; - file: File; - mediaToken: string; + fullUrl?: string; + file?: File; + mediaToken?: string; + isCustom?: boolean; } export interface RefVideo { diff --git a/frontend/apps/artcraft-webapp/src/pages/create-image/create-image.tsx b/frontend/apps/artcraft-webapp/src/pages/create-image/create-image.tsx index b7fb66a510..4c6fefba0c 100644 --- a/frontend/apps/artcraft-webapp/src/pages/create-image/create-image.tsx +++ b/frontend/apps/artcraft-webapp/src/pages/create-image/create-image.tsx @@ -268,6 +268,7 @@ export default function CreateImage() { .map((item) => ({ id: Math.random().toString(36).substring(7), url: item.thumbnail || item.fullImage || "", + fullUrl: item.fullImage || undefined, file: new File([], "library-image"), mediaToken: item.id, })); @@ -295,7 +296,7 @@ export default function CreateImage() { const imageMediaTokens = selectedModel.image_refs_supported ? referenceImages .map((img) => img.mediaToken) - .filter((t) => t.length > 0) + .filter((t): t is string => typeof t === "string" && t.length > 0) : undefined; const result = await enqueueImageGeneration({ diff --git a/frontend/apps/artcraft-webapp/src/pages/create-video/create-video.tsx b/frontend/apps/artcraft-webapp/src/pages/create-video/create-video.tsx index 287bcb5c35..44e23e9fa7 100644 --- a/frontend/apps/artcraft-webapp/src/pages/create-video/create-video.tsx +++ b/frontend/apps/artcraft-webapp/src/pages/create-video/create-video.tsx @@ -683,6 +683,7 @@ export default function CreateVideo() { .map((item) => ({ id: Math.random().toString(36).substring(7), url: item.thumbnail || item.fullImage || "", + fullUrl: item.fullImage || undefined, file: new File([], "library-image"), mediaToken: item.id, })); @@ -698,6 +699,7 @@ export default function CreateVideo() { setEndFrameImage({ id: Math.random().toString(36).substring(7), url: item.thumbnail || item.fullImage || "", + fullUrl: item.fullImage || undefined, file: new File([], "library-image"), mediaToken: item.id, }); @@ -744,15 +746,15 @@ export default function CreateVideo() { isReferenceMode && referenceImages.length > 0 ? referenceImages .map((img) => img.mediaToken) - .filter((t) => t.length > 0) + .filter((t): t is string => typeof t === "string" && t.length > 0) : undefined; const referenceVideoTokens = isReferenceMode && referenceVideos.length > 0 - ? referenceVideos.map((v) => v.mediaToken).filter((t) => t.length > 0) + ? referenceVideos.map((v) => v.mediaToken).filter((t): t is string => typeof t === "string" && t.length > 0) : undefined; const referenceAudioTokens = isReferenceMode && referenceAudios.length > 0 - ? referenceAudios.map((a) => a.mediaToken).filter((t) => t.length > 0) + ? referenceAudios.map((a) => a.mediaToken).filter((t): t is string => typeof t === "string" && t.length > 0) : undefined; // Extract character tokens from @-mentions in the prompt. Match longest diff --git a/frontend/apps/artcraft-webapp/tsconfig.app.json b/frontend/apps/artcraft-webapp/tsconfig.app.json index 01dfbdd56f..047f725fac 100644 --- a/frontend/apps/artcraft-webapp/tsconfig.app.json +++ b/frontend/apps/artcraft-webapp/tsconfig.app.json @@ -40,9 +40,6 @@ { "path": "../../libs/components/upload-modal/tsconfig.lib.json" }, - { - "path": "../../libs/components/pagescene/tsconfig.lib.json" - }, { "path": "../../libs/components/slider-v2/tsconfig.lib.json" }, @@ -52,9 +49,21 @@ { "path": "../../libs/components/gallery-modal/tsconfig.lib.json" }, + { + "path": "../../libs/components/pricing-modal/tsconfig.lib.json" + }, + { + "path": "../../libs/components/promptbox/tsconfig.lib.json" + }, + { + "path": "../../libs/components/model-selector/tsconfig.lib.json" + }, { "path": "../../libs/components/close-button/tsconfig.lib.json" }, + { + "path": "../../libs/components/pagescene/tsconfig.lib.json" + }, { "path": "../../libs/components/switch/tsconfig.lib.json" }, @@ -82,9 +91,6 @@ { "path": "../../libs/components/tooltip/tsconfig.lib.json" }, - { - "path": "../../libs/common/tsconfig.lib.json" - }, { "path": "../../libs/components/popover/tsconfig.lib.json" }, @@ -97,6 +103,9 @@ { "path": "../../libs/components/button/tsconfig.lib.json" }, + { + "path": "../../libs/common/tsconfig.lib.json" + }, { "path": "../../libs/api/tsconfig.lib.json" } diff --git a/frontend/apps/artcraft-webapp/tsconfig.json b/frontend/apps/artcraft-webapp/tsconfig.json index 9e7a2045bd..8908c1a38d 100644 --- a/frontend/apps/artcraft-webapp/tsconfig.json +++ b/frontend/apps/artcraft-webapp/tsconfig.json @@ -5,9 +5,6 @@ { "path": "../../libs/components/upload-modal" }, - { - "path": "../../libs/components/pagescene" - }, { "path": "../../libs/components/slider-v2" }, @@ -17,9 +14,21 @@ { "path": "../../libs/components/gallery-modal" }, + { + "path": "../../libs/components/pricing-modal" + }, + { + "path": "../../libs/components/promptbox" + }, + { + "path": "../../libs/components/model-selector" + }, { "path": "../../libs/components/close-button" }, + { + "path": "../../libs/components/pagescene" + }, { "path": "../../libs/components/switch" }, @@ -47,9 +56,6 @@ { "path": "../../libs/components/tooltip" }, - { - "path": "../../libs/common" - }, { "path": "../../libs/components/popover" }, @@ -62,6 +68,9 @@ { "path": "../../libs/components/button" }, + { + "path": "../../libs/common" + }, { "path": "../../libs/api" }, diff --git a/frontend/apps/artcraft/app/src/components/reusable/index.ts b/frontend/apps/artcraft/app/src/components/reusable/index.ts index 21c7079488..c1865a80ae 100644 --- a/frontend/apps/artcraft/app/src/components/reusable/index.ts +++ b/frontend/apps/artcraft/app/src/components/reusable/index.ts @@ -1,3 +1 @@ -export * from "./UploadModal3D"; export * from "./UploadModalMedia"; -export * from "./UploadModalSplat"; diff --git a/frontend/libs/components/pagescene/tsconfig.json b/frontend/libs/components/pagescene/tsconfig.json index 4259ed1a3a..c829e1ba51 100644 --- a/frontend/libs/components/pagescene/tsconfig.json +++ b/frontend/libs/components/pagescene/tsconfig.json @@ -11,9 +11,6 @@ { "path": "../label" }, - { - "path": "../settings-modal" - }, { "path": "../button-icon-select" }, @@ -32,18 +29,12 @@ { "path": "../checkbox" }, - { - "path": "../tooltip" - }, { "path": "../button" }, { "path": "../button-dropdown" }, - { - "path": "../popover" - }, { "path": "../promptbox" }, @@ -62,6 +53,12 @@ { "path": "../../model-list" }, + { + "path": "../tooltip" + }, + { + "path": "../popover" + }, { "path": "../model-selector" }, diff --git a/frontend/libs/components/pagescene/tsconfig.lib.json b/frontend/libs/components/pagescene/tsconfig.lib.json index 8d9afd3fb0..e97c7a6f3f 100644 --- a/frontend/libs/components/pagescene/tsconfig.lib.json +++ b/frontend/libs/components/pagescene/tsconfig.lib.json @@ -59,9 +59,6 @@ { "path": "../label/tsconfig.lib.json" }, - { - "path": "../settings-modal/tsconfig.lib.json" - }, { "path": "../button-icon-select/tsconfig.lib.json" }, @@ -80,18 +77,12 @@ { "path": "../checkbox/tsconfig.lib.json" }, - { - "path": "../tooltip/tsconfig.lib.json" - }, { "path": "../button/tsconfig.lib.json" }, { "path": "../button-dropdown/tsconfig.lib.json" }, - { - "path": "../popover/tsconfig.lib.json" - }, { "path": "../promptbox/tsconfig.lib.json" }, @@ -110,6 +101,12 @@ { "path": "../../model-list/tsconfig.lib.json" }, + { + "path": "../tooltip/tsconfig.lib.json" + }, + { + "path": "../popover/tsconfig.lib.json" + }, { "path": "../model-selector/tsconfig.lib.json" },