mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-31 15:37:49 +00:00
lightbox prompt fetching
This commit is contained in:
1 parent
a6fb661fbe
commit
a7270f4685
4 files changed
+129
-13
No files matched your search
@@ -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
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<void>;
|
||||
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<boolean>(false);
|
||||
const [prompt, setPrompt] = React.useState<string | null>(null);
|
||||
const [promptLoading, setPromptLoading] = React.useState<boolean>(false);
|
||||
const [hasPromptToken, setHasPromptToken] = React.useState<boolean>(false);
|
||||
const [isPromptHovered, setIsPromptHovered] = React.useState<boolean>(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 (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
className="rounded-xl bg-[#2C2C2C] h-[80vh] w-[60vw] max-w-screen min-w-[35vw] min-h-[40vh]"
|
||||
className="rounded-xl bg-[#2C2C2C] h-[75vh] w-[60vw] max-w-screen min-w-[35vw] min-h-[40vh]"
|
||||
draggable
|
||||
allowBackgroundInteraction={true}
|
||||
showClose={true}
|
||||
@@ -115,13 +172,62 @@ export function LightboxModal({
|
||||
|
||||
{/* info + actions */}
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="flex-1 space-y-4">
|
||||
<div className="text-xl font-medium">
|
||||
<div className="flex-1 space-y-5">
|
||||
{/* <div className="text-xl font-medium">
|
||||
{title || "Image Generation"}
|
||||
</div>
|
||||
</div> */}
|
||||
{createdAt && (
|
||||
<div className="text-sm text-white/60">
|
||||
Created: {dayjs(createdAt).format("MMM D, YYYY HH:mm:ss")}
|
||||
<div className="space-y-1.5">
|
||||
<div className="text-sm font-medium text-white/90">Created</div>
|
||||
<div className="text-sm text-white/70">
|
||||
{dayjs(createdAt).format("MMM D, YYYY")} at{" "}
|
||||
{dayjs(createdAt).format("hh:mm A")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{hasPromptToken && (
|
||||
<div className="relative space-y-1.5">
|
||||
<div className="text-sm font-medium text-white/90">Prompt</div>
|
||||
<div
|
||||
className={twMerge(
|
||||
"relative text-sm text-white/90 break-words bg-black/20 p-3 rounded-lg cursor-pointer transition-colors duration-100 leading-relaxed",
|
||||
isPromptHovered && "bg-black/30"
|
||||
)}
|
||||
onMouseEnter={() => setIsPromptHovered(true)}
|
||||
onMouseLeave={() => setIsPromptHovered(false)}
|
||||
onClick={() => {
|
||||
if (!prompt) return;
|
||||
navigator.clipboard.writeText(prompt).catch(() => {});
|
||||
toast.success("Prompt copied");
|
||||
}}
|
||||
>
|
||||
{promptLoading ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<LoadingSpinner className="h-4 w-4" />
|
||||
<span className="text-sm text-white/80">
|
||||
Loading prompt...
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
prompt || (
|
||||
<span className="text-sm text-white/90">No prompt</span>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!promptLoading && (
|
||||
<div
|
||||
className={twMerge(
|
||||
"pointer-events-none absolute inset-0 flex items-end justify-end opacity-0 transition-opacity duration-50",
|
||||
isPromptHovered && "opacity-100"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-1 text-xs text-white/80 bg-black/80 backdrop-blur-md p-1.5 rounded-tl-lg rounded-br-lg">
|
||||
<FontAwesomeIcon icon={faCopy} className="h-3 w-3" />
|
||||
<span>Copy prompt</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{additionalInfo}
|
||||
|
||||
Reference in new issue
Block a user