From f6dfd02cfc0f4de672781e02f6f36fc8054a2905 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 23 Sep 2025 10:54:14 -0400 Subject: [PATCH] fix production issues from minification / instanceof (#838) --- .../src/lib/classy-model-selector-store.ts | 18 ++++++++++++++---- .../src/lib/classy-model-selector.tsx | 1 + .../promptbox/src/lib/PromptBoxImage.tsx | 5 +++++ .../model-list/src/lib/classes/ImageModel.ts | 7 ++++++- .../libs/model-list/src/lib/classes/Model.ts | 7 +++++++ .../model-list/src/lib/classes/VideoModel.ts | 7 ++++++- .../libs/model-list/src/lib/legacy/Models.ts | 5 ++--- .../lib/enqueue/EnqueueContextualEditImage.ts | 13 ++++++++----- .../src/lib/enqueue/EnqueueImageInpaint.ts | 8 ++++++-- .../src/lib/enqueue/EnqueueTextToImage.ts | 13 ++++++++----- 10 files changed, 63 insertions(+), 21 deletions(-) diff --git a/frontend/libs/components/model-selector/src/lib/classy-model-selector-store.ts b/frontend/libs/components/model-selector/src/lib/classy-model-selector-store.ts index 35f13b293f..75886413d5 100644 --- a/frontend/libs/components/model-selector/src/lib/classy-model-selector-store.ts +++ b/frontend/libs/components/model-selector/src/lib/classy-model-selector-store.ts @@ -21,17 +21,27 @@ export const useClassyModelSelectorStore = create((set export const getSelectedImageModel = (page: ModelPage) : ImageModel | undefined => { const { selectedModels } = useClassyModelSelectorStore(); const maybeModel = selectedModels[page]; - if (!maybeModel || !(maybeModel instanceof ImageModel)) { + if (!maybeModel) { return undefined; } - return maybeModel; + // NB: We can't use "instanceof" checks with Vite minification and class name mangling. + // We have to do type tagging a different way. + if (maybeModel.kind === "image_model") { + return maybeModel as ImageModel; + } + return undefined; }; export const getSelectedVideoModel = (page: ModelPage) : VideoModel | undefined => { const { selectedModels } = useClassyModelSelectorStore(); const maybeModel = selectedModels[page]; - if (!maybeModel || !(maybeModel instanceof VideoModel)) { + if (!maybeModel) { return undefined; } - return maybeModel; + // NB: We can't use "instanceof" checks with Vite minification and class name mangling. + // We have to do type tagging a different way. + if (maybeModel.kind !== "video_model") { + return undefined; + } + return maybeModel as VideoModel; }; diff --git a/frontend/libs/components/model-selector/src/lib/classy-model-selector.tsx b/frontend/libs/components/model-selector/src/lib/classy-model-selector.tsx index c8f0373ae2..e4f6425989 100644 --- a/frontend/libs/components/model-selector/src/lib/classy-model-selector.tsx +++ b/frontend/libs/components/model-selector/src/lib/classy-model-selector.tsx @@ -31,6 +31,7 @@ export function ClassyModelSelector({ }, []); const handleModelSelect = (item: PopoverItem) => { + console.log(`Model selector changed on page "${page}": `, item.model); setSelectedModel(page, item.model!); }; diff --git a/frontend/libs/components/promptbox/src/lib/PromptBoxImage.tsx b/frontend/libs/components/promptbox/src/lib/PromptBoxImage.tsx index 44afab9fad..c6729de157 100644 --- a/frontend/libs/components/promptbox/src/lib/PromptBoxImage.tsx +++ b/frontend/libs/components/promptbox/src/lib/PromptBoxImage.tsx @@ -64,6 +64,8 @@ export const PromptBoxImage = ({ }: PromptBoxImageProps) => { useSignals(); + console.log("Selected model:", selectedModel); + useEffect(() => { if (imageMediaId && url) { const referenceImage: RefImage = { @@ -233,6 +235,9 @@ export const PromptBoxImage = ({ return; } + console.log("Selected model:", selectedModel) + console.log("Prompt:", prompt) + setIsEnqueueing(true); gtagEvent("enqueue_image"); diff --git a/frontend/libs/model-list/src/lib/classes/ImageModel.ts b/frontend/libs/model-list/src/lib/classes/ImageModel.ts index 61ca3b9c88..57c026e1d7 100644 --- a/frontend/libs/model-list/src/lib/classes/ImageModel.ts +++ b/frontend/libs/model-list/src/lib/classes/ImageModel.ts @@ -1,9 +1,14 @@ import { ModelCreator } from "src/index.js"; -import { Model } from "./Model.js"; +import { Model, ModelKind } from "./Model.js"; import { ModelCategory } from "../legacy/ModelConfig.js"; import { ModelTag } from "./metadata/ModelTag.js"; export class ImageModel extends Model { + // Typescript type discriminator property + // Since Vite minification and class name mangling can break instanceof checks, + // we have a type discriminator property to check against. + override readonly kind: ModelKind = "image_model"; + // Maximum number of images that can be generated at once readonly maxGenerationCount: number; diff --git a/frontend/libs/model-list/src/lib/classes/Model.ts b/frontend/libs/model-list/src/lib/classes/Model.ts index 4cb06767af..6f822d90c3 100644 --- a/frontend/libs/model-list/src/lib/classes/Model.ts +++ b/frontend/libs/model-list/src/lib/classes/Model.ts @@ -2,8 +2,15 @@ import { ModelCreator } from "./metadata/ModelCreator.js"; import { ModelCategory, ModelConfig } from "../legacy/ModelConfig.js"; import { ModelTag } from "./metadata/ModelTag.js"; +export type ModelKind = "model" | "image_model" | "video_model"; + // NB: Do not create instances of this class directly, use subclasses. export class Model { + // Typescript type discriminator property + // Since Vite minification and class name mangling can break instanceof checks, + // we have a type discriminator property to check against. + readonly kind: ModelKind = "model"; + // A unique frontend-only string for the model readonly id: string; diff --git a/frontend/libs/model-list/src/lib/classes/VideoModel.ts b/frontend/libs/model-list/src/lib/classes/VideoModel.ts index 308e6e1f09..cd71219c07 100644 --- a/frontend/libs/model-list/src/lib/classes/VideoModel.ts +++ b/frontend/libs/model-list/src/lib/classes/VideoModel.ts @@ -1,9 +1,14 @@ import { ModelCreator } from "src/index.js"; -import { Model } from "./Model.js"; +import { Model, ModelKind } from "./Model.js"; import { ModelCategory } from "../legacy/ModelConfig.js"; import { ModelTag } from "./metadata/ModelTag.js"; export class VideoModel extends Model { + // Typescript type discriminator property + // Since Vite minification and class name mangling can break instanceof checks, + // we have a type discriminator property to check against. + override readonly kind: ModelKind = "video_model"; + // Whether the model supports image starting frames readonly startFrame: boolean; diff --git a/frontend/libs/model-list/src/lib/legacy/Models.ts b/frontend/libs/model-list/src/lib/legacy/Models.ts index de3822a65b..f31a653592 100644 --- a/frontend/libs/model-list/src/lib/legacy/Models.ts +++ b/frontend/libs/model-list/src/lib/legacy/Models.ts @@ -240,10 +240,9 @@ export const lookupModelByTauriId = ( // Single exported capability resolver so callers never need to touch anything else export const getCapabilitiesForModel = ( - model?: Model | ModelInfo + model?: Model ): ModelCapabilities => { if (!model) return DEFAULT_CAPABILITIES; - const tauriId = model instanceof Model ? model.tauriId : model.tauri_id; - const cfg = lookupModelByTauriId(tauriId); + const cfg = lookupModelByTauriId(model.tauriId); return cfg?.capabilities ?? DEFAULT_CAPABILITIES; }; diff --git a/frontend/libs/tauri-api/src/lib/enqueue/EnqueueContextualEditImage.ts b/frontend/libs/tauri-api/src/lib/enqueue/EnqueueContextualEditImage.ts index 809bdf14b3..50c15b9fa8 100644 --- a/frontend/libs/tauri-api/src/lib/enqueue/EnqueueContextualEditImage.ts +++ b/frontend/libs/tauri-api/src/lib/enqueue/EnqueueContextualEditImage.ts @@ -1,11 +1,11 @@ import { invoke } from "@tauri-apps/api/core"; import { CommandResult } from "../common/CommandStatus"; -import { ImageModel, ModelInfo } from "@storyteller/model-list"; +import { ImageModel } from "@storyteller/model-list"; export interface EnqueueContextualEditImageRequest { /// The model to use. - model?: ImageModel | ModelInfo | EnqueueContextualEditImageModel; + model?: ImageModel | EnqueueContextualEditImageModel; /// If set, this is the first image in the contextual image set. /// This gets submitted along with `image_media_tokens` and will @@ -93,15 +93,18 @@ export const EnqueueContextualEditImage = async (request: EnqueueContextualEditI let modelName = undefined; if (!!request.model) { + // NB: We can't use "instanceof" checks with Vite minification and class name mangling. if (typeof request.model === "string") { modelName = request.model; - } else if (request.model instanceof ImageModel) { + } else if (typeof request.model.tauriId === "string") { modelName = request.model.tauriId; - } else { - modelName = request.model.tauri_id; } } + if (!modelName) { + throw new Error("No model specified in request: " + JSON.stringify(request)); + } + let mutableRequest : RawEnqueueContextualEditImageRequest = { model: modelName, prompt: request.prompt, diff --git a/frontend/libs/tauri-api/src/lib/enqueue/EnqueueImageInpaint.ts b/frontend/libs/tauri-api/src/lib/enqueue/EnqueueImageInpaint.ts index 3190530e5a..91c3c2c241 100644 --- a/frontend/libs/tauri-api/src/lib/enqueue/EnqueueImageInpaint.ts +++ b/frontend/libs/tauri-api/src/lib/enqueue/EnqueueImageInpaint.ts @@ -74,13 +74,17 @@ export const EnqueueImageInpaint = async (request: EnqueueImageInpaintRequest) : if (!!request.model) { if (typeof request.model === "string") { modelName = request.model; - } else if (request.model instanceof ImageModel) { + } else if ("tauriId" in request.model && typeof request.model.tauriId === "string") { modelName = request.model.tauriId; - } else { + } else if ("tauri_id" in request.model && typeof request.model.tauri_id === "string") { modelName = request.model.tauri_id; } } + if (!modelName) { + throw new Error("No model specified in request: " + JSON.stringify(request)); + } + let mutableRequest : RawEnqueueImageInpaintRequest = { model: modelName, prompt: request.prompt, diff --git a/frontend/libs/tauri-api/src/lib/enqueue/EnqueueTextToImage.ts b/frontend/libs/tauri-api/src/lib/enqueue/EnqueueTextToImage.ts index e515da758d..a65dd3fcc8 100644 --- a/frontend/libs/tauri-api/src/lib/enqueue/EnqueueTextToImage.ts +++ b/frontend/libs/tauri-api/src/lib/enqueue/EnqueueTextToImage.ts @@ -1,6 +1,6 @@ import { invoke } from "@tauri-apps/api/core"; import { CommandResult } from "../common/CommandStatus"; -import { Model, ModelInfo } from "@storyteller/model-list"; +import { Model } from "@storyteller/model-list"; export enum EnqueueTextToImageErrorType { /// Caller didn't specify a model @@ -15,7 +15,7 @@ export enum EnqueueTextToImageErrorType { export interface EnqueueTextToImageRequest { // The model to use. - model?: Model | ModelInfo | EnqueueTextToImageModel; + model?: Model | EnqueueTextToImageModel; // The text prompt. prompt?: string; @@ -86,15 +86,18 @@ export const EnqueueTextToImage = async (request: EnqueueTextToImageRequest) : P let modelName = undefined; if (!!request.model) { + // NB: We can't use "instanceof" checks with Vite minification and class name mangling. if (typeof request.model === "string") { modelName = request.model; - } else if (request.model instanceof Model) { + } else if (typeof request.model.tauriId === "string") { modelName = request.model.tauriId; - } else { - modelName = request.model.tauri_id; } } + if (!modelName) { + throw new Error("No model specified in request: " + JSON.stringify(request)); + } + let mutableRequest : EnqueueTextToImageRawRequest = { model: modelName, prompt: request.prompt,