mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-03 00:47:53 +00:00
lightbox context images, generation details, canvas snapshot bug fix (#746)
- canvas snapshot aspect ratio is fixed (it was snapshotting only as squared images) - added reference/context images in the lightbox under prompt, hovering shows a larger preview - added generation details: model used with logo and provider
This commit is contained in:
1 parent
b53e138dab
commit
8d4edcfafd
10 files changed
+426
-141
No files matched your search
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Recraft</title><path d="M19.667 8.275c0-4.57-4.15-8.275-9.27-8.275-1.774 0-3.213 3.705-3.213 8.275 0 1.143.09 2.233.253 3.224H4.29L1 23h9.4v-6.447c5.117 0 9.266-3.707 9.266-8.275l.001-.002zm-9.27-6.76c.93 0 1.682 3.028 1.682 6.76 0 3.733-.752 6.76-1.681 6.76-.93 0-1.681-3.027-1.681-6.76 0-3.732.752-6.76 1.68-6.76z"></path><path d="M19.848 16.552h-9.44L14.028 23h9.438l-3.618-6.448z"></path></svg>
|
||||
|
After Width: | Height: | Size: 562 B |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Tencent</title><path d="M9.976 1L24 9.8l-10.587.015L10.723 23H5.489L8.18 9.8H3.244L1 5.4h8.077L9.976 1z"></path></svg>
|
||||
|
After Width: | Height: | Size: 282 B |
@@ -170,8 +170,15 @@ const PageDraw = () => {
|
||||
};
|
||||
|
||||
const onEnqueuedPressed = async () => {
|
||||
const { width, height } = store.getAspectRatioDimensions();
|
||||
|
||||
// takes snap shot and then a global variable in the engine will invoke the inference.
|
||||
const image = await captureStageImageBitmap(stageRef, transformerRefs);
|
||||
const image = await captureStageImageBitmap(
|
||||
stageRef,
|
||||
transformerRefs,
|
||||
width,
|
||||
height,
|
||||
);
|
||||
if (!image) {
|
||||
console.error("Failed to capture stage image");
|
||||
return;
|
||||
|
||||
@@ -1,114 +1,129 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect } from "react";
|
||||
import Konva from "konva";
|
||||
|
||||
const debugOutput = (uri:string, name:string)=> {
|
||||
var link = document.createElement('a');
|
||||
link.download = name;
|
||||
link.href = uri;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
export const captureStageImageBitmap = async (
|
||||
stageRef: React.RefObject<Konva.Stage>,
|
||||
transformerRefs: React.RefObject<{ [key: string]: Konva.Transformer }>,
|
||||
width: number = 1024,
|
||||
height: number = 1024,
|
||||
): Promise<ImageBitmap | undefined> => {
|
||||
if (!stageRef.current) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const oldPos = { ...stageRef.current.position() };
|
||||
const oldScale = {
|
||||
x: stageRef.current.scaleX(),
|
||||
y: stageRef.current.scaleY(),
|
||||
};
|
||||
|
||||
export const captureStageImageBitmap = async (stageRef: any,transformerRefs: any): Promise<ImageBitmap | undefined> => {
|
||||
if (!stageRef.current) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const oldPos = { ...stageRef.current.position() };
|
||||
const oldScale = { x: stageRef.current.scaleX(), y: stageRef.current.scaleY() };
|
||||
|
||||
// Hide all transformers
|
||||
Object.values(transformerRefs.current).forEach(transformer => {
|
||||
// Hide all transformers
|
||||
Object.values(transformerRefs.current || {}).forEach(
|
||||
(transformer: Konva.Transformer) => {
|
||||
transformer.visible(false);
|
||||
},
|
||||
);
|
||||
|
||||
stageRef.current.position({ x: 0, y: 0 });
|
||||
stageRef.current.scale({ x: 1, y: 1 });
|
||||
stageRef.current.draw();
|
||||
|
||||
try {
|
||||
const canvas = stageRef.current.toCanvas({
|
||||
width,
|
||||
height,
|
||||
pixelRatio: 1,
|
||||
});
|
||||
|
||||
|
||||
return await createImageBitmap(canvas);
|
||||
} catch (error) {
|
||||
console.error("Failed to create ImageBitmap:", error);
|
||||
return undefined;
|
||||
} finally {
|
||||
// Restore transformers
|
||||
Object.values(transformerRefs.current || {}).forEach(
|
||||
(transformer: Konva.Transformer) => {
|
||||
transformer.visible(true);
|
||||
},
|
||||
);
|
||||
|
||||
stageRef.current.position(oldPos);
|
||||
stageRef.current.scale(oldScale);
|
||||
stageRef.current.draw();
|
||||
}
|
||||
};
|
||||
|
||||
export const useStageSnapshot = (
|
||||
stageRef: React.RefObject<Konva.Stage>,
|
||||
imageRef: React.RefObject<Konva.Image>,
|
||||
isSelectingRef: React.RefObject<boolean>,
|
||||
transformerRefs: React.RefObject<{ [key: string]: Konva.Transformer }>,
|
||||
) => {
|
||||
const [animationFrameId, setAnimationFrameId] = useState<number | null>(null);
|
||||
|
||||
const captureStageImage = (
|
||||
stageRef: React.RefObject<Konva.Stage>,
|
||||
width: number = 1024,
|
||||
height: number = 1024,
|
||||
): string => {
|
||||
if (!stageRef.current) return "";
|
||||
|
||||
const oldPos = { ...stageRef.current.position() };
|
||||
const oldScale = {
|
||||
x: stageRef.current.scaleX(),
|
||||
y: stageRef.current.scaleY(),
|
||||
};
|
||||
|
||||
// Hide all transformers
|
||||
Object.values(transformerRefs.current || {}).forEach(
|
||||
(transformer: Konva.Transformer) => {
|
||||
transformer.visible(false);
|
||||
},
|
||||
);
|
||||
|
||||
stageRef.current.position({ x: 0, y: 0 });
|
||||
stageRef.current.scale({ x: 1, y: 1 });
|
||||
stageRef.current.draw();
|
||||
|
||||
try {
|
||||
const canvas = stageRef.current.toCanvas({
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
pixelRatio: 1
|
||||
});
|
||||
|
||||
return await createImageBitmap(canvas);
|
||||
} catch (error) {
|
||||
console.error('Failed to create ImageBitmap:', error);
|
||||
return undefined;
|
||||
} finally {
|
||||
// Restore transformers
|
||||
Object.values(transformerRefs.current).forEach(transformer => {
|
||||
const uri = stageRef.current.toDataURL({ width, height, pixelRatio: 1 });
|
||||
|
||||
// Restore transformers
|
||||
Object.values(transformerRefs.current || {}).forEach(
|
||||
(transformer: Konva.Transformer) => {
|
||||
transformer.visible(true);
|
||||
});
|
||||
|
||||
stageRef.current.position(oldPos);
|
||||
stageRef.current.scale(oldScale);
|
||||
stageRef.current.draw();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
stageRef.current.position(oldPos);
|
||||
stageRef.current.scale(oldScale);
|
||||
stageRef.current.draw();
|
||||
return uri;
|
||||
};
|
||||
|
||||
export const useStageSnapshot = (
|
||||
stageRef: React.RefObject<any>,
|
||||
imageRef: React.RefObject<any>,
|
||||
isSelectingRef: React.RefObject<boolean>,
|
||||
transformerRefs: React.RefObject<{ [key: string]: Konva.Transformer }>
|
||||
) => {
|
||||
const [animationFrameId, setAnimationFrameId] = useState<number | null>(null);
|
||||
|
||||
const captureStageImage = (stageRef: any): string => {
|
||||
const oldPos = { ...stageRef.current.position() };
|
||||
const oldScale = { x: stageRef.current.scaleX(), y: stageRef.current.scaleY() };
|
||||
|
||||
// Hide all transformers
|
||||
Object.values(transformerRefs.current).forEach(transformer => {
|
||||
transformer.visible(false);
|
||||
});
|
||||
|
||||
stageRef.current.position({ x: 0, y: 0 });
|
||||
stageRef.current.scale({ x: 1, y: 1 });
|
||||
stageRef.current.draw();
|
||||
const uri = stageRef.current.toDataURL({ width: 1024, height: 1024, pixelRatio: 1 });
|
||||
|
||||
// Restore transformers
|
||||
Object.values(transformerRefs.current).forEach(transformer => {
|
||||
transformer.visible(true);
|
||||
});
|
||||
|
||||
stageRef.current.position(oldPos);
|
||||
stageRef.current.scale(oldScale);
|
||||
stageRef.current.draw();
|
||||
return uri;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const updateSnapshot = () => {
|
||||
if (!isSelectingRef.current && stageRef.current && imageRef.current) {
|
||||
const uri = captureStageImage(stageRef);
|
||||
if (uri) {
|
||||
const img = new window.Image();
|
||||
img.onload = () => {
|
||||
imageRef.current?.setAttrs({
|
||||
image: img
|
||||
});
|
||||
};
|
||||
img.src = uri;
|
||||
}
|
||||
useEffect(() => {
|
||||
const updateSnapshot = () => {
|
||||
if (!isSelectingRef.current && stageRef.current && imageRef.current) {
|
||||
const uri = captureStageImage(stageRef);
|
||||
if (uri) {
|
||||
const img = new window.Image();
|
||||
img.onload = () => {
|
||||
imageRef.current?.setAttrs({
|
||||
image: img,
|
||||
});
|
||||
};
|
||||
img.src = uri;
|
||||
}
|
||||
const frameId = requestAnimationFrame(updateSnapshot);
|
||||
setAnimationFrameId(frameId);
|
||||
};
|
||||
|
||||
}
|
||||
const frameId = requestAnimationFrame(updateSnapshot);
|
||||
setAnimationFrameId(frameId);
|
||||
|
||||
return () => {
|
||||
if (animationFrameId) {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
const frameId = requestAnimationFrame(updateSnapshot);
|
||||
setAnimationFrameId(frameId);
|
||||
|
||||
return () => {
|
||||
if (animationFrameId) {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
@@ -13,4 +13,22 @@ export interface Prompts {
|
||||
use_cinematic: boolean;
|
||||
used_face_detailer: boolean;
|
||||
used_upscaler: boolean;
|
||||
maybe_model_type: string;
|
||||
maybe_generation_provider: string;
|
||||
maybe_context_images: [
|
||||
{
|
||||
media_links: {
|
||||
cdn_url: string;
|
||||
maybe_thumbnail_template: string;
|
||||
maybe_video_previews: {
|
||||
animated: string;
|
||||
animated_thumbnail_template: string;
|
||||
still: string;
|
||||
still_thumbnail_template: string;
|
||||
};
|
||||
};
|
||||
media_token: string;
|
||||
semantic: string;
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -14,6 +14,12 @@ 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";
|
||||
import {
|
||||
getModelCreatorIcon,
|
||||
getModelDisplayName,
|
||||
getProviderDisplayName,
|
||||
} from "@storyteller/model-list";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
|
||||
interface LightboxModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -63,6 +69,18 @@ export function LightboxModal({
|
||||
const [promptLoading, setPromptLoading] = React.useState<boolean>(false);
|
||||
const [hasPromptToken, setHasPromptToken] = React.useState<boolean>(false);
|
||||
const [isPromptHovered, setIsPromptHovered] = React.useState<boolean>(false);
|
||||
const [generationProvider, setGenerationProvider] = React.useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [modelType, setModelType] = React.useState<string | null>(null);
|
||||
const [contextImages, setContextImages] = React.useState<Array<{
|
||||
media_links: {
|
||||
cdn_url: string;
|
||||
maybe_thumbnail_template: string;
|
||||
};
|
||||
media_token: string;
|
||||
semantic: string;
|
||||
}> | null>(null);
|
||||
|
||||
// Reset when imageUrl changes
|
||||
React.useEffect(() => {
|
||||
@@ -75,6 +93,9 @@ export function LightboxModal({
|
||||
if (!mediaId) {
|
||||
setPrompt(null);
|
||||
setHasPromptToken(false);
|
||||
setGenerationProvider(null);
|
||||
setModelType(null);
|
||||
setContextImages(null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -92,22 +113,32 @@ export function LightboxModal({
|
||||
token: mediaResponse.data.maybe_prompt_token,
|
||||
});
|
||||
|
||||
if (
|
||||
promptResponse.success &&
|
||||
promptResponse.data?.maybe_positive_prompt
|
||||
) {
|
||||
setPrompt(promptResponse.data.maybe_positive_prompt);
|
||||
if (promptResponse.success && promptResponse.data) {
|
||||
const promptData = promptResponse.data;
|
||||
setPrompt(promptData.maybe_positive_prompt || null);
|
||||
setGenerationProvider(promptData.maybe_generation_provider || null);
|
||||
setModelType(promptData.maybe_model_type || null);
|
||||
setContextImages(promptData.maybe_context_images || null);
|
||||
} else {
|
||||
setPrompt(null);
|
||||
setGenerationProvider(null);
|
||||
setModelType(null);
|
||||
setContextImages(null);
|
||||
}
|
||||
} else {
|
||||
setHasPromptToken(false);
|
||||
setPrompt(null);
|
||||
setGenerationProvider(null);
|
||||
setModelType(null);
|
||||
setContextImages(null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching prompt:", error);
|
||||
setHasPromptToken(false);
|
||||
setPrompt(null);
|
||||
setGenerationProvider(null);
|
||||
setModelType(null);
|
||||
setContextImages(null);
|
||||
} finally {
|
||||
setPromptLoading(false);
|
||||
}
|
||||
@@ -185,51 +216,165 @@ export function LightboxModal({
|
||||
</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>
|
||||
<>
|
||||
{/* Prompt */}
|
||||
<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>
|
||||
) : (
|
||||
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>
|
||||
{/* Context Images */}
|
||||
{contextImages && contextImages.length > 0 && (
|
||||
<div className="space-y-1.5">
|
||||
<div className="text-sm font-medium text-white/90">
|
||||
Reference Images
|
||||
</div>
|
||||
<div className="grid grid-cols-6 gap-2">
|
||||
{contextImages.map((contextImage, index) => {
|
||||
const thumbnailUrl = contextImage.media_links
|
||||
.maybe_thumbnail_template
|
||||
? contextImage.media_links.maybe_thumbnail_template.replace(
|
||||
"{WIDTH}",
|
||||
"128"
|
||||
)
|
||||
: contextImage.media_links.cdn_url;
|
||||
|
||||
const fullSizeUrl = contextImage.media_links
|
||||
.maybe_thumbnail_template
|
||||
? contextImage.media_links.maybe_thumbnail_template.replace(
|
||||
"{WIDTH}",
|
||||
"512"
|
||||
)
|
||||
: contextImage.media_links.cdn_url;
|
||||
|
||||
return (
|
||||
// Hover preview
|
||||
<Tooltip
|
||||
key={contextImage.media_token}
|
||||
className="bg-black p-1.5"
|
||||
content={
|
||||
<div>
|
||||
<div className="flex flex-col items-center bg-white/10 rounded-lg">
|
||||
<img
|
||||
src={fullSizeUrl}
|
||||
alt={`Reference image ${index + 1} preview`}
|
||||
className="w-auto h-48 object-cover rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
{contextImage.semantic && (
|
||||
<div className="mt-2 text-xs text-white/90 text-center max-w-48 px-1">
|
||||
{contextImage.semantic}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
position="top"
|
||||
delay={300}
|
||||
closeOnClick={true}
|
||||
>
|
||||
<div
|
||||
className="relative group cursor-pointer"
|
||||
onClick={() => {
|
||||
window.open(
|
||||
contextImage.media_links.cdn_url,
|
||||
"_blank"
|
||||
);
|
||||
}}
|
||||
>
|
||||
<div className="relative overflow-hidden rounded-lg border border-white/5 bg-white/10 aspect-square">
|
||||
<img
|
||||
src={thumbnailUrl}
|
||||
alt={`Reference image ${index + 1}`}
|
||||
className="w-full h-full object-cover transition-transform duration-200 group-hover:scale-105"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Generation Details */}
|
||||
{(generationProvider || modelType) && (
|
||||
<div className="space-y-1.5">
|
||||
<div className="text-sm font-medium text-white/90">
|
||||
Generation Details
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
{modelType && (
|
||||
<div className="flex items-center justify-between py-2 px-3 bg-black/20 rounded-lg border border-white/5">
|
||||
<span className="text-sm text-white/70 font-medium">
|
||||
Model
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
{getModelCreatorIcon(modelType)}
|
||||
<span className="text-sm text-white/90 rounded">
|
||||
{getModelDisplayName(modelType)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{generationProvider && (
|
||||
<div className="flex items-center justify-between py-2 px-3 bg-black/20 rounded-lg border border-white/5">
|
||||
<span className="text-sm text-white/70 font-medium">
|
||||
Provider
|
||||
</span>
|
||||
<span className="text-sm text-white/90 rounded">
|
||||
{getProviderDisplayName(generationProvider)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{additionalInfo}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,3 +4,4 @@ export * from "./lib/ModelCreator.js";
|
||||
export * from "./lib/ImageModels.js";
|
||||
export * from "./lib/VideoModels.js";
|
||||
export * from "./lib/ModelCreatorIcons.js";
|
||||
export * from "./lib/ModelTypeMapping.js";
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
export enum ModelCreator {
|
||||
BlackForestLabs = "BlackForestLabs",
|
||||
Bytedance = "Bytedance",
|
||||
@@ -9,4 +8,6 @@ export enum ModelCreator {
|
||||
OpenAi = "OpenAi",
|
||||
Runway = "Runway",
|
||||
Stability = "Stability",
|
||||
Tencent = "Tencent",
|
||||
Recraft = "Recraft",
|
||||
}
|
||||
@@ -9,6 +9,8 @@ export const CREATOR_ICON_PATHS: Partial<Record<ModelCreator, string>> = {
|
||||
[ModelCreator.OpenAi]: "/resources/images/services/openai.svg",
|
||||
[ModelCreator.Bytedance]: "/resources/images/services/bytedance.svg",
|
||||
[ModelCreator.Google]: "/resources/images/services/google.svg",
|
||||
[ModelCreator.Recraft]: "/resources/images/services/recraft.svg",
|
||||
[ModelCreator.Tencent]: "/resources/images/services/tencent.svg",
|
||||
};
|
||||
|
||||
export const getCreatorIconPath = (creator: ModelCreator): string | undefined =>
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import React from "react";
|
||||
import { ModelCreator } from "./ModelCreator.js";
|
||||
import { getCreatorIcon } from "./ModelCreatorIcons.js";
|
||||
|
||||
// Map from model type strings to ModelCreator enum values
|
||||
export const MODEL_TYPE_TO_CREATOR: Record<string, ModelCreator> = {
|
||||
flux_1_dev: ModelCreator.BlackForestLabs,
|
||||
flux_1_schnell: ModelCreator.BlackForestLabs,
|
||||
flux_pro_1p1: ModelCreator.BlackForestLabs,
|
||||
flux_pro_1p1_ultra: ModelCreator.BlackForestLabs,
|
||||
gpt_image_1: ModelCreator.OpenAi,
|
||||
kling_1p6_pro: ModelCreator.Kling,
|
||||
kling_2p1_pro: ModelCreator.Kling,
|
||||
kling_2p1_master: ModelCreator.Kling,
|
||||
seedance_1p0_lite: ModelCreator.Bytedance,
|
||||
veo_2: ModelCreator.Google,
|
||||
recraft_3: ModelCreator.Recraft,
|
||||
hunyuan_3d: ModelCreator.Tencent,
|
||||
};
|
||||
|
||||
// Get creator icon for a model type
|
||||
export const getModelCreatorIcon = (
|
||||
modelType: string
|
||||
): React.ReactNode | null => {
|
||||
const creator = MODEL_TYPE_TO_CREATOR[modelType];
|
||||
if (!creator) return null;
|
||||
return getCreatorIcon(creator, "h-4 w-4 invert");
|
||||
};
|
||||
|
||||
// Get creator name for display
|
||||
export const getModelCreatorName = (modelType: string): string | null => {
|
||||
const creator = MODEL_TYPE_TO_CREATOR[modelType];
|
||||
|
||||
// Convert enum value to display name
|
||||
switch (creator) {
|
||||
case ModelCreator.BlackForestLabs:
|
||||
return "Black Forest Labs";
|
||||
case ModelCreator.OpenAi:
|
||||
return "OpenAI";
|
||||
case ModelCreator.Kling:
|
||||
return "Kling AI";
|
||||
case ModelCreator.Bytedance:
|
||||
return "ByteDance";
|
||||
case ModelCreator.Google:
|
||||
return "Google";
|
||||
case ModelCreator.Midjourney:
|
||||
return "Midjourney";
|
||||
case ModelCreator.Stability:
|
||||
return "Stability AI";
|
||||
case ModelCreator.Runway:
|
||||
return "Runway";
|
||||
case ModelCreator.Hailuo:
|
||||
return "Hailuo";
|
||||
case ModelCreator.Recraft:
|
||||
return "Recraft";
|
||||
case ModelCreator.Tencent:
|
||||
return "Tencent";
|
||||
default:
|
||||
return creator;
|
||||
}
|
||||
};
|
||||
|
||||
// Convert model type string to human-readable display name
|
||||
export const getModelDisplayName = (modelType: string): string => {
|
||||
const displayNames: Record<string, string> = {
|
||||
flux_1_dev: "Flux 1 Dev",
|
||||
flux_1_schnell: "Flux 1 Schnell",
|
||||
flux_pro_1p1: "Flux Pro 1.1",
|
||||
flux_pro_1p1_ultra: "Flux Pro 1.1 Ultra",
|
||||
gpt_image_1: "GPT Image 1",
|
||||
kling_1p6_pro: "Kling 1.6 Pro",
|
||||
kling_2p1_pro: "Kling 2.1 Pro",
|
||||
kling_2p1_master: "Kling 2.1 Master",
|
||||
seedance_1p0_lite: "Seedance 1.0 Lite",
|
||||
veo_2: "Veo 2",
|
||||
recraft_3: "Recraft 3",
|
||||
hunyuan_3d_2p0: "Hunyuan 3D 2.0",
|
||||
hunyuan_3d_2p1: "Hunyuan 3D 2.1",
|
||||
hunyuan_3d: "Hunyuan 3D",
|
||||
};
|
||||
|
||||
return displayNames[modelType] || modelType;
|
||||
};
|
||||
|
||||
// Convert provider string to human-readable display name
|
||||
export const getProviderDisplayName = (provider: string): string => {
|
||||
const displayNames: Record<string, string> = {
|
||||
artcraft: "ArtCraft",
|
||||
fal: "FAL",
|
||||
sora: "Sora",
|
||||
};
|
||||
|
||||
return displayNames[provider] || provider;
|
||||
};
|
||||
Reference in new issue
Block a user