|
<script lang="ts"> |
|
import { density, composing, style, temperature, notesImage, notesTokens, audioBlob } from '$lib/stores'; |
|
import { styles } from './config.json'; |
|
|
|
const updateMetadata = () => { |
|
if ('mediaSession' in navigator) { |
|
navigator.mediaSession.metadata = new MediaMetadata({ |
|
title: `${styles[$style]} Composition`, |
|
artist: 'AI Guru Composer', |
|
album: 'Hugging Face', |
|
artwork: [ |
|
{ |
|
src: 'static/hugging-face-headphones.png', |
|
sizes: '512x512', |
|
type: 'image/png', |
|
}, |
|
], |
|
}); |
|
} |
|
}; |
|
|
|
const createTask = async ({ |
|
music_style, |
|
density, |
|
temperature, |
|
}: { |
|
music_style: string; |
|
density: string; |
|
temperature: string; |
|
}) => { |
|
const taskResponse = await fetch('task/create', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json', |
|
}, |
|
body: JSON.stringify({ |
|
music_style, |
|
density, |
|
temperature, |
|
}), |
|
}); |
|
|
|
if (!taskResponse.ok || !taskResponse.headers.get('content-type')?.includes('application/json')) { |
|
throw new Error(`Unable to create composition: [${taskResponse.status}] ${await taskResponse.text()}`); |
|
} |
|
|
|
const task = await taskResponse.json(); |
|
|
|
return task; |
|
}; |
|
|
|
const pollTask = async (task: Record<string, any>) => { |
|
const taskResponse = await fetch(`task/poll?task_id=${task.task_id}`); |
|
|
|
if (!taskResponse.ok || !taskResponse.headers.get('content-type')?.includes('application/json')) { |
|
throw new Error(`Unable to create composition: [${taskResponse.status}] ${await taskResponse.text()}`); |
|
} |
|
|
|
return await taskResponse.json(); |
|
}; |
|
|
|
const longPollTask = async (task: Record<string, any>, interval = 1_000, max = 100): Promise<Record<string, any>> => { |
|
task = await pollTask(task); |
|
|
|
if (task.status === 'completed' || task.status === 'failed' || (max && task.poll_count > max)) { |
|
return task; |
|
} |
|
|
|
await new Promise((resolve) => setTimeout(resolve, interval)); |
|
|
|
return await longPollTask(task, interval, max); |
|
}; |
|
|
|
const compose = async () => { |
|
$composing = true; |
|
try { |
|
const task = await createTask({ |
|
music_style: $style, |
|
density: $density, |
|
temperature: $temperature, |
|
}); |
|
|
|
const completedTask = await longPollTask(task); |
|
const { audio, image, tokens } = completedTask.output; |
|
$audioBlob = audio; |
|
$notesImage = image; |
|
$notesTokens = tokens; |
|
|
|
updateMetadata(); |
|
} catch (err) { |
|
console.error(err); |
|
} finally { |
|
$composing = false; |
|
} |
|
}; |
|
</script> |
|
|
|
<button disabled={$composing} on:click={compose}> |
|
{#if $composing} |
|
Composing... |
|
{:else} |
|
Compose <img src="static/wand.svg" alt="Magic wand" /> |
|
{/if} |
|
</button> |
|
|
|
<style> |
|
button { |
|
display: block; |
|
font-size: 1.2rem; |
|
font-family: 'Lato', sans-serif; |
|
font-weight: 700; |
|
color: hsl(0 0% 97%); |
|
background: transparent; |
|
border: 3px solid hsl(0 0% 97%); |
|
border-radius: 0.375rem; |
|
padding: 0.5rem 1rem; |
|
cursor: pointer; |
|
margin: 1rem auto 2rem; |
|
} |
|
|
|
button[disabled] { |
|
border-color: hsl(0 0% 50%); |
|
color: hsl(0 0% 50%); |
|
cursor: initial; |
|
} |
|
|
|
img { |
|
height: 1.2rem; |
|
aspect-ratio: 1 / 1; |
|
vertical-align: bottom; |
|
} |
|
|
|
@media (min-width: 900px) { |
|
button { |
|
margin-top: 0; |
|
} |
|
} |
|
</style> |
|
|