From a4ef7ffb059c553bafc16e9f1f63b17934dbfced Mon Sep 17 00:00:00 2001 From: Tanish Manku Date: Thu, 11 Jun 2026 14:29:53 -0700 Subject: [PATCH] Fix desktop video generation ignoring selected resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The desktop video promptbox built the GenerateVideoRequest without ever setting `resolution`, so generation always omitted it and the backend fell back to the model's default (720p) — and billed for it. The cost preview reads the same store value and sends it, so the preview was correct (e.g. 117 for 480p) while the charge stayed at the 720p price (240). Populate request.resolution in buildRequest, mapping the store's legacy display string ("480p"/"720p"/"1080p") to CommonResolution, guarded on the model exposing resolutionOptions. Also fix useImageCostEstimate (webapp) omitting `quality` from its effect deps, which meant the image cost preview didn't refresh when only the quality option changed. --- .../src/lib/cost-estimate-api.ts | 1 + .../promptbox/src/lib/PromptBoxVideo.tsx | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/frontend/apps/artcraft-webapp/src/lib/cost-estimate-api.ts b/frontend/apps/artcraft-webapp/src/lib/cost-estimate-api.ts index 69845e2e68..4a8c91adb1 100644 --- a/frontend/apps/artcraft-webapp/src/lib/cost-estimate-api.ts +++ b/frontend/apps/artcraft-webapp/src/lib/cost-estimate-api.ts @@ -62,6 +62,7 @@ export function useImageCostEstimate(params: ImageCostParams): number | null { params.model, params.aspectRatio, params.resolution, + params.quality, params.numImages, params.hasReferenceImages, params.imageMediaTokenCount, diff --git a/frontend/libs/components/promptbox/src/lib/PromptBoxVideo.tsx b/frontend/libs/components/promptbox/src/lib/PromptBoxVideo.tsx index 0d5c7dad28..c344db9359 100644 --- a/frontend/libs/components/promptbox/src/lib/PromptBoxVideo.tsx +++ b/frontend/libs/components/promptbox/src/lib/PromptBoxVideo.tsx @@ -18,6 +18,7 @@ import { faCircleInfo } from "@fortawesome/pro-regular-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { GalleryItem, GalleryModal } from "@storyteller/ui-gallery-modal"; import { + CommonResolution, SizeIconOption, SizeOption, VideoModel, @@ -58,6 +59,27 @@ type GROK_ASPECT_RATIO = "landscape" | "portrait" | "square"; const EMPTY_CHARACTERS: StoredCharacter[] = []; +// The video store keeps resolution as a legacy display string ("480p" / "720p" +// / "1080p") taken from the model's `resolutionOptions`. The generate request, +// however, needs the `CommonResolution` enum. Map here so the user's resolution +// choice is actually sent: omitting it makes the backend fall back to the +// model's default resolution (and bill for it), which is what made the charge +// disagree with the cost preview — the preview reads the same store value and +// converts it correctly. Snake_case forms are accepted too for robustness. +const RESOLUTION_STRING_TO_COMMON: Record = { + "480p": CommonResolution.FourEightyP, + "720p": CommonResolution.SevenTwentyP, + "1080p": CommonResolution.TenEightyP, + half_k: CommonResolution.HalfK, + one_k: CommonResolution.OneK, + two_k: CommonResolution.TwoK, + three_k: CommonResolution.ThreeK, + four_k: CommonResolution.FourK, + four_eighty_p: CommonResolution.FourEightyP, + seven_twenty_p: CommonResolution.SevenTwentyP, + ten_eighty_p: CommonResolution.TenEightyP, +}; + const DEFAULT_RESOLUTIONS: SizeOption[] = [ { tauriValue: "720p", @@ -767,6 +789,17 @@ export const PromptBoxVideo = ({ request.duration_seconds = duration; } + // Pass the chosen resolution when the model exposes a resolution picker. + // Guarded on `resolutionOptions` so a stale store value (left over from a + // model that did support resolution) isn't sent for one that doesn't. + if (selectedModel.resolutionOptions?.length) { + const mappedResolution = + RESOLUTION_STRING_TO_COMMON[resolution as string]; + if (mappedResolution) { + request.resolution = mappedResolution; + } + } + switch (selectedModel?.tauriId) { case "grok_video": request.grok_aspect_ratio = getGrokAspectRatio();