import {readJobStream} from '@/lib/stream'; import type {Download} from '@/lib/types'; import {encodeWav} from './audio'; /** Raised when the session has lapsed, so the studio can show sign-in without losing the project. */ export class SignedOut extends Error{} type Call={signal:AbortSignal;status:(text:string)=>void}; /** Call a generation route and follow its progress stream to the result. */ export async function callEngine(url:string,body:FormData|object,{signal,status}:Call):Promise{ const init:RequestInit=body instanceof FormData ?{method:'POST',body,signal} :{method:'POST',body:JSON.stringify(body),headers:{'Content-Type':'application/json'},signal}; const response=await fetch(url,init); if(!response.ok){ const data=await response.json().catch(()=>({})); if(response.status===401)throw new SignedOut(data.error||'Sign in again to continue.'); throw new Error(data.error||'That could not start.'); } if(!response.body)throw new Error('The connection closed. Please try again.'); const result=await readJobStream(response.body,{status}); if(!result)throw new Error('The engine returned nothing. Please try again.'); return result as T; } /** * Fetch an engine result through the app, which relays it with the user's * token. Hugging Face's gateway drops some requests with a 5xx (sometimes an * HTML error page), so those are retried; the finished job is not lost. */ export async function fetchResult(file:Download,signal:AbortSignal){ let last=''; for(let attempt=0;attempt<5;attempt++){ if(attempt)await new Promise(done=>setTimeout(done,2000*attempt)); signal.throwIfAborted(); const response=await fetch('/api/audio?url='+encodeURIComponent(file.url),{signal}).catch(error=>{ if(signal.aborted)throw error; return null; }); if(response?.ok)return response.arrayBuffer(); const text=response?await response.text().catch(()=>''):''; // The relay's own messages are plain sentences; anything else is a gateway page. last=text&&!/