Fix bench draft structuredClone 500 and Prompt Builder button contrast.

Draft snapshots use plain JSON of picked fields with restore try/catch that clears bad storage; Prompt Builder actions use amber primary/secondary styles with 44px tap targets.
This commit is contained in:
Towsty
2026-10-03 19:38:55 -05:00
parent a8ae61704e
commit d803d57a78
2 changed files with 1003 additions and 916 deletions
+127 -40
View File
@@ -520,6 +520,58 @@ function defaultEngineLook(engine:string):EngineLook{
if(engine==='qwen21') return {steps:25,cfg:1,aspect:'auto',seed:null,seedMode:'random',loraStack:[]}
return {steps:24,cfg:4,aspect:'auto',seed:null,seedMode:'random',loraStack:[]}
}
/** Plain JSON snapshot only — never structuredClone Vue proxies / Files. */
function plainJson<T>(value:unknown, fallback:T):T{
try{return JSON.parse(JSON.stringify(value)) as T}catch{return fallback}
}
function plainLoraStack(stack:unknown){
if(!Array.isArray(stack)) return [] as any[]
return stack.map((item:any)=>{
if(!item || typeof item!=='object') return null
const name=String(item.name||item.filename||item.lora||'').trim()
if(!name) return null
const out:any={name}
if(item.strength!=null && Number.isFinite(Number(item.strength))) out.strength=Number(item.strength)
if(item.strengthModel!=null && Number.isFinite(Number(item.strengthModel))) out.strengthModel=Number(item.strengthModel)
if(item.strengthClip!=null && Number.isFinite(Number(item.strengthClip))) out.strengthClip=Number(item.strengthClip)
if(item.clipStrength!=null && Number.isFinite(Number(item.clipStrength))) out.clipStrength=Number(item.clipStrength)
return out
}).filter(Boolean)
}
function plainPromptSections(sections:any){
const src=sections&&typeof sections==='object'?sections:{}
return {
action:String(src.action||''),
camera:String(src.camera||''),
soundscape:String(src.soundscape||''),
music:String(src.music||''),
locks:String(src.locks||''),
cameraBeats:plainJson(src.cameraBeats||[], [] as any[]),
soundscapeChips:Array.isArray(src.soundscapeChips)?src.soundscapeChips.map((c:any)=>String(c||'')).filter(Boolean):[],
soundscapeLevel:String(src.soundscapeLevel||'normal'),
musicChip:String(src.musicChip||'N/A'),
dialogue:plainJson(src.dialogue||{mode:'none',line:''}, {mode:'none',line:''})
}
}
function plainEngineSamplingMap(){
const out:Record<string,EngineLook>={}
for(const key of ['flux','krea','qwen21'] as const){
const row=engineSampling[key]||defaultEngineLook(key)
out[key]={
steps:Number(row.steps)||defaultEngineLook(key).steps,
cfg:Number(row.cfg)||defaultEngineLook(key).cfg,
aspect:String(row.aspect||'auto'),
seed:row.seed==null || row.seed==='' ? null : Number(row.seed),
seedMode:row.seedMode==='locked'?'locked':'random',
loraStack:plainLoraStack(row.loraStack)
}
}
return out
}
function clearDraftStores(){
try{localStorage.removeItem(BENCH_DRAFT_KEY)}catch{/* ignore */}
try{sessionStorage.removeItem(BENCH_DRAFT_SESSION_KEY)}catch{/* ignore */}
}
function readStoredNegatives(){
if(!import.meta.client)return [...defaultNegatives]
try{
@@ -550,36 +602,64 @@ function writeStoredTurbo(on:boolean){
try{localStorage.setItem(TURBO_STORAGE_KEY,on?'1':'0')}catch{/* ignore */}
}
function benchDraftPayload(){
if(['flux','krea','qwen21'].includes(form.engine)) rememberEngineSampling(form.engine)
return {
v:2,
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||[])
},
engineSampling:structuredClone(engineSampling),
batchMode:!!batchMode.value,
eyeOpen:!!eyeOpen.value,
hideInputs:!!hideInputs.value,
queueCollapsed:!!queueCollapsed.value,
selectedId:selected.value?.id||'',
music:structuredClone({tags:music.tags,lyrics:music.lyrics,duration:music.duration,seed:music.seed,name:music.name})
try{
if(['flux','krea','qwen21'].includes(form.engine)) rememberEngineSampling(form.engine)
const styles=form.imageStyles||{positive:[],negative:[]}
return {
v:2,
mode:String(form.mode||'generate'),
engine:String(form.engine||'flux'),
folderId:String(form.folderId||''),
enhancePrompt:!!form.enhancePrompt,
turbo:!!form.turbo,
lockFace:!!form.lockFace,
lockOutfit:!!form.lockOutfit,
promptSections:plainPromptSections(form.promptSections),
photos:(form.photos||[])
.filter((p:any)=>p && typeof p.stillId==='string' && p.stillId)
.map((p:any)=>({stillId:String(p.stillId),role:String(p.role||'change')})),
imageStyles:{
positive:Array.isArray(styles.positive)?styles.positive.map((s:any)=>String(s||'')).filter(Boolean):[],
negative:Array.isArray(styles.negative)?styles.negative.map((s:any)=>String(s||'')).filter(Boolean):[]
},
settings:{
aspect:String(form.settings?.aspect||'auto'),
seed:form.settings?.seed==null || form.settings?.seed==='' ? null : Number(form.settings.seed),
seedMode:form.settings?.seedMode==='locked'?'locked':'random',
steps:Number(form.settings?.steps)||24,
cfg:Number(form.settings?.cfg)||4,
duration:Number(form.settings?.duration)||5,
fps:Number(form.settings?.fps)||24,
loraStack:plainLoraStack(form.settings?.loraStack)
},
engineSampling:plainEngineSamplingMap(),
batchMode:!!batchMode.value,
eyeOpen:!!eyeOpen.value,
hideInputs:!!hideInputs.value,
queueCollapsed:!!queueCollapsed.value,
selectedId:typeof selected.value?.id==='string'?selected.value.id:'',
music:{
tags:String(music.tags||''),
lyrics:String(music.lyrics||''),
duration:Number(music.duration)||30,
seed:String(music.seed||''),
name:String(music.name||'')
}
}
}catch{
return {
v:2,
mode:String(form.mode||'generate'),
engine:String(form.engine||'flux'),
folderId:String(form.folderId||''),
promptSections:{action:String(form.promptSections?.action||'')},
engineSampling:plainEngineSamplingMap(),
settings:{
aspect:String(form.settings?.aspect||'auto'),
steps:Number(form.settings?.steps)||24,
cfg:Number(form.settings?.cfg)||4
}
}
}
}
function writeDraftStores(payload:string){
@@ -609,18 +689,18 @@ function mergeEngineLook(row:any, fallback:EngineLook):EngineLook{
aspect:typeof row?.aspect==='string' && row.aspect ? row.aspect : fallback.aspect,
seed:row?.seed==null || row?.seed==='' ? null : Number(row.seed),
seedMode:row?.seedMode==='locked'?'locked':'random',
loraStack:Array.isArray(row?.loraStack)?structuredClone(row.loraStack):structuredClone(fallback.loraStack)
loraStack:plainLoraStack(row?.loraStack ?? fallback.loraStack)
}
}
function restoreBenchDraft(){
if(!import.meta.client)return
const raw=readDraftRaw()
if(!raw)return
let draft:any
try{draft=JSON.parse(raw)}catch{return}
if(!draft|| (draft.v!==1 && draft.v!==2))return
restoringDraft=true
try{
let draft:any
try{draft=JSON.parse(raw)}catch{clearDraftStores();return}
if(!draft|| (draft.v!==1 && draft.v!==2)){clearDraftStores();return}
if(draft.engineSampling && typeof draft.engineSampling==='object'){
for(const key of ['flux','krea','qwen21'] as const){
const row=draft.engineSampling[key]
@@ -636,8 +716,13 @@ function restoreBenchDraft(){
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(Array.isArray(draft.photos))form.photos=draft.photos
.filter((p:any)=>p && typeof p.stillId==='string' && p.stillId)
.map((p:any)=>({stillId:String(p.stillId),role:String(p.role||'change')}))
if(draft.imageStyles)form.imageStyles={
positive:Array.isArray(draft.imageStyles.positive)?draft.imageStyles.positive.map((s:any)=>String(s||'')).filter(Boolean):[],
negative:Array.isArray(draft.imageStyles.negative)?draft.imageStyles.negative.map((s:any)=>String(s||'')).filter(Boolean):[]
}
if(draft.settings){
Object.assign(form.settings,{
aspect:draft.settings.aspect||form.settings.aspect,
@@ -647,7 +732,7 @@ function restoreBenchDraft(){
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
loraStack:Array.isArray(draft.settings.loraStack)?plainLoraStack(draft.settings.loraStack):form.settings.loraStack
})
}
if(['flux','krea','qwen21'].includes(form.engine)) applyEngineSampling(form.engine)
@@ -663,7 +748,9 @@ function restoreBenchDraft(){
if(draft.music.name!=null)music.name=String(draft.music.name)
}
syncPhotosBindings()
} finally {
}catch{
clearDraftStores()
}finally{
restoringDraft=false
}
}
@@ -746,7 +833,7 @@ function rememberEngineSampling(engine:string){
aspect:String(form.settings.aspect||fallback.aspect),
seed:form.settings.seed==null || form.settings.seed==='' ? null : Number(form.settings.seed),
seedMode:form.settings.seedMode==='locked'?'locked':'random',
loraStack:Array.isArray(form.settings.loraStack)?structuredClone(form.settings.loraStack):[]
loraStack:plainLoraStack(form.settings.loraStack)
}
}
function applyEngineSampling(engine:string){
@@ -758,7 +845,7 @@ function applyEngineSampling(engine:string){
form.settings.aspect=saved.aspect || form.settings.aspect || 'auto'
form.settings.seed=saved.seed
form.settings.seedMode=saved.seedMode || 'random'
form.settings.loraStack=Array.isArray(saved.loraStack)?structuredClone(saved.loraStack):[]
form.settings.loraStack=plainLoraStack(saved.loraStack)
}
function clearPrompt(){
if(form.promptSections) form.promptSections.action=''