Persist the bench draft and hide input thumbs only.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
:aria-label="asset ? 'Replace photo' : 'Choose a photo'"
|
:aria-label="asset ? 'Replace photo' : 'Choose a photo'"
|
||||||
@click="$emit('pick')"
|
@click="$emit('pick')"
|
||||||
>
|
>
|
||||||
<span v-if="asset && hidden" class="p-2 text-center text-[var(--type-sm)] text-[var(--muted)]">Still loaded · preview hidden</span>
|
<span v-if="asset && hidden" class="p-2 text-center text-[var(--type-sm)] text-[var(--muted)]">Photo loaded</span>
|
||||||
<img v-else-if="asset" :src="thumbSrc" alt="">
|
<img v-else-if="asset" :src="thumbSrc" alt="">
|
||||||
<span v-else class="inline-flex flex-col items-center gap-1 px-2 text-center">
|
<span v-else class="inline-flex flex-col items-center gap-1 px-2 text-center">
|
||||||
<span>Choose a photo</span>
|
<span>Choose a photo</span>
|
||||||
|
|||||||
+253
-45
@@ -89,24 +89,13 @@
|
|||||||
>{{ hideInputs ? 'Show inputs' : 'Hide inputs' }}</button>
|
>{{ hideInputs ? 'Show inputs' : 'Hide inputs' }}</button>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="photosPlanState.handoffReadonly" class="text-[var(--type-sm)] text-[var(--muted)]">Continues from last frame</p>
|
<p v-if="photosPlanState.handoffReadonly" class="text-[var(--type-sm)] text-[var(--muted)]">Continues from last frame</p>
|
||||||
<template v-else-if="hideInputs">
|
|
||||||
<p class="photos-collapsed">
|
|
||||||
<span>{{ photoFilledCount }} photo{{ photoFilledCount===1 ? '' : 's' }}</span>
|
|
||||||
<button
|
|
||||||
v-if="form.photos.length < photosPlanState.max"
|
|
||||||
type="button"
|
|
||||||
class="photos-add-link"
|
|
||||||
@click="hideInputs=false; addPhoto()"
|
|
||||||
>+ Add photo</button>
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<p v-if="!form.photos.length && photosPlanState.emptyHint" class="text-[var(--type-sm)] text-[var(--muted)]">{{ photosPlanState.emptyHint }}</p>
|
<p v-if="!form.photos.length && photosPlanState.emptyHint" class="text-[var(--type-sm)] text-[var(--muted)]">{{ photosPlanState.emptyHint }}</p>
|
||||||
<Studio2AssetSlot
|
<Studio2AssetSlot
|
||||||
v-for="(card, index) in form.photos"
|
v-for="(card, index) in form.photos"
|
||||||
:key="'photo-'+index+'-'+card.stillId"
|
:key="'photo-'+index+'-'+card.stillId"
|
||||||
:asset="stillById(card.stillId)"
|
:asset="stillById(card.stillId)"
|
||||||
:hidden="false"
|
:hidden="hideInputs"
|
||||||
:role="card.role"
|
:role="card.role"
|
||||||
:role-options="photoRoleOptions"
|
:role-options="photoRoleOptions"
|
||||||
@pick="picker='photo:'+index"
|
@pick="picker='photo:'+index"
|
||||||
@@ -171,6 +160,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Studio2StyleChips v-if="!video && form.mode!=='music'" v-model="form.imageStyles" class="mt-1" />
|
||||||
|
<Studio2PromptControls v-if="video" v-model="form.promptSections" class="mt-2" />
|
||||||
|
|
||||||
<div v-if="['flux','krea','qwen21'].includes(form.engine)">
|
<div v-if="['flux','krea','qwen21'].includes(form.engine)">
|
||||||
<span class="field-label">LoRA</span>
|
<span class="field-label">LoRA</span>
|
||||||
<LoraSelect
|
<LoraSelect
|
||||||
@@ -253,9 +245,12 @@
|
|||||||
class="stage-actions"
|
class="stage-actions"
|
||||||
aria-label="Selected still actions"
|
aria-label="Selected still actions"
|
||||||
>
|
>
|
||||||
<button type="button" @click.stop="applyUseAsSource">Use as photo</button>
|
<button type="button" @click.stop="applyUseImage">Use image</button>
|
||||||
|
<button type="button" @click.stop="applyUsePrompt">Use prompt</button>
|
||||||
<button type="button" @click.stop="copySelectedPrompt">Copy prompt</button>
|
<button type="button" @click.stop="copySelectedPrompt">Copy prompt</button>
|
||||||
<button type="button" @click.stop="loadSelectedJob">Load</button>
|
<button type="button" @click.stop="copySelectedRawPrompt">Copy raw prompt</button>
|
||||||
|
<button type="button" @click.stop="loadInputUsed">Load input</button>
|
||||||
|
<button type="button" @click.stop="loadInputRaw">Load input raw</button>
|
||||||
<button type="button" class="stage-action-danger" @click.stop="deleteOutput">Delete</button>
|
<button type="button" class="stage-action-danger" @click.stop="deleteOutput">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -318,9 +313,12 @@
|
|||||||
:preview-hidden="!eyeOpen"
|
:preview-hidden="!eyeOpen"
|
||||||
@close="libraryOpen=false"
|
@close="libraryOpen=false"
|
||||||
@select="selectOutput"
|
@select="selectOutput"
|
||||||
@use-as-photo="onLibraryUseAsPhoto"
|
@use-image="onLibraryUseImage"
|
||||||
|
@use-prompt="onLibraryUsePrompt"
|
||||||
@copy-prompt="onLibraryCopyPrompt"
|
@copy-prompt="onLibraryCopyPrompt"
|
||||||
@load="onLibraryLoad"
|
@copy-raw-prompt="onLibraryCopyRawPrompt"
|
||||||
|
@load-input="onLibraryLoadInput"
|
||||||
|
@load-input-raw="onLibraryLoadInputRaw"
|
||||||
@delete="onLibraryDelete"
|
@delete="onLibraryDelete"
|
||||||
@toggle-preview="toggleReveal()"
|
@toggle-preview="toggleReveal()"
|
||||||
/>
|
/>
|
||||||
@@ -451,6 +449,8 @@ import {LOAD_GENERATION_PAYLOAD_KEY} from '~/utils/queuedJob'
|
|||||||
const NEG_STORAGE_KEY='studio2-negative-chips'
|
const NEG_STORAGE_KEY='studio2-negative-chips'
|
||||||
const ENHANCE_STORAGE_KEY='studio2-qwen21-enhance-prompt'
|
const ENHANCE_STORAGE_KEY='studio2-qwen21-enhance-prompt'
|
||||||
const TURBO_STORAGE_KEY='studio2-qwen21-turbo'
|
const TURBO_STORAGE_KEY='studio2-qwen21-turbo'
|
||||||
|
const BENCH_DRAFT_KEY='aigen:bench-draft'
|
||||||
|
let draftTimer:ReturnType<typeof setTimeout>|null=null
|
||||||
function readStoredNegatives(){
|
function readStoredNegatives(){
|
||||||
if(!import.meta.client)return [...defaultNegatives]
|
if(!import.meta.client)return [...defaultNegatives]
|
||||||
try{
|
try{
|
||||||
@@ -480,6 +480,84 @@ function writeStoredTurbo(on:boolean){
|
|||||||
if(!import.meta.client)return
|
if(!import.meta.client)return
|
||||||
try{localStorage.setItem(TURBO_STORAGE_KEY,on?'1':'0')}catch{/* ignore */}
|
try{localStorage.setItem(TURBO_STORAGE_KEY,on?'1':'0')}catch{/* ignore */}
|
||||||
}
|
}
|
||||||
|
function benchDraftPayload(){
|
||||||
|
return {
|
||||||
|
v:1,
|
||||||
|
mode:form.mode,
|
||||||
|
engine:form.engine,
|
||||||
|
folderId:form.folderId,
|
||||||
|
enhancePrompt:!!form.enhancePrompt,
|
||||||
|
turbo:!!form.turbo,
|
||||||
|
lockFace:!!form.lockFace,
|
||||||
|
lockOutfit:!!form.lockOutfit,
|
||||||
|
promptSections:structuredClone(form.promptSections||{}),
|
||||||
|
photos:(form.photos||[]).filter((p:any)=>p?.stillId).map((p:any)=>({stillId:p.stillId,role:p.role})),
|
||||||
|
imageStyles:structuredClone(form.imageStyles||{positive:[],negative:[]}),
|
||||||
|
settings:{
|
||||||
|
aspect:form.settings?.aspect||'auto',
|
||||||
|
seed:form.settings?.seed ?? null,
|
||||||
|
seedMode:form.settings?.seedMode||'random',
|
||||||
|
steps:form.settings?.steps,
|
||||||
|
cfg:form.settings?.cfg,
|
||||||
|
duration:form.settings?.duration,
|
||||||
|
fps:form.settings?.fps,
|
||||||
|
loraStack:structuredClone(form.settings?.loraStack||[])
|
||||||
|
},
|
||||||
|
eyeOpen:!!eyeOpen.value,
|
||||||
|
hideInputs:!!hideInputs.value,
|
||||||
|
music:structuredClone({tags:music.tags,lyrics:music.lyrics,duration:music.duration,seed:music.seed,name:music.name})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function saveBenchDraft(immediate=false){
|
||||||
|
if(!import.meta.client)return
|
||||||
|
const write=()=>{
|
||||||
|
try{localStorage.setItem(BENCH_DRAFT_KEY,JSON.stringify(benchDraftPayload()))}catch{/* ignore */}
|
||||||
|
}
|
||||||
|
if(draftTimer)clearTimeout(draftTimer)
|
||||||
|
if(immediate){write();return}
|
||||||
|
draftTimer=setTimeout(write,400)
|
||||||
|
}
|
||||||
|
function restoreBenchDraft(){
|
||||||
|
if(!import.meta.client)return
|
||||||
|
let raw=''
|
||||||
|
try{raw=localStorage.getItem(BENCH_DRAFT_KEY)||''}catch{return}
|
||||||
|
if(!raw)return
|
||||||
|
let draft:any
|
||||||
|
try{draft=JSON.parse(raw)}catch{return}
|
||||||
|
if(!draft||draft.v!==1)return
|
||||||
|
if(draft.mode)form.mode=draft.mode
|
||||||
|
if(draft.engine)form.engine=draft.engine
|
||||||
|
if(draft.folderId)form.folderId=draft.folderId
|
||||||
|
if(draft.enhancePrompt!=null)form.enhancePrompt=!!draft.enhancePrompt
|
||||||
|
if(draft.turbo!=null)form.turbo=!!draft.turbo
|
||||||
|
if(draft.lockFace!=null)form.lockFace=!!draft.lockFace
|
||||||
|
if(draft.lockOutfit!=null)form.lockOutfit=!!draft.lockOutfit
|
||||||
|
if(draft.promptSections)form.promptSections=sectionsFrom(draft.promptSections)
|
||||||
|
if(Array.isArray(draft.photos))form.photos=draft.photos.filter((p:any)=>p?.stillId).map((p:any)=>({stillId:String(p.stillId),role:String(p.role||'change')}))
|
||||||
|
if(draft.imageStyles)form.imageStyles={positive:draft.imageStyles.positive||[],negative:draft.imageStyles.negative||[]}
|
||||||
|
if(draft.settings){
|
||||||
|
Object.assign(form.settings,{
|
||||||
|
aspect:draft.settings.aspect||form.settings.aspect,
|
||||||
|
seed:draft.settings.seed ?? form.settings.seed,
|
||||||
|
seedMode:draft.settings.seedMode||form.settings.seedMode,
|
||||||
|
steps:draft.settings.steps ?? form.settings.steps,
|
||||||
|
cfg:draft.settings.cfg ?? form.settings.cfg,
|
||||||
|
duration:draft.settings.duration ?? form.settings.duration,
|
||||||
|
fps:draft.settings.fps ?? form.settings.fps,
|
||||||
|
loraStack:Array.isArray(draft.settings.loraStack)?draft.settings.loraStack:form.settings.loraStack
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if(draft.eyeOpen!=null)eyeOpen.value=!!draft.eyeOpen
|
||||||
|
if(draft.hideInputs!=null)hideInputs.value=!!draft.hideInputs
|
||||||
|
if(draft.music){
|
||||||
|
music.tags=String(draft.music.tags||music.tags)
|
||||||
|
music.lyrics=String(draft.music.lyrics||music.lyrics)
|
||||||
|
if(draft.music.duration!=null)music.duration=Number(draft.music.duration)||music.duration
|
||||||
|
if(draft.music.seed!=null)music.seed=String(draft.music.seed)
|
||||||
|
if(draft.music.name!=null)music.name=String(draft.music.name)
|
||||||
|
}
|
||||||
|
syncPhotosBindings()
|
||||||
|
}
|
||||||
|
|
||||||
const xaigen=isXaigenStudio(useRuntimeConfig().public)
|
const xaigen=isXaigenStudio(useRuntimeConfig().public)
|
||||||
const instanceName=String(useRuntimeConfig().public.instanceName || 'AIGen')
|
const instanceName=String(useRuntimeConfig().public.instanceName || 'AIGen')
|
||||||
@@ -560,13 +638,19 @@ watch(()=>[form.mode,form.engine],([mode,engine])=>{
|
|||||||
form.lockOutfit=false
|
form.lockOutfit=false
|
||||||
}
|
}
|
||||||
const plan=photosPlan({mode,engine})
|
const plan=photosPlan({mode,engine})
|
||||||
if(!plan.show){form.photos=[];form.imageAId='';form.imageBId='';if(engine==='qwen21')form.identityStillId='';return}
|
if(!plan.show){
|
||||||
|
if(engine==='qwen21')form.identityStillId=''
|
||||||
|
// Keep photos + prompt; section may hide for this mode.
|
||||||
|
saveBenchDraft(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
const allowed=new Set(plan.roles)
|
const allowed=new Set(plan.roles)
|
||||||
form.photos=form.photos
|
form.photos=(form.photos||[])
|
||||||
.filter((p:any)=>p?.stillId)
|
.filter((p:any)=>p?.stillId)
|
||||||
.slice(0,plan.max)
|
.slice(0,plan.max)
|
||||||
.map((p:any)=>({stillId:p.stillId,role:allowed.has(p.role)?p.role:plan.defaultRole}))
|
.map((p:any)=>({stillId:p.stillId,role:allowed.has(p.role)?p.role:plan.defaultRole}))
|
||||||
syncPhotosBindings()
|
syncPhotosBindings()
|
||||||
|
saveBenchDraft(true)
|
||||||
})
|
})
|
||||||
watch(()=>form.folderId,id=>{selected.value=null;form.imageAId='';form.imageBId='';form.photos=[];form.startClipId='';loadRevealed(id);if(import.meta.client && form.engine!=='qwen21')form.identityStillId=localStorage.getItem(`studio2-hero:${id}`)||''},{flush:'sync'})
|
watch(()=>form.folderId,id=>{selected.value=null;form.imageAId='';form.imageBId='';form.photos=[];form.startClipId='';loadRevealed(id);if(import.meta.client && form.engine!=='qwen21')form.identityStillId=localStorage.getItem(`studio2-hero:${id}`)||''},{flush:'sync'})
|
||||||
watch(()=>form.identityStillId,id=>{if(import.meta.client&&form.folderId&&form.engine!=='qwen21')localStorage.setItem(`studio2-hero:${form.folderId}`,id)})
|
watch(()=>form.identityStillId,id=>{if(import.meta.client&&form.folderId&&form.engine!=='qwen21')localStorage.setItem(`studio2-hero:${form.folderId}`,id)})
|
||||||
@@ -718,17 +802,20 @@ function addPhoto(){
|
|||||||
if(form.photos.length>=plan.max)return
|
if(form.photos.length>=plan.max)return
|
||||||
form.photos.push({stillId:'',role:plan.defaultRole||'change'})
|
form.photos.push({stillId:'',role:plan.defaultRole||'change'})
|
||||||
picker.value='photo:'+(form.photos.length-1)
|
picker.value='photo:'+(form.photos.length-1)
|
||||||
|
saveBenchDraft(true)
|
||||||
}
|
}
|
||||||
function removePhoto(index:number){
|
function removePhoto(index:number){
|
||||||
if(!Number.isInteger(index)||index<0||index>=form.photos.length)return
|
if(!Number.isInteger(index)||index<0||index>=form.photos.length)return
|
||||||
form.photos.splice(index,1)
|
form.photos.splice(index,1)
|
||||||
// Explicit empty photos array clears imageAId / imageBId / identityStillId.
|
// Explicit empty photos array clears imageAId / imageBId / identityStillId.
|
||||||
syncPhotosBindings()
|
syncPhotosBindings()
|
||||||
|
saveBenchDraft(true)
|
||||||
}
|
}
|
||||||
function setPhotoRole(index:number,role:string){
|
function setPhotoRole(index:number,role:string){
|
||||||
if(!form.photos[index])return
|
if(!form.photos[index])return
|
||||||
form.photos[index].role=role
|
form.photos[index].role=role
|
||||||
syncPhotosBindings()
|
syncPhotosBindings()
|
||||||
|
saveBenchDraft(true)
|
||||||
}
|
}
|
||||||
function setPhotoStill(index:number,stillId:string){
|
function setPhotoStill(index:number,stillId:string){
|
||||||
if(!Number.isInteger(index)||!form.photos[index])return
|
if(!Number.isInteger(index)||!form.photos[index])return
|
||||||
@@ -738,6 +825,7 @@ function setPhotoStill(index:number,stillId:string){
|
|||||||
form.photos[index].stillId=stillId
|
form.photos[index].stillId=stillId
|
||||||
}
|
}
|
||||||
syncPhotosBindings()
|
syncPhotosBindings()
|
||||||
|
saveBenchDraft(true)
|
||||||
}
|
}
|
||||||
function discardEmptyPhotoDraft(index:number){
|
function discardEmptyPhotoDraft(index:number){
|
||||||
if(!Number.isInteger(index)||!form.photos[index])return
|
if(!Number.isInteger(index)||!form.photos[index])return
|
||||||
@@ -765,33 +853,118 @@ function mentionPhoto(index:number){
|
|||||||
promptCaret.value={start:next.selectionStart,end:next.selectionEnd}
|
promptCaret.value={start:next.selectionStart,end:next.selectionEnd}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
function applyUseAsSource(){
|
function usedPromptOf(item:any){
|
||||||
if(!selected.value||selected.value.kind==='track')return
|
return String(
|
||||||
|
item?.studio2?.prompt
|
||||||
|
|| item?.prompt
|
||||||
|
|| item?.studio2?.compiledPrompt
|
||||||
|
|| item?.studio2?.promptSections?.action
|
||||||
|
|| ''
|
||||||
|
).trim()
|
||||||
|
}
|
||||||
|
function rawPromptOf(item:any){
|
||||||
|
return String(item?.promptRaw || item?.studio2?.promptRaw || '').trim()
|
||||||
|
}
|
||||||
|
function sourceStillIdsOf(item:any){
|
||||||
|
const meta=item?.studio2 || {}
|
||||||
|
const ids:string[]=[]
|
||||||
|
const push=(id:any)=>{const v=String(id||'').trim();if(v && !ids.includes(v))ids.push(v)}
|
||||||
|
push(meta.sourceStillId)
|
||||||
|
push(meta.imageAId)
|
||||||
|
for(const card of meta.photos||[]) push(card?.stillId)
|
||||||
|
if(meta.imageBId) push(meta.imageBId)
|
||||||
|
return ids.filter(id=>stillById(id))
|
||||||
|
}
|
||||||
|
function useImageRole(){
|
||||||
const plan=photosPlanState.value
|
const plan=photosPlanState.value
|
||||||
if(!plan.show||plan.handoffReadonly){
|
const filled=(form.photos||[]).filter((p:any)=>p?.stillId)
|
||||||
Object.assign(form,useAsSource(form,selected.value))
|
if((form.mode==='edit' || video.value) && !filled.length && plan.roles.includes('change')) return 'change'
|
||||||
uploadMessage.value=`Using ${assetLabel(selected.value)}.`
|
if(plan.roles.includes('extra')) return 'extra'
|
||||||
|
if(plan.roles.includes('outfit')) return 'outfit'
|
||||||
|
return plan.defaultRole || 'change'
|
||||||
|
}
|
||||||
|
function applyUseImage(item?:any){
|
||||||
|
const asset=item || selected.value
|
||||||
|
if(!asset||asset.kind==='track')return
|
||||||
|
const plan=photosPlanState.value
|
||||||
|
const role=useImageRole()
|
||||||
|
if(!plan.show){
|
||||||
|
form.photos=[{stillId:asset.id,role}]
|
||||||
|
syncPhotosBindings()
|
||||||
|
uploadMessage.value=`Added ${assetLabel(asset)} to Photos.`
|
||||||
|
saveBenchDraft(true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if(form.photos.length>=plan.max){
|
if(form.photos.length>=plan.max){
|
||||||
form.photos[form.photos.length-1]={stillId:selected.value.id,role:plan.defaultRole||form.photos.at(-1)?.role||'change'}
|
form.photos[form.photos.length-1]={stillId:asset.id,role}
|
||||||
}else{
|
}else{
|
||||||
form.photos.push({stillId:selected.value.id,role:plan.defaultRole||'change'})
|
form.photos.push({stillId:asset.id,role})
|
||||||
}
|
}
|
||||||
syncPhotosBindings()
|
syncPhotosBindings()
|
||||||
uploadMessage.value=`Added ${assetLabel(selected.value)} to Photos.`
|
uploadMessage.value=`Added ${assetLabel(asset)} to Photos.`
|
||||||
|
saveBenchDraft(true)
|
||||||
}
|
}
|
||||||
async function copySelectedPrompt(){
|
function applyUsePrompt(item?:any){
|
||||||
const text=selected.value?.studio2?.prompt
|
const asset=item || selected.value
|
||||||
|| selected.value?.studio2?.compiledPrompt
|
if(!asset)return
|
||||||
|| selected.value?.prompt
|
const text=usedPromptOf(asset)
|
||||||
|| selected.value?.studio2?.promptSections?.action
|
form.promptSections.action=text
|
||||||
|| ''
|
uploadMessage.value=text?'Prompt set from this item.':'No Used prompt on this item.'
|
||||||
|
saveBenchDraft(true)
|
||||||
|
}
|
||||||
|
async function copySelectedPrompt(item?:any){
|
||||||
|
const asset=item || selected.value
|
||||||
|
const text=usedPromptOf(asset)
|
||||||
try{
|
try{
|
||||||
if(import.meta.client && navigator.clipboard)await navigator.clipboard.writeText(String(text||''))
|
if(import.meta.client && navigator.clipboard)await navigator.clipboard.writeText(String(text||''))
|
||||||
uploadMessage.value=text?'Prompt copied.':'No prompt on this item.'
|
uploadMessage.value=text?'Prompt copied.':'No prompt on this item.'
|
||||||
}catch{error.value='Could not copy prompt.'}
|
}catch{error.value='Could not copy prompt.'}
|
||||||
}
|
}
|
||||||
|
async function copySelectedRawPrompt(item?:any){
|
||||||
|
const asset=item || selected.value
|
||||||
|
const raw=rawPromptOf(asset)
|
||||||
|
const used=usedPromptOf(asset)
|
||||||
|
const text=raw || used
|
||||||
|
try{
|
||||||
|
if(import.meta.client && navigator.clipboard)await navigator.clipboard.writeText(String(text||''))
|
||||||
|
uploadMessage.value=raw?'Raw prompt copied.':(used?'Used prompt copied (no Typed on this item).':'No prompt on this item.')
|
||||||
|
}catch{error.value='Could not copy prompt.'}
|
||||||
|
}
|
||||||
|
function loadInputPhotos(ids:string[]){
|
||||||
|
if(!ids.length)return
|
||||||
|
const plan=photosPlanState.value
|
||||||
|
const roles=plan.roles?.length?plan.roles:['change']
|
||||||
|
form.photos=ids.slice(0,plan.max || ids.length).map((id,i)=>({
|
||||||
|
stillId:id,
|
||||||
|
role:i===0 && roles.includes('change')?'change':(roles.includes('extra')?'extra':roles[Math.min(i,roles.length-1)]||'change')
|
||||||
|
}))
|
||||||
|
syncPhotosBindings()
|
||||||
|
}
|
||||||
|
function loadInputUsed(item?:any){
|
||||||
|
const asset=item || selected.value
|
||||||
|
if(!asset)return
|
||||||
|
const ids=sourceStillIdsOf(asset)
|
||||||
|
const text=usedPromptOf(asset)
|
||||||
|
if(ids.length) loadInputPhotos(ids)
|
||||||
|
if(text) form.promptSections.action=text
|
||||||
|
uploadMessage.value=ids.length
|
||||||
|
?(text?'Input photos and Used prompt loaded.':'Input photos loaded.')
|
||||||
|
:(text?'Used prompt loaded (no source still on this item).':'Nothing to load from this item.')
|
||||||
|
saveBenchDraft(true)
|
||||||
|
}
|
||||||
|
function loadInputRaw(item?:any){
|
||||||
|
const asset=item || selected.value
|
||||||
|
if(!asset)return
|
||||||
|
const ids=sourceStillIdsOf(asset)
|
||||||
|
const raw=rawPromptOf(asset)
|
||||||
|
const used=usedPromptOf(asset)
|
||||||
|
if(ids.length) loadInputPhotos(ids)
|
||||||
|
form.promptSections.action=raw || used
|
||||||
|
uploadMessage.value=ids.length
|
||||||
|
?(raw?'Input photos and Typed prompt loaded.':(used?'Input photos loaded; Typed missing so Used was applied.':'Input photos loaded.'))
|
||||||
|
:(raw?'Typed prompt loaded (no source still).':(used?'Used prompt loaded (no Typed / source still).':'Nothing to load from this item.'))
|
||||||
|
saveBenchDraft(true)
|
||||||
|
}
|
||||||
function stillIdSet(){return new Set(stills.value.map((s:any)=>s.id))}
|
function stillIdSet(){return new Set(stills.value.map((s:any)=>s.id))}
|
||||||
function clipIdSet(){return new Set(clips.value.map((c:any)=>c.id))}
|
function clipIdSet(){return new Set(clips.value.map((c:any)=>c.id))}
|
||||||
function applyHydratedJob(saved:any,output?:any){
|
function applyHydratedJob(saved:any,output?:any){
|
||||||
@@ -806,6 +979,7 @@ function applyHydratedJob(saved:any,output?:any){
|
|||||||
error.value=missing||''
|
error.value=missing||''
|
||||||
if(missing)uploadMessage.value=missing
|
if(missing)uploadMessage.value=missing
|
||||||
else uploadMessage.value='Job loaded into the bench.'
|
else uploadMessage.value='Job loaded into the bench.'
|
||||||
|
saveBenchDraft(true)
|
||||||
}
|
}
|
||||||
function loadSelectedJob(){
|
function loadSelectedJob(){
|
||||||
if(!selected.value)return
|
if(!selected.value)return
|
||||||
@@ -892,17 +1066,29 @@ function stepFamily(delta:number){
|
|||||||
const next=list[(from + delta + list.length) % list.length]
|
const next=list[(from + delta + list.length) % list.length]
|
||||||
if(next) selectOutput(next)
|
if(next) selectOutput(next)
|
||||||
}
|
}
|
||||||
function onLibraryUseAsPhoto(item:any){
|
function onLibraryUseImage(item:any){
|
||||||
if(item)selectOutput(item)
|
if(item)selectOutput(item)
|
||||||
applyUseAsSource()
|
applyUseImage(item)
|
||||||
|
}
|
||||||
|
function onLibraryUsePrompt(item:any){
|
||||||
|
if(item)selectOutput(item)
|
||||||
|
applyUsePrompt(item)
|
||||||
}
|
}
|
||||||
function onLibraryCopyPrompt(item:any){
|
function onLibraryCopyPrompt(item:any){
|
||||||
if(item)selectOutput(item)
|
if(item)selectOutput(item)
|
||||||
void copySelectedPrompt()
|
void copySelectedPrompt(item)
|
||||||
}
|
}
|
||||||
function onLibraryLoad(item:any){
|
function onLibraryCopyRawPrompt(item:any){
|
||||||
if(item)selectOutput(item)
|
if(item)selectOutput(item)
|
||||||
loadSelectedJob()
|
void copySelectedRawPrompt(item)
|
||||||
|
}
|
||||||
|
function onLibraryLoadInput(item:any){
|
||||||
|
if(item)selectOutput(item)
|
||||||
|
loadInputUsed(item)
|
||||||
|
}
|
||||||
|
function onLibraryLoadInputRaw(item:any){
|
||||||
|
if(item)selectOutput(item)
|
||||||
|
loadInputRaw(item)
|
||||||
}
|
}
|
||||||
function onLibraryDelete(item:any){
|
function onLibraryDelete(item:any){
|
||||||
if(item)selectOutput(item)
|
if(item)selectOutput(item)
|
||||||
@@ -1080,14 +1266,23 @@ async function refresh(){
|
|||||||
selected.value=next
|
selected.value=next
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(()=>form.folderId,id=>{selected.value=null;form.imageAId='';form.imageBId='';form.photos=[];form.startClipId='';loadRevealed(id);if(import.meta.client && form.engine!=='qwen21')form.identityStillId=localStorage.getItem(`studio2-hero:${id}`)||''},{flush:'sync'})
|
watch(()=>[
|
||||||
watch(()=>form.identityStillId,id=>{if(import.meta.client&&form.folderId&&form.engine!=='qwen21')localStorage.setItem(`studio2-hero:${form.folderId}`,id)})
|
form.mode,form.engine,form.folderId,form.enhancePrompt,form.turbo,form.lockFace,form.lockOutfit,
|
||||||
|
form.promptSections,form.photos,form.imageStyles,form.settings,
|
||||||
|
eyeOpen.value,hideInputs.value,
|
||||||
|
music.tags,music.lyrics,music.duration,music.seed,music.name
|
||||||
|
],()=>saveBenchDraft(),{deep:true})
|
||||||
let timer:ReturnType<typeof setTimeout>,clock:ReturnType<typeof setInterval>,disposed=false
|
let timer:ReturnType<typeof setTimeout>,clock:ReturnType<typeof setInterval>,disposed=false
|
||||||
onMounted(async()=>{
|
onMounted(async()=>{
|
||||||
let storage;try{storage=localStorage}catch{}
|
let storage;try{storage=localStorage}catch{}
|
||||||
loraMemory=createLoraMemory(xaigen,storage);loraMemory.sync(form);form.settings.seed=randomSeed()
|
loraMemory=createLoraMemory(xaigen,storage);loraMemory.sync(form);form.settings.seed=randomSeed()
|
||||||
clock=setInterval(()=>{now.value=Date.now()},1000);void loadLoras(true)
|
clock=setInterval(()=>{now.value=Date.now()},1000);void loadLoras(true)
|
||||||
library.value=await $fetch('/api/studio-2/library');form.folderId=library.value.folders[0]?.id||'';loadRevealed(form.folderId)
|
library.value=await $fetch('/api/studio-2/library')
|
||||||
|
restoreBenchDraft()
|
||||||
|
if(!form.folderId)form.folderId=library.value.folders[0]?.id||''
|
||||||
|
// Folder watch may have cleared photos when folderId was assigned — re-apply draft photos/prompt.
|
||||||
|
restoreBenchDraft()
|
||||||
|
loadRevealed(form.folderId)
|
||||||
await applyIncomingGenerationLoad()
|
await applyIncomingGenerationLoad()
|
||||||
const id=String(route.query.still||route.query.clip||route.query.track||'')
|
const id=String(route.query.still||route.query.clip||route.query.track||'')
|
||||||
const asset=[...library.value.stills,...library.value.clips,...(library.value.tracks||[])].find((a:any)=>a.id===id)
|
const asset=[...library.value.stills,...library.value.clips,...(library.value.tracks||[])].find((a:any)=>a.id===id)
|
||||||
@@ -1098,7 +1293,11 @@ onMounted(async()=>{
|
|||||||
})
|
})
|
||||||
onBeforeUnmount(()=>{
|
onBeforeUnmount(()=>{
|
||||||
disposed=true;clearTimeout(timer);clearInterval(clock)
|
disposed=true;clearTimeout(timer);clearInterval(clock)
|
||||||
if(import.meta.client)window.removeEventListener('keydown',onStageHotkey)
|
if(draftTimer)clearTimeout(draftTimer)
|
||||||
|
if(import.meta.client){
|
||||||
|
saveBenchDraft(true)
|
||||||
|
window.removeEventListener('keydown',onStageHotkey)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function randomizeSeed(){form.settings.seedMode='random';form.settings.seed=randomSeed()}
|
function randomizeSeed(){form.settings.seedMode='random';form.settings.seed=randomSeed()}
|
||||||
@@ -1128,7 +1327,7 @@ const mobileCtx=computed(()=>({
|
|||||||
photoFilledCount:photoFilledCount.value,
|
photoFilledCount:photoFilledCount.value,
|
||||||
openLibrary:()=>{libraryOpen.value=true},
|
openLibrary:()=>{libraryOpen.value=true},
|
||||||
closeLibrary:()=>{libraryOpen.value=false},
|
closeLibrary:()=>{libraryOpen.value=false},
|
||||||
toggleHideInputs:()=>{hideInputs.value=!hideInputs.value},
|
toggleHideInputs:()=>{hideInputs.value=!hideInputs.value;saveBenchDraft(true)},
|
||||||
showInputsAndAddPhoto:()=>{hideInputs.value=false;addPhoto()},
|
showInputsAndAddPhoto:()=>{hideInputs.value=false;addPhoto()},
|
||||||
sourceSlot:sourceSlot.value,
|
sourceSlot:sourceSlot.value,
|
||||||
video:video.value,
|
video:video.value,
|
||||||
@@ -1168,12 +1367,21 @@ const mobileCtx=computed(()=>({
|
|||||||
removePhoto,
|
removePhoto,
|
||||||
setPhotoRole,
|
setPhotoRole,
|
||||||
mentionPhoto,
|
mentionPhoto,
|
||||||
applyUseAsSource,
|
applyUseAsSource:applyUseImage,
|
||||||
|
applyUseImage,
|
||||||
|
applyUsePrompt,
|
||||||
copySelectedPrompt,
|
copySelectedPrompt,
|
||||||
|
copySelectedRawPrompt,
|
||||||
|
loadInputUsed,
|
||||||
|
loadInputRaw,
|
||||||
loadSelectedJob,
|
loadSelectedJob,
|
||||||
onLibraryUseAsPhoto,
|
onLibraryUseAsPhoto:onLibraryUseImage,
|
||||||
|
onLibraryUseImage,
|
||||||
|
onLibraryUsePrompt,
|
||||||
onLibraryCopyPrompt,
|
onLibraryCopyPrompt,
|
||||||
onLibraryLoad,
|
onLibraryCopyRawPrompt,
|
||||||
|
onLibraryLoadInput,
|
||||||
|
onLibraryLoadInputRaw,
|
||||||
onLibraryDelete,
|
onLibraryDelete,
|
||||||
randomizeSeed,
|
randomizeSeed,
|
||||||
setPicker,
|
setPicker,
|
||||||
|
|||||||
Reference in New Issue
Block a user