Move Studio locks to slots and Classic hide to header checkboxes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-18 13:09:29 -05:00
co-authored by Cursor
parent 292d53a20e
commit 1b29452455
+48 -16
View File
@@ -11,18 +11,23 @@
<option v-for="f in library.folders" :key="f.id" :value="f.id">{{ f.name }}</option> <option v-for="f in library.folders" :key="f.id" :value="f.id">{{ f.name }}</option>
</select> </select>
</label> </label>
<div v-if="isClassic && form.mode!=='music'" class="flex flex-wrap items-center gap-3 text-sm text-zinc-300" aria-label="Identity locks">
<label class="inline-flex items-center gap-1.5"><input v-model="form.lockFace" type="checkbox" class="accent-amber-400">Lock face</label>
<label class="inline-flex items-center gap-1.5"><input v-model="form.lockOutfit" type="checkbox" class="accent-amber-400">Lock outfit</label>
</div>
<div v-if="isClassic && xaigen" class="flex flex-wrap items-center gap-3 text-sm text-zinc-300" aria-label="Preview hide">
<label class="inline-flex items-center gap-1.5"><input v-model="classicHideInput" type="checkbox" class="accent-amber-400">Hide input</label>
<label class="inline-flex items-center gap-1.5"><input v-model="classicHideOutput" type="checkbox" class="accent-amber-400">Hide output</label>
</div>
<nav class="flex flex-wrap items-center gap-3 text-sm" aria-label="Studio links"> <nav class="flex flex-wrap items-center gap-3 text-sm" aria-label="Studio links">
<NuxtLink to="/library" class="text-amber-200 outline-none ring-amber-300/40 focus-visible:ring-2">Library</NuxtLink> <Studio2GpuStatus :status="gpu" />
<NuxtLink to="/queue" class="text-zinc-300 outline-none ring-amber-300/40 focus-visible:ring-2">Queue</NuxtLink> <NuxtLink to="/queue" class="text-zinc-300 outline-none ring-amber-300/40 focus-visible:ring-2">Queue</NuxtLink>
<NuxtLink to="/library" class="text-amber-200 outline-none ring-amber-300/40 focus-visible:ring-2">Library</NuxtLink>
<NuxtLink to="/settings" class="text-zinc-300 outline-none ring-amber-300/40 focus-visible:ring-2">Settings</NuxtLink> <NuxtLink to="/settings" class="text-zinc-300 outline-none ring-amber-300/40 focus-visible:ring-2">Settings</NuxtLink>
</nav> </nav>
</div> </div>
</header> </header>
<div class="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-white/10 px-4 py-2 text-sm">
<Studio2GpuStatus :status="gpu" />
</div>
<nav class="flex flex-wrap gap-2" aria-label="Tasks"> <nav class="flex flex-wrap gap-2" aria-label="Tasks">
<button <button
v-for="task in tasks" v-for="task in tasks"
@@ -39,14 +44,20 @@
<section class="min-w-0 space-y-4"> <section class="min-w-0 space-y-4">
<div class="relative flex min-h-80 items-center justify-center overflow-hidden rounded-3xl border border-white/10 bg-black"> <div class="relative flex min-h-80 items-center justify-center overflow-hidden rounded-3xl border border-white/10 bg-black">
<button <button
v-if="selected && xaigen" v-if="selected && xaigen && isStudio"
type="button" type="button"
class="absolute right-3 top-3 z-10 rounded-full border border-white/20 bg-zinc-950/80 p-2 text-zinc-200 outline-none ring-amber-300/50 hover:border-amber-300/50 focus-visible:ring-2" class="absolute right-3 top-3 z-10 rounded-full border border-white/20 bg-zinc-950/80 p-2 text-zinc-200 outline-none ring-amber-300/50 hover:border-amber-300/50 focus-visible:ring-2"
:aria-pressed="!isHidden(selected)" :aria-pressed="!isHidden(selected)"
:aria-label="isHidden(selected) ? 'Show preview' : 'Hide preview'" :aria-label="isHidden(selected) ? 'Show preview' : 'Hide preview'"
@click.stop="toggleReveal(selected.id)" @click.stop="toggleReveal(selected.id)"
> >
<span aria-hidden="true">{{ isHidden(selected) ? 'Show' : 'Hide' }}</span> <svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true">
<path v-if="isHidden(selected)" d="M3 3l18 18M10.5 10.6a2.5 2.5 0 0 0 3 3M9.4 5.1A10.4 10.4 0 0 1 12 4.5c5 0 9.3 3.1 11 7.5a11.6 11.6 0 0 1-4.2 5.1M6.2 6.2A11.5 11.5 0 0 0 1 12c1.2 2.7 3.3 4.9 5.9 6.3A10.5 10.5 0 0 0 12 19.5c1.2 0 2.4-.2 3.5-.6" stroke-linecap="round"/>
<template v-else>
<path d="M2 12s3.5-7.5 10-7.5S22 12 22 12s-3.5 7.5-10 7.5S2 12 2 12Z"/>
<circle cx="12" cy="12" r="3"/>
</template>
</svg>
</button> </button>
<p v-if="selected && isHidden(selected)" class="text-zinc-400">{{ selected.kind==='track' ? 'Track loaded · preview hidden' : selected.role==='input' ? 'Still loaded · preview hidden' : 'Output hidden' }}</p> <p v-if="selected && isHidden(selected)" class="text-zinc-400">{{ selected.kind==='track' ? 'Track loaded · preview hidden' : selected.role==='input' ? 'Still loaded · preview hidden' : 'Output hidden' }}</p>
<div v-else-if="selected?.kind==='track'" class="w-full max-w-xl px-6 py-4"> <div v-else-if="selected?.kind==='track'" class="w-full max-w-xl px-6 py-4">
@@ -79,22 +90,30 @@
<img v-else :src="o.kind==='clip' ? `/api/library/clips/${o.id}/thumb` : stillUrl(o.id)" class="h-20 w-24 rounded object-cover" alt="Family output"> <img v-else :src="o.kind==='clip' ? `/api/library/clips/${o.id}/thumb` : stillUrl(o.id)" class="h-20 w-24 rounded object-cover" alt="Family output">
</button> </button>
<button <button
v-if="xaigen" v-if="xaigen && isStudio"
type="button" type="button"
class="absolute right-1 top-1 rounded-md border border-white/20 bg-zinc-950/80 px-1.5 py-0.5 text-[10px] text-zinc-200 outline-none ring-amber-300/50 focus-visible:ring-2" class="absolute right-1 top-1 rounded-md border border-white/20 bg-zinc-950/80 p-1 text-zinc-200 outline-none ring-amber-300/50 focus-visible:ring-2"
:aria-label="isHidden(o) ? 'Show tile' : 'Hide tile'" :aria-label="isHidden(o) ? 'Show tile' : 'Hide tile'"
:aria-pressed="!isHidden(o)" :aria-pressed="!isHidden(o)"
@click.stop="toggleReveal(o.id)" @click.stop="toggleReveal(o.id)"
>{{ isHidden(o) ? 'Show' : 'Hide' }}</button> >
<svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true">
<path v-if="isHidden(o)" d="M3 3l18 18M10.5 10.6a2.5 2.5 0 0 0 3 3M9.4 5.1A10.4 10.4 0 0 1 12 4.5c5 0 9.3 3.1 11 7.5a11.6 11.6 0 0 1-4.2 5.1M6.2 6.2A11.5 11.5 0 0 0 1 12c1.2 2.7 3.3 4.9 5.9 6.3A10.5 10.5 0 0 0 12 19.5c1.2 0 2.4-.2 3.5-.6" stroke-linecap="round"/>
<template v-else>
<path d="M2 12s3.5-7.5 10-7.5S22 12 22 12s-3.5 7.5-10 7.5S2 12 2 12Z"/>
<circle cx="12" cy="12" r="3"/>
</template>
</svg>
</button>
</div> </div>
</div> </div>
<section v-if="form.mode!=='music'" aria-label="Source" class="rounded-2xl border border-white/10 p-3"> <section v-if="form.mode!=='music'" aria-label="Source" class="rounded-2xl border border-white/10 p-3">
<div class="mb-3 flex flex-wrap items-start justify-between gap-3"> <div class="mb-3 flex flex-wrap items-start justify-between gap-3">
<h2 class="text-xs uppercase tracking-wider text-zinc-400">Source</h2> <h2 class="text-xs uppercase tracking-wider text-zinc-400">Source</h2>
<div class="flex flex-wrap gap-4 text-sm text-zinc-300" aria-label="Identity locks"> <div v-if="isStudio" class="flex flex-wrap gap-4 text-sm text-zinc-300" aria-label="Identity locks">
<label class="flex items-start gap-2"><input v-model="form.lockFace" type="checkbox" class="mt-1 accent-amber-400"><span>Lock face<span class="block text-xs text-zinc-500">Face, hair, glasses, body</span></span></label> <label class="flex items-start gap-2"><input v-model="form.lockFace" type="checkbox" class="mt-1 accent-amber-400"><span>Lock face<span class="block text-xs text-zinc-500">Hero encode + lock on Klein / Krea stills</span></span></label>
<label class="flex items-start gap-2"><input v-model="form.lockOutfit" type="checkbox" class="mt-1 accent-amber-400"><span>Lock outfit<span class="block text-xs text-zinc-500">Wardrobe from hero</span></span></label> <label class="flex items-start gap-2"><input v-model="form.lockOutfit" type="checkbox" class="mt-1 accent-amber-400"><span>Lock outfit<span class="block text-xs text-zinc-500">Hero encode + lock on Klein / Krea stills</span></span></label>
</div> </div>
</div> </div>
<div class="flex flex-wrap gap-4"> <div class="flex flex-wrap gap-4">
@@ -255,7 +274,9 @@ import {randomSeed} from '~/shared/studio2/seed.mjs'
import {DEFAULT_MUSIC_LYRICS,DEFAULT_MUSIC_TAGS} from '~/utils/music' import {DEFAULT_MUSIC_LYRICS,DEFAULT_MUSIC_TAGS} from '~/utils/music'
const xaigen=useRuntimeConfig().public.studio==='xaigen' const xaigen=useRuntimeConfig().public.studio==='xaigen'
const {displayMode}=useDisplayMode() const {displayMode,isStudio,isClassic}=useDisplayMode()
const classicHideInput=ref(true)
const classicHideOutput=ref(true)
const picker=ref('') const picker=ref('')
const uploading=ref(false),uploadMessage=ref('') const uploading=ref(false),uploadMessage=ref('')
const revealed=ref<Set<string>>(new Set()) const revealed=ref<Set<string>>(new Set())
@@ -293,8 +314,19 @@ const sourceAsset=computed(()=>form.startClipId?clips.value.find((c:any)=>c.id==
const sourceSize=computed(()=>measured[sourceAsset.value?.id] || sourceAsset.value) const sourceSize=computed(()=>measured[sourceAsset.value?.id] || sourceAsset.value)
const resolvedSize=computed(()=>resolveSize(form.settings.aspect,sourceSize.value)) const resolvedSize=computed(()=>resolveSize(form.settings.aspect,sourceSize.value))
function isHidden(asset:any){return previewHidden(asset,revealed.value,xaigen)} function isHidden(asset:any){
function slotHidden(id:string){return xaigen && !!id && !revealed.value.has(id)} if(!xaigen || !asset?.id)return false
if(isClassic.value){
const input=asset.role==='input'
return input?classicHideInput.value:classicHideOutput.value
}
return previewHidden(asset,revealed.value,xaigen)
}
function slotHidden(id:string){
if(!xaigen || !id)return false
if(isClassic.value)return classicHideInput.value
return !revealed.value.has(id)
}
function toggleReveal(id:string){ function toggleReveal(id:string){
if(!id)return if(!id)return
const next=new Set(revealed.value) const next=new Set(revealed.value)