Flatten image edit controls and let CFG/steps be typed past the old slider caps.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+108
-68
@@ -173,21 +173,37 @@
|
||||
<p v-else-if="identityPrompting" class="mb-2 text-xs text-zinc-500">
|
||||
Write camera, action, and audio. That is the dynamic action prompt. We concatenate the <span class="font-medium text-zinc-300"><Picture 1></span> identity prefix in front of it and send the result to <span class="font-medium text-zinc-300">Reference to Video</span>, not Image to Video.
|
||||
</p>
|
||||
<textarea
|
||||
v-model="prompt"
|
||||
:rows="shotScriptMode || identityPrompting ? 12 : 6"
|
||||
class="hidden max-h-[min(28rem,70vh)] w-full overflow-y-auto rounded-2xl border border-white/10 bg-zinc-950/70 px-4 py-3 text-sm outline-none ring-amber-300/40 placeholder:text-zinc-600 [overflow-wrap:anywhere] focus:ring-2 md:block"
|
||||
:placeholder="promptPlaceholder"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="w-full rounded-2xl border border-white/10 bg-zinc-950/70 px-4 py-3 text-left md:hidden"
|
||||
@click="promptDrawerOpen = true"
|
||||
<div
|
||||
class="grid gap-3"
|
||||
:class="studioMode === 'edit' ? 'md:grid-cols-2' : ''"
|
||||
>
|
||||
<p v-if="prompt.trim()" class="line-clamp-4 whitespace-pre-wrap text-sm text-zinc-200 [overflow-wrap:anywhere]">{{ prompt }}</p>
|
||||
<p v-else class="text-sm text-zinc-600">{{ promptPlaceholder }}</p>
|
||||
<span class="mt-2 inline-block text-xs text-amber-200">{{ prompt.trim() ? 'View / edit prompt' : 'Write prompt' }}</span>
|
||||
</button>
|
||||
<div class="min-w-0">
|
||||
<textarea
|
||||
v-model="prompt"
|
||||
:rows="shotScriptMode || identityPrompting ? 12 : 6"
|
||||
class="hidden max-h-[min(28rem,70vh)] w-full overflow-y-auto rounded-2xl border border-white/10 bg-zinc-950/70 px-4 py-3 text-sm outline-none ring-amber-300/40 placeholder:text-zinc-600 [overflow-wrap:anywhere] focus:ring-2 md:block"
|
||||
:placeholder="promptPlaceholder"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="w-full rounded-2xl border border-white/10 bg-zinc-950/70 px-4 py-3 text-left md:hidden"
|
||||
@click="promptDrawerOpen = true"
|
||||
>
|
||||
<p v-if="prompt.trim()" class="line-clamp-4 whitespace-pre-wrap text-sm text-zinc-200 [overflow-wrap:anywhere]">{{ prompt }}</p>
|
||||
<p v-else class="text-sm text-zinc-600">{{ promptPlaceholder }}</p>
|
||||
<span class="mt-2 inline-block text-xs text-amber-200">{{ prompt.trim() ? 'View / edit prompt' : 'Write prompt' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<label v-if="studioMode === 'edit'" class="block min-w-0 text-sm">
|
||||
<span class="mb-2 block font-medium text-zinc-300">Negative prompt</span>
|
||||
<textarea
|
||||
v-model="editNegative"
|
||||
rows="2"
|
||||
class="min-h-[4.5rem] w-full rounded-2xl border border-white/10 bg-zinc-950/70 px-4 py-3 text-sm outline-none ring-amber-300/40 placeholder:text-zinc-600 [overflow-wrap:anywhere] focus:ring-2 md:h-[calc(100%-1.75rem)] md:min-h-[8.5rem]"
|
||||
placeholder="What to avoid…"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<p v-if="shotScriptMode && studioMode === 'video' && parsedShots.length" class="mt-2 text-xs text-zinc-500 md:hidden">
|
||||
{{ parsedShots.length }} shot{{ parsedShots.length === 1 ? '' : 's' }} in this script. Open the prompt to scroll through them.
|
||||
</p>
|
||||
@@ -601,16 +617,50 @@
|
||||
@remove="deleteGenerationPreset"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="max-w-md space-y-2">
|
||||
<LoraSelect v-model="imageLoraStack" :names="imageLoraOptions" label="LoRA" empty-label="None" compact />
|
||||
<GenerationPresetBar
|
||||
:presets="imageGenerationPresets"
|
||||
v-model:selected-id="imagePresetId"
|
||||
v-model:name="imagePresetName"
|
||||
@load="loadGenerationPreset"
|
||||
@save="saveGenerationPreset"
|
||||
@remove="deleteGenerationPreset"
|
||||
/>
|
||||
<div v-else class="space-y-3">
|
||||
<p class="text-xs text-zinc-500">Flux.2 Klein 9B. Shares the desktop GPU with video.</p>
|
||||
<div class="max-w-md space-y-2">
|
||||
<LoraSelect v-model="imageLoraStack" :names="imageLoraOptions" label="LoRA" empty-label="None" compact />
|
||||
<GenerationPresetBar
|
||||
:presets="imageGenerationPresets"
|
||||
v-model:selected-id="imagePresetId"
|
||||
v-model:name="imagePresetName"
|
||||
@load="loadGenerationPreset"
|
||||
@save="saveGenerationPreset"
|
||||
@remove="deleteGenerationPreset"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<label class="block text-sm">
|
||||
<span class="mb-1 block text-zinc-400">Steps</span>
|
||||
<input
|
||||
v-model.number="editSteps"
|
||||
type="number"
|
||||
:min="IMAGE_STEPS_MIN"
|
||||
:max="IMAGE_STEPS_MAX"
|
||||
step="1"
|
||||
class="w-full rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm outline-none ring-amber-300/40 focus:ring-2"
|
||||
@change="onEditStepsNumber"
|
||||
>
|
||||
</label>
|
||||
<label class="block text-sm">
|
||||
<span class="mb-1 block text-zinc-400">CFG</span>
|
||||
<input
|
||||
v-model.number="editCfg"
|
||||
type="number"
|
||||
:min="IMAGE_CFG_MIN"
|
||||
:max="IMAGE_CFG_MAX"
|
||||
:step="IMAGE_CFG_STEP"
|
||||
class="w-full rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm outline-none ring-amber-300/40 focus:ring-2"
|
||||
@change="onEditCfgNumber"
|
||||
>
|
||||
</label>
|
||||
<label class="col-span-2 block text-sm">
|
||||
<span class="mb-1 block text-zinc-400">Seed</span>
|
||||
<input v-model="seedInput" class="w-full rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm" placeholder="random">
|
||||
</label>
|
||||
</div>
|
||||
<p class="text-xs text-zinc-500">Graph default is 20 steps, CFG 1, euler. CFG 0–20 (0.1 steps), steps 1–50. A second still switches the two-image branch on.</p>
|
||||
</div>
|
||||
|
||||
<details v-if="studioMode === 'video'" class="rounded-2xl border border-white/10 bg-zinc-950/40 px-4 py-3">
|
||||
@@ -660,11 +710,11 @@
|
||||
:min="CFG_MIN"
|
||||
:max="CFG_MAX"
|
||||
:step="CFG_STEP"
|
||||
class="w-20 rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm outline-none ring-amber-300/40 focus:ring-2"
|
||||
class="w-24 rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm outline-none ring-amber-300/40 focus:ring-2"
|
||||
@change="onCfgNumber"
|
||||
>
|
||||
</div>
|
||||
<p class="mt-1 text-[11px] text-zinc-500">{{ ltxVideo ? 'Default 3.5 for PinkCherry' : (turbo ? 'Default 1.5 with Turbo LoRA' : 'Default 4.0 with High-Fidelity') }}</p>
|
||||
<p class="mt-1 text-[11px] text-zinc-500">{{ ltxVideo ? 'Default 3.5 for PinkCherry' : (turbo ? 'Default 1.5 with Turbo LoRA' : 'Default 4.0 with High-Fidelity') }}. Type 0–20 (0.1 steps); the box is the source of truth.</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<label class="text-sm">
|
||||
@@ -688,7 +738,6 @@
|
||||
<span class="mb-1 block text-zinc-400">Seed</span>
|
||||
<input v-model="seedInput" class="w-full rounded-xl border border-white/10 bg-zinc-950 px-3 py-2" placeholder="random">
|
||||
</label>
|
||||
<LoraSelect v-model="videoLoraStack" :names="videoLoraOptions" label="LoRA" empty-label="None" :clip-strength="false" />
|
||||
<div>
|
||||
<p class="mb-2 text-xs uppercase tracking-wider text-zinc-500">Sampler & scheduler</p>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
@@ -713,35 +762,6 @@
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details v-else class="rounded-2xl border border-white/10 bg-zinc-950/40 px-4 py-3">
|
||||
<summary class="cursor-pointer text-sm font-medium">Image edit settings</summary>
|
||||
<div class="mt-4 space-y-4">
|
||||
<p class="text-xs text-zinc-500">
|
||||
Flux.2 Klein 9B. Shares the desktop GPU with video.
|
||||
</p>
|
||||
<label class="block text-sm">
|
||||
<span class="mb-1 block text-zinc-400">Negative prompt</span>
|
||||
<textarea v-model="editNegative" rows="2" class="w-full rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm" />
|
||||
</label>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<label class="block text-sm">
|
||||
<span class="mb-1 block text-zinc-400">Steps · {{ editSteps }}</span>
|
||||
<input v-model.number="editSteps" type="range" min="4" max="40" step="1" class="w-full">
|
||||
</label>
|
||||
<label class="block text-sm">
|
||||
<span class="mb-1 block text-zinc-400">CFG · {{ editCfg }}</span>
|
||||
<input v-model.number="editCfg" type="range" min="0.5" max="4" step="0.1" class="w-full">
|
||||
</label>
|
||||
<label class="block text-sm sm:col-span-2">
|
||||
<span class="mb-1 block text-zinc-400">Seed</span>
|
||||
<input v-model="seedInput" class="w-full rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm" placeholder="random">
|
||||
</label>
|
||||
</div>
|
||||
<LoraSelect v-model="imageLoraStack" :names="imageLoraOptions" label="LoRA" empty-label="None" />
|
||||
<p class="text-xs text-zinc-500">Graph default is 20 steps, CFG 1, euler. A second still switches the two-image branch on.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<label
|
||||
v-if="studioMode === 'video' && queuedExtensionCount > 0"
|
||||
class="flex cursor-pointer items-start gap-3 rounded-2xl border border-white/10 bg-zinc-950/40 px-4 py-3"
|
||||
@@ -1605,7 +1625,20 @@ import {
|
||||
readLoraStack,
|
||||
type LoraStackItem
|
||||
} from '~/utils/loras'
|
||||
import type { GenerationPreset } from '~/utils/generationPresets'
|
||||
import {
|
||||
clampImageCfg,
|
||||
clampImageSteps,
|
||||
clampVideoCfg,
|
||||
IMAGE_CFG_MAX,
|
||||
IMAGE_CFG_MIN,
|
||||
IMAGE_CFG_STEP,
|
||||
IMAGE_STEPS_MAX,
|
||||
IMAGE_STEPS_MIN,
|
||||
VIDEO_CFG_MAX,
|
||||
VIDEO_CFG_MIN,
|
||||
VIDEO_CFG_STEP,
|
||||
type GenerationPreset
|
||||
} from '~/utils/generationPresets'
|
||||
import {
|
||||
coerceVideoWorkflow,
|
||||
composeVideoWorkflow,
|
||||
@@ -1800,9 +1833,9 @@ const videoPresetId = ref('')
|
||||
const imagePresetId = ref('')
|
||||
const videoPresetName = ref('')
|
||||
const imagePresetName = ref('')
|
||||
const CFG_MIN = 1
|
||||
const CFG_MAX = 10
|
||||
const CFG_STEP = 0.5
|
||||
const CFG_MIN = VIDEO_CFG_MIN
|
||||
const CFG_MAX = VIDEO_CFG_MAX
|
||||
const CFG_STEP = VIDEO_CFG_STEP
|
||||
const CFG_TURBO = 1.5
|
||||
const CFG_QUALITY = 4
|
||||
const cfg = ref(CFG_TURBO)
|
||||
@@ -2847,8 +2880,15 @@ function clampDuration(value: number) {
|
||||
}
|
||||
|
||||
function clampCfg(value: number) {
|
||||
if (!Number.isFinite(value)) return turbo.value ? CFG_TURBO : CFG_QUALITY
|
||||
return Math.min(CFG_MAX, Math.max(CFG_MIN, Math.round(value / CFG_STEP) * CFG_STEP))
|
||||
return clampVideoCfg(value, turbo.value ? CFG_TURBO : CFG_QUALITY)
|
||||
}
|
||||
|
||||
function onEditStepsNumber() {
|
||||
editSteps.value = clampImageSteps(editSteps.value)
|
||||
}
|
||||
|
||||
function onEditCfgNumber() {
|
||||
editCfg.value = clampImageCfg(editCfg.value)
|
||||
}
|
||||
|
||||
function onCfgSlider(event: Event) {
|
||||
@@ -2925,8 +2965,8 @@ function currentPresetSnapshot() {
|
||||
kind: 'image' as const,
|
||||
loraStack: filterLoraStackForStudio(imageLoraStack.value, ltxEnabled.value),
|
||||
settings: {
|
||||
steps: editSteps.value,
|
||||
cfg: editCfg.value,
|
||||
steps: clampImageSteps(editSteps.value),
|
||||
cfg: clampImageCfg(editCfg.value),
|
||||
negative: editNegative.value
|
||||
}
|
||||
}
|
||||
@@ -2954,8 +2994,8 @@ function applyGenerationPreset(preset: GenerationPreset) {
|
||||
if (preset.kind === 'image') {
|
||||
imageLoraStack.value = stack
|
||||
const settings = preset.settings as { steps?: number; cfg?: number; negative?: string }
|
||||
if (typeof settings.steps === 'number') editSteps.value = settings.steps
|
||||
if (typeof settings.cfg === 'number') editCfg.value = settings.cfg
|
||||
if (typeof settings.steps === 'number') editSteps.value = clampImageSteps(settings.steps)
|
||||
if (typeof settings.cfg === 'number') editCfg.value = clampImageCfg(settings.cfg)
|
||||
if (typeof settings.negative === 'string') editNegative.value = settings.negative
|
||||
imagePresetId.value = preset.id
|
||||
imagePresetName.value = preset.name
|
||||
@@ -4603,8 +4643,8 @@ async function editImage() {
|
||||
if (editRefFile.value) body.append('image2', editRefFile.value)
|
||||
body.append('prompt', prompt.value.trim())
|
||||
body.append('negative', editNegative.value)
|
||||
body.append('steps', String(editSteps.value))
|
||||
body.append('cfg', String(editCfg.value))
|
||||
body.append('steps', String(clampImageSteps(editSteps.value)))
|
||||
body.append('cfg', String(clampImageCfg(editCfg.value)))
|
||||
body.append('seed', seedInput.value || 'random')
|
||||
body.append('folderId', folderId.value)
|
||||
body.append('hideInput', String(showPrivacyToggles.value && hideInputPreview.value))
|
||||
|
||||
Reference in New Issue
Block a user