Add LoRA activation words and hide LoRAs that do not match the current engine.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+98
-14
@@ -451,12 +451,15 @@
|
||||
class="mt-2"
|
||||
:model-value="shotLoraStacks[shot.n] || []"
|
||||
:names="videoLoraOptions"
|
||||
:triggers="loraTriggers"
|
||||
label="LoRA"
|
||||
empty-label="Default"
|
||||
add-label="Add LoRA"
|
||||
compact
|
||||
:clip-strength="false"
|
||||
@update:model-value="setShotLoraStack(shot.n, $event)"
|
||||
@insert-trigger="insertLoraTrigger($event, { shot: shot.n })"
|
||||
@update-trigger="saveLoraTrigger"
|
||||
/>
|
||||
<PermanenceRefStrip
|
||||
class="mt-2"
|
||||
@@ -504,12 +507,15 @@
|
||||
class="mt-3"
|
||||
:model-value="item.loraStack || []"
|
||||
:names="videoLoraOptions"
|
||||
:triggers="loraTriggers"
|
||||
label="LoRA"
|
||||
empty-label="Default"
|
||||
add-label="Add LoRA"
|
||||
compact
|
||||
:clip-strength="false"
|
||||
@update:model-value="item.loraStack = $event"
|
||||
@insert-trigger="insertLoraTrigger($event, { extensionId: item.id })"
|
||||
@update-trigger="saveLoraTrigger"
|
||||
/>
|
||||
<PermanenceRefStrip
|
||||
class="mt-3"
|
||||
@@ -724,7 +730,17 @@
|
||||
</div>
|
||||
|
||||
<div v-if="studioMode === 'video'" class="max-w-md space-y-2">
|
||||
<LoraSelect v-model="videoLoraStack" :names="videoLoraOptions" label="LoRA" empty-label="None" compact :clip-strength="false" />
|
||||
<LoraSelect
|
||||
v-model="videoLoraStack"
|
||||
:names="videoLoraOptions"
|
||||
:triggers="loraTriggers"
|
||||
label="LoRA"
|
||||
empty-label="None"
|
||||
compact
|
||||
:clip-strength="false"
|
||||
@insert-trigger="insertLoraTrigger($event)"
|
||||
@update-trigger="saveLoraTrigger"
|
||||
/>
|
||||
<GenerationPresetBar
|
||||
:presets="videoGenerationPresets"
|
||||
v-model:selected-id="videoPresetId"
|
||||
@@ -779,10 +795,13 @@
|
||||
<LoraSelect
|
||||
v-model="imageV2LoraStack"
|
||||
:names="imageV2LoraOptions"
|
||||
:triggers="loraTriggers"
|
||||
label="LoRA"
|
||||
empty-label="Add LoRA"
|
||||
missing-label="Wake GPU to load LoRAs"
|
||||
compact
|
||||
@insert-trigger="insertLoraTrigger($event)"
|
||||
@update-trigger="saveLoraTrigger"
|
||||
/>
|
||||
<p v-if="v2Engine === 'krea'" class="mt-1 text-[11px] text-zinc-500">Flux LoRAs stay on the Flux engine. Switch back to restore that chain.</p>
|
||||
</div>
|
||||
@@ -897,7 +916,16 @@
|
||||
<p class="mt-2 text-xs text-zinc-500">{{ imageAspectHelp }}</p>
|
||||
</div>
|
||||
<div class="max-w-md space-y-2">
|
||||
<LoraSelect v-model="imageLoraStack" :names="imageLoraOptions" label="LoRA" empty-label="None" compact />
|
||||
<LoraSelect
|
||||
v-model="imageLoraStack"
|
||||
:names="imageLoraOptions"
|
||||
:triggers="loraTriggers"
|
||||
label="LoRA"
|
||||
empty-label="None"
|
||||
compact
|
||||
@insert-trigger="insertLoraTrigger($event)"
|
||||
@update-trigger="saveLoraTrigger"
|
||||
/>
|
||||
<label class="flex cursor-pointer items-center gap-2 text-xs text-zinc-400">
|
||||
<span class="relative inline-flex h-5 w-9 shrink-0 items-center">
|
||||
<input v-model="editScaleToTotalPixels" type="checkbox" class="peer sr-only" role="switch" aria-label="Scale to 1 MP">
|
||||
@@ -1345,7 +1373,17 @@
|
||||
label="Extension duration"
|
||||
hint="Type any length, or hover the wheel and scroll. Shift-scroll jumps by 1s."
|
||||
/>
|
||||
<LoraSelect v-model="extendLoraStack" :names="videoLoraOptions" label="LoRA" empty-label="Same as clip / none" compact :clip-strength="false" />
|
||||
<LoraSelect
|
||||
v-model="extendLoraStack"
|
||||
:names="videoLoraOptions"
|
||||
:triggers="loraTriggers"
|
||||
label="LoRA"
|
||||
empty-label="Same as clip / none"
|
||||
compact
|
||||
:clip-strength="false"
|
||||
@insert-trigger="insertLoraTrigger($event, 'extend')"
|
||||
@update-trigger="saveLoraTrigger"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="w-full rounded-2xl bg-amber-400 px-4 py-2.5 text-sm font-semibold text-zinc-950 transition hover:bg-amber-300 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
@@ -2043,8 +2081,12 @@ import {
|
||||
filterLoraStackForImageEngine,
|
||||
filterLoraStackForStudio,
|
||||
filterLorasForImageEngine,
|
||||
filterLorasForUse,
|
||||
insertActivationInShotScript,
|
||||
insertActivationPhrase,
|
||||
isKleinImageLora,
|
||||
kleinLoraOnKreaMessage,
|
||||
loraIdentityKey,
|
||||
loraStacksEqual,
|
||||
normalizeLoraStack,
|
||||
persistLoraFields,
|
||||
@@ -2524,6 +2566,7 @@ const clips = ref<LibraryClip[]>([])
|
||||
const stills = ref<LibraryStill[]>([])
|
||||
const drafts = ref<RetryDraft[]>([])
|
||||
const loraAliases = ref<Record<string, string>>({})
|
||||
const loraTriggers = ref<Record<string, string>>({})
|
||||
const folderId = ref('')
|
||||
const browseFolderId = ref('')
|
||||
const renamingId = ref('')
|
||||
@@ -2578,17 +2621,23 @@ const minimaxGraph = ref<MiniMaxGraphId>('v2')
|
||||
const videoWorkflow = computed(() => composeVideoWorkflow(videoEngine.value, videoStart.value, minimaxGraph.value))
|
||||
const textToVideo = computed(() => isTextToVideo(videoWorkflow.value))
|
||||
const ltxVideo = computed(() => isLtxWorkflow(videoWorkflow.value))
|
||||
const videoLoraOptions = computed(() => mergeLoraOptions(videoLoras.value, [
|
||||
...videoLoraStack.value.map(item => item.name),
|
||||
...extendLoraStack.value.map(item => item.name),
|
||||
...Object.values(shotLoraStacks.value).flatMap(stack => stack.map(item => item.name)),
|
||||
...extensionQueue.value.flatMap(item => (item.loraStack || []).map(entry => entry.name))
|
||||
]))
|
||||
const imageLoraOptions = computed(() => mergeLoraOptions(imageLoras.value, imageLoraStack.value.map(item => item.name)))
|
||||
const videoLoraOptions = computed(() => mergeLoraOptions(
|
||||
filterLorasForUse(videoLoras.value, ltxVideo.value ? 'ltx' : 'minimax'),
|
||||
[
|
||||
...videoLoraStack.value.map(item => item.name),
|
||||
...extendLoraStack.value.map(item => item.name),
|
||||
...Object.values(shotLoraStacks.value).flatMap(stack => stack.map(item => item.name)),
|
||||
...extensionQueue.value.flatMap(item => (item.loraStack || []).map(entry => entry.name))
|
||||
]
|
||||
))
|
||||
const imageLoraOptions = computed(() => mergeLoraOptions(
|
||||
filterLorasForUse(imageLoras.value, 'flux'),
|
||||
imageLoraStack.value.map(item => item.name)
|
||||
))
|
||||
const imageV2ConceptFile = computed(() => imageV2ConceptName())
|
||||
const imageV2SnofsOn = computed(() => imageV2LoraStack.value.some(item => isImageV2ConceptLora(item.name, v2Engine.value)))
|
||||
const imageV2LoraOptions = computed(() => mergeLoraOptions(
|
||||
filterLorasForImageEngine(imageLoras.value, v2Engine.value),
|
||||
filterLorasForUse(filterLorasForImageEngine(imageLoras.value, v2Engine.value), v2Engine.value),
|
||||
filterLorasForImageEngine([
|
||||
...imageV2LoraStack.value.map(item => item.name),
|
||||
...(v2ConceptLoraEnabled.value && imageV2ConceptFile.value ? [imageV2ConceptFile.value] : [])
|
||||
@@ -3454,8 +3503,8 @@ function applyInstancePrivacy() {
|
||||
hideThumbnail.value = readPrivacyToggle('aigen-hide-output-preview', true)
|
||||
}
|
||||
|
||||
function applyLibrary(data: { folders?: LibraryFolder[]; clips?: LibraryClip[]; stills?: LibraryStill[]; drafts?: RetryDraft[]; loraAliases?: Record<string, string> } | unknown) {
|
||||
const payload = (data || {}) as { folders?: LibraryFolder[]; clips?: LibraryClip[]; stills?: LibraryStill[]; drafts?: RetryDraft[]; loraAliases?: Record<string, string> }
|
||||
function applyLibrary(data: { folders?: LibraryFolder[]; clips?: LibraryClip[]; stills?: LibraryStill[]; drafts?: RetryDraft[]; loraAliases?: Record<string, string>; loraTriggers?: Record<string, string> } | unknown) {
|
||||
const payload = (data || {}) as { folders?: LibraryFolder[]; clips?: LibraryClip[]; stills?: LibraryStill[]; drafts?: RetryDraft[]; loraAliases?: Record<string, string>; loraTriggers?: Record<string, string> }
|
||||
folders.value = payload.folders || []
|
||||
clips.value = payload.clips || []
|
||||
stills.value = payload.stills || []
|
||||
@@ -3463,6 +3512,9 @@ function applyLibrary(data: { folders?: LibraryFolder[]; clips?: LibraryClip[];
|
||||
if (payload.loraAliases && typeof payload.loraAliases === 'object') {
|
||||
loraAliases.value = payload.loraAliases
|
||||
}
|
||||
if (payload.loraTriggers && typeof payload.loraTriggers === 'object') {
|
||||
loraTriggers.value = payload.loraTriggers
|
||||
}
|
||||
if (!folderId.value || !folders.value.some(folder => folder.id === folderId.value)) {
|
||||
folderId.value = folders.value[0]?.id || ''
|
||||
}
|
||||
@@ -3472,7 +3524,7 @@ function applyLibrary(data: { folders?: LibraryFolder[]; clips?: LibraryClip[];
|
||||
}
|
||||
|
||||
async function loadLibrary(light = false) {
|
||||
const data = await $fetch<{ folders?: LibraryFolder[]; clips?: LibraryClip[]; stills?: LibraryStill[]; drafts?: RetryDraft[]; loraAliases?: Record<string, string> }>(light ? '/api/library?light=1' : '/api/library')
|
||||
const data = await $fetch<{ folders?: LibraryFolder[]; clips?: LibraryClip[]; stills?: LibraryStill[]; drafts?: RetryDraft[]; loraAliases?: Record<string, string>; loraTriggers?: Record<string, string> }>(light ? '/api/library?light=1' : '/api/library')
|
||||
applyLibrary(data)
|
||||
if (light) return
|
||||
const queued = await $fetch<{ pendingCount?: number }>('/api/queues').catch(() => ({ pendingCount: 0 }))
|
||||
@@ -3933,6 +3985,38 @@ function mergeLoraOptions(listed: string[], extra: string[]) {
|
||||
return names
|
||||
}
|
||||
|
||||
async function saveLoraTrigger(name: string, trigger: string) {
|
||||
try {
|
||||
applyLibrary(await $fetch('/api/library/lora-triggers', {
|
||||
method: 'PUT',
|
||||
body: { key: loraIdentityKey(name), trigger }
|
||||
}))
|
||||
} catch (error: any) {
|
||||
toast(error?.data?.statusMessage || 'Could not save activation word')
|
||||
}
|
||||
}
|
||||
|
||||
function insertLoraTrigger(phrase: string, target: 'main' | 'extend' | { shot: number } | { extensionId: string } = 'main') {
|
||||
const token = String(phrase || '').trim()
|
||||
if (!token) return
|
||||
if (target === 'extend') {
|
||||
extendPrompt.value = insertActivationPhrase(extendPrompt.value, token)
|
||||
return
|
||||
}
|
||||
if (typeof target === 'object' && 'extensionId' in target) {
|
||||
const item = extensionQueue.value.find(entry => entry.id === target.extensionId)
|
||||
if (item) item.prompt = insertActivationPhrase(item.prompt, token)
|
||||
return
|
||||
}
|
||||
if (typeof target === 'object' && 'shot' in target) {
|
||||
prompt.value = insertActivationInShotScript(prompt.value, target.shot, token)
|
||||
promptDrawerOpen.value = true
|
||||
return
|
||||
}
|
||||
prompt.value = insertActivationPhrase(prompt.value, token)
|
||||
promptDrawerOpen.value = true
|
||||
}
|
||||
|
||||
function setShotLoraStack(shotN: number, stack: LoraStackItem[]) {
|
||||
const next = { ...shotLoraStacks.value }
|
||||
const items = normalizeLoraStack(stack)
|
||||
|
||||
Reference in New Issue
Block a user