Clear Queuing when the optimistic job snapshot avoids structuredClone.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -839,6 +839,14 @@
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.queue-foot-error {
|
||||||
|
margin: 8px 0 0;
|
||||||
|
color: #fca5a5;
|
||||||
|
font-size: var(--type-sm);
|
||||||
|
line-height: 1.35;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
.queue-foot .tiny {
|
.queue-foot .tiny {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
+101
-32
@@ -389,6 +389,7 @@
|
|||||||
<span aria-hidden="true">▶</span>
|
<span aria-hidden="true">▶</span>
|
||||||
{{ submitting ? 'Queuing…' : 'Generate' }}
|
{{ submitting ? 'Queuing…' : 'Generate' }}
|
||||||
</button>
|
</button>
|
||||||
|
<p v-if="error" class="queue-foot-error">{{ error }}</p>
|
||||||
<p class="tiny">
|
<p class="tiny">
|
||||||
Job queue
|
Job queue
|
||||||
<button v-if="canClearFinished" type="button" @click="clearFinished"> · Clear finished</button>
|
<button v-if="canClearFinished" type="button" @click="clearFinished"> · Clear finished</button>
|
||||||
@@ -1399,52 +1400,117 @@ async function queue(){
|
|||||||
const t=typeof performance!=='undefined'?performance.now():Date.now()
|
const t=typeof performance!=='undefined'?performance.now():Date.now()
|
||||||
console.info(`[bench-queue] ${label} +${Math.round(t-t0)}ms`)
|
console.info(`[bench-queue] ${label} +${Math.round(t-t0)}ms`)
|
||||||
}
|
}
|
||||||
submitting.value=true;error.value=''
|
|
||||||
// Optimistic card in <100ms — never await library / object_info before the list updates
|
|
||||||
const localId=`local-${Date.now()}`
|
const localId=`local-${Date.now()}`
|
||||||
if(form.mode!=='music'){
|
submitting.value=true
|
||||||
const optimistic={
|
error.value=''
|
||||||
id:localId,
|
|
||||||
state:'waiting',
|
|
||||||
queuedAt:Date.now(),
|
|
||||||
request:structuredClone({
|
|
||||||
...form,
|
|
||||||
promptSections:video.value?form.promptSections:{action:form.promptSections.action},
|
|
||||||
settings:{...form.settings,...resolvedSize.value}
|
|
||||||
}),
|
|
||||||
outputs:[],
|
|
||||||
purgeResult:'Queued'
|
|
||||||
}
|
|
||||||
jobs.value=[optimistic,...jobs.value]
|
|
||||||
focusedJobId.value=localId
|
|
||||||
logTiming('local-card')
|
|
||||||
// Button live in one frame — POST may still be in flight
|
|
||||||
submitting.value=false
|
|
||||||
}
|
|
||||||
try{
|
try{
|
||||||
if(form.mode==='music'){
|
if(form.mode==='music'){
|
||||||
logTiming('POST /api/generate/music start')
|
logTiming('POST /api/generate/music start')
|
||||||
submitting.value=false
|
|
||||||
void $fetch('/api/generate/music',{method:'POST',body:{engine:'yue2',tags:music.tags,lyrics:music.lyrics,duration:music.duration,seed:music.seed||'random',folderId:form.folderId,name:music.name}})
|
void $fetch('/api/generate/music',{method:'POST',body:{engine:'yue2',tags:music.tags,lyrics:music.lyrics,duration:music.duration,seed:music.seed||'random',folderId:form.folderId,name:music.name}})
|
||||||
.then(()=>{logTiming('POST music ack');void refreshJobs()})
|
.then(()=>{logTiming('POST music ack');void refreshJobs()})
|
||||||
.catch((e:any)=>{error.value=e.data?.statusMessage||e.message})
|
.catch((e:any)=>{error.value=e.data?.statusMessage||e.message||String(e)})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if(photoRoleWarning.value){
|
if(photoRoleWarning.value){
|
||||||
jobs.value=jobs.value.filter(j=>j.id!==localId)
|
|
||||||
error.value=photoRoleWarning.value
|
error.value=photoRoleWarning.value
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Plain snapshot only — never structuredClone(form) (Vue proxies / Files throw DataCloneError)
|
||||||
|
const size={...resolvedSize.value}
|
||||||
|
const photos=(form.photos||[])
|
||||||
|
.filter((p:any)=>p?.stillId)
|
||||||
|
.map((p:any)=>({stillId:String(p.stillId),role:String(p.role||'change')}))
|
||||||
|
const promptAction=String(form.promptSections?.action||'')
|
||||||
|
const promptSections=video.value
|
||||||
|
?{
|
||||||
|
action:promptAction,
|
||||||
|
camera:String(form.promptSections?.camera||''),
|
||||||
|
soundscape:String(form.promptSections?.soundscape||''),
|
||||||
|
music:String(form.promptSections?.music||''),
|
||||||
|
locks:String(form.promptSections?.locks||''),
|
||||||
|
cameraBeats:Array.isArray(form.promptSections?.cameraBeats)?JSON.parse(JSON.stringify(form.promptSections.cameraBeats)):[],
|
||||||
|
soundscapeChips:[...(form.promptSections?.soundscapeChips||[])],
|
||||||
|
soundscapeLevel:form.promptSections?.soundscapeLevel||'normal',
|
||||||
|
musicChip:form.promptSections?.musicChip||'N/A',
|
||||||
|
dialogue:form.promptSections?.dialogue?JSON.parse(JSON.stringify(form.promptSections.dialogue)):{mode:'none',line:''}
|
||||||
|
}
|
||||||
|
:{action:promptAction}
|
||||||
|
const settings={
|
||||||
|
aspect:String(form.settings?.aspect||'auto'),
|
||||||
|
fps:Number(form.settings?.fps)||24,
|
||||||
|
steps:Number(form.settings?.steps)||24,
|
||||||
|
cfg:Number(form.settings?.cfg)||4,
|
||||||
|
seed:form.settings?.seed ?? null,
|
||||||
|
seedMode:String(form.settings?.seedMode||'random'),
|
||||||
|
duration:Number(form.settings?.duration)||5,
|
||||||
|
turbo:!!form.settings?.turbo,
|
||||||
|
width:size.width,
|
||||||
|
height:size.height,
|
||||||
|
loraStack:Array.isArray(form.settings?.loraStack)
|
||||||
|
?form.settings.loraStack.map((l:any)=>({
|
||||||
|
name:String(l?.name||l?.file||''),
|
||||||
|
strength:Number(l?.strength ?? 1),
|
||||||
|
clipStrength:l?.clipStrength != null ? Number(l.clipStrength) : undefined
|
||||||
|
})).filter((l:any)=>l.name)
|
||||||
|
:[]
|
||||||
|
}
|
||||||
|
const optimisticRequest={
|
||||||
|
mode:String(form.mode),
|
||||||
|
engine:String(form.engine),
|
||||||
|
folderId:String(form.folderId||''),
|
||||||
|
enhancePrompt:!!form.enhancePrompt,
|
||||||
|
turbo:!!form.turbo,
|
||||||
|
lockFace:!!form.lockFace,
|
||||||
|
lockOutfit:!!form.lockOutfit,
|
||||||
|
hideInput:!!xaigen,
|
||||||
|
hideOutput:false,
|
||||||
|
imageAId:String(form.imageAId||''),
|
||||||
|
imageBId:String(form.imageBId||''),
|
||||||
|
identityStillId:String(form.identityStillId||''),
|
||||||
|
startClipId:String(form.startClipId||''),
|
||||||
|
photos,
|
||||||
|
promptSections,
|
||||||
|
imageStyles:{
|
||||||
|
positive:[...(form.imageStyles?.positive||[])],
|
||||||
|
negative:[...(form.imageStyles?.negative||[])]
|
||||||
|
},
|
||||||
|
settings
|
||||||
|
}
|
||||||
|
jobs.value=[{
|
||||||
|
id:localId,
|
||||||
|
state:'waiting',
|
||||||
|
queuedAt:Date.now(),
|
||||||
|
request:optimisticRequest,
|
||||||
|
outputs:[],
|
||||||
|
purgeResult:'Queued'
|
||||||
|
},...jobs.value]
|
||||||
|
focusedJobId.value=localId
|
||||||
|
logTiming('local-card')
|
||||||
|
|
||||||
syncPhotosBindings()
|
syncPhotosBindings()
|
||||||
if(form.settings.seedMode!=='locked')form.settings.seed=randomSeed()
|
if(form.settings.seedMode!=='locked')form.settings.seed=randomSeed()
|
||||||
const body=applyPhotosToRequest({
|
const body=applyPhotosToRequest({
|
||||||
...form,
|
mode:optimisticRequest.mode==='edit' && parseBatch(batchText.value).length?'iterate':optimisticRequest.mode,
|
||||||
mode:form.mode==='edit' && parseBatch(batchText.value).length?'iterate':form.mode,
|
engine:optimisticRequest.engine,
|
||||||
hideInput:xaigen,
|
folderId:optimisticRequest.folderId,
|
||||||
|
enhancePrompt:optimisticRequest.enhancePrompt,
|
||||||
|
turbo:optimisticRequest.turbo,
|
||||||
|
lockFace:optimisticRequest.lockFace,
|
||||||
|
lockOutfit:optimisticRequest.lockOutfit,
|
||||||
|
hideInput:optimisticRequest.hideInput,
|
||||||
hideOutput:false,
|
hideOutput:false,
|
||||||
promptSections:video.value?form.promptSections:{action:form.promptSections.action},
|
imageAId:optimisticRequest.imageAId,
|
||||||
settings:{...form.settings,...resolvedSize.value},
|
imageBId:optimisticRequest.imageBId,
|
||||||
batch:video.value || form.mode==='edit'?parseBatch(batchText.value):[]
|
identityStillId:optimisticRequest.identityStillId,
|
||||||
|
startClipId:optimisticRequest.startClipId,
|
||||||
|
photos,
|
||||||
|
promptSections,
|
||||||
|
imageStyles:optimisticRequest.imageStyles,
|
||||||
|
settings:{...settings,seed:form.settings.seed},
|
||||||
|
batch:video.value || form.mode==='edit'?parseBatch(batchText.value):[],
|
||||||
|
guides:[],
|
||||||
|
maskId:'',
|
||||||
|
endStillId:''
|
||||||
})
|
})
|
||||||
logTiming('POST /api/studio-2/jobs start')
|
logTiming('POST /api/studio-2/jobs start')
|
||||||
void $fetch<any>('/api/studio-2/jobs',{method:'POST',body})
|
void $fetch<any>('/api/studio-2/jobs',{method:'POST',body})
|
||||||
@@ -1459,12 +1525,14 @@ async function queue(){
|
|||||||
})
|
})
|
||||||
.catch((e:any)=>{
|
.catch((e:any)=>{
|
||||||
jobs.value=jobs.value.filter(j=>j.id!==localId)
|
jobs.value=jobs.value.filter(j=>j.id!==localId)
|
||||||
error.value=e.data?.statusMessage||e.message
|
error.value=e.data?.statusMessage||e.message||String(e)
|
||||||
})
|
})
|
||||||
}catch(e:any){
|
}catch(e:any){
|
||||||
jobs.value=jobs.value.filter(j=>j.id!==localId)
|
jobs.value=jobs.value.filter(j=>j.id!==localId)
|
||||||
error.value=e.data?.statusMessage||e.message
|
error.value=e.data?.statusMessage||e.message||String(e)
|
||||||
|
}finally{
|
||||||
submitting.value=false
|
submitting.value=false
|
||||||
|
logTiming('done')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function cancel(job:any){await $fetch('/api/studio-2/cancel',{method:'POST',body:{id:job.id}});await refreshJobs()}
|
async function cancel(job:any){await $fetch('/api/studio-2/cancel',{method:'POST',body:{id:job.id}});await refreshJobs()}
|
||||||
@@ -1505,6 +1573,7 @@ watch(()=>[
|
|||||||
],()=>saveBenchDraft(),{deep:true})
|
],()=>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(()=>{
|
onMounted(()=>{
|
||||||
|
submitting.value=false
|
||||||
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()
|
||||||
// First paint in ~2s: chrome + draft. Do not await library before the prompt works.
|
// First paint in ~2s: chrome + draft. Do not await library before the prompt works.
|
||||||
|
|||||||
Reference in New Issue
Block a user