Add optional reference photo conditioning to Flux generation

This commit is contained in:
Towsty
2026-09-06 23:05:36 -05:00
parent bb91b4fd3f
commit 53bb58bbd5
7 changed files with 66 additions and 15 deletions
+9 -8
View File
@@ -182,9 +182,9 @@
</label>
</div>
<div v-if="studioMode === 'edit' || (studioMode === 'editv2' && (v2Mode === 'compose' || (v2Mode === 'iterate' && file)))" class="rounded-2xl border border-dashed border-white/15 bg-zinc-950/50 p-4">
<div v-if="fluxTextReference || studioMode === 'edit' || (studioMode === 'editv2' && (v2Mode === 'compose' || (v2Mode === 'iterate' && file)))" class="rounded-2xl border border-dashed border-white/15 bg-zinc-950/50 p-4">
<div class="mb-2 flex items-center justify-between gap-2">
<p class="text-sm font-medium text-zinc-300">{{ studioMode === 'editv2' && v2Mode === 'compose' ? 'Still B · required' : studioMode === 'editv2' ? 'Still B · optional' : 'Image 2 · optional' }}</p>
<p class="text-sm font-medium text-zinc-300">{{ fluxTextReference ? 'Reference photo · optional' : studioMode === 'editv2' && v2Mode === 'compose' ? 'Still B · required' : studioMode === 'editv2' ? 'Still B · optional' : 'Image 2 · optional' }}</p>
<button
v-if="editRefFile"
type="button"
@@ -213,11 +213,11 @@
class="max-h-40 w-full object-contain bg-black/40"
>
<span v-else class="flex min-h-28 items-center justify-center px-3 py-6 text-center text-sm text-zinc-500">
{{ studioMode === 'editv2' ? stillBHint : 'Apply this still onto image 1' }}
{{ fluxTextReference ? 'Choose a reference photo from your library or upload one' : studioMode === 'editv2' ? stillBHint : 'Apply this still onto image 1' }}
</span>
</button>
<p class="mt-2 text-xs text-zinc-500">
{{ studioMode === 'editv2'
{{ fluxTextReference ? 'Guides a new image from your text. This is not a starting image; your selected output size stays in effect. Describe which details to use from the photo.' : studioMode === 'editv2'
? (editRefFile ? stillBHint : 'Compose will reject this job until still B is loaded.')
: (editRefFile
? 'Two-image branch on. Image 2 is applied onto image 1.'
@@ -2716,6 +2716,7 @@ const v2Mode = computed<ImageV2Mode>({
set: mode => { selectedV2Mode.value = mode }
})
const v2Engine = ref<ImageV2Engine>('flux')
const fluxTextReference = computed(() => studioMode.value === 'editv2' && v2Engine.value === 'flux' && (v2Mode.value === 'generate' || (v2Mode.value === 'iterate' && !file.value)))
const v2Task = ref<ImageV2Task>('scene')
const v2Negative = ref('')
const v2Steps = ref(IMAGE_V2_STEPS_DEFAULT)
@@ -2923,7 +2924,7 @@ const REFS_STORE = 'aigen-permanence-refs'
const pickerHeading = computed(() => {
if (pickerKind.value === 'permanence') return 'Choose a still to name'
if (pickerSlot.value === 'main') return studioMode.value === 'edit' || studioMode.value === 'editv2' ? (studioMode.value === 'editv2' ? 'Choose still A' : 'Choose image 1') : 'Choose an image'
if (pickerSlot.value === 'editRef') return studioMode.value === 'editv2' ? 'Choose still B' : 'Choose image 2'
if (pickerSlot.value === 'editRef') return fluxTextReference.value ? 'Choose a reference photo' : studioMode.value === 'editv2' ? 'Choose still B' : 'Choose image 2'
return `Choose Picture ${Number(pickerSlot.value) + 2}`
})
const permanencePicker = computed(() => pickerKind.value === 'permanence')
@@ -5552,7 +5553,7 @@ async function restoreStudioEditJob(payload: {
if (payload.hideInput && showPrivacyToggles.value) hideInputPreview.value = true
readFile(new File([blob], stillFileName(stills.value.find(item => item.id === payload.stillId)) || payload.stillFilename || 'still.png', { type: blob.type || 'image/png' }), { stillId: payload.stillId })
}
if (v2 && payload.v2Mode === 'compose' && payload.referenceStillId) {
if (v2 && ['compose', 'generate', 'iterate'].includes(payload.v2Mode || '') && payload.referenceStillId) {
await loadStillAsEditRef(payload.referenceStillId).catch(() => null)
} else if (!(v2 && payload.v2Mode === 'compose')) {
clearEditRef()
@@ -6472,7 +6473,7 @@ async function useStillAsInput(still: LibraryStill) {
const blob = await $fetch<Blob>(`/api/library/stills/${still.id}`, { responseType: 'blob' })
if (still.hideInput && showPrivacyToggles.value) hideInputPreview.value = true
readFile(new File([blob], stillFileName(still), { type: blob.type || 'image/png' }), { stillId: still.id })
if (dest === 'imagev2' && still.settings?.mode === 'compose' && still.settings.referenceStillId) {
if (dest === 'imagev2' && ['compose', 'generate', 'iterate'].includes(still.settings?.mode || '') && still.settings?.referenceStillId) {
try {
await loadStillAsEditRef(still.settings.referenceStillId)
} catch {
@@ -7316,7 +7317,7 @@ async function editImageV2() {
? v2Task.value
: 'scene')
if (v2Mode.value !== 'generate' && file.value) body.append('image_a', file.value)
if ((v2Mode.value === 'compose' || v2Mode.value === 'iterate') && editRefFile.value) body.append('image_b', editRefFile.value)
if ((fluxTextReference.value || v2Mode.value === 'compose' || v2Mode.value === 'iterate') && editRefFile.value) body.append('image_b', editRefFile.value)
if (v2Mode.value === 'refine' || (v2Engine.value === 'krea' && v2Mode.value !== 'generate' && !iterateGenerate)) {
if (v2Mode.value === 'refine') {
const maskBlob = await refinePainter.value?.exportPng()