From f91f1965a49f9cdc9ff7a0aa91b5b4adae8939fc Mon Sep 17 00:00:00 2001 From: BFlat Date: Thu, 14 Aug 2025 21:54:04 +0700 Subject: [PATCH] centralized models refactor (#760) - simplified adding models (use **Models.ts** to add more models) --- .../model-selector/src/lib/model-lists.tsx | 172 ++++---------- .../promptbox/src/lib/PromptBox2D.tsx | 74 ++++++- .../promptbox/src/lib/PromptBoxImage.tsx | 65 +++--- .../promptbox/src/lib/promptStore.ts | 4 + frontend/libs/model-list/src/index.ts | 6 +- .../libs/model-list/src/lib/ImageModels.ts | 35 --- .../{ModelTypeMapping.ts => ModelMapping.ts} | 3 +- frontend/libs/model-list/src/lib/Models.ts | 209 ++++++++++++++++++ .../libs/model-list/src/lib/VideoModels.ts | 30 --- .../model-list/src/lib/model-list.spec.ts | 7 - .../libs/model-list/src/lib/model-list.ts | 3 - 11 files changed, 360 insertions(+), 248 deletions(-) delete mode 100644 frontend/libs/model-list/src/lib/ImageModels.ts rename frontend/libs/model-list/src/lib/{ModelTypeMapping.ts => ModelMapping.ts} (95%) create mode 100644 frontend/libs/model-list/src/lib/Models.ts delete mode 100644 frontend/libs/model-list/src/lib/VideoModels.ts delete mode 100644 frontend/libs/model-list/src/lib/model-list.spec.ts delete mode 100644 frontend/libs/model-list/src/lib/model-list.ts diff --git a/frontend/libs/components/model-selector/src/lib/model-lists.tsx b/frontend/libs/components/model-selector/src/lib/model-lists.tsx index faadfb20ef..e4f303bb04 100644 --- a/frontend/libs/components/model-selector/src/lib/model-lists.tsx +++ b/frontend/libs/components/model-selector/src/lib/model-lists.tsx @@ -2,144 +2,52 @@ import type { PopoverItem } from "@storyteller/ui-popover"; import { faClock, faFilm, faImage } from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { - IMAGE_MODELS, - VIDEO_MODELS, getCreatorIcon, - ModelCreator, + getModelsByCategory, + getInstructiveImageEditModels, } from "@storyteller/model-list"; export type ModelList = Omit[]; +const withIcon = (creatorIcon: any, fallback: any) => creatorIcon || fallback; + +const buildItems = ( + models: { + label: string; + description?: string; + badges?: { label: string }[]; + info: { creator: any }; + }[], + 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) => ({ + label: b.label, + icon: , + })), + modelInfo: (m as any).info, + })); + +const imageModels = getModelsByCategory("image"); +const videoModels = getModelsByCategory("video"); +const instructiveModels = getInstructiveImageEditModels(); + export const allModels = { - video: [ - { - label: "Kling 1.6 Pro", - icon: getCreatorIcon(ModelCreator.Kling) || ( - - ), - description: "Good quality model", - badges: [{ label: "2 min.", icon: }], - modelInfo: VIDEO_MODELS.kling_1_6_pro, - }, - { - label: "Kling 2.1 Pro", - icon: getCreatorIcon(ModelCreator.Kling) || ( - - ), - description: "High quality model", - badges: [{ label: "2 min.", icon: }], - modelInfo: VIDEO_MODELS.kling_2_1_pro, - }, - { - label: "Kling 2.1 Master", - icon: getCreatorIcon(ModelCreator.Kling) || ( - - ), - description: "Master quality model ($$)", - badges: [{ label: "2 min.", icon: }], - modelInfo: VIDEO_MODELS.kling_2_1_master, - }, - { - label: "Seedance 1.0 Lite", - icon: getCreatorIcon(ModelCreator.Bytedance) || ( - - ), - description: "Fast and high-quality model", - badges: [{ label: "2 min.", icon: }], - modelInfo: VIDEO_MODELS.seedance_1_0_lite, - }, - { - label: "Google Veo 2", - icon: getCreatorIcon(ModelCreator.Google) || ( - - ), - description: "Fast and high-quality model", - badges: [{ label: "2 min.", icon: }], - modelInfo: VIDEO_MODELS.veo_2, - }, - ], - image: [ - { - label: "Flux Pro 1.1 Ultra", - icon: getCreatorIcon(ModelCreator.BlackForestLabs) || ( - - ), - description: "High quality model", - badges: [{ label: "15 sec.", icon: }], - modelInfo: IMAGE_MODELS.flux_pro_1_1_ultra, - }, - { - label: "Flux Pro 1.1", - icon: getCreatorIcon(ModelCreator.BlackForestLabs) || ( - - ), - description: "High quality model", - badges: [{ label: "15 sec.", icon: }], - modelInfo: IMAGE_MODELS.flux_pro_1_1, - }, - { - label: "Flux 1 Dev", - icon: getCreatorIcon(ModelCreator.BlackForestLabs) || ( - - ), - description: "High quality model", - badges: [{ label: "15 sec.", icon: }], - modelInfo: IMAGE_MODELS.flux_1_dev, - }, - { - label: "Flux 1 Schnell", - icon: getCreatorIcon(ModelCreator.BlackForestLabs) || ( - - ), - description: "High quality model", - badges: [{ label: "15 sec.", icon: }], - modelInfo: IMAGE_MODELS.flux_1_schnell, - }, - { - label: "GPT Image 1 (GPT-4o)", - icon: getCreatorIcon(ModelCreator.OpenAi) || ( - - ), - description: "Slow, ultra instructive model", - badges: [{ label: "45 sec.", icon: }], - modelInfo: IMAGE_MODELS.gpt_image_1, - }, - //{ - // id: "todo", - // label: "Recraft 3", - // icon: , - // description: "Fast and high-quality model", - // badges: [{ label: "15 sec.", icon: }], - //}, - //{ - // id: "todo", - // label: "Flux.1 Kontext", - // icon: , - // description: "New model with advanced context", - // badges: [{ label: "25 sec.", icon: }], - //}, - ], - // These models are used for 2D and 3D editing and scene building. - instructiveImageEdits: [ - { - label: "GPT-4o", - icon: getCreatorIcon(ModelCreator.OpenAi) || ( - - ), - description: "High quality model", - badges: [{ label: "2 min.", icon: }], - modelInfo: IMAGE_MODELS.gpt_image_1, - }, - { - label: "FLUX.1 Pro Kontext Max", - icon: getCreatorIcon(ModelCreator.BlackForestLabs) || ( - - ), - description: "Fast and high-quality model", - badges: [{ label: "20 sec.", icon: }], - modelInfo: IMAGE_MODELS.flux_pro_kontext_max, - }, - ], + video: buildItems( + videoModels as any, + + ), + image: buildItems( + imageModels as any, + + ), + instructiveImageEdits: buildItems( + instructiveModels as any, + + ), }; export const videoGenerationModels: ModelList = allModels.video; diff --git a/frontend/libs/components/promptbox/src/lib/PromptBox2D.tsx b/frontend/libs/components/promptbox/src/lib/PromptBox2D.tsx index 84ca95ae76..24e7b87551 100644 --- a/frontend/libs/components/promptbox/src/lib/PromptBox2D.tsx +++ b/frontend/libs/components/promptbox/src/lib/PromptBox2D.tsx @@ -22,6 +22,7 @@ import { faUpload, faImages, faFrame, + faCopy, } from "@fortawesome/pro-solid-svg-icons"; import { faRectangleVertical, @@ -46,7 +47,7 @@ import { // import { invoke } from "@tauri-apps/api/core"; import { usePrompt2DStore, RefImage } from "./promptStore"; import { gtagEvent } from "@storyteller/google-analytics"; -import { ModelInfo } from "@storyteller/model-list"; +import { ModelInfo, getCapabilitiesForModel } from "@storyteller/model-list"; export type AspectRatio = "1:1" | "3:2" | "2:3"; @@ -92,6 +93,8 @@ export const PromptBox2D = ({ const setUseSystemPrompt = usePrompt2DStore((s) => s.setUseSystemPrompt); const aspectRatio = usePrompt2DStore((s) => s.aspectRatio); const setAspectRatio = usePrompt2DStore((s) => s.setAspectRatio); + const generationCount = usePrompt2DStore((s) => s.generationCount); + const setGenerationCount = usePrompt2DStore((s) => s.setGenerationCount); const [isEnqueueing, setIsEnqueueing] = useState(false); const [selectedGalleryImages, setSelectedGalleryImages] = useState( @@ -120,6 +123,41 @@ export const PromptBox2D = ({ }, ]); + const [generationCountList, setGenerationCountList] = useState( + [] + ); + + // Update generation count options from selected model capabilities + useEffect(() => { + const caps = getCapabilitiesForModel(selectedModelInfo); + const items: PopoverItem[] = Array.from( + { length: caps.maxGenerationCount }, + (_, i) => i + 1 + ).map((count) => ({ + label: String(count), + selected: count === generationCount, + })); + setGenerationCountList(items); + + // Clamp selection to allowed range + if (generationCount < 1 || generationCount > caps.maxGenerationCount) { + setGenerationCount( + Math.min(Math.max(1, generationCount), caps.maxGenerationCount) + ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedModelInfo]); + + // Keep UI selection in sync when store value changes + useEffect(() => { + setGenerationCountList((prev) => + prev.map((item) => ({ + ...item, + selected: item.label === String(generationCount), + })) + ); + }, [generationCount]); + const fileInputRef = useRef(null); const textareaRef = useRef(null); @@ -249,6 +287,17 @@ export const PromptBox2D = ({ } }; + const handleGenerationCountSelect = (selectedItem: PopoverItem) => { + const count = parseInt(selectedItem.label, 10); + setGenerationCount(count); + setGenerationCountList((prev) => + prev.map((item) => ({ + ...item, + selected: item.label === selectedItem.label, + })) + ); + }; + const handlePaste = (e: React.ClipboardEvent) => { e.preventDefault(); e.stopPropagation(); @@ -327,7 +376,7 @@ export const PromptBox2D = ({ image_media_tokens: referenceImages.map((image) => image.mediaToken), disable_system_prompt: !useSystemPrompt, prompt: prompt, - image_count: 1, + image_count: generationCount, aspect_ratio: aspectRatio, }); @@ -564,8 +613,8 @@ export const PromptBox2D = ({ onChange={handleChange} onPaste={handlePaste} onKeyDown={handleKeyDown} - onFocus={() => { }} - onBlur={() => { }} + onFocus={() => {}} + onBlur={() => {}} />
@@ -626,6 +675,23 @@ export const PromptBox2D = ({
+ + + } + buttonClassName="h-9" + /> +