Name outputs from the input, engine, and LoRA keys instead of aigen_0001. Image-edit presets now store prompts, and aspect fill pads stills without stretching.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-08-30 16:24:49 -05:00
co-authored by Cursor
parent 4d411f83d4
commit 0e1222d035
23 changed files with 668 additions and 93 deletions
+79 -1
View File
@@ -5,7 +5,7 @@
<div class="flex items-start justify-between gap-3">
<div>
<h2 class="font-display text-2xl font-bold">Settings</h2>
<p class="mt-1 text-sm text-zinc-400">Library storage, folders, passwords, and prompt-helper instruction sets for this account.</p>
<p class="mt-1 text-sm text-zinc-400">Library storage, folders, passwords, LoRA filename keys, and prompt-helper instruction sets for this account.</p>
</div>
<button type="button" class="rounded-full border border-white/10 px-3 py-1 text-sm text-zinc-400 hover:text-white" @click="emit('close')">Close</button>
</div>
@@ -123,6 +123,35 @@
</form>
</section>
<section class="mt-8 space-y-3">
<div>
<h3 class="text-sm font-semibold uppercase tracking-wider text-zinc-500">LoRA filename keys</h3>
<p class="mt-1 text-sm text-zinc-400">
Short names used in auto-generated output titles. Leave blank to use the file stem
(<span class="text-zinc-300">XAIgen-cline</span> becomes <span class="text-zinc-300">cline</span>).
</p>
</div>
<p v-if="loraRowsLoading" class="text-sm text-zinc-500">Loading LoRAs…</p>
<p v-else-if="!loraRows.length" class="text-sm text-zinc-500">No LoRA files found on Comfy yet.</p>
<div
v-for="row in loraRows"
:key="row.key"
class="flex flex-col gap-2 rounded-2xl border border-white/10 bg-zinc-950/50 px-4 py-3 sm:flex-row sm:items-center"
>
<div class="min-w-0 flex-1">
<p class="truncate text-sm text-zinc-200">{{ row.file }}</p>
<p class="text-[10px] uppercase tracking-wide text-zinc-600">{{ row.kind === 'both' ? 'Image · Video' : row.kind }}</p>
</div>
<input
v-model="row.draft"
class="w-full rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm sm:w-44"
:placeholder="row.placeholder"
maxlength="24"
@change="saveLoraAlias(row)"
>
</div>
</section>
<section class="mt-8 space-y-3">
<div>
<h3 class="text-sm font-semibold uppercase tracking-wider text-zinc-500">Prompt helper</h3>
@@ -190,6 +219,9 @@
</template>
<script setup lang="ts">
import { defaultLoraFilenameKey, isFilenameSystemLora } from '~/utils/outputNames'
import { loraDisplayName, loraIdentityKey } from '~/utils/loras'
export interface SettingsFolder {
id: string
name: string
@@ -216,6 +248,8 @@ const newName = ref('')
const newPresetName = ref('')
const newPresetPrompt = ref('')
const promptPresets = ref<Array<{ id: string; name: string; description?: string; builtin?: boolean; systemPrompt?: string }>>([])
const loraRowsLoading = ref(true)
const loraRows = ref<Array<{ key: string; file: string; kind: 'image' | 'video' | 'both'; draft: string; placeholder: string }>>([])
const loginUser = ref(props.username || '')
const loginCurrent = ref('')
const loginNext = ref('')
@@ -279,6 +313,7 @@ onMounted(() => {
window.addEventListener('keydown', onKey)
void loadPresets()
void loadStorage()
void loadLoraKeys()
})
onBeforeUnmount(() => window.removeEventListener('keydown', onKey))
@@ -357,6 +392,49 @@ async function loadStorage() {
}
}
async function loadLoraKeys() {
loraRowsLoading.value = true
try {
const listed = await $fetch<{ image?: string[]; video?: string[] }>('/api/loras').catch(() => ({ image: [], video: [] }))
const library = await $fetch<{ loraAliases?: Record<string, string> }>('/api/library?light=1').catch(() => ({ loraAliases: {} }))
const aliases = library.loraAliases || {}
const map = new Map<string, { key: string; file: string; kind: 'image' | 'video' | 'both'; draft: string; placeholder: string }>()
const add = (name: string, kind: 'image' | 'video') => {
if (isFilenameSystemLora(name)) return
const key = loraIdentityKey(name)
if (!key) return
const existing = map.get(key)
if (existing) {
if (existing.kind !== kind) existing.kind = 'both'
return
}
map.set(key, {
key,
file: loraDisplayName(name),
kind,
draft: aliases[key] || '',
placeholder: defaultLoraFilenameKey(name)
})
}
for (const name of listed.image || []) add(name, 'image')
for (const name of listed.video || []) add(name, 'video')
loraRows.value = [...map.values()].sort((a, b) => a.file.localeCompare(b.file))
} finally {
loraRowsLoading.value = false
}
}
async function saveLoraAlias(row: { key: string; draft: string }) {
try {
emit('updated', await $fetch('/api/library/lora-aliases', {
method: 'PUT',
body: { key: row.key, alias: row.draft }
}))
} catch (error: any) {
emit('error', error?.data?.statusMessage || 'Could not save LoRA filename key')
}
}
async function addPreset() {
const name = newPresetName.value.trim()
const systemPrompt = newPresetPrompt.value.trim()