import { scopedFile } from '~/shared/studio2/contracts.mjs'; import { sharedGpuHeaders, withSharedGpuStart } from '../sharedGpu'; import { records, saveRecord } from './store'; /** Called only after the output and handoff frame are durably saved. */ export async function purge(r: any, includeCurrent = true) { const config = useRuntimeConfig(), prefix = String(config.comfyFilenamePrefix); const pending = r.cleanupPending || []; const files = [...pending, ...(includeCurrent ? r.files || [] : [])].filter((file, i, all) => all.findIndex(f => f.filename === file.filename && f.subfolder === file.subfolder && f.type === file.type) === i); r.cleanupPending = files; r.purgeResult = 'Cleanup pending'; saveRecord(r); if (!files.length) return; try { if (files.some(file => !scopedFile(file, prefix))) throw new Error('Cleanup manifest is outside this studio'); for (let offset = 0; offset < files.length; offset += 100) { const batch = files.slice(offset, offset + 100); const response = await fetch(String(config.comfyControlUrl).replace(/\/$/, '') + '/studio2/purge', { method: 'POST', headers: { 'Content-Type': 'application/json', ...sharedGpuHeaders() }, body: JSON.stringify({ prefix, files: batch }), signal: AbortSignal.timeout(12000) }); const result = await response.json() as any; if (!response.ok || !result.ok || result.cleared !== batch.length) throw new Error(`Local cleanup was not confirmed (${response.status})`); r.cleanupPending = files.slice(offset + batch.length); saveRecord(r); } r.purgeResult = 'Cleared input + output'; r.cleanupError = ''; } catch (error: any) { r.cleanupError = error.message || 'Local cleanup unavailable'; console.warn('[Studio 2 cleanup]', r.id, r.cleanupError); } saveRecord(r); } let retrying = false; export async function retryCleanup() { if (retrying) return; retrying = true; try { const pending = records().filter(r => r.cleanupPending?.length && ['complete', 'failed', 'cancelled'].includes(r.state)); if (!pending.length) return; await withSharedGpuStart(async () => { for (const r of pending) { // Never add a later failed shot's unsaved files to the saved cleanup manifest. await purge(r, false); } }, async () => {}); } finally { retrying = false; } }