import {restoreLoras,loraUse} from './lora-memory.mjs' import { restoreInput } from './contracts.mjs' export const aspectOptions = [{id:'auto',label:'Automatic',hint:'Match start or hero still'},{id:'16:9',label:'16:9',hint:'1344 × 768 · padded'},{id:'9:16',label:'9:16',hint:'768 × 1344 · padded'},{id:'1:1',label:'1:1',hint:'960 × 960 · padded'}] export function resolveQwen21Size(aspect, source) { const presets = {'16:9':[1024,576],'9:16':[576,1024],'1:1':[1024,1024]} if (presets[aspect]) return {width:presets[aspect][0],height:presets[aspect][1]} if (source?.width && source?.height) { const ratio = source.width / source.height const width = Math.max(32, Math.round(Math.sqrt(1024 * 1024 * ratio) / 32) * 32) const height = Math.max(32, Math.round(Math.sqrt(1024 * 1024 / ratio) / 32) * 32) return {width, height} } return {width:1024,height:1024} } export function resolveSize(aspect, source, fallback = {width:1344,height:768}, engine = '') { if (engine === 'qwen21') return resolveQwen21Size(aspect, source) const presets = {'16:9':[1344,768],'9:16':[768,1344],'1:1':[960,960]} if (presets[aspect]) return {width:presets[aspect][0],height:presets[aspect][1]} if (!source?.width || !source?.height) return {width:fallback.width || 1344,height:fallback.height || 768} const factor = Math.min(1344/Math.max(source.width,source.height),768/Math.min(source.width,source.height)) return {width:Math.max(256,Math.round(source.width*factor/32)*32),height:Math.max(256,Math.round(source.height*factor/32)*32)} } export function bindClip(form, clip, xaigen = false, memory) { if (!clip?.id) return form const saved = clip.studio2 || clip.job || {} const settings = saved.settings || clip.settings || {} const next = {...form, startClipId:clip.id, mode:'extend', settings:{...form.settings}} next.settings.aspect=['auto','16:9','9:16','1:1'].includes(settings.aspect)?settings.aspect:(form.settings?.aspect || 'auto') for (const key of ['duration','width','height','fps','loraStack','turbo','steps','cfg']) { if (settings[key] == null) continue try { next.settings[key] = typeof structuredClone === 'function' ? structuredClone(settings[key]) : JSON.parse(JSON.stringify(settings[key])) } catch { next.settings[key] = settings[key] } } next.engine = xaigen && (saved.engine || clip.engine)==='ltx' ? 'ltx' : 'minimax' if (saved.identityStillId) next.identityStillId = saved.identityStillId for (const key of ['lockFace','lockOutfit']) if (typeof saved[key] === 'boolean') next[key] = saved[key] return restoreLoras(next,settings.loraStack,xaigen,memory) } export function restoreAsset(form, item, xaigen = false, memory) { if (item.kind==='clip' || item.studio2?.kind==='video') { const next=bindClip(form,item,xaigen,memory) const seed=item.studio2?.settings?.seed ?? item.settings?.seed ?? item.seed if (seed != null) next.settings.seed=seed next.settings.seedMode='random' return {...next,mode:'extend'} } const restored = restoreInput(item) const next = {...form,...restored,settings:{...form.settings,...restored.settings}} next.settings.seedMode='random' next.engine=restored.engine==='qwen21'?'qwen21':restored.engine==='krea'?'krea':'flux' next.mode=['generate','edit','compose','refine','iterate'].includes(restored.mode)?restored.mode:'edit' next.imageAId=item.id next.startClipId='' next.settings.aspect=item.studio2?.settings?.aspect || item.settings?.aspect || 'auto' return restoreLoras(next,item.studio2?.settings?.loraStack ?? item.settings?.loraStack,xaigen,memory) } export function extendSelected(form, clip, xaigen = false, memory) { const next = { ...form, settings:{...form.settings}, mode: 'extend', engine: xaigen && form.engine === 'ltx' ? 'ltx' : 'minimax' } const target = clip?.id ? clip : null if (!target?.id) return restoreLoras(next,loraUse(form)===loraUse(next)?form.settings?.loraStack:[],xaigen,memory) // Already bound to this clip: switching task must not stomp the user's overrides // (locks, CFG, etc.) by re-copying the clip's saved settings over them. if (form.startClipId === target.id) return restoreLoras(next,loraUse(form)===loraUse(next)?form.settings?.loraStack:[],xaigen,memory) try { return bindClip(next, target, xaigen, memory) } catch { return { ...next, startClipId: target.id } } } export function changeSource(form) { return form.mode==='extend' ? form : {...form,startClipId:'',settings:{...form.settings,aspect:'auto'}} } export function useAsHero(form, item) { return {...form,identityStillId:item.id} } export function useAsSource(form, item) { return {...form,imageAId:item.id} } export function restoreAll(form,item,xaigen=false,memory) { const restored=restoreInput(item),next={...form,...restored,settings:{...form.settings,...restored.settings,seedMode:'random'}} if(item.kind==='clip') return {...bindClip(next,item,xaigen,memory),mode:'extend'} next.mode=restored.mode==='iterate'?'edit':restored.mode || 'edit' next.engine=restored.engine==='qwen21'?'qwen21':restored.engine==='krea'?'krea':'flux' next.imageAId=item.id next.startClipId='' return restoreLoras(next,item.studio2?.settings?.loraStack ?? item.settings?.loraStack,xaigen,memory) }