Add per-image iteration settings and steps/CFG sweeps
This commit is contained in:
+43
-8
@@ -326,7 +326,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<p v-if="studioMode === 'editv2' && v2Mode === 'generate'" class="mt-3 text-xs text-zinc-500">No reference image. Prompt only.</p>
|
||||
<p v-else-if="studioMode === 'editv2' && v2Mode === 'iterate'" class="mt-3 text-xs text-zinc-500">Add more iterations below. Everything saves as one nested library collection.</p>
|
||||
<p v-else-if="studioMode === 'editv2' && v2Mode === 'iterate'" class="mt-3 text-xs text-zinc-500">Use one prompt with different steps, CFG, seeds, and LoRAs below. Everything saves as one library collection.</p>
|
||||
<div v-if="studioMode === 'editv2' && v2Mode === 'refine'" class="mt-3 space-y-2">
|
||||
<p class="text-xs text-zinc-500">Prompt only what should change in the painted area.</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@@ -380,6 +380,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="studioMode === 'edit' || v2PassChaining" class="mt-3 space-y-3">
|
||||
<ImageIterationSweep v-if="studioMode === 'editv2' && v2Mode === 'iterate'" @apply="applyIterationSweep" />
|
||||
<div v-if="!(v2Mode === 'iterate' && !file)" class="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
@@ -435,6 +436,16 @@
|
||||
:keep-items="activeKeepPrompts"
|
||||
:keep-heading="keepPromptHeading"
|
||||
/>
|
||||
<ImageIterationSettings
|
||||
v-if="studioMode === 'editv2' && v2Mode === 'iterate'"
|
||||
:model-value="item"
|
||||
:base-steps="v2Steps"
|
||||
:base-cfg="v2Cfg"
|
||||
:lora-names="imageV2LoraOptions"
|
||||
:engine="v2Engine"
|
||||
:allow-strength="v2Engine === 'krea' && Boolean(file)"
|
||||
@update:model-value="editPassQueue[index] = { ...$event, id: item.id }"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@@ -2242,6 +2253,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { normalizeImageIterations, type ImageIteration } from '~/utils/imageIterations'
|
||||
import { composePromptParts, formatPromptParts, resolvePromptWrappers, restorePromptParts } from '~/utils/promptParts'
|
||||
import { parseEditPasses, looksLikeShotScript } from '~/utils/parseRecommend'
|
||||
import { parseImageBatchScript, parseShotScript } from '~/utils/parseShots'
|
||||
@@ -2496,7 +2508,7 @@ interface QueuedExtension {
|
||||
loraStack?: LoraStackItem[]
|
||||
}
|
||||
|
||||
interface QueuedEditPass {
|
||||
interface QueuedEditPass extends ImageIteration {
|
||||
id: string
|
||||
prompt: string
|
||||
}
|
||||
@@ -3213,7 +3225,7 @@ const editV2Blocked = computed(() => {
|
||||
if (v2Mode.value !== 'generate' && v2Mode.value !== 'iterate' && !file.value) return true
|
||||
if (v2Mode.value === 'compose' && !editRefFile.value) return true
|
||||
if (v2Mode.value === 'refine' && !refineMaskDirty.value) return true
|
||||
if (v2PassChaining.value && editPassQueue.value.some(item => !item.prompt.trim())) return true
|
||||
if (v2Mode.value !== 'iterate' && v2PassChaining.value && editPassQueue.value.some(item => !item.prompt.trim())) return true
|
||||
if (v2Mode.value === 'iterate' && !editPassQueue.value.length) return true
|
||||
return false
|
||||
})
|
||||
@@ -3226,8 +3238,8 @@ const editV2BlockReason = computed(() => {
|
||||
? 'Write a prompt first.'
|
||||
: 'Write a prompt, or pick Landscape / Portrait / Square to fill the frame.'
|
||||
}
|
||||
if (v2Mode.value === 'iterate' && !editPassQueue.value.length) return 'Add at least one more iteration prompt.'
|
||||
if (v2PassChaining.value && editPassQueue.value.some(item => !item.prompt.trim())) return 'Each extra pass needs a prompt.'
|
||||
if (v2Mode.value === 'iterate' && !editPassQueue.value.length) return 'Add a variation or build a steps/CFG sweep.'
|
||||
if (v2Mode.value !== 'iterate' && v2PassChaining.value && editPassQueue.value.some(item => !item.prompt.trim())) return 'Each extra pass needs a prompt.'
|
||||
if (!folderId.value) return 'Choose a library folder before generating.'
|
||||
if (!imageComfyConfigured.value && !comfyOk.value) return 'Image v2 is not configured. Set COMFY_HOST.'
|
||||
return ''
|
||||
@@ -3265,7 +3277,7 @@ const extraPassHint = computed(() => {
|
||||
return 'Each extra pass is another Edit on the last output. You cannot switch graphs between passes.'
|
||||
})
|
||||
const extraPassPlaceholder = computed(() => {
|
||||
if (v2Mode.value === 'iterate') return 'Next iteration prompt…'
|
||||
if (v2Mode.value === 'iterate') return 'Leave blank to use the first prompt…'
|
||||
if (imagePassMode.value === 'batch') return 'Another prompt for the same start still…'
|
||||
return studioMode.value === 'editv2' && v2Mode.value === 'compose'
|
||||
? 'Next compose prompt, applied to the previous pass output…'
|
||||
@@ -5481,6 +5493,9 @@ async function restoreStudioEditJob(payload: {
|
||||
cfg?: number
|
||||
turbo?: boolean
|
||||
referenceStillId?: string
|
||||
passes?: ImageIteration[]
|
||||
passMode?: ImagePassMode
|
||||
seed?: number
|
||||
}) {
|
||||
closeExtend()
|
||||
applyPromptRestore(payload.prompt || '', payload)
|
||||
@@ -5489,6 +5504,10 @@ async function restoreStudioEditJob(payload: {
|
||||
folderId.value = payload.folderId
|
||||
browseFolderId.value = payload.folderId
|
||||
}
|
||||
editPassQueue.value = (payload.passes || []).map(pass => ({ ...pass, id: crypto.randomUUID() }))
|
||||
imagePassMode.value = payload.passMode || 'batch'
|
||||
imageBatchScript.value = ''
|
||||
seedInput.value = payload.seed == null ? '' : String(payload.seed)
|
||||
const v2 = payload.imagePipeline === 'v2'
|
||||
if (v2) {
|
||||
studioMode.value = 'editv2'
|
||||
@@ -5612,6 +5631,8 @@ async function restoreStudioPayload(kind: string, payload: {
|
||||
scaleMegapixels?: number
|
||||
steps?: number
|
||||
referenceStillId?: string
|
||||
passes?: ImageIteration[]
|
||||
passMode?: ImagePassMode
|
||||
}) {
|
||||
if (kind === 'edit' || payload.imagePipeline === 'v2') {
|
||||
await restoreStudioEditJob(payload)
|
||||
@@ -6965,6 +6986,18 @@ function removeQueuedExtension(id: string) {
|
||||
extensionQueue.value = extensionQueue.value.filter(item => item.id !== id)
|
||||
}
|
||||
|
||||
function applyIterationSweep(rows: ImageIteration[]) {
|
||||
if (rows.length < 2) { toast('Use at least two variations for Iterate.'); return }
|
||||
v2Steps.value = rows[0].steps!
|
||||
v2Cfg.value = rows[0].cfg!
|
||||
v2Turbo.value = false
|
||||
imagePassMode.value = 'batch'
|
||||
if (!seedInput.value || seedInput.value === 'random') seedInput.value = String(Math.floor(Math.random() * 2_147_483_647))
|
||||
editPassQueue.value = rows.slice(1).map(row => ({ ...row, id: crypto.randomUUID() }))
|
||||
imageBatchScript.value = ''
|
||||
toast(rows.length + ' variations ready. The first uses the shared settings; each following variation can be edited.')
|
||||
}
|
||||
|
||||
function queueEditPass() {
|
||||
editPassQueue.value.push({
|
||||
id: crypto.randomUUID(),
|
||||
@@ -7271,12 +7304,14 @@ async function editImageV2() {
|
||||
toast('Paint a mask on Still A first. Refine will not fall back to Edit.')
|
||||
return
|
||||
}
|
||||
if (v2PassChaining.value && editPassQueue.value.some(item => !item.prompt.trim())) {
|
||||
if (v2Mode.value !== 'iterate' && v2PassChaining.value && editPassQueue.value.some(item => !item.prompt.trim())) {
|
||||
toast('Each extra pass needs a prompt.')
|
||||
return
|
||||
}
|
||||
absorbPastedImageBatch()
|
||||
const extraPasses = v2PassChaining.value ? editPassQueue.value.map(item => ({ prompt: item.prompt.trim() })) : []
|
||||
let extraPasses: ImageIteration[]
|
||||
try { extraPasses = v2PassChaining.value ? normalizeImageIterations(editPassQueue.value) : [] }
|
||||
catch (error) { toast(error instanceof Error ? error.message : String(error)); return }
|
||||
const hideOut = hideThumbnail.value
|
||||
try {
|
||||
await ensureGenerationNotifyPermission()
|
||||
|
||||
Reference in New Issue
Block a user