Compare commits
1
Commits
main
...
4831b32d20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4831b32d20 |
+159
-52
@@ -6,22 +6,21 @@
|
||||
<li
|
||||
v-for="(item, index) in model"
|
||||
:key="item.name"
|
||||
class="rounded-xl border bg-zinc-950/80"
|
||||
class="rounded-lg border border-[#2A2A2E] bg-[#1A1A1D] p-2.5"
|
||||
:class="[
|
||||
compact ? 'px-2 py-1.5' : 'px-3 py-2',
|
||||
dragOverIndex === index ? 'border-amber-300/50' : 'border-white/10',
|
||||
dragOverIndex === index ? 'border-amber-300/50' : '',
|
||||
draggingIndex === index ? 'opacity-60' : ''
|
||||
]"
|
||||
@dragover.prevent="onDragOver(index)"
|
||||
@drop.prevent="onDrop(index)"
|
||||
@dragleave="onDragLeave(index)"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="flex min-w-0 flex-1 items-start gap-2">
|
||||
<button
|
||||
v-if="model.length > 1"
|
||||
type="button"
|
||||
class="shrink-0 cursor-grab touch-none text-zinc-500 hover:text-zinc-300 active:cursor-grabbing disabled:cursor-not-allowed disabled:opacity-40"
|
||||
class="mt-0.5 shrink-0 cursor-grab touch-none text-zinc-500 hover:text-zinc-300 active:cursor-grabbing disabled:cursor-not-allowed disabled:opacity-40"
|
||||
:disabled="disabled"
|
||||
draggable="true"
|
||||
title="Drag to reorder"
|
||||
@@ -31,61 +30,91 @@
|
||||
>
|
||||
<span class="block leading-none" aria-hidden="true">⋮⋮</span>
|
||||
</button>
|
||||
<span class="min-w-0"><span class="block truncate text-zinc-200">{{ labelFor(item.name) }}</span><span v-if="filenameSubtitle" class="block truncate text-[11px] text-zinc-500" :title="item.name">{{ item.name }}</span></span>
|
||||
<LoraTriggerTip
|
||||
:text="triggerFor(item.name)"
|
||||
:aria-label="'Activation for ' + labelFor(item.name)"
|
||||
@insert="emit('insert-trigger', $event)"
|
||||
@save="saveTrigger(item.name, $event)"
|
||||
/>
|
||||
<span class="min-w-0 flex-1">
|
||||
<span class="flex min-w-0 items-center gap-1.5">
|
||||
<span class="block truncate font-medium text-[#F3F1EA]" :title="labelFor(item.name)">{{ labelFor(item.name) }}</span>
|
||||
<LoraTriggerTip
|
||||
:text="triggerFor(item.name)"
|
||||
:aria-label="'Activation for ' + labelFor(item.name)"
|
||||
@insert="emit('insert-trigger', $event)"
|
||||
@save="saveTrigger(item.name, $event)"
|
||||
/>
|
||||
</span>
|
||||
<span v-if="filenameSubtitle" class="mt-0.5 block truncate text-[11px] text-[#9A9588]" :title="item.name">{{ stemFor(item.name) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="shrink-0 text-[11px] text-zinc-500 hover:text-red-300 disabled:opacity-50"
|
||||
class="shrink-0 text-[11px] text-[#9A9588] hover:text-red-300 disabled:opacity-50"
|
||||
:disabled="disabled"
|
||||
@click="removeAt(index)"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-1.5 flex flex-wrap gap-3">
|
||||
<Thumbwheel
|
||||
:model-value="item.strengthModel"
|
||||
label="Model"
|
||||
:lock-key="`lora-model:${item.name}`"
|
||||
:min="LORA_STRENGTH_MIN"
|
||||
:max="LORA_STRENGTH_MAX"
|
||||
:step="LORA_STRENGTH_STEP"
|
||||
:fine-step="LORA_STRENGTH_STEP"
|
||||
:coarse-step="0.25"
|
||||
:digits="2"
|
||||
compact
|
||||
:disabled="disabled"
|
||||
@update:model-value="setStrength(index, 'strengthModel', $event)"
|
||||
/>
|
||||
<Thumbwheel
|
||||
:model-value="item.strengthClip"
|
||||
label="CLIP"
|
||||
:lock-key="`lora-clip:${item.name}`"
|
||||
:min="LORA_STRENGTH_MIN"
|
||||
:max="LORA_STRENGTH_MAX"
|
||||
:step="LORA_STRENGTH_STEP"
|
||||
:fine-step="LORA_STRENGTH_STEP"
|
||||
:coarse-step="0.25"
|
||||
:digits="2"
|
||||
compact
|
||||
:disabled="disabled || !clipStrength"
|
||||
@update:model-value="setStrength(index, 'strengthClip', $event)"
|
||||
/>
|
||||
|
||||
<div class="mt-2 space-y-1.5">
|
||||
<label class="grid grid-cols-[3.25rem_minmax(0,1fr)_2.75rem] items-center gap-2 text-[11px] text-[#9A9588]">
|
||||
<span>{{ clipStrength ? 'Model' : 'Strength' }}</span>
|
||||
<input
|
||||
type="range"
|
||||
class="lora-strength-range min-w-0"
|
||||
:min="0"
|
||||
:max="2"
|
||||
:step="LORA_STRENGTH_STEP"
|
||||
:disabled="disabled"
|
||||
:value="displayStrength(item.strengthModel)"
|
||||
:aria-label="(clipStrength ? 'Model' : 'Strength') + ' for ' + labelFor(item.name)"
|
||||
@input="onRange(index, 'strengthModel', $event)"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
class="lora-strength-num w-full rounded border border-[#2A2A2E] bg-[#0B0B0D] px-1 py-0.5 text-right font-mono text-[11px] text-[#F3F1EA] outline-none focus:border-[#F5A524]"
|
||||
:min="0"
|
||||
:max="2"
|
||||
:step="LORA_STRENGTH_STEP"
|
||||
:disabled="disabled"
|
||||
:value="displayStrength(item.strengthModel).toFixed(2)"
|
||||
:aria-label="(clipStrength ? 'Model' : 'Strength') + ' value'"
|
||||
@change="onNumber(index, 'strengthModel', $event)"
|
||||
>
|
||||
</label>
|
||||
<label
|
||||
v-if="clipStrength"
|
||||
class="grid grid-cols-[3.25rem_minmax(0,1fr)_2.75rem] items-center gap-2 text-[11px] text-[#9A9588]"
|
||||
>
|
||||
<span>CLIP</span>
|
||||
<input
|
||||
type="range"
|
||||
class="lora-strength-range min-w-0"
|
||||
:min="0"
|
||||
:max="2"
|
||||
:step="LORA_STRENGTH_STEP"
|
||||
:disabled="disabled"
|
||||
:value="displayStrength(item.strengthClip)"
|
||||
:aria-label="'CLIP for ' + labelFor(item.name)"
|
||||
@input="onRange(index, 'strengthClip', $event)"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
class="lora-strength-num w-full rounded border border-[#2A2A2E] bg-[#0B0B0D] px-1 py-0.5 text-right font-mono text-[11px] text-[#F3F1EA] outline-none focus:border-[#F5A524]"
|
||||
:min="0"
|
||||
:max="2"
|
||||
:step="LORA_STRENGTH_STEP"
|
||||
:disabled="disabled"
|
||||
:value="displayStrength(item.strengthClip).toFixed(2)"
|
||||
:aria-label="'CLIP value'"
|
||||
@change="onNumber(index, 'strengthClip', $event)"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-if="!clipStrength && model.length" class="mt-1 text-[11px] text-zinc-500">CLIP unused on this graph — model strength only.</p>
|
||||
<select
|
||||
v-if="model.length < LORA_STACK_MAX"
|
||||
class="w-full rounded-xl border border-white/10 bg-zinc-950 outline-none ring-amber-300/40 focus:ring-2 disabled:opacity-50"
|
||||
class="w-full rounded-lg border border-[#2A2A2E] bg-[#0B0B0D] text-[#F3F1EA] outline-none ring-[#F5A524]/40 focus:ring-2 disabled:opacity-50"
|
||||
:class="[
|
||||
compact ? 'px-2 py-1 text-xs' : 'px-3 py-2 text-sm',
|
||||
compact ? 'px-2 py-1.5 text-xs' : 'px-3 py-2 text-sm',
|
||||
model.length ? 'mt-2' : ''
|
||||
]"
|
||||
:disabled="disabled || !availableNames.length"
|
||||
@@ -103,13 +132,55 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.lora-strength-range {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
height: 6px;
|
||||
border-radius: 999px;
|
||||
background: #2a2a2e;
|
||||
outline: none;
|
||||
}
|
||||
.lora-strength-range::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 999px;
|
||||
background: #f5a524;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.lora-strength-range::-moz-range-track {
|
||||
height: 6px;
|
||||
border-radius: 999px;
|
||||
background: #2a2a2e;
|
||||
}
|
||||
.lora-strength-range::-moz-range-thumb {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 999px;
|
||||
background: #f5a524;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.lora-strength-range:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.lora-strength-range::-webkit-slider-runnable-track {
|
||||
height: 6px;
|
||||
border-radius: 999px;
|
||||
background: #2a2a2e;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
LORA_STACK_MAX,
|
||||
LORA_STRENGTH_MAX,
|
||||
LORA_STRENGTH_MIN,
|
||||
LORA_STRENGTH_DEFAULT,
|
||||
LORA_STRENGTH_STEP,
|
||||
clampLoraStrength,
|
||||
loraDisplayName,
|
||||
loraFriendlyName,
|
||||
loraIdentityKey,
|
||||
loraMatchesImageEngine,
|
||||
@@ -120,6 +191,9 @@ import {
|
||||
type LoraStackItem
|
||||
} from '~/utils/loras'
|
||||
|
||||
const UI_MIN = 0
|
||||
const UI_MAX = 2
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
names: string[]
|
||||
filenameSubtitle?: boolean
|
||||
@@ -175,6 +249,17 @@ const selectPlaceholder = computed(() => {
|
||||
return model.value.length ? props.addLabel : props.emptyLabel
|
||||
})
|
||||
|
||||
function clampUi(raw: unknown) {
|
||||
const value = Number(raw)
|
||||
if (!Number.isFinite(value)) return LORA_STRENGTH_DEFAULT
|
||||
const snapped = Math.round(value / LORA_STRENGTH_STEP) * LORA_STRENGTH_STEP
|
||||
return Math.min(UI_MAX, Math.max(UI_MIN, Math.round(snapped * 100) / 100))
|
||||
}
|
||||
|
||||
function displayStrength(value: unknown) {
|
||||
return clampUi(value ?? LORA_STRENGTH_DEFAULT)
|
||||
}
|
||||
|
||||
function onAdd(event: Event) {
|
||||
const name = (event.target as HTMLSelectElement).value
|
||||
;(event.target as HTMLSelectElement).value = ''
|
||||
@@ -186,7 +271,11 @@ function addLora(name: string) {
|
||||
if (!item || model.value.length >= LORA_STACK_MAX) return
|
||||
const taken = new Set(model.value.map(entry => loraIdentityKey(entry.name)))
|
||||
if (taken.has(loraIdentityKey(item.name))) return
|
||||
model.value = [...model.value, item]
|
||||
model.value = [...model.value, {
|
||||
...item,
|
||||
strengthModel: clampUi(item.strengthModel ?? LORA_STRENGTH_DEFAULT),
|
||||
strengthClip: clampUi(item.strengthClip ?? LORA_STRENGTH_DEFAULT)
|
||||
}]
|
||||
}
|
||||
|
||||
function removeAt(index: number) {
|
||||
@@ -234,8 +323,22 @@ function onDragEnd() {
|
||||
}
|
||||
|
||||
function setStrength(index: number, key: 'strengthModel' | 'strengthClip', value: number) {
|
||||
const next = clampLoraStrength(value)
|
||||
model.value = model.value.map((item, i) => i === index ? { ...item, [key]: next } : item)
|
||||
const next = clampUi(value)
|
||||
model.value = model.value.map((item, i) => {
|
||||
if (i !== index) return item
|
||||
if (!props.clipStrength) {
|
||||
return { ...item, strengthModel: next, strengthClip: next }
|
||||
}
|
||||
return { ...item, [key]: next }
|
||||
})
|
||||
}
|
||||
|
||||
function onRange(index: number, key: 'strengthModel' | 'strengthClip', event: Event) {
|
||||
setStrength(index, key, Number((event.target as HTMLInputElement).value))
|
||||
}
|
||||
|
||||
function onNumber(index: number, key: 'strengthModel' | 'strengthClip', event: Event) {
|
||||
setStrength(index, key, Number((event.target as HTMLInputElement).value))
|
||||
}
|
||||
|
||||
function triggerFor(name: string) {
|
||||
@@ -246,6 +349,10 @@ function labelFor(name: string) {
|
||||
return loraFriendlyName(name, props.aliases)
|
||||
}
|
||||
|
||||
function stemFor(name: string) {
|
||||
return loraDisplayName(name)
|
||||
}
|
||||
|
||||
function saveTrigger(name: string, trigger: string) {
|
||||
emit('update-trigger', name, trigger)
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@
|
||||
</label>
|
||||
<button type="button" class="mb-4 text-sm text-amber-200 outline-none ring-amber-300/40 focus-visible:ring-2" @click="form.settings.seedMode='random';form.settings.seed=randomSeed()">Random</button>
|
||||
<label v-if="video"><input v-model="form.settings.turbo" type="checkbox" class="accent-amber-400"> Turbo</label>
|
||||
<LoraSelect v-model="form.settings.loraStack" class="mt-5" :names="pickerLoras" :use="currentLoraUse" :clip-strength="true" filename-subtitle :aliases="{...loraNames.aliases,...library.loraAliases}" :triggers="{...loraNames.triggers,...library.loraTriggers}" missing-label="No cached LoRAs yet — Refresh to check Comfy" @insert-trigger="form.promptSections.action += ' '+$event" />
|
||||
<LoraSelect v-model="form.settings.loraStack" class="mt-5" :names="pickerLoras" :use="currentLoraUse" :clip-strength="!video" filename-subtitle :aliases="{...loraNames.aliases,...library.loraAliases}" :triggers="{...loraNames.triggers,...library.loraTriggers}" missing-label="No cached LoRAs yet — Refresh to check Comfy" @insert-trigger="form.promptSections.action += ' '+$event" />
|
||||
<button type="button" class="mt-2 text-sm text-amber-200 outline-none ring-amber-300/40 focus-visible:ring-2" @click="loadLoras(true)">Refresh LoRAs</button>
|
||||
<details class="mt-6"><summary>Compiled prompt</summary>
|
||||
<pre class="whitespace-pre-wrap text-xs text-zinc-400">{{ compiled }}</pre>
|
||||
|
||||
Reference in New Issue
Block a user