From a7270f46858640379206dd67e8ff36ae79db63c1 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Thu, 24 Jul 2025 16:21:21 +0700 Subject: [PATCH] lightbox prompt fetching --- .../comps/GenerateModals/Activity.tsx | 11 +- .../comps/GenerateModals/CompletedCard.tsx | 4 + frontend/libs/api/src/lib/PromptsApi.ts | 9 +- .../lightbox-modal/src/lib/lightbox-modal.tsx | 118 +++++++++++++++++- 4 files changed, 129 insertions(+), 13 deletions(-) diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/GenerateModals/Activity.tsx b/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/GenerateModals/Activity.tsx index a9162666c5..8d98f78b33 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/GenerateModals/Activity.tsx +++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/GenerateModals/Activity.tsx @@ -18,6 +18,9 @@ interface CompletedItem { public_bucket_path: string; // TODO: STOP USING THIS. maybe_media_links_thumbnail?: string; updated_at: string; + maybe_result?: { + entity_token?: string; + }; } // { @@ -144,11 +147,15 @@ export function Activity() { job.maybe_result?.maybe_public_bucket_media_path || "", maybe_media_links_thumbnail: ((job) => { // Try the video thumbnail first, then the regular thumbnail. - const maybeVideoThumb = job.maybe_result?.media_links?.maybe_video_previews?.animated; - const maybeGenericThumb = (job.maybe_result?.media_links?.maybe_thumbnail_template || "").replace("{WIDTH}", "500"); + const maybeVideoThumb = + job.maybe_result?.media_links?.maybe_video_previews?.animated; + const maybeGenericThumb = ( + job.maybe_result?.media_links?.maybe_thumbnail_template || "" + ).replace("{WIDTH}", "500"); return maybeVideoThumb || maybeGenericThumb; })(job), updated_at: job.updated_at || new Date().toISOString(), + maybe_result: job.maybe_result, })); // Show toast only if not first load diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/GenerateModals/CompletedCard.tsx b/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/GenerateModals/CompletedCard.tsx index 5c67a3c297..e2bbcad3dc 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/GenerateModals/CompletedCard.tsx +++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/GenerateModals/CompletedCard.tsx @@ -11,6 +11,9 @@ interface Props { public_bucket_path: string; maybe_style_name?: string; maybe_media_links_thumbnail?: string; + maybe_result?: { + entity_token?: string; + }; }; } @@ -76,6 +79,7 @@ export function CompletedCard({ job }: Props) { onImageError={() => setLoadError(true)} title={job.maybe_title} createdAt={job.updated_at} + mediaId={job.maybe_result?.entity_token} downloadUrl={downloadLink} additionalInfo={ job.maybe_style_name && ( diff --git a/frontend/libs/api/src/lib/PromptsApi.ts b/frontend/libs/api/src/lib/PromptsApi.ts index 1da418d3e7..ce3859d4a4 100644 --- a/frontend/libs/api/src/lib/PromptsApi.ts +++ b/frontend/libs/api/src/lib/PromptsApi.ts @@ -1,9 +1,8 @@ -import { ApiManager, ApiResponse } from "./ApiManager.js"; +import { ApiManager, ApiResponse } from "./ApiManager.js"; import { Prompts } from "./models/Prompts.js"; import { FetchProxy as fetch } from "@storyteller/tauri-utils"; export class PromptsApi extends ApiManager { - public async enqueueImageGeneration({ disableSystemPrompt, prompt, @@ -48,7 +47,7 @@ export class PromptsApi extends ApiManager { const result = { success: isSuccess, - data: isSuccess ? postResponse.inference_job_token : undefined, + data: isSuccess ? postResponse.job_token : undefined, errorMessage: isSuccess ? undefined : postResponse.BadInput, }; @@ -57,7 +56,7 @@ export class PromptsApi extends ApiManager { public async pollJobSession( jobToken: string, - thumbnailWidth: number = 256, + thumbnailWidth = 256 ): Promise< ApiResponse<{ job_token: string; @@ -92,7 +91,7 @@ export class PromptsApi extends ApiManager { const thumbnail_url = response.state.maybe_result?.media_links?.maybe_thumbnail_template?.replace( "{WIDTH}", - thumbnailWidth.toString(), + thumbnailWidth.toString() ); const progress_percentage = response.state.status.progress_percentage; diff --git a/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx b/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx index 6fad123eae..df59dee523 100644 --- a/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx +++ b/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx @@ -9,6 +9,11 @@ import { import { LoadingSpinner } from "@storyteller/ui-loading-spinner"; import React from "react"; import { gtagEvent } from "@storyteller/google-analytics"; +import { MediaFilesApi, PromptsApi } from "@storyteller/api"; +import { toast } from "@storyteller/ui-toaster"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCopy } from "@fortawesome/pro-solid-svg-icons"; +import { twMerge } from "tailwind-merge"; interface LightboxModalProps { isOpen: boolean; @@ -28,6 +33,7 @@ interface LightboxModalProps { media_id: string | undefined ) => Promise; mediaClass?: string; + onPromptCopy?: (prompt: string) => void; } export function LightboxModal({ @@ -53,17 +59,68 @@ export function LightboxModal({ const imageTagImageUrl = imageUrl ? imageUrl + "?cors=1" : ""; const [mediaLoaded, setMediaLoaded] = React.useState(false); + const [prompt, setPrompt] = React.useState(null); + const [promptLoading, setPromptLoading] = React.useState(false); + const [hasPromptToken, setHasPromptToken] = React.useState(false); + const [isPromptHovered, setIsPromptHovered] = React.useState(false); // Reset when imageUrl changes React.useEffect(() => { setMediaLoaded(false); }, [imageUrl]); + // Fetch prompt when mediaId changes + React.useEffect(() => { + const fetchPrompt = async () => { + if (!mediaId) { + setPrompt(null); + setHasPromptToken(false); + return; + } + + setPromptLoading(true); + try { + const mediaFilesApi = new MediaFilesApi(); + const mediaResponse = await mediaFilesApi.GetMediaFileByToken({ + mediaFileToken: mediaId, + }); + + if (mediaResponse.success && mediaResponse.data?.maybe_prompt_token) { + setHasPromptToken(true); + const promptsApi = new PromptsApi(); + const promptResponse = await promptsApi.GetPromptsByToken({ + token: mediaResponse.data.maybe_prompt_token, + }); + + if ( + promptResponse.success && + promptResponse.data?.maybe_positive_prompt + ) { + setPrompt(promptResponse.data.maybe_positive_prompt); + } else { + setPrompt(null); + } + } else { + setHasPromptToken(false); + setPrompt(null); + } + } catch (error) { + console.error("Error fetching prompt:", error); + setHasPromptToken(false); + setPrompt(null); + } finally { + setPromptLoading(false); + } + }; + + fetchPrompt(); + }, [mediaId]); + return ( -
-
+
+ {/*
{title || "Image Generation"} -
+
*/} {createdAt && ( -
- Created: {dayjs(createdAt).format("MMM D, YYYY HH:mm:ss")} +
+
Created
+
+ {dayjs(createdAt).format("MMM D, YYYY")} at{" "} + {dayjs(createdAt).format("hh:mm A")} +
+
+ )} + {hasPromptToken && ( +
+
Prompt
+
setIsPromptHovered(true)} + onMouseLeave={() => setIsPromptHovered(false)} + onClick={() => { + if (!prompt) return; + navigator.clipboard.writeText(prompt).catch(() => {}); + toast.success("Prompt copied"); + }} + > + {promptLoading ? ( +
+ + + Loading prompt... + +
+ ) : ( + prompt || ( + No prompt + ) + )} +
+ + {!promptLoading && ( +
+
+ + Copy prompt +
+
+ )}
)} {additionalInfo}