mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-01 16:08:30 +00:00
clean up model metadata and plumbing (#770)
This commit is contained in:
1 parent
fbf914e24e
commit
85c4e0edab
19 files changed
+241
-152
No files matched your search
+2
-2
@@ -145,8 +145,8 @@ pub async fn enqueue_image_inpaint_command(
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
) -> ResponseOrErrorType<EnqueueImageInpaintSuccessResponse, EnqueueInpaintImageErrorType> {
|
||||
|
||||
info!("enqueue_image_inpaint_command called; model: {:?}, image_media_token: {:?}, prompt: {:?}",
|
||||
&request.model, &request.image_media_token, &request.prompt);
|
||||
info!("enqueue_image_inpaint_command called; model: {:?}, image_media_token: {:?}, prompt: {:?}, image_count: {:?}",
|
||||
&request.model, &request.image_media_token, &request.prompt, &request.image_count);
|
||||
|
||||
let result = handle_request(
|
||||
&request,
|
||||
|
||||
@@ -124,6 +124,25 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getPageTitle = () : string => {
|
||||
switch (tabStore.activeTabId) {
|
||||
case "2D":
|
||||
return "Canvas";
|
||||
case "3D":
|
||||
return "3D Editor";
|
||||
case "IMAGE":
|
||||
return "Text to Image";
|
||||
case "VIDEO":
|
||||
return "Image to Video";
|
||||
case "EDIT":
|
||||
return "Edit Image";
|
||||
default:
|
||||
return "Artcraft";
|
||||
}
|
||||
};
|
||||
|
||||
const pageTitle = getPageTitle();
|
||||
|
||||
// const { toggleModal } = usePricingModalStore(); - Uncomment for pricing modal - BFlat
|
||||
|
||||
return (
|
||||
@@ -179,11 +198,7 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
<SceneTitleInput pageName={pageName} />
|
||||
) : (
|
||||
<h1>
|
||||
{tabStore.activeTabId === "2D"
|
||||
? "Canvas"
|
||||
: tabStore.activeTabId === "VIDEO"
|
||||
? "Generate Video"
|
||||
: "Generate Image"}
|
||||
{pageTitle}
|
||||
</h1>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -7,24 +7,21 @@ import { AspectRatioType, useSceneStore } from "./stores/SceneState";
|
||||
import { useUndoRedoHotkeys } from "./hooks/useUndoRedoHotkeys";
|
||||
import { useDeleteHotkeys } from "./hooks/useDeleteHotkeys";
|
||||
import { useCopyPasteHotkeys } from "./hooks/useCopyPasteHotkeys";
|
||||
import { PopoverItem, PopoverMenu } from "@storyteller/ui-popover";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faClock, faImage } from "@fortawesome/pro-solid-svg-icons";
|
||||
import Konva from "konva";
|
||||
import { setCanvasRenderBitmap } from "../../signals/canvasRenderBitmap";
|
||||
import { captureStageImageBitmap } from "./hooks/useUpdateSnapshot";
|
||||
import { ContextMenuContainer } from "./components/ui/ContextMenu";
|
||||
import { FalBackgroundRemoval } from "@storyteller/tauri-api";
|
||||
import { EnqueueImageBgRemoval } from "@storyteller/tauri-api";
|
||||
import { ModelInfo } from "@storyteller/model-list";
|
||||
import {
|
||||
instructiveImageEditModels,
|
||||
ModelCategory,
|
||||
CANVAS_2D_PAGE_MODEL_LIST,
|
||||
ModelPage,
|
||||
ModelSelector,
|
||||
useModelSelectorStore,
|
||||
} from "@storyteller/ui-model-selector";
|
||||
import { useCanvasBgRemovedEvent } from "@storyteller/tauri-api";
|
||||
|
||||
const PAGE_ID : ModelPage = ModelPage.Canvas2D;
|
||||
|
||||
export const DecodeBase64ToImage = async (
|
||||
base64String: string,
|
||||
): Promise<ImageBitmap> => {
|
||||
@@ -60,11 +57,11 @@ const PageDraw = () => {
|
||||
const store = useSceneStore();
|
||||
|
||||
const selectedModel =
|
||||
selectedModels[ModelCategory.Canvas2D] ||
|
||||
instructiveImageEditModels[0]?.label;
|
||||
selectedModels[PAGE_ID] ||
|
||||
CANVAS_2D_PAGE_MODEL_LIST[0]?.label;
|
||||
|
||||
const selectedModelInfo: ModelInfo | undefined =
|
||||
instructiveImageEditModels.find(
|
||||
CANVAS_2D_PAGE_MODEL_LIST.find(
|
||||
(m) => m.label === selectedModel,
|
||||
)?.modelInfo;
|
||||
|
||||
@@ -450,8 +447,8 @@ const PageDraw = () => {
|
||||
</div>
|
||||
<div className="absolute bottom-6 left-6 z-20 flex items-center gap-2">
|
||||
<ModelSelector
|
||||
items={instructiveImageEditModels}
|
||||
category={ModelCategory.Canvas2D}
|
||||
items={CANVAS_2D_PAGE_MODEL_LIST}
|
||||
page={PAGE_ID}
|
||||
panelTitle="Select Model"
|
||||
panelClassName="min-w-[280px]"
|
||||
buttonClassName="bg-transparent p-0 text-lg hover:bg-transparent text-white/80 hover:text-white"
|
||||
|
||||
@@ -1,39 +1,44 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
// https://github.com/SaladTechnologies/comfyui-api
|
||||
|
||||
import { PopoverItem, PopoverMenu } from "@storyteller/ui-popover";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faClock, faImage } from "@fortawesome/pro-solid-svg-icons";
|
||||
import Konva from "konva"; // just for types
|
||||
|
||||
// import { setCanvasRenderBitmap } from "../../signals/canvasRenderBitmap"
|
||||
import {
|
||||
EnqueueImageInpaint,
|
||||
EnqueueImageInpaintModel,
|
||||
} from "@storyteller/tauri-api";
|
||||
import { ContextMenuContainer } from "../PageDraw/components/ui/ContextMenu";
|
||||
// import SideToolbar from "../PageDraw/components/ui/SideToolbar";
|
||||
import { useCopyPasteHotkeys } from "../PageDraw/hooks/useCopyPasteHotkeys";
|
||||
import { useDeleteHotkeys } from "../PageDraw/hooks/useDeleteHotkeys";
|
||||
import { useUndoRedoHotkeys } from "../PageDraw/hooks/useUndoRedoHotkeys";
|
||||
// import { captureStageImageBitmap } from "../PageDraw/hooks/useUpdateSnapshot";
|
||||
import PromptEditor from "./PromptEditor/PromptEditor";
|
||||
// import { AspectRatioType } from "../PageDraw/stores/SceneState";
|
||||
import { ActiveEditTool, useEditStore } from "./stores/EditState";
|
||||
import { EditPaintSurface } from "./EditPaintSurface";
|
||||
import { normalizeCanvas } from "../../Helpers/CanvasHelpers";
|
||||
import { BaseImageSelector, BaseSelectorImage } from "./BaseImageSelector";
|
||||
import DrawToolControlBar from "./DrawToolControlBar";
|
||||
import { IMAGE_EDITOR_PAGE_MODEL_LIST, ModelPage, ModelSelector, useModelSelectorStore } from "@storyteller/ui-model-selector";
|
||||
import { ModelInfo } from "@storyteller/model-list";
|
||||
|
||||
const PAGE_ID : ModelPage = ModelPage.ImageEditor;
|
||||
|
||||
const PageEdit = () => {
|
||||
//useStateSceneLoader();
|
||||
const { selectedModels } = useModelSelectorStore();
|
||||
|
||||
const selectedModel =
|
||||
selectedModels[PAGE_ID] ||
|
||||
IMAGE_EDITOR_PAGE_MODEL_LIST[0]?.label;
|
||||
|
||||
const selectedModelInfo: ModelInfo | undefined =
|
||||
IMAGE_EDITOR_PAGE_MODEL_LIST.find(
|
||||
(m) => m.label === selectedModel,
|
||||
)?.modelInfo;
|
||||
|
||||
// State for canvas dimensions
|
||||
const canvasWidth = useRef<number>(1024);
|
||||
const canvasHeight = useRef<number>(1024);
|
||||
|
||||
// Add new state to track if user is selecting
|
||||
const [isSelecting, setIsSelecting] = useState<boolean>(false);
|
||||
const [selectedModel, setSelectedModel] = useState<string>("GPT-4o");
|
||||
|
||||
// Create refs for stage and image
|
||||
const stageRef = useRef<Konva.Stage>({} as Konva.Stage);
|
||||
const leftPanelRef = useRef<Konva.Layer>({} as Konva.Layer);
|
||||
@@ -42,6 +47,7 @@ const PageEdit = () => {
|
||||
const [isEnqueuing, setIsEnqueuing] = useState<boolean>(false);
|
||||
const [generationCount, setGenerationCount] = useState<number>(1);
|
||||
|
||||
|
||||
// Use the Zustand store
|
||||
const store = useEditStore();
|
||||
|
||||
@@ -115,27 +121,6 @@ const PageEdit = () => {
|
||||
};
|
||||
}, [store]);
|
||||
|
||||
const modelList: PopoverItem[] = [
|
||||
{
|
||||
label: "GPT-4o",
|
||||
icon: <FontAwesomeIcon icon={faImage} className="h-4 w-4" />,
|
||||
selected: selectedModel === "GPT-4o",
|
||||
description: "High quality model",
|
||||
badges: [{ label: "2 min.", icon: <FontAwesomeIcon icon={faClock} /> }],
|
||||
},
|
||||
{
|
||||
label: "FLUX.1 Kontext",
|
||||
icon: <FontAwesomeIcon icon={faImage} className="h-4 w-4" />,
|
||||
selected: selectedModel === "FLUX.1 Kontext",
|
||||
description: "Fast and high-quality model",
|
||||
badges: [{ label: "20 sec.", icon: <FontAwesomeIcon icon={faClock} /> }],
|
||||
},
|
||||
];
|
||||
|
||||
const handleModelSelect = (item: PopoverItem) => {
|
||||
setSelectedModel(item.label);
|
||||
};
|
||||
|
||||
const onFitPressed = async () => {
|
||||
// Get the stage and its container dimensions
|
||||
const stage = stageRef.current;
|
||||
@@ -263,6 +248,7 @@ const PageEdit = () => {
|
||||
}`}
|
||||
>
|
||||
<PromptEditor
|
||||
modelInfo={selectedModelInfo}
|
||||
onModeChange={(mode: string) => {
|
||||
store.setActiveTool(mode as ActiveEditTool);
|
||||
}}
|
||||
@@ -328,10 +314,9 @@ const PageEdit = () => {
|
||||
</ContextMenuContainer>
|
||||
</div>
|
||||
<div className="absolute bottom-6 left-6 z-20 flex items-center gap-2">
|
||||
<PopoverMenu
|
||||
items={modelList}
|
||||
onSelect={handleModelSelect}
|
||||
mode="hoverSelect"
|
||||
<ModelSelector
|
||||
items={IMAGE_EDITOR_PAGE_MODEL_LIST}
|
||||
page={PAGE_ID}
|
||||
panelTitle="Select Model"
|
||||
panelClassName="min-w-[280px]"
|
||||
buttonClassName="bg-transparent p-0 text-lg hover:bg-transparent text-white/80 hover:text-white"
|
||||
|
||||
@@ -66,13 +66,10 @@ import {
|
||||
removeImageDropListener,
|
||||
} from "@storyteller/ui-gallery-modal";
|
||||
import {
|
||||
instructiveImageEditModels,
|
||||
ModelCategory,
|
||||
// ModelCategory,
|
||||
STAGE_3D_PAGE_MODEL_LIST,
|
||||
ModelPage,
|
||||
ModelSelector,
|
||||
useModelSelectorStore,
|
||||
// videoGenerationModels,
|
||||
// useModelSelectorStore,
|
||||
} from "@storyteller/ui-model-selector";
|
||||
import { LoginModal, useLoginModalStore } from "@storyteller/ui-login-modal";
|
||||
import PageDraw from "../PageDraw/PageDraw";
|
||||
@@ -80,6 +77,8 @@ import { useTabStore } from "../Stores/TabState";
|
||||
import PageEdit from "../PageEdit/PageEdit";
|
||||
import { ModelInfo } from "@storyteller/model-list";
|
||||
|
||||
const PAGE_ID : ModelPage = ModelPage.Stage3D;
|
||||
|
||||
export const PageEditor = () => {
|
||||
useSignals();
|
||||
const { triggerRecheck } = useLoginModalStore();
|
||||
@@ -112,11 +111,11 @@ export const PageEditor = () => {
|
||||
}, []);
|
||||
|
||||
const selectedModel =
|
||||
selectedModels[ModelCategory.Editor3D] ||
|
||||
instructiveImageEditModels[0]?.label;
|
||||
selectedModels[PAGE_ID] ||
|
||||
STAGE_3D_PAGE_MODEL_LIST[0]?.label;
|
||||
|
||||
const selectedModelInfo: ModelInfo | undefined =
|
||||
instructiveImageEditModels.find(
|
||||
STAGE_3D_PAGE_MODEL_LIST.find(
|
||||
(m) => m.label === selectedModel,
|
||||
)?.modelInfo;
|
||||
|
||||
@@ -550,8 +549,8 @@ export const PageEditor = () => {
|
||||
|
||||
<div className="absolute bottom-6 left-6 z-20 flex items-center gap-2">
|
||||
<ModelSelector
|
||||
items={instructiveImageEditModels}
|
||||
category={ModelCategory.Editor3D}
|
||||
items={STAGE_3D_PAGE_MODEL_LIST}
|
||||
page={PAGE_ID}
|
||||
panelTitle="Select Model"
|
||||
panelClassName="min-w-[280px]"
|
||||
buttonClassName="bg-transparent p-0 text-lg hover:bg-transparent text-white/80 hover:text-white"
|
||||
|
||||
@@ -3,8 +3,8 @@ import { JobContextType } from "@storyteller/common";
|
||||
import { PromptBoxImage } from "@storyteller/ui-promptbox";
|
||||
import BackgroundGallery from "./BackgroundGallery";
|
||||
import {
|
||||
imageGenerationModels,
|
||||
ModelCategory,
|
||||
TEXT_TO_IMAGE_PAGE_MODEL_LIST,
|
||||
ModelPage,
|
||||
ModelSelector,
|
||||
useModelSelectorStore,
|
||||
} from "@storyteller/ui-model-selector";
|
||||
@@ -15,15 +15,17 @@ interface TextToImageProps {
|
||||
}
|
||||
import { ModelInfo } from "@storyteller/model-list";
|
||||
|
||||
const PAGE_ID : ModelPage = ModelPage.TextToImage;
|
||||
|
||||
const TextToImage = ({ imageMediaId, imageUrl }: TextToImageProps) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const { selectedModels } = useModelSelectorStore();
|
||||
|
||||
const selectedModel =
|
||||
selectedModels[ModelCategory.TextToImage] ||
|
||||
imageGenerationModels[0]?.label;
|
||||
selectedModels[PAGE_ID] ||
|
||||
TEXT_TO_IMAGE_PAGE_MODEL_LIST[0]?.label;
|
||||
|
||||
const selectedModelInfo: ModelInfo | undefined = imageGenerationModels.find(
|
||||
const selectedModelInfo: ModelInfo | undefined = TEXT_TO_IMAGE_PAGE_MODEL_LIST.find(
|
||||
(m) => m.label === selectedModel,
|
||||
)?.modelInfo;
|
||||
|
||||
@@ -66,8 +68,8 @@ const TextToImage = ({ imageMediaId, imageUrl }: TextToImageProps) => {
|
||||
|
||||
<div className="absolute bottom-6 left-6 z-20 flex items-center gap-2">
|
||||
<ModelSelector
|
||||
items={imageGenerationModels}
|
||||
category={ModelCategory.TextToImage}
|
||||
items={TEXT_TO_IMAGE_PAGE_MODEL_LIST}
|
||||
page={PAGE_ID}
|
||||
mode="hoverSelect"
|
||||
panelTitle="Select Model"
|
||||
panelClassName="min-w-[280px]"
|
||||
|
||||
@@ -3,13 +3,15 @@ import { JobContextType } from "@storyteller/common";
|
||||
import { PromptBoxVideo } from "@storyteller/ui-promptbox";
|
||||
import BackgroundGallery from "./BackgroundGallery";
|
||||
import {
|
||||
ModelCategory,
|
||||
ModelPage,
|
||||
ModelSelector,
|
||||
useModelSelectorStore,
|
||||
videoGenerationModels,
|
||||
IMAGE_TO_VIDEO_PAGE_MODEL_LIST,
|
||||
} from "@storyteller/ui-model-selector";
|
||||
import { ModelInfo } from "@storyteller/model-list";
|
||||
|
||||
const PAGE_ID : ModelPage = ModelPage.ImageToVideo;
|
||||
|
||||
interface ImageToVideoProps {
|
||||
imageMediaId?: string;
|
||||
imageUrl?: string;
|
||||
@@ -20,10 +22,10 @@ const ImageToVideo = ({ imageMediaId, imageUrl }: ImageToVideoProps) => {
|
||||
const { selectedModels } = useModelSelectorStore();
|
||||
|
||||
const selectedModel =
|
||||
selectedModels[ModelCategory.ImageToVideo] ||
|
||||
videoGenerationModels[0]?.label;
|
||||
selectedModels[PAGE_ID] ||
|
||||
IMAGE_TO_VIDEO_PAGE_MODEL_LIST[0]?.label;
|
||||
|
||||
const selectedModelInfo: ModelInfo | undefined = videoGenerationModels.find(
|
||||
const selectedModelInfo: ModelInfo | undefined = IMAGE_TO_VIDEO_PAGE_MODEL_LIST.find(
|
||||
(m) => m.label === selectedModel,
|
||||
)?.modelInfo;
|
||||
|
||||
@@ -67,8 +69,8 @@ const ImageToVideo = ({ imageMediaId, imageUrl }: ImageToVideoProps) => {
|
||||
|
||||
<div className="absolute bottom-6 left-6 z-20 flex items-center gap-2">
|
||||
<ModelSelector
|
||||
items={videoGenerationModels}
|
||||
category={ModelCategory.ImageToVideo}
|
||||
items={IMAGE_TO_VIDEO_PAGE_MODEL_LIST}
|
||||
page={PAGE_ID}
|
||||
panelTitle="Select Model"
|
||||
panelClassName="min-w-[280px]"
|
||||
buttonClassName="bg-transparent p-0 text-lg hover:bg-transparent text-white/80 hover:text-white"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from "./lib/model-selector";
|
||||
export * from "./lib/model-selector-store";
|
||||
export * from "./lib/model-lists";
|
||||
export * from "./lib/model-categories";
|
||||
export * from "./lib/model-pages";
|
||||
@@ -1,6 +0,0 @@
|
||||
export enum ModelCategory {
|
||||
TextToImage = "text-to-image",
|
||||
ImageToVideo = "image-to-video",
|
||||
Canvas2D = "2d-canvas",
|
||||
Editor3D = "3d-editor",
|
||||
}
|
||||
@@ -3,8 +3,12 @@ import { faClock, faFilm, faImage } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
getCreatorIcon,
|
||||
getModelsByCategory,
|
||||
getInstructiveImageEditModels,
|
||||
ModelConfig,
|
||||
TEXT_TO_IMAGE_PAGE_MODELS,
|
||||
IMAGE_TO_VIDEO_PAGE_MODELS,
|
||||
CANVAS_2D_PAGE_MODELS,
|
||||
STAGE_3D_PAGE_MODELS,
|
||||
IMAGE_EDITOR_PAGE_MODELS,
|
||||
} from "@storyteller/model-list";
|
||||
|
||||
export type ModelList = Omit<PopoverItem, "selected">[];
|
||||
@@ -12,45 +16,42 @@ export type ModelList = Omit<PopoverItem, "selected">[];
|
||||
const withIcon = (creatorIcon: any, fallback: any) => creatorIcon || fallback;
|
||||
|
||||
const buildItems = (
|
||||
models: {
|
||||
label: string;
|
||||
description?: string;
|
||||
badges?: { label: string }[];
|
||||
info: { creator: any };
|
||||
}[],
|
||||
models: ModelConfig[],
|
||||
fallbackIcon: any
|
||||
) =>
|
||||
models.map((m) => ({
|
||||
label: m.label,
|
||||
icon: withIcon(getCreatorIcon((m as any).info.creator), fallbackIcon),
|
||||
description: (m as any).description,
|
||||
badges: (m as any).badges?.map((b: any) => ({
|
||||
icon: withIcon(getCreatorIcon(m.info.creator), fallbackIcon),
|
||||
description: m.description,
|
||||
badges: m.badges?.map((b) => ({
|
||||
label: b.label,
|
||||
icon: <FontAwesomeIcon icon={faClock} />,
|
||||
})),
|
||||
modelInfo: (m as any).info,
|
||||
modelConfig: m, // Access to full object.
|
||||
modelInfo: m.info,
|
||||
}));
|
||||
|
||||
const imageModels = getModelsByCategory("image");
|
||||
const videoModels = getModelsByCategory("video");
|
||||
const instructiveModels = getInstructiveImageEditModels();
|
||||
export const TEXT_TO_IMAGE_PAGE_MODEL_LIST : ModelList = buildItems(
|
||||
TEXT_TO_IMAGE_PAGE_MODELS,
|
||||
<FontAwesomeIcon icon={faImage} className="h-4 w-4" />
|
||||
);
|
||||
|
||||
export const allModels = {
|
||||
video: buildItems(
|
||||
videoModels as any,
|
||||
<FontAwesomeIcon icon={faFilm} className="h-4 w-4" />
|
||||
),
|
||||
image: buildItems(
|
||||
imageModels as any,
|
||||
<FontAwesomeIcon icon={faImage} className="h-4 w-4" />
|
||||
),
|
||||
instructiveImageEdits: buildItems(
|
||||
instructiveModels as any,
|
||||
<FontAwesomeIcon icon={faImage} className="h-4 w-4" />
|
||||
),
|
||||
};
|
||||
export const IMAGE_TO_VIDEO_PAGE_MODEL_LIST : ModelList = buildItems(
|
||||
IMAGE_TO_VIDEO_PAGE_MODELS,
|
||||
<FontAwesomeIcon icon={faFilm} className="h-4 w-4" />
|
||||
);
|
||||
|
||||
export const videoGenerationModels: ModelList = allModels.video;
|
||||
export const imageGenerationModels: ModelList = allModels.image;
|
||||
export const instructiveImageEditModels: ModelList =
|
||||
allModels.instructiveImageEdits;
|
||||
export const CANVAS_2D_PAGE_MODEL_LIST : ModelList = buildItems(
|
||||
CANVAS_2D_PAGE_MODELS,
|
||||
<FontAwesomeIcon icon={faImage} className="h-4 w-4" />
|
||||
);
|
||||
|
||||
export const STAGE_3D_PAGE_MODEL_LIST : ModelList = buildItems(
|
||||
STAGE_3D_PAGE_MODELS,
|
||||
<FontAwesomeIcon icon={faImage} className="h-4 w-4" />
|
||||
);
|
||||
|
||||
export const IMAGE_EDITOR_PAGE_MODEL_LIST : ModelList = buildItems(
|
||||
IMAGE_EDITOR_PAGE_MODELS,
|
||||
<FontAwesomeIcon icon={faImage} className="h-4 w-4" />
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
export enum ModelPage {
|
||||
TextToImage = "text-to-image",
|
||||
ImageToVideo = "image-to-video",
|
||||
Canvas2D = "canvas-2d",
|
||||
Stage3D = "stage-3d",
|
||||
|
||||
// The "inpainting + outpainting + text" image editor
|
||||
ImageEditor = "image-editor",
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
import { create } from "zustand";
|
||||
import { ModelCategory } from "./model-categories";
|
||||
import { ModelPage } from "./model-pages";
|
||||
|
||||
interface ModelSelectorState {
|
||||
selectedModels: { [category in ModelCategory]?: string };
|
||||
setSelectedModel: (category: ModelCategory, modelLabel: string) => void;
|
||||
selectedModels: { [page in ModelPage]?: string };
|
||||
setSelectedModel: (page: ModelPage, modelLabel: string) => void;
|
||||
}
|
||||
|
||||
export const useModelSelectorStore = create<ModelSelectorState>((set) => ({
|
||||
selectedModels: {},
|
||||
setSelectedModel: (category, modelLabel) =>
|
||||
setSelectedModel: (page, modelLabel) =>
|
||||
set((state) => ({
|
||||
selectedModels: {
|
||||
...state.selectedModels,
|
||||
[category]: modelLabel,
|
||||
[page]: modelLabel,
|
||||
},
|
||||
})),
|
||||
}));
|
||||
@@ -1,11 +1,11 @@
|
||||
import { PopoverMenu, type PopoverItem } from "@storyteller/ui-popover";
|
||||
import { useModelSelectorStore } from "./model-selector-store";
|
||||
import { useMemo } from "react";
|
||||
import { ModelCategory } from "./model-categories";
|
||||
import { ModelPage } from "./model-pages";
|
||||
|
||||
interface ModelSelectorProps {
|
||||
items: Omit<PopoverItem, "selected">[];
|
||||
category: ModelCategory;
|
||||
page: ModelPage;
|
||||
mode?: "hoverSelect" | "default" | "toggle" | "button";
|
||||
panelTitle?: string;
|
||||
buttonClassName?: string;
|
||||
@@ -16,14 +16,14 @@ interface ModelSelectorProps {
|
||||
|
||||
export function ModelSelector({
|
||||
items,
|
||||
category,
|
||||
page,
|
||||
...popoverProps
|
||||
}: ModelSelectorProps) {
|
||||
const { selectedModels, setSelectedModel } = useModelSelectorStore();
|
||||
const selectedModel = selectedModels[category] || items[0]?.label;
|
||||
const selectedModel = selectedModels[page] || items[0]?.label;
|
||||
|
||||
const handleModelSelect = (item: PopoverItem) => {
|
||||
setSelectedModel(category, item.label);
|
||||
setSelectedModel(page, item.label);
|
||||
};
|
||||
|
||||
const modelList = useMemo(
|
||||
|
||||
@@ -37,9 +37,11 @@ export const PromptBoxEdit = ({
|
||||
}: PromptBoxEditProps) => {
|
||||
const [prompt, setPrompt] = useState("");
|
||||
const [useSystemPrompt, setUseSystemPrompt] = useState(true);
|
||||
|
||||
const [generationCount, setGenerationCount] = useState<number>(
|
||||
typeof generationCountProp === "number" ? generationCountProp : 1
|
||||
);
|
||||
|
||||
const [generationCountList, setGenerationCountList] = useState<PopoverItem[]>(
|
||||
[]
|
||||
);
|
||||
@@ -53,6 +55,8 @@ export const PromptBoxEdit = ({
|
||||
}
|
||||
});
|
||||
|
||||
console.log(">>> Model info", modelInfo);
|
||||
|
||||
// Sync internal state if a controlled prop is provided
|
||||
useEffect(() => {
|
||||
if (typeof generationCountProp === "number") {
|
||||
@@ -63,6 +67,7 @@ export const PromptBoxEdit = ({
|
||||
// Build generation count options based on selected model
|
||||
useEffect(() => {
|
||||
const caps = getCapabilitiesForModel(modelInfo);
|
||||
console.log(">>> caps", caps)
|
||||
const items: PopoverItem[] = Array.from(
|
||||
{ length: caps.maxGenerationCount },
|
||||
(_, i) => i + 1
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export * from "./lib/ModelInfo.js";
|
||||
export * from "./lib/ModelConfig.js";
|
||||
export * from "./lib/ModelCreator.js";
|
||||
export * from "./lib/ModelCreatorIcons.js";
|
||||
export * from "./lib/ModelInfo.js";
|
||||
export * from "./lib/ModelMapping.js";
|
||||
export * from "./lib/Models.js";
|
||||
export * from "./lib/ModelsByPage.js";
|
||||
@@ -0,0 +1,22 @@
|
||||
import { ModelInfo } from "./ModelInfo.js";
|
||||
import { ModelTag } from "./ModelTag.js";
|
||||
|
||||
export type ModelCategory = "image" | "video";
|
||||
|
||||
// Centralized model capability definition
|
||||
// For setting options for the model
|
||||
// TODO: add more capabilities here - BFlat
|
||||
export interface ModelCapabilities {
|
||||
maxGenerationCount: number;
|
||||
}
|
||||
|
||||
export interface ModelConfig {
|
||||
id: string;
|
||||
label: string; // UI label
|
||||
description?: string;
|
||||
badges?: { label: string }[];
|
||||
category: ModelCategory;
|
||||
info: ModelInfo;
|
||||
capabilities: ModelCapabilities;
|
||||
tags?: (ModelTag|string)[]; // optional tags, e.g. ["instructiveEdit"] - for filtering
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
export enum ModelTag {
|
||||
// Models that show up in the 2D and 3D editors
|
||||
InstructiveEdit = "instructiveEdit",
|
||||
|
||||
// Models that show up in the inpainting editor and that use masking.
|
||||
MaskedInpainting = "maskedInpainting",
|
||||
|
||||
// Models that show up in the inpainting editor, but that do not use masking.
|
||||
NonMaskedInpainting = "nonMaskedInpainting",
|
||||
}
|
||||
@@ -1,28 +1,10 @@
|
||||
import { ModelCreator } from "./ModelCreator.js";
|
||||
import { ModelInfo } from "./ModelInfo.js";
|
||||
|
||||
export type ModelCategory = "image" | "video";
|
||||
|
||||
export interface ModelConfig {
|
||||
id: string;
|
||||
label: string; // UI label
|
||||
description?: string;
|
||||
badges?: { label: string }[];
|
||||
category: ModelCategory;
|
||||
info: ModelInfo;
|
||||
capabilities: ModelCapabilities;
|
||||
tags?: string[]; // optional tags, e.g. ["instructiveEdit"] - for filtering
|
||||
}
|
||||
import { ModelTag } from "./ModelTag.js";
|
||||
import { ModelConfig, ModelCapabilities, ModelCategory } from "./ModelConfig.js";
|
||||
|
||||
const mc = ModelCreator;
|
||||
|
||||
// Centralized model capability definition
|
||||
// For setting options for the model
|
||||
// TODO: add more capabilities here - BFlat
|
||||
export interface ModelCapabilities {
|
||||
maxGenerationCount: number;
|
||||
}
|
||||
|
||||
const DEFAULT_CAPABILITIES: ModelCapabilities = {
|
||||
maxGenerationCount: 1,
|
||||
};
|
||||
@@ -129,8 +111,28 @@ export const ALL_MODELS: ModelConfig[] = [
|
||||
},
|
||||
description: "Fast and high-quality model",
|
||||
badges: [{ label: "20 sec." }],
|
||||
capabilities: { maxGenerationCount: 4 },
|
||||
tags: ["instructiveEdit"],
|
||||
capabilities: {
|
||||
maxGenerationCount: 4,
|
||||
},
|
||||
tags: [
|
||||
ModelTag.InstructiveEdit,
|
||||
ModelTag.NonMaskedInpainting,
|
||||
],
|
||||
}),
|
||||
cfg({
|
||||
id: "flux_pro_inpaint",
|
||||
category: "image",
|
||||
info: {
|
||||
name: "Flux Pro 1 (Inpainting)",
|
||||
tauri_id: "flux_pro_1",
|
||||
creator: mc.BlackForestLabs,
|
||||
},
|
||||
description: "Fast and high-quality model",
|
||||
badges: [{ label: "20 sec." }],
|
||||
capabilities: {
|
||||
maxGenerationCount: 1 // NB: For some reason Fal only supports ONE image!
|
||||
},
|
||||
tags: [ModelTag.MaskedInpainting],
|
||||
}),
|
||||
|
||||
//////////////////////////////
|
||||
@@ -194,6 +196,9 @@ export const ALL_MODELS: ModelConfig[] = [
|
||||
}),
|
||||
];
|
||||
|
||||
export const ALL_MODELS_BY_ID : Map<string, ModelConfig> =
|
||||
new Map(ALL_MODELS.map(model => [model.id, model]));
|
||||
|
||||
export const getAllModels = (): ModelConfig[] => ALL_MODELS;
|
||||
|
||||
export const getModelsByCategory = (category: ModelCategory): ModelConfig[] =>
|
||||
@@ -204,6 +209,11 @@ export const getInstructiveImageEditModels = (): ModelConfig[] =>
|
||||
(m) => m.category === "image" && m.tags?.includes("instructiveEdit")
|
||||
);
|
||||
|
||||
export const getMaskedInpaintModels = (): ModelConfig[] =>
|
||||
ALL_MODELS.filter(
|
||||
(m) => m.category === "image" && m.tags?.includes(ModelTag.MaskedInpainting)
|
||||
);
|
||||
|
||||
export const lookupModelByTauriId = (
|
||||
tauriId: string
|
||||
): ModelConfig | undefined =>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ALL_MODELS_BY_ID } from "./Models.js";
|
||||
import { ModelConfig } from "./ModelConfig.js";
|
||||
import { ModelTag } from "./ModelTag.js";
|
||||
import { getModelsByCategory } from "./Models.js";
|
||||
|
||||
/**
|
||||
* We might not want to always programatically build the list of models, because
|
||||
* we likely want to control the order of models in the UI to prioritize
|
||||
* more popular models. We may also want to selectively prune non-useful models.
|
||||
*/
|
||||
|
||||
export const TEXT_TO_IMAGE_PAGE_MODELS : ModelConfig[] =
|
||||
getModelsByCategory("image")
|
||||
// Remove inpainting and editing models.
|
||||
.filter((m) => m.id !== "flux_pro_inpaint")
|
||||
.filter((m) => m.id !== "flux_pro_kontext_max");
|
||||
|
||||
export const IMAGE_TO_VIDEO_PAGE_MODELS : ModelConfig[] =
|
||||
getModelsByCategory("video");
|
||||
|
||||
export const CANVAS_2D_PAGE_MODELS : ModelConfig[] =
|
||||
getModelsByCategory("image")
|
||||
// Remove non-instructive models.
|
||||
.filter((m) => m.tags?.includes(ModelTag.InstructiveEdit))
|
||||
|
||||
export const STAGE_3D_PAGE_MODELS : ModelConfig[] =
|
||||
getModelsByCategory("image")
|
||||
// Remove non-instructive models.
|
||||
.filter((m) => m.tags?.includes(ModelTag.InstructiveEdit))
|
||||
|
||||
export const IMAGE_EDITOR_PAGE_MODELS : ModelConfig[] = [
|
||||
// Explicit list for now.
|
||||
ALL_MODELS_BY_ID.get("flux_pro_inpaint")!,
|
||||
ALL_MODELS_BY_ID.get("flux_pro_kontext_max")!,
|
||||
];
|
||||
Reference in new issue
Block a user