feat(generation): add a 1K/2K base resolution tier to Krea 2

Krea 2 generated at a fixed ~1MP across every aspect ratio. The comfy
builds now take a resolution tier where 2K doubles each bucket to ~4MP,
which is the low end of what was asked for (CU-868m15v7g) and what the
orchestrator is now ready to serve.

Defaults to 1K, so per-generation Buzz cost only moves for someone who
opts in. The FAL medium/large tiers get no selector: that API takes size
+ aspectRatio with no width/height, so a tier there would promise
dimensions the orchestrator never reads. Edit and community checkpoints
keep it, since both always run a comfy build.

Mirrored across both graph lanes for the dual-graph window, with three
new differential shapes covering comfy-2K, FAL-rejects-tier and
edit-keeps-tier. The Resolution control also moves above Aspect Ratio in
both forms, and the docs row claiming it lives in the Advanced accordion
under Wan/Sora was wrong in both halves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
briant
2026-09-09 11:02:15 -06:00
parent 50037507a1
commit d54868ebf5
8 changed files with 175 additions and 81 deletions
+22 -21
View File
@@ -1,29 +1,30 @@
# Advanced Section Controllers (GenerationForm)
All controllers/nodes rendered inside the `<AccordionLayout label="Advanced">` section in [GenerationForm.tsx](src/components/generation_v2/GenerationForm.tsx#L757-L1087).
All controllers/nodes rendered inside the `<AccordionLayout label="Advanced">` section in [GenerationForm.tsx](src/components/generation_v2/GenerationForm.tsx#L1974-L2629).
| # | Node Name | Label | Input Component | Description | Conditional |
|---|-----------|-------|-----------------|-------------|-------------|
| 1 | `resolution` | Resolution | `SegmentedControlWrapper` | Video quality/resolution selector | Wan/Sora video ecosystems |
| 2 | `cfgScale` | CFG Scale | `SliderInput` | Controls how closely generation follows the text prompt | — |
| 3 | `sampler` | Sampler | `SelectInput` | Sampling method — each produces different results | — |
| 4 | `scheduler` | Scheduler | `SelectInput` | Controls the noise schedule during generation | SdCpp ecosystems |
| 5 | `steps` | Steps | `SliderInput` | Number of iterations spent generating | — |
| 6 | `movementAmplitude` | Movement Amplitude | `SegmentedControlWrapper` | Camera movement and subject action scale | Vidu |
| 7 | `seed` | Seed | `SeedInput` | Random seed for reproducibility | — |
| 8 | `clipSkip` | CLIP Skip | `SliderInput` | Skip CLIP layers | SD only |
| 9 | `denoise` | Denoise Strength | `SliderInput` | Denoising strength for img2img | img2img only (renders `null` when no meta) |
| 10 | `vae` | VAE | `ResourceSelectInput` | Additional color and detail improvements | SD only |
| 11 | `enhancedCompatibility` | Enhanced Compatibility | `Checkbox` | Toggle enhanced compatibility mode | — |
| 12 | `usePro` | Pro Mode | `Checkbox` | Higher quality generation (more credits) | Sora |
| 13 | `fluxUltraRaw` | Raw Mode | `Checkbox` | More natural, less processed look | Flux Ultra |
| 14 | `transparent` | Transparent Background | `Checkbox` | Generate image with transparent background | OpenAI |
| 15 | `quality` | Quality | `SelectInput` | Quality level selector | OpenAI |
| 16 | `enablePromptEnhancer` | Enhance prompt | `Checkbox` | Automatically improve prompt for better results | Video ecosystems |
| 17 | `draft` | Draft Mode | `Checkbox` | Generate faster at lower quality | Wan v2.2-5b |
| 18 | `shift` | Shift | `SliderInput` | Shift parameter | Wan v2.2, v2.2-5b |
| 19 | `interpolatorModel` | Interpolator | `SelectInput` | Interpolator model selector | Wan v2.2 |
| 1 | `cfgScale` | CFG Scale | `SliderInput` | Controls how closely generation follows the text prompt | — |
| 2 | `sampler` | Sampler | `SelectInput` | Sampling method — each produces different results | — |
| 3 | `scheduler` | Scheduler | `SelectInput` | Controls the noise schedule during generation | SdCpp ecosystems |
| 4 | `steps` | Steps | `SliderInput` | Number of iterations spent generating | |
| 5 | `movementAmplitude` | Movement Amplitude | `SegmentedControlWrapper` | Camera movement and subject action scale | Vidu |
| 6 | `seed` | Seed | `SeedInput` | Random seed for reproducibility | — |
| 7 | `clipSkip` | CLIP Skip | `SliderInput` | Skip CLIP layers | SD only |
| 8 | `denoise` | Denoise Strength | `SliderInput` | Denoising strength for img2img | img2img only (renders `null` when no meta) |
| 9 | `vae` | VAE | `ResourceSelectInput` | Additional color and detail improvements | SD only |
| 10 | `enhancedCompatibility` | Enhanced Compatibility | `Checkbox` | Toggle enhanced compatibility mode | — |
| 11 | `usePro` | Pro Mode | `Checkbox` | Higher quality generation (more credits) | Sora |
| 12 | `fluxUltraRaw` | Raw Mode | `Checkbox` | More natural, less processed look | Flux Ultra |
| 13 | `transparent` | Transparent Background | `Checkbox` | Generate image with transparent background | OpenAI |
| 14 | `quality` | Quality | `SelectInput` | Quality level selector | OpenAI |
| 15 | `enablePromptEnhancer` | Enhance prompt | `Checkbox` | Automatically improve prompt for better results | Video ecosystems |
| 16 | `draft` | Draft Mode | `Checkbox` | Generate faster at lower quality | Wan v2.2-5b |
| 17 | `shift` | Shift | `SliderInput` | Shift parameter | Wan v2.2, v2.2-5b |
| 18 | `interpolatorModel` | Interpolator | `SelectInput` | Interpolator model selector | Wan v2.2 |
**Note:** There is also a commented-out duplicate `draft` controller (lines 10751086) labeled "Turbo Mode" for Wan v2.2 — currently inactive.
**Not in this section:** `resolution` renders in the main form body, directly above `aspectRatio` — not in Advanced. It is a quality tier on the video ecosystems and a base-resolution tier on the image ecosystems that expose it (Seedream, Nano Banana, Lens, HiDream-O1, and Krea 2's comfy builds, where 1K/2K scales the aspect-ratio dimensions).
**Note:** There is also a commented-out duplicate `draft` controller (lines 25872598) labeled "Turbo Mode" for Wan v2.2 — currently inactive.
All controllers use the `<Controller graph={graph} name="..." />` pattern and only render when the current workflow/ecosystem graph exposes the corresponding node.
+1 -1
View File
@@ -442,7 +442,7 @@ home); full suite + typecheck + lint green; Briant reviews the final diff.
| image: flux2 (dev/flex/pro/max) | DONE | DONE | mode by model.id; only dev carries resources |
| image: flux2-klein (9B/9B-base/4B/4B-base) | DONE | DONE | FOUR ecosystems share the graph — mode from ecosystem, not model. negativePrompt is NOT a snippet target (v1's own comment claims it self-registers; the differential says no). Handler pins distilled steps/cfg even though the graph exposes a steps slider — v1 quirk, mirrored |
| image: boogu (base/turbo/edit/editTurbo) | DONE | DONE | workflowVersions: version options are WORKFLOW-scoped and the MODEL WINS the workflow (probed — an edit checkpoint on txt2img parses as img2img:edit, model kept; v1's index-remap transform is dead code). The cross-workflow rewrite lives in `reconcile.ts` (`deriveWorkflowFromModel`, registry `workflowScopedVersions`) since a family cannot change the root workflow. negativePrompt only in base/edit modes |
| image: krea2 (fal/raw/turbo/editRaw/editTurbo) | DONE | DONE | locked checkpoint; version selector splits across FAL (creativity/styleReferences) and comfy (LoRA/cfg/steps) engines; img2img:edit narrows the picker to comfy builds and the lock substitutes FAL tiers to the edit default. NO cross-workflow pull (unlike boogu) |
| image: krea2 (fal/raw/turbo/editRaw/editTurbo) | DONE | DONE | locked checkpoint; version selector splits across FAL (creativity/styleReferences) and comfy (LoRA/cfg/steps) engines; img2img:edit narrows the picker to comfy builds and the lock substitutes FAL tiers to the edit default. 1K/2K resolution tier on the comfy builds only (`defFamily` AR, 2K doubles each side); FAL takes size + aspectRatio, so no tier. NO cross-workflow pull (unlike boogu) |
| image: imagen4 | DONE | DONE | locked single version; negative prompt registers at top level |
| image: pony-v7 | DONE | DONE | LoRAs, no negative prompt; ecosystem-defaults lock applies |
| image: reve | DONE | DONE | locked; AR on txt2img only, edit takes 1-4 reference frames |
@@ -336,6 +336,23 @@ export function ImageGenerationForm({ store }: { store: GenerationStore }) {
/>
)}
/>
<Controller
graph={imageHub}
name="resolution"
render={({ value, meta, onChange }) => (
<div className="flex flex-col gap-1">
<Input.Label>Resolution</Input.Label>
<SegmentedControlWrapper
value={value}
onChange={(v) => onChange(v as typeof value)}
data={(meta as { options: { label: string; value: string }[] }).options.map((o) => ({
label: o.label,
value: o.value,
}))}
/>
</div>
)}
/>
<Controller
graph={imageHub}
name="aspectRatio"
@@ -356,23 +373,6 @@ export function ImageGenerationForm({ store }: { store: GenerationStore }) {
);
}}
/>
<Controller
graph={imageHub}
name="resolution"
render={({ value, meta, onChange }) => (
<div className="flex flex-col gap-1">
<Input.Label>Resolution</Input.Label>
<SegmentedControlWrapper
value={value}
onChange={(v) => onChange(v as typeof value)}
data={(meta as { options: { label: string; value: string }[] }).options.map((o) => ({
label: o.label,
value: o.value,
}))}
/>
</div>
)}
/>
<Controller
graph={imageHub}
name="creativity"
+19 -19
View File
@@ -1772,6 +1772,25 @@ export function GenerationForm() {
)}
/>
{/* Resolution */}
<Controller
graph={graph}
name="resolution"
render={({ value, meta, onChange }) => (
<div className="flex flex-col gap-1">
<Input.Label>Resolution</Input.Label>
<SegmentedControlWrapper
value={value}
onChange={(v) => onChange(v as typeof value)}
data={meta.options.map((o: { label: string; value: string }) => ({
label: o.label,
value: o.value,
}))}
/>
</div>
)}
/>
{/* Aspect ratio */}
<Controller
graph={graph}
@@ -1858,25 +1877,6 @@ export function GenerationForm() {
)}
/>
{/* Resolution (Wan/Sora video quality) */}
<Controller
graph={graph}
name="resolution"
render={({ value, meta, onChange }) => (
<div className="flex flex-col gap-1">
<Input.Label>Resolution</Input.Label>
<SegmentedControlWrapper
value={value}
onChange={(v) => onChange(v as typeof value)}
data={meta.options.map((o: { label: string; value: string }) => ({
label: o.label,
value: o.value,
}))}
/>
</div>
)}
/>
{/* Prime (Wan 3.0) */}
<Controller
graph={graph}
@@ -75,3 +75,38 @@ describe('krea2 img2img:edit bases', () => {
expect(stepsMax(g)).toBe(60);
});
});
describe('krea2 base resolution tier', () => {
const dimsOf = (g: any) => {
const { width, height } = g.getSnapshot().aspectRatio ?? {};
return { width, height };
};
it('is offered on the comfy builds and defaults to 1K', () => {
const g = init('txt2img', krea2VersionIds.raw);
expect(g.hasNode('resolution')).toBe(true);
expect(g.getSnapshot().resolution).toBe('1K');
expect(dimsOf(g)).toEqual({ width: 1024, height: 1024 });
});
it('doubles every side at 2K, keeping each divisible by 32', () => {
const g = init('txt2img', krea2VersionIds.turbo);
g.set({ resolution: '2K', aspectRatio: '16:9' });
expect(dimsOf(g)).toEqual({ width: 2752, height: 1536 });
const { width, height } = dimsOf(g);
expect(width % 32).toBe(0);
expect(height % 32).toBe(0);
});
// FAL takes size + aspectRatio only, so a tier there would promise dimensions
// the orchestrator never reads.
it('is hidden on the FAL size tiers', () => {
expect(init('txt2img', krea2VersionIds.medium).hasNode('resolution')).toBe(false);
expect(init('txt2img', krea2VersionIds.large).hasNode('resolution')).toBe(false);
});
it('survives on edit, where a FAL version still runs a comfy build', () => {
expect(init('img2img:edit', krea2VersionIds.medium).hasNode('resolution')).toBe(true);
expect(init('txt2img', communityVersionId).hasNode('resolution')).toBe(true);
});
});
+40 -11
View File
@@ -136,12 +136,31 @@ const krea2AspectRatioDimensions = {
'9:16': { width: 768, height: 1376 },
} satisfies Partial<Record<GenerationAspectRatio, AspectRatioDimensions>>;
const krea2AspectRatioOptions = (
Object.keys(krea2AspectRatioDimensions) as (keyof typeof krea2AspectRatioDimensions)[]
).map((ratio) => {
const { width, height } = krea2AspectRatioDimensions[ratio];
return { label: ratio, value: ratio, width, height };
});
/** 2K doubles each ~1MP bucket to ~4MP. The FAL tiers take `size` + `aspectRatio` only, so they get no tier. */
const krea2ResolutionOptions = [
{ label: '1K', value: '1K' },
{ label: '2K', value: '2K' },
] as const;
const krea2AspectRatioOptionsFor = (scale: number) =>
(Object.keys(krea2AspectRatioDimensions) as (keyof typeof krea2AspectRatioDimensions)[]).map(
(ratio) => {
const { width, height } = krea2AspectRatioDimensions[ratio];
return { label: ratio, value: ratio, width: width * scale, height: height * scale };
}
);
const krea2AspectRatioOptionsByResolution: Record<
string,
ReturnType<typeof krea2AspectRatioOptionsFor>
> = {
'1K': krea2AspectRatioOptionsFor(1),
'2K': krea2AspectRatioOptionsFor(2),
};
/** Edit runs a comfy build whichever version is picked, so it keeps the tier; unknown ids are community checkpoints, comfy-only. */
const krea2UsesComfyEngine = (modelId?: number, workflow?: string) =>
workflow === 'img2img:edit' || modelId === undefined || !krea2VersionIdToSize.has(modelId);
/** Standard preferred ratios — substitute 4:5 for 3:4 since Krea lacks 3:4. */
const krea2PriorityRatios = ['16:9', '4:3', '1:1', '4:5', '9:16'];
@@ -326,13 +345,23 @@ export const krea2Graph = new DataGraph<
},
['workflow']
)
.node(
'resolution',
(ctx) => ({
...enumNode({ options: krea2ResolutionOptions, defaultValue: '1K' }),
when: krea2UsesComfyEngine(ctx.model?.id, ctx.workflow),
}),
['model', 'workflow']
)
.node(
'aspectRatio',
aspectRatioNode({
options: krea2AspectRatioOptions,
defaultValue: '1:1',
priorityOptions: krea2PriorityRatios,
})
(ctx) =>
aspectRatioNode({
options: krea2AspectRatioOptionsByResolution[ctx.resolution ?? '1K'],
defaultValue: '1:1',
priorityOptions: krea2PriorityRatios,
}),
['resolution']
)
// Unknown ids are community checkpoints. Only the comfy builds can load one via
// `diffusionModel`, so they fall back off the FAL tiers — and to the full-step
@@ -203,6 +203,9 @@ const KREA2_ONLY_SHAPES: AnyRecord[] = [
styleReferences: [{ image: 'https://example.com/s.png', strength: 0.7 }, {}],
},
{ prompt: 'a cat', model: 3072332 }, // comfy turbo
{ prompt: 'a cat', model: 3072332, resolution: '2K' }, // comfy 2K tier
{ prompt: 'a cat', model: 2983023, resolution: '2K' }, // FAL: no tier to accept
{ prompt: 'a cat', model: 2983023, images: [IMG], resolution: '2K' }, // edit keeps the tier
{ prompt: 'a cat', model: 2983023, images: [IMG] }, // fal model where edit rows substitute
// community checkpoints (unknown ids): comfy-only, so they fall to the RAW
// full-step build — and to editRaw on edit (kaydaxter's custom-checkpoint fix)
@@ -1,5 +1,5 @@
import { z } from 'zod';
import { branch, defineGraph } from 'form-graph';
import { branch, defFamily, defineGraph } from 'form-graph';
import { checkpointDef } from '../checkpoint';
import { SEED, aspectRatioDef, enumDef, imagesDef, workflowScoped } from '../defs';
import {
@@ -70,10 +70,29 @@ const krea2AspectRatioDimensions: Record<string, { width: number; height: number
'9:16': { width: 768, height: 1376 },
};
const krea2AspectRatioOptions = Object.keys(krea2AspectRatioDimensions).map((ratio) => {
const { width, height } = krea2AspectRatioDimensions[ratio]!;
return { label: ratio, value: ratio, width, height };
});
/** 2K doubles each ~1MP bucket to ~4MP. */
const krea2ResolutionOptions = [
{ label: '1K', value: '1K' },
{ label: '2K', value: '2K' },
] as const;
const krea2AspectRatioOptionsFor = (scale: number) =>
Object.keys(krea2AspectRatioDimensions).map((ratio) => {
const { width, height } = krea2AspectRatioDimensions[ratio]!;
return { label: ratio, value: ratio, width: width * scale, height: height * scale };
});
const krea2AspectRatioOptionsByResolution: Record<
string,
ReturnType<typeof krea2AspectRatioOptionsFor>
> = {
'1K': krea2AspectRatioOptionsFor(1),
'2K': krea2AspectRatioOptionsFor(2),
};
/** Edit and community checkpoints are comfy-only, so they keep the tier too. */
const krea2UsesComfyEngine = (modelId?: number, workflow?: string) =>
workflow === 'img2img:edit' || modelId == null || !krea2VersionIdToSize.has(modelId);
const krea2PriorityRatios = ['16:9', '4:3', '1:1', '4:5', '9:16'];
@@ -207,6 +226,17 @@ const editRaw = defineGraph<Krea2VariantExt>()
/** Tagged: v1's `krea2Variant` computed becomes the branch key. */
const variants = branch('krea2Variant', variantOf, { fal, raw, turbo, editRaw, editTurbo });
const RESOLUTION = enumDef({ options: krea2ResolutionOptions, default: '1K' });
const AR = defFamily((resolution: string) =>
aspectRatioDef({
options:
krea2AspectRatioOptionsByResolution[resolution] ?? krea2AspectRatioOptionsByResolution['1K']!,
default: '1:1',
priorityOptions: krea2PriorityRatios,
})
);
export const krea2 = defineGraph<FamilyExt>({ scope: familyScope })
.field('model', ({ _ext }) => {
const isEdit = _ext.workflow === 'img2img:edit';
@@ -218,14 +248,10 @@ export const krea2 = defineGraph<FamilyExt>({ scope: familyScope })
defaultModelId: isEdit ? KREA2_EDIT_DEFAULT_VERSION_ID : krea2VersionIds.raw,
});
})
.field(
'aspectRatio',
aspectRatioDef({
options: krea2AspectRatioOptions,
default: '1:1',
priorityOptions: krea2PriorityRatios,
})
.field('resolution', ({ model, _ext }) =>
krea2UsesComfyEngine(modelIdOf(model) ?? undefined, _ext.workflow) ? RESOLUTION : null
)
.field('aspectRatio', ({ resolution }) => AR(resolution ?? '1K'))
.use(variants)
// negativePrompt exists only in the comfy variants; its in-branch snippet
// registration never fires