diff --git a/frontend/apps/artcraft-webapp/src/app/app.tsx b/frontend/apps/artcraft-webapp/src/app/app.tsx index 4f0003483e..e45f81f926 100644 --- a/frontend/apps/artcraft-webapp/src/app/app.tsx +++ b/frontend/apps/artcraft-webapp/src/app/app.tsx @@ -90,15 +90,16 @@ function ProtectedContent() { ? "var(--sidebar-width)" : "calc(var(--sidebar-width-icon) + 1.5rem)"; - // The Edit 3D and video editors host the header's actions + // The Edit 3D, Edit Image, and video editors host the header's actions // (pricing/credits/task queue/profile) inside their own toolbar/header to // reclaim vertical space, so the global header is hidden there — desktop - // only, since the mobile route shows a gate that still needs the header's - // nav chrome. + // only, since the mobile routes show the global chrome (Edit Image keeps the + // bar on mobile; Edit 3D shows a gate that still needs the header's nav). const hideTopBar = !isMobile && (pathname === "/edit-3d" || pathname.startsWith("/edit-3d/") || + pathname === "/edit-image" || pathname === "/video-editor" || pathname.startsWith("/video-editor/")); @@ -108,7 +109,14 @@ function ProtectedContent() { style={{ "--ac-sidebar-offset": sidebarOffset } as React.CSSProperties} > {!hideTopBar && } - + {/* The immersive editors (hideTopBar routes) paint their own #101014 + canvas backdrop; match the inset to it so no lighter strip shows at + the edges. Other pages keep the standard #121212 surface. */} + {isMobile && } diff --git a/frontend/apps/artcraft-webapp/src/main.tsx b/frontend/apps/artcraft-webapp/src/main.tsx index 72e9caa1e0..c605a64d68 100644 --- a/frontend/apps/artcraft-webapp/src/main.tsx +++ b/frontend/apps/artcraft-webapp/src/main.tsx @@ -19,7 +19,10 @@ if (import.meta.env.DEV) { window.location.origin, ); // NB: This is for Brandon to test with storyteller-web locally: - StorytellerApiHostStore.getInstance().setDevelopment(); + // (disabled — it overrides the origin above and points at localhost:12345, + // which breaks dev unless a local backend is running. Re-enable only when + // testing against a local storyteller-web.) + // StorytellerApiHostStore.getInstance().setDevelopment(); } catch (e) { console.warn("Failed to set dev API host override", e); } diff --git a/frontend/apps/artcraft-webapp/src/pages/pagedraw/base-image-selector.tsx b/frontend/apps/artcraft-webapp/src/pages/pagedraw/base-image-selector.tsx index b18919e90f..10f5222d9b 100644 --- a/frontend/apps/artcraft-webapp/src/pages/pagedraw/base-image-selector.tsx +++ b/frontend/apps/artcraft-webapp/src/pages/pagedraw/base-image-selector.tsx @@ -148,8 +148,8 @@ export function BaseImageSelector({ onDrop={onDrop} className={`flex h-full w-full cursor-pointer flex-col items-center justify-center gap-4 rounded-2xl border-2 border-dashed p-8 text-center transition-colors ${ isDragging - ? "border-blue-400 bg-blue-500/10" - : "border-ui-panel-border bg-ui-background hover:border-blue-400/50" + ? "border-primary-400 bg-primary/10" + : "border-ui-panel-border bg-ui-background hover:border-primary-400/50" } ${busy ? "pointer-events-none opacity-60" : ""}`} > -
+
); diff --git a/frontend/apps/artcraft-webapp/src/pages/pagedraw/web-adapter.tsx b/frontend/apps/artcraft-webapp/src/pages/pagedraw/web-adapter.tsx index ae316ce501..3ecee971c6 100644 --- a/frontend/apps/artcraft-webapp/src/pages/pagedraw/web-adapter.tsx +++ b/frontend/apps/artcraft-webapp/src/pages/pagedraw/web-adapter.tsx @@ -21,6 +21,7 @@ import { type BaseSelectorImage, useSceneStore, } from "@storyteller/ui-pagedraw"; +import { CommonAspectRatio, CommonResolution } from "@storyteller/model-list"; import { OmniGenApi, StorytellerApiHostStore, @@ -141,6 +142,29 @@ const uploadBlobAsImage = async ( return mediaToken; }; +// The PromptBox UI emits resolution/aspect-ratio as display labels ("1k", +// "wide", …); the omni-gen REST body wants the CommonResolution / +// CommonAspectRatio enum values ("one_k", "wide", …). Mirror the desktop +// PageDraw adapter's mappers so an unmapped "1k" doesn't 400 the request. +const mapAspectRatio = (ratio?: string): CommonAspectRatio | undefined => { + switch (ratio) { + case "auto": return CommonAspectRatio.Auto; + case "wide": return CommonAspectRatio.Wide; + case "tall": return CommonAspectRatio.Tall; + case "square": return CommonAspectRatio.Square; + default: return undefined; + } +}; + +const mapResolution = (res?: string): CommonResolution | undefined => { + switch (res) { + case "1k": return CommonResolution.OneK; + case "2k": return CommonResolution.TwoK; + case "4k": return CommonResolution.FourK; + default: return undefined; + } +}; + // PageDrawEditRequest.model is an ImageModel object (or possibly a raw id); // the omni-gen REST body wants the string id. const modelIdOf = (model: unknown): string => { @@ -177,8 +201,8 @@ export const useWebPageDrawAdapter = (): PageDrawAdapter => { model: modelIdOf(req.model), prompt: req.prompt ?? null, idempotency_token: crypto.randomUUID(), - aspect_ratio: req.aspectRatio ?? null, - resolution: req.imageResolution ?? null, + aspect_ratio: mapAspectRatio(req.aspectRatio) ?? null, + resolution: mapResolution(req.imageResolution) ?? null, image_batch_count: req.imageCount ?? 1, image_media_tokens: refTokens.length ? refTokens : null, }; 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 dd4c35b0c8..aa87522920 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 @@ -34,6 +34,14 @@ interface ClassyModelSelectorProps { * provider-selection rows. */ variant?: "floating" | "embedded"; + /** + * Whether models that support multiple providers expose the provider-picker + * submenu (hover panel) and the per-row provider icon chips. Defaults to + * true. Pages locked to a single provider (e.g. PageDraw) pass false to show + * only the model name — the default provider is still resolved into the + * store so downstream generation calls keep working. + */ + showProviderSelection?: boolean; } const DEFAULT_PROVIDER_OPTIONS: GenerationProvider[] = [GenerationProvider.Artcraft]; @@ -113,6 +121,7 @@ export function ClassyModelSelector({ providerTooltipDelayMs = 300, maxListHeight = "60vh", variant = "floating", + showProviderSelection = true, ...popoverProps }: ClassyModelSelectorProps) { const { selectedModels, setSelectedModel, setSelectedProvider } = @@ -159,7 +168,10 @@ export function ClassyModelSelector({ const modelId = item.model?.id; const allowedProviders = item.model?.getProviders() || DEFAULT_PROVIDER_OPTIONS; - const hasMultipleProviders = allowedProviders.length >= 2; + // When provider selection is hidden, treat every model as + // single-provider so the submenu and provider chips never render. + const hasMultipleProviders = + showProviderSelection && allowedProviders.length >= 2; return { ...item, @@ -213,6 +225,7 @@ export function ClassyModelSelector({ page, providersByModel, providerTooltipDelayMs, + showProviderSelection, ], ); diff --git a/frontend/libs/components/pagedraw/src/lib/HistoryStack.tsx b/frontend/libs/components/pagedraw/src/lib/HistoryStack.tsx index 856b9896f6..014ca2d00c 100644 --- a/frontend/libs/components/pagedraw/src/lib/HistoryStack.tsx +++ b/frontend/libs/components/pagedraw/src/lib/HistoryStack.tsx @@ -170,7 +170,7 @@ export const HistoryStack = ({
e.stopPropagation()} onClick={() => onEdit(nodeId)} @@ -185,7 +185,7 @@ function DragScrubButton({ return ( - {item.divider &&
} + {item.divider &&
} ))}
, diff --git a/frontend/libs/components/pagedraw/src/lib/components/ui/SideToolbar.tsx b/frontend/libs/components/pagedraw/src/lib/components/ui/SideToolbar.tsx index da59324a0f..4cbe2fad03 100644 --- a/frontend/libs/components/pagedraw/src/lib/components/ui/SideToolbar.tsx +++ b/frontend/libs/components/pagedraw/src/lib/components/ui/SideToolbar.tsx @@ -169,7 +169,7 @@ const SideToolbar: React.FC = ({ extra?: React.ReactNode, ) => (
- @@ -377,7 +377,7 @@ const SideToolbar: React.FC = ({ {open === "shape-color" && (
setOpen(null)} - className="absolute left-14 top-1/2 -translate-y-1/2 rounded-xl border border-[#404040] bg-[#303030] transition-all duration-200 ease-in-out" + className="absolute left-14 top-1/2 -translate-y-1/2 rounded-xl border border-white/10 bg-ui-panel transition-all duration-200 ease-in-out" > {ShapePopout}
@@ -546,7 +546,7 @@ const SideToolbar: React.FC = ({ onMouseLeave={() => { setOpen(null); }} - className="absolute left-14 top-1/2 -translate-y-1/2 rounded-xl border border-[#404040] bg-[#303030] transition-all duration-200 ease-in-out" + className="absolute left-14 top-1/2 -translate-y-1/2 rounded-xl border border-white/10 bg-ui-panel transition-all duration-200 ease-in-out" > {popout}
diff --git a/frontend/libs/components/pagedraw/src/lib/components/ui/SliderWithIndicator.tsx b/frontend/libs/components/pagedraw/src/lib/components/ui/SliderWithIndicator.tsx index 07d41602d4..37ab63ad83 100644 --- a/frontend/libs/components/pagedraw/src/lib/components/ui/SliderWithIndicator.tsx +++ b/frontend/libs/components/pagedraw/src/lib/components/ui/SliderWithIndicator.tsx @@ -58,7 +58,7 @@ const SliderWithIndicator: React.FC = ({ absolute top-6 -translate-x-1/2 transform rounded - bg-[#5F5F68] + bg-ui-controls px-2 py-1 text-xs font-medium text-white shadow-lg diff --git a/frontend/libs/components/pagedraw/src/lib/components/ui/SplitPane.tsx b/frontend/libs/components/pagedraw/src/lib/components/ui/SplitPane.tsx index d8a5a8f938..900effdd97 100644 --- a/frontend/libs/components/pagedraw/src/lib/components/ui/SplitPane.tsx +++ b/frontend/libs/components/pagedraw/src/lib/components/ui/SplitPane.tsx @@ -16,6 +16,12 @@ export type SplitPaneProps = { /** Extra classes for the outer wrapper */ className?: string; singlePaneMode: boolean; + /** + * Fill the parent box instead of reserving 56px for a global top bar. Hosts + * that hide their top bar on this route pass true so the backdrop reaches + * every edge. + */ + fillParentHeight?: boolean; }; export default function SplitPane({ @@ -27,7 +33,9 @@ export default function SplitPane({ onChange, className = "", singlePaneMode = true, + fillParentHeight = false, }: SplitPaneProps) { + const heightClass = fillParentHeight ? "h-full" : "h-[calc(100vh-56px)]"; const rootRef = useRef(null); const [leftPct, setLeftPct] = useState(initialPercent); @@ -65,7 +73,7 @@ export default function SplitPane({ return singlePaneMode ? (
{left} @@ -74,15 +82,15 @@ export default function SplitPane({ ) : (
{left}
diff --git a/frontend/libs/components/pagedraw/src/lib/pagedraw.css b/frontend/libs/components/pagedraw/src/lib/pagedraw.css index e6002b3ec2..2740dbe11f 100644 --- a/frontend/libs/components/pagedraw/src/lib/pagedraw.css +++ b/frontend/libs/components/pagedraw/src/lib/pagedraw.css @@ -3,7 +3,9 @@ .pegboard-bg { inset: 0; z-index: 0; - background-color: #1b1b1b; + /* Themeable canvas backdrop; hosts may set --st-canvas, otherwise the + original dark grey is used. */ + background-color: var(--st-canvas, #101014); background-size: 50px 50px; background-position: 0 0, diff --git a/frontend/libs/components/promptbox/src/lib/PromptBox2D.tsx b/frontend/libs/components/promptbox/src/lib/PromptBox2D.tsx index 4142da259e..1c66983548 100644 --- a/frontend/libs/components/promptbox/src/lib/PromptBox2D.tsx +++ b/frontend/libs/components/promptbox/src/lib/PromptBox2D.tsx @@ -358,7 +358,7 @@ export const PromptBox2D = ({ > {content} -
+
{selectedImageModel?.canUseImagePrompt && isImageRowVisible && ( {content} -
+
{aboveStackSlot} {selectedImageModel?.canUseImagePrompt && isImageRowVisible && (