Move settings out of a modal and onto its own page.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-08-31 20:44:45 -05:00
co-authored by Cursor
parent a3380870ad
commit 1dba62bf54
5 changed files with 127 additions and 122 deletions
+8 -16
View File
@@ -73,14 +73,14 @@
role="menuitem" role="menuitem"
@click="sleepGpu" @click="sleepGpu"
>{{ gpuSleeping ? 'Sleeping…' : 'Sleep' }}</button> >{{ gpuSleeping ? 'Sleeping…' : 'Sleep' }}</button>
<div v-if="authEnabled || showSettings" class="my-1 border-t border-white/10" /> <div class="my-1 border-t border-white/10" />
<button <NuxtLink
v-if="showSettings" to="/settings"
type="button" class="block rounded-xl px-3 py-2 text-sm hover:bg-white/5 hover:text-white"
class="block w-full rounded-xl px-3 py-2 text-left text-sm text-zinc-200 hover:bg-white/5 hover:text-white" :class="current === 'settings' ? 'text-amber-100' : 'text-zinc-200'"
role="menuitem" role="menuitem"
@click="openSettings" @click="menuOpen = false"
>Settings</button> >Settings</NuxtLink>
<a <a
v-if="authEnabled" v-if="authEnabled"
href="/api/auth/logout" href="/api/auth/logout"
@@ -116,19 +116,16 @@
<script setup lang="ts"> <script setup lang="ts">
withDefaults(defineProps<{ withDefaults(defineProps<{
subtitle: string subtitle: string
current?: 'studio' | 'library' | 'director' | 'queue' current?: 'studio' | 'library' | 'director' | 'queue' | 'settings'
wide?: boolean wide?: boolean
showSettings?: boolean
queueCount?: number queueCount?: number
}>(), { }>(), {
current: 'studio', current: 'studio',
wide: false, wide: false,
showSettings: false,
queueCount: 0 queueCount: 0
}) })
const emit = defineEmits<{ const emit = defineEmits<{
settings: []
gpu: [] gpu: []
}>() }>()
@@ -198,11 +195,6 @@ function closeMenu() {
menuOpen.value = false menuOpen.value = false
} }
function openSettings() {
menuOpen.value = false
emit('settings')
}
function applyHealth(health: { function applyHealth(health: {
comfy?: { ok?: boolean; busy?: boolean; asleep?: boolean } comfy?: { ok?: boolean; busy?: boolean; asleep?: boolean }
imageComfy?: { ok?: boolean; busy?: boolean } imageComfy?: { ok?: boolean; busy?: boolean }
@@ -1,16 +1,22 @@
<template> <template>
<Teleport to="body"> <div class="lg:grid lg:grid-cols-[13rem_minmax(0,1fr)] lg:items-start lg:gap-10">
<div class="fixed inset-0 z-40 flex items-start justify-center overflow-y-auto bg-black/70 p-4 sm:p-8" @click.self="emit('close')"> <nav class="mb-6 flex flex-wrap gap-2 lg:sticky lg:top-24 lg:mb-0 lg:flex-col lg:gap-1">
<div class="glass w-full max-w-2xl rounded-3xl p-5 sm:p-6"> <a
<div class="flex items-start justify-between gap-3"> v-for="item in navItems"
<div> :key="item.id"
<h2 class="font-display text-2xl font-bold">Settings</h2> :href="`#${item.id}`"
<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> class="rounded-full border border-white/10 px-3 py-1.5 text-xs text-zinc-400 hover:border-amber-300/40 hover:text-amber-100 lg:rounded-xl lg:text-sm"
</div> >{{ item.label }}</a>
<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> </nav>
</div>
<section class="mt-6"> <div class="space-y-10">
<p
v-if="notice"
class="rounded-2xl border px-4 py-3 text-sm"
:class="noticeError ? 'border-red-400/30 bg-red-950/40 text-red-100' : 'border-emerald-400/20 bg-emerald-950/30 text-emerald-100'"
>{{ notice }}</p>
<section id="settings-gpu" class="scroll-mt-24">
<h3 class="text-sm font-semibold uppercase tracking-wider text-zinc-500">GPU</h3> <h3 class="text-sm font-semibold uppercase tracking-wider text-zinc-500">GPU</h3>
<div class="mt-2 space-y-3 rounded-2xl border border-white/10 bg-zinc-950/50 px-4 py-3"> <div class="mt-2 space-y-3 rounded-2xl border border-white/10 bg-zinc-950/50 px-4 py-3">
<p class="text-sm text-zinc-400"> <p class="text-sm text-zinc-400">
@@ -40,7 +46,7 @@
</div> </div>
</section> </section>
<section class="mt-6"> <section id="settings-storage" class="scroll-mt-24">
<h3 class="text-sm font-semibold uppercase tracking-wider text-zinc-500">Storage</h3> <h3 class="text-sm font-semibold uppercase tracking-wider text-zinc-500">Storage</h3>
<div class="mt-2 rounded-2xl border border-white/10 bg-zinc-950/50 px-4 py-3"> <div class="mt-2 rounded-2xl border border-white/10 bg-zinc-950/50 px-4 py-3">
<p v-if="storageLoading" class="text-sm text-zinc-500">Measuring…</p> <p v-if="storageLoading" class="text-sm text-zinc-500">Measuring…</p>
@@ -53,7 +59,7 @@
</div> </div>
</section> </section>
<section v-if="authMode === 'password'" class="mt-6 space-y-3"> <section v-if="authMode === 'password'" id="settings-login" class="scroll-mt-24 space-y-3">
<h3 class="text-sm font-semibold uppercase tracking-wider text-zinc-500">Site login</h3> <h3 class="text-sm font-semibold uppercase tracking-wider text-zinc-500">Site login</h3>
<p class="text-sm text-zinc-400">Username and password are stored encrypted on this server. Changing them does not move the library.</p> <p class="text-sm text-zinc-400">Username and password are stored encrypted on this server. Changing them does not move the library.</p>
<form class="grid gap-2 sm:grid-cols-2" @submit.prevent="saveLogin"> <form class="grid gap-2 sm:grid-cols-2" @submit.prevent="saveLogin">
@@ -64,7 +70,7 @@
</form> </form>
</section> </section>
<section class="mt-6 space-y-3"> <section id="settings-folders" class="scroll-mt-24 space-y-3">
<div class="flex items-center justify-between gap-3"> <div class="flex items-center justify-between gap-3">
<h3 class="text-sm font-semibold uppercase tracking-wider text-zinc-500">Folders</h3> <h3 class="text-sm font-semibold uppercase tracking-wider text-zinc-500">Folders</h3>
<span class="text-xs text-zinc-500">{{ folders.length }} total</span> <span class="text-xs text-zinc-500">{{ folders.length }} total</span>
@@ -153,7 +159,7 @@
</form> </form>
</section> </section>
<section class="mt-8 space-y-3"> <section id="settings-loras" class="scroll-mt-24 space-y-3">
<div> <div>
<h3 class="text-sm font-semibold uppercase tracking-wider text-zinc-500">LoRA filename keys</h3> <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"> <p class="mt-1 text-sm text-zinc-400">
@@ -192,7 +198,7 @@
</div> </div>
</section> </section>
<section class="mt-8 space-y-4"> <section id="settings-keeps" class="scroll-mt-24 space-y-4">
<div> <div>
<h3 class="text-sm font-semibold uppercase tracking-wider text-zinc-500">Keep prompts</h3> <h3 class="text-sm font-semibold uppercase tracking-wider text-zinc-500">Keep prompts</h3>
<p class="mt-1 text-sm text-zinc-400"> <p class="mt-1 text-sm text-zinc-400">
@@ -249,7 +255,7 @@
</div> </div>
</section> </section>
<section class="mt-8 space-y-3"> <section id="settings-prompts" class="scroll-mt-24 space-y-3">
<div> <div>
<h3 class="text-sm font-semibold uppercase tracking-wider text-zinc-500">Prompt helper</h3> <h3 class="text-sm font-semibold uppercase tracking-wider text-zinc-500">Prompt helper</h3>
<p class="mt-1 text-sm text-zinc-400"> <p class="mt-1 text-sm text-zinc-400">
@@ -310,9 +316,8 @@
<button type="submit" class="rounded-xl bg-amber-400 px-4 py-2 text-sm font-semibold text-zinc-950">Add instruction set</button> <button type="submit" class="rounded-xl bg-amber-400 px-4 py-2 text-sm font-semibold text-zinc-950">Add instruction set</button>
</form> </form>
</section> </section>
</div>
</div> </div>
</Teleport> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -337,18 +342,27 @@ export interface SettingsFolder {
unlocked: boolean unlocked: boolean
} }
const props = defineProps<{ const folders = ref<SettingsFolder[]>([])
folders: SettingsFolder[] const authMode = ref('')
authMode?: string const userName = ref('')
username?: string const notice = ref('')
}>() const noticeError = ref(false)
let noticeTimer: ReturnType<typeof setTimeout> | null = null
const emit = defineEmits<{ const navItems = computed(() => {
close: [] const items = [
updated: [payload: unknown] { id: 'settings-gpu', label: 'GPU' },
error: [message: string] { id: 'settings-storage', label: 'Storage' }
renamed: [username: string] ]
}>() if (authMode.value === 'password') items.push({ id: 'settings-login', label: 'Site login' })
items.push(
{ id: 'settings-folders', label: 'Folders' },
{ id: 'settings-loras', label: 'LoRA keys' },
{ id: 'settings-keeps', label: 'Keep prompts' },
{ id: 'settings-prompts', label: 'Prompt helper' }
)
return items
})
const newName = ref('') const newName = ref('')
const newPresetName = ref('') const newPresetName = ref('')
@@ -365,7 +379,7 @@ const newKeep = reactive<Record<KeepPromptKind, { label: string; text: string }>
const promptPresets = ref<Array<{ id: string; name: string; description?: string; builtin?: boolean; systemPrompt?: string }>>([]) const promptPresets = ref<Array<{ id: string; name: string; description?: string; builtin?: boolean; systemPrompt?: string }>>([])
const loraRowsLoading = ref(true) const loraRowsLoading = ref(true)
const loraRows = ref<Array<{ key: string; file: string; kind: 'image' | 'video' | 'both'; draft: string; trigger: string; placeholder: string }>>([]) const loraRows = ref<Array<{ key: string; file: string; kind: 'image' | 'video' | 'both'; draft: string; trigger: string; placeholder: string }>>([])
const loginUser = ref(props.username || '') const loginUser = ref('')
const loginCurrent = ref('') const loginCurrent = ref('')
const loginNext = ref('') const loginNext = ref('')
const drafts = reactive<Record<string, { name: string; protect: boolean; password: string; currentPassword: string }>>({}) const drafts = reactive<Record<string, { name: string; protect: boolean; password: string; currentPassword: string }>>({})
@@ -424,20 +438,42 @@ const storageNote = computed(() => {
: "This account's clips, stills, thumbs, and temp. Volume free space is shared." : "This account's clips, stills, thumbs, and temp. Volume free space is shared."
}) })
function onKey(event: KeyboardEvent) { function toast(message: string, error = true) {
if (event.key === 'Escape') emit('close') notice.value = message
noticeError.value = error
if (noticeTimer) clearTimeout(noticeTimer)
noticeTimer = setTimeout(() => { notice.value = '' }, 4200)
}
function applyLibrary(data: unknown) {
const payload = (data || {}) as { folders?: SettingsFolder[] }
if (Array.isArray(payload.folders)) folders.value = payload.folders
}
async function loadAccount() {
const [library, me] = await Promise.all([
$fetch<{ folders?: SettingsFolder[] }>('/api/library?light=1').catch(() => ({ folders: [] })),
$fetch<{ user?: { name?: string; email?: string }; authMode?: string }>('/api/auth/me').catch(() => ({}))
])
applyLibrary(library)
userName.value = me.user?.name || me.user?.email || ''
authMode.value = me.authMode || ''
if (userName.value) loginUser.value = userName.value
} }
onMounted(() => { onMounted(() => {
window.addEventListener('keydown', onKey) void loadAccount()
void loadPresets() void loadPresets()
void loadStorage() void loadStorage()
void loadLoraKeys() void loadLoraKeys()
}) })
onBeforeUnmount(() => window.removeEventListener('keydown', onKey))
watch(() => props.folders, (folders) => { onBeforeUnmount(() => {
for (const folder of folders) { if (noticeTimer) clearTimeout(noticeTimer)
})
watch(folders, (list) => {
for (const folder of list) {
if (!drafts[folder.id]) { if (!drafts[folder.id]) {
drafts[folder.id] = { drafts[folder.id] = {
name: folder.name, name: folder.name,
@@ -451,10 +487,6 @@ watch(() => props.folders, (folders) => {
} }
}, { immediate: true, deep: true }) }, { immediate: true, deep: true })
watch(() => props.username, (value) => {
if (value) loginUser.value = value
})
async function saveLogin() { async function saveLogin() {
try { try {
const data = await $fetch<{ username: string }>('/api/auth/credentials', { const data = await $fetch<{ username: string }>('/api/auth/credentials', {
@@ -467,9 +499,10 @@ async function saveLogin() {
}) })
loginCurrent.value = '' loginCurrent.value = ''
loginNext.value = '' loginNext.value = ''
emit('renamed', data.username) userName.value = data.username
toast('Login updated', false)
} catch (error: any) { } catch (error: any) {
emit('error', error?.data?.statusMessage || 'Could not update site login') toast(error?.data?.statusMessage || 'Could not update site login')
} }
} }
@@ -479,9 +512,9 @@ async function addFolder() {
try { try {
await $fetch('/api/library/folders', { method: 'POST', body: { name } }) await $fetch('/api/library/folders', { method: 'POST', body: { name } })
newName.value = '' newName.value = ''
emit('updated', await $fetch('/api/library')) applyLibrary(await $fetch('/api/library'))
} catch (error: any) { } catch (error: any) {
emit('error', error?.data?.statusMessage || error?.statusMessage || 'Could not create folder') toast( error?.data?.statusMessage || error?.statusMessage || 'Could not create folder')
} }
} }
@@ -589,20 +622,20 @@ async function saveKeepKind(kind: KeepPromptKind) {
body: { kind, items: keepPrompts.value[kind] } body: { kind, items: keepPrompts.value[kind] }
}) })
keepPrompts.value = normalizeKeepPrompts(data.keepPrompts) keepPrompts.value = normalizeKeepPrompts(data.keepPrompts)
emit('updated', data) applyLibrary(data)
} catch (error: any) { } catch (error: any) {
emit('error', error?.data?.statusMessage || 'Could not save keep prompts') toast( error?.data?.statusMessage || 'Could not save keep prompts')
} }
} }
function addKeepItem(kind: KeepPromptKind) { function addKeepItem(kind: KeepPromptKind) {
const text = sanitizeKeepPromptText(newKeep[kind].text) const text = sanitizeKeepPromptText(newKeep[kind].text)
if (!text) { if (!text) {
emit('error', 'Snippet text is required') toast( 'Snippet text is required')
return return
} }
if (keepPrompts.value[kind].length >= KEEP_PROMPT_MAX) { if (keepPrompts.value[kind].length >= KEEP_PROMPT_MAX) {
emit('error', `At most ${KEEP_PROMPT_MAX} snippets per list`) toast( `At most ${KEEP_PROMPT_MAX} snippets per list`)
return return
} }
keepPrompts.value[kind] = [ keepPrompts.value[kind] = [
@@ -625,23 +658,23 @@ function removeKeepItem(kind: KeepPromptKind, id: string) {
async function saveLoraAlias(row: { key: string; draft: string }) { async function saveLoraAlias(row: { key: string; draft: string }) {
try { try {
emit('updated', await $fetch('/api/library/lora-aliases', { applyLibrary(await $fetch('/api/library/lora-aliases', {
method: 'PUT', method: 'PUT',
body: { key: row.key, alias: row.draft } body: { key: row.key, alias: row.draft }
})) }))
} catch (error: any) { } catch (error: any) {
emit('error', error?.data?.statusMessage || 'Could not save LoRA filename key') toast( error?.data?.statusMessage || 'Could not save LoRA filename key')
} }
} }
async function saveLoraTrigger(row: { key: string; trigger: string }) { async function saveLoraTrigger(row: { key: string; trigger: string }) {
try { try {
emit('updated', await $fetch('/api/library/lora-triggers', { applyLibrary(await $fetch('/api/library/lora-triggers', {
method: 'PUT', method: 'PUT',
body: { key: row.key, trigger: row.trigger } body: { key: row.key, trigger: row.trigger }
})) }))
} catch (error: any) { } catch (error: any) {
emit('error', error?.data?.statusMessage || 'Could not save LoRA activation word') toast( error?.data?.statusMessage || 'Could not save LoRA activation word')
} }
} }
@@ -649,7 +682,7 @@ async function addPreset() {
const name = newPresetName.value.trim() const name = newPresetName.value.trim()
const systemPrompt = newPresetPrompt.value.trim() const systemPrompt = newPresetPrompt.value.trim()
if (!name || !systemPrompt) { if (!name || !systemPrompt) {
emit('error', 'Name and instruction text are required') toast( 'Name and instruction text are required')
return return
} }
try { try {
@@ -661,7 +694,7 @@ async function addPreset() {
newPresetName.value = '' newPresetName.value = ''
newPresetPrompt.value = '' newPresetPrompt.value = ''
} catch (error: any) { } catch (error: any) {
emit('error', error?.data?.statusMessage || 'Could not save instruction set') toast( error?.data?.statusMessage || 'Could not save instruction set')
} }
} }
@@ -674,7 +707,7 @@ async function savePreset(preset: PromptPresetRow) {
}) })
promptPresets.value = data.presets || promptPresets.value promptPresets.value = data.presets || promptPresets.value
} catch (error: any) { } catch (error: any) {
emit('error', error?.data?.statusMessage || 'Could not update instruction set') toast( error?.data?.statusMessage || 'Could not update instruction set')
} }
} }
@@ -684,7 +717,7 @@ async function removePreset(id: string) {
const data = await $fetch<{ presets?: PromptPresetRow[] }>(`/api/prompt/presets/${id}`, { method: 'DELETE' }) const data = await $fetch<{ presets?: PromptPresetRow[] }>(`/api/prompt/presets/${id}`, { method: 'DELETE' })
promptPresets.value = data.presets || [] promptPresets.value = data.presets || []
} catch (error: any) { } catch (error: any) {
emit('error', error?.data?.statusMessage || 'Could not delete instruction set') toast( error?.data?.statusMessage || 'Could not delete instruction set')
} }
} }
@@ -692,20 +725,20 @@ async function rename(folder: SettingsFolder) {
const name = drafts[folder.id]?.name?.trim() const name = drafts[folder.id]?.name?.trim()
if (!name || name === folder.name) return if (!name || name === folder.name) return
try { try {
emit('updated', await $fetch(`/api/library/folders/${folder.id}`, { method: 'PATCH', body: { name } })) applyLibrary(await $fetch(`/api/library/folders/${folder.id}`, { method: 'PATCH', body: { name } }))
} catch (error: any) { } catch (error: any) {
emit('error', error?.data?.statusMessage || 'Could not rename folder') toast( error?.data?.statusMessage || 'Could not rename folder')
} }
} }
async function setThumbnail(folder: SettingsFolder, thumbnailDefault: boolean) { async function setThumbnail(folder: SettingsFolder, thumbnailDefault: boolean) {
try { try {
emit('updated', await $fetch(`/api/library/folders/${folder.id}`, { applyLibrary(await $fetch(`/api/library/folders/${folder.id}`, {
method: 'PATCH', method: 'PATCH',
body: { thumbnailDefault } body: { thumbnailDefault }
})) }))
} catch (error: any) { } catch (error: any) {
emit('error', error?.data?.statusMessage || 'Could not update thumbnail default') toast( error?.data?.statusMessage || 'Could not update thumbnail default')
} }
} }
@@ -720,17 +753,17 @@ async function savePassword(folder: SettingsFolder) {
const draft = drafts[folder.id] const draft = drafts[folder.id]
try { try {
if (!draft.protect && folder.protected) { if (!draft.protect && folder.protected) {
emit('updated', await $fetch(`/api/library/folders/${folder.id}`, { applyLibrary(await $fetch(`/api/library/folders/${folder.id}`, {
method: 'PATCH', method: 'PATCH',
body: { password: '', currentPassword: draft.currentPassword } body: { password: '', currentPassword: draft.currentPassword }
})) }))
} else if (draft.protect) { } else if (draft.protect) {
if (!folder.protected && !draft.password.trim()) { if (!folder.protected && !draft.password.trim()) {
emit('error', 'Set a password with at least 4 characters.') toast( 'Set a password with at least 4 characters.')
return return
} }
if (!folder.protected || draft.password.trim()) { if (!folder.protected || draft.password.trim()) {
emit('updated', await $fetch(`/api/library/folders/${folder.id}`, { applyLibrary(await $fetch(`/api/library/folders/${folder.id}`, {
method: 'PATCH', method: 'PATCH',
body: { password: draft.password, currentPassword: draft.currentPassword } body: { password: draft.password, currentPassword: draft.currentPassword }
})) }))
@@ -739,16 +772,16 @@ async function savePassword(folder: SettingsFolder) {
draft.password = '' draft.password = ''
draft.currentPassword = '' draft.currentPassword = ''
} catch (error: any) { } catch (error: any) {
emit('error', error?.data?.statusMessage || 'Could not update folder password') toast( error?.data?.statusMessage || 'Could not update folder password')
} }
} }
async function remove(folder: SettingsFolder) { async function remove(folder: SettingsFolder) {
if (!confirm(`Delete “${folder.name}” and everything inside it?`)) return if (!confirm(`Delete “${folder.name}” and everything inside it?`)) return
try { try {
emit('updated', await $fetch(`/api/library/folders/${folder.id}`, { method: 'DELETE' })) applyLibrary(await $fetch(`/api/library/folders/${folder.id}`, { method: 'DELETE' }))
} catch (error: any) { } catch (error: any) {
emit('error', error?.data?.statusMessage || 'Could not delete folder') toast( error?.data?.statusMessage || 'Could not delete folder')
} }
} }
</script> </script>
-21
View File
@@ -3,9 +3,7 @@
<AppHeader <AppHeader
:subtitle="ltxEnabled ? 'MiniMax H3 · LTX-2.3 · Flux.2 Klein' : 'MiniMax H3 · Flux.2 Klein'" :subtitle="ltxEnabled ? 'MiniMax H3 · LTX-2.3 · Flux.2 Klein' : 'MiniMax H3 · Flux.2 Klein'"
current="studio" current="studio"
show-settings
:queue-count="queueBadgeCount" :queue-count="queueBadgeCount"
@settings="settingsOpen = true"
@gpu="pollComfyHealth" @gpu="pollComfyHealth"
/> />
@@ -1976,16 +1974,6 @@
</section> </section>
</main> </main>
<SettingsModal
v-if="settingsOpen"
:folders="folders"
:auth-mode="authMode"
:username="userName"
@close="settingsOpen = false"
@updated="applyLibrary"
@error="toast"
@renamed="userName = $event"
/>
<PromptDrawer <PromptDrawer
:open="promptDrawerOpen" :open="promptDrawerOpen"
v-model:text="prompt" v-model:text="prompt"
@@ -2548,8 +2536,6 @@ const comfyBusy = ref(false)
const comfyAsleep = ref(false) const comfyAsleep = ref(false)
const gpuWaking = ref(false) const gpuWaking = ref(false)
const gpuWakeError = ref('') const gpuWakeError = ref('')
const userName = ref('')
const authMode = ref('')
const instanceName = ref(String(runtimeConfig.public.instanceName || 'AIGen')) const instanceName = ref(String(runtimeConfig.public.instanceName || 'AIGen'))
const ltxEnabled = computed(() => ltxWorkflowEnabled({ const ltxEnabled = computed(() => ltxWorkflowEnabled({
studio: runtimeConfig.public.studio, studio: runtimeConfig.public.studio,
@@ -2606,7 +2592,6 @@ const renamingFamily = ref(false)
const renameDraft = ref('') const renameDraft = ref('')
const renameInput = ref<HTMLInputElement | null>(null) const renameInput = ref<HTMLInputElement | null>(null)
const unlockPassword = ref('') const unlockPassword = ref('')
const settingsOpen = ref(false)
const queueAutoRun = ref(false) const queueAutoRun = ref(false)
const queuePendingCount = ref(0) const queuePendingCount = ref(0)
const activeQueueId = ref('') const activeQueueId = ref('')
@@ -3761,8 +3746,6 @@ onMounted(async () => {
loraPollAt = Date.now() loraPollAt = Date.now()
void loadGenerationPresets() void loadGenerationPresets()
await loadDirectorScripts().catch(() => null) await loadDirectorScripts().catch(() => null)
userName.value = me.user?.name || me.user?.email || ''
authMode.value = me.authMode || ''
instanceName.value = me.instanceName || instanceName.value || 'AIGen' instanceName.value = me.instanceName || instanceName.value || 'AIGen'
applyInstancePrivacy() applyInstancePrivacy()
setTimeout(() => { void loadLibrary(true).catch(() => null) }, 6000) setTimeout(() => { void loadLibrary(true).catch(() => null) }, 6000)
@@ -3794,10 +3777,6 @@ watch(folderId, () => {
applyThumbnailDefault() applyThumbnailDefault()
}) })
watch(settingsOpen, (open) => {
if (!open) void loadPromptPresets()
})
watch(recommendPresetId, (id) => { watch(recommendPresetId, (id) => {
try { localStorage.setItem('aigen-prompt-preset', id) } catch { /* ignore */ } try { localStorage.setItem('aigen-prompt-preset', id) } catch { /* ignore */ }
}) })
-18
View File
@@ -4,8 +4,6 @@
subtitle="Library" subtitle="Library"
current="library" current="library"
wide wide
show-settings
@settings="settingsOpen = true"
/> />
<main class="mx-auto flex min-h-0 w-full max-w-[90rem] flex-1 flex-col gap-4 px-4 py-4 sm:px-6 lg:flex-row"> <main class="mx-auto flex min-h-0 w-full max-w-[90rem] flex-1 flex-col gap-4 px-4 py-4 sm:px-6 lg:flex-row">
@@ -282,17 +280,6 @@
</aside> </aside>
</main> </main>
<SettingsModal
v-if="settingsOpen"
:folders="folders"
:auth-mode="authMode"
:username="userName"
@close="settingsOpen = false"
@updated="applyLibrary"
@error="toast"
@renamed="userName = $event"
/>
<Teleport to="body"> <Teleport to="body">
<div <div
v-if="pendingDelete" v-if="pendingDelete"
@@ -366,9 +353,6 @@ const showPrivacyToggles = computed(() => isXaigenStudio({
studio: runtimeConfig.public.studio, studio: runtimeConfig.public.studio,
instanceName: runtimeConfig.public.instanceName || instanceName.value instanceName: runtimeConfig.public.instanceName || instanceName.value
})) }))
const authMode = ref('')
const userName = ref('')
const settingsOpen = ref(false)
const folders = ref<LibraryFolder[]>([]) const folders = ref<LibraryFolder[]>([])
const clips = ref<LibraryClip[]>([]) const clips = ref<LibraryClip[]>([])
const stills = ref<LibraryStill[]>([]) const stills = ref<LibraryStill[]>([])
@@ -892,8 +876,6 @@ onMounted(async () => {
$fetch<{ user?: { name?: string; email?: string }; authEnabled?: boolean; authMode?: string; instanceName?: string }>('/api/auth/me').catch(() => ({ user: null })), $fetch<{ user?: { name?: string; email?: string }; authEnabled?: boolean; authMode?: string; instanceName?: string }>('/api/auth/me').catch(() => ({ user: null })),
loadLibrary().catch(() => applyLibrary({ folders: [], clips: [], stills: [] })) loadLibrary().catch(() => applyLibrary({ folders: [], clips: [], stills: [] }))
]) ])
userName.value = me.user?.name || me.user?.email || ''
authMode.value = me.authMode || ''
instanceName.value = me.instanceName || instanceName.value instanceName.value = me.instanceName || instanceName.value
await nextTick() await nextTick()
measure() measure()
+19
View File
@@ -0,0 +1,19 @@
<template>
<div class="min-h-dvh min-w-0">
<AppHeader subtitle="Settings" current="settings" />
<main class="mx-auto min-w-0 max-w-6xl px-4 py-6 sm:px-6">
<div class="mb-8">
<h1 class="font-display text-2xl font-bold">Settings</h1>
<p class="mt-1 max-w-2xl text-sm text-zinc-400">
GPU, storage, folders, LoRA keys, keep-prompt snippets, and prompt-helper instruction sets for this account.
</p>
</div>
<SettingsForm />
</main>
</div>
</template>
<script setup lang="ts">
useHead({ title: 'Settings' })
</script>