File size: 2,387 Bytes
a63c2c0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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<T>(url:string,body:FormData|object,{signal,status}:Call):Promise<T>{
 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&&!/<html|<!doctype/i.test(text)&&text.length<300?text:'';
  if(response&&response.status<500&&response.status!==429)break;
 }
 throw new Error(last||'Hugging Face’s servers had a hiccup while sending the result. Run it again; your project is safe.');
}

export function wavFile(buffer:AudioBuffer,name='selection.wav'){
 return new File([encodeWav(buffer)],name,{type:'audio/wav'});
}