219 lines
5.4 KiB
Svelte
219 lines
5.4 KiB
Svelte
<script lang="ts">
|
|
import { ProgressRadial, getToastStore, RadioGroup, RadioItem } from '@skeletonlabs/skeleton';
|
|
|
|
let inputText = '';
|
|
let imageUrl = '';
|
|
|
|
const models = [
|
|
{
|
|
name: "Playground v2.5",
|
|
value: "playground-v2.5",
|
|
},
|
|
{
|
|
name: "Dall-E 3",
|
|
value: "dall-e-3",
|
|
},
|
|
{
|
|
name: "Stable Diffusion 3",
|
|
value: "stable-diffusion-3",
|
|
},
|
|
{
|
|
name: "DreamShaper XL Turbo",
|
|
value: "dreamshaper-xl-turbo"
|
|
},
|
|
{
|
|
name: "RealVis XL 4",
|
|
value: "realvisxl-v4",
|
|
},
|
|
{
|
|
name: "CyberRealistic",
|
|
value: "cyberrealistic-v3.3",
|
|
}
|
|
]
|
|
|
|
let currentModel = models[0].value;
|
|
|
|
let generateFutureAbortController = new AbortController();
|
|
let generateFutureAbortSignal = generateFutureAbortController.signal;
|
|
|
|
function resetAbortController() {
|
|
generateFutureAbortController = new AbortController();
|
|
generateFutureAbortSignal = generateFutureAbortController.signal;
|
|
}
|
|
|
|
function promiseState(p) {
|
|
const t = {};
|
|
return Promise.race([p, t]).then(
|
|
(v) => (v === t ? 'pending' : 'fulfilled'),
|
|
() => 'rejected'
|
|
);
|
|
}
|
|
|
|
let generateFuture: Promise<string> | null = null;
|
|
let isGenerating = false;
|
|
|
|
const toastStore = getToastStore();
|
|
|
|
async function generateImage(signal?: AbortSignal): Promise<string> {
|
|
if (isGenerating) {
|
|
throw Error('Bilderzeugung bereits im Gange');
|
|
}
|
|
|
|
isGenerating = true;
|
|
try {
|
|
const response = await fetch(
|
|
`/api/forward/gen_image?prompt=${encodeURIComponent(inputText)}&model=${encodeURIComponent(currentModel)}`,
|
|
|
|
{ signal }
|
|
);
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
return data.url;
|
|
} else {
|
|
throw new Error(
|
|
`Fehler beim Generieren des Bildes<br\><b>Grund:</b> ${await response.text()}`
|
|
);
|
|
}
|
|
} catch (error) {
|
|
if (error.name === 'AbortError') {
|
|
console.log('Anfrage wurde abgebrochen');
|
|
throw Error('Bilderzeugung abgebrochen');
|
|
} else {
|
|
console.error('Fehler:', error);
|
|
toastStore.trigger({
|
|
message: `Generierung fehlgeschlagen: ${(error as Error).message}`,
|
|
background: 'variant-filled-error'
|
|
});
|
|
|
|
// rethrow
|
|
throw error;
|
|
}
|
|
} finally {
|
|
isGenerating = false;
|
|
}
|
|
}
|
|
|
|
async function handleSubmit() {
|
|
try {
|
|
if (generateFuture !== null && (await promiseState(generateFuture)) === 'pending') {
|
|
console.log('Wird bereits generiert');
|
|
toastStore.trigger({
|
|
message: `Generierung bereits im Gange`,
|
|
background: 'variant-filled-warning',
|
|
action: {
|
|
label: 'Abbrechen',
|
|
response: () => {
|
|
generateFutureAbortController.abort();
|
|
resetAbortController();
|
|
toastStore.trigger({
|
|
message: 'Bilderzeugung erfolgreich abgebrochen',
|
|
background: 'variant-filled-success'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
generateFuture = generateImage(generateFutureAbortSignal);
|
|
}
|
|
} catch (error) {
|
|
toastStore.trigger({
|
|
message: `Generierung fehlgeschlagen: ${(error as Error).message}`,
|
|
background: 'variant-filled-error'
|
|
});
|
|
} finally {
|
|
}
|
|
}
|
|
|
|
let improvePromptRunning: boolean = false;
|
|
|
|
async function improvePrompt() {
|
|
if (improvePromptRunning) {
|
|
toastStore.trigger({
|
|
message: 'Prompt-Verbesserung läuft. Bitte warten...',
|
|
background: 'variant-filled-warning'
|
|
});
|
|
}
|
|
improvePromptRunning = true;
|
|
try {
|
|
const response = await fetch(
|
|
`/api/forward/gen_prompt?prompt=${encodeURIComponent(inputText)}`
|
|
);
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
inputText = data.text;
|
|
} else {
|
|
throw new Error(
|
|
`Fehler beim Verbessern des Prompts<br\><b>Grund:</b> ${await response.text()}`
|
|
);
|
|
}
|
|
} catch (error) {
|
|
toastStore.trigger({
|
|
message: `Prompt-Verbesserung fehlgeschlagen: ${(error as Error).message}`,
|
|
background: 'variant-filled-error'
|
|
});
|
|
} finally {
|
|
improvePromptRunning = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div class="container mx-auto p-8">
|
|
<h1 class="mb-4 text-2xl font-bold">Bildgenerator</h1>
|
|
|
|
<center>
|
|
<div
|
|
class="rounded-container-token flex aspect-square w-full items-center justify-center bg-gray-800 md:m-16 md:w-[70%]"
|
|
>
|
|
{#await generateFuture}
|
|
<ProgressRadial />
|
|
{:then url}
|
|
{#if url !== null}
|
|
<div class="h-full w-full">
|
|
<img src={url} alt="Generiertes Bild" class="h-full w-full object-cover" />
|
|
</div>
|
|
{:else}
|
|
<p>Generiere ein Bild!</p>
|
|
{/if}
|
|
{:catch error}
|
|
<div class="card variant-glass-error p-4">
|
|
<p>{@html error.message}</p>
|
|
</div>
|
|
{/await}
|
|
</div>
|
|
<div class="mb-4 px-4">
|
|
<p>Bildgenerations-Model</p>
|
|
<RadioGroup active="variant-filled-primary" hover="hover:variant-soft-primary">
|
|
{#each models as model}
|
|
<RadioItem bind:group={currentModel} value={model.value}>
|
|
{model.name}
|
|
</RadioItem>
|
|
{/each}
|
|
</RadioGroup>
|
|
</div>
|
|
</center>
|
|
|
|
<form on:submit|preventDefault={handleSubmit} class="flex flex-col md:flex-row space-x-4">
|
|
<label class="label flex-1">
|
|
<span>Prompt eingeben:</span>
|
|
<textarea class="textarea p-2" rows="4" bind:value={inputText} disabled={improvePromptRunning}
|
|
></textarea>
|
|
</label>
|
|
<div class="flex flex-col items-center justify-center space-y-4 pt-2">
|
|
<button class="btn variant-filled-primary mt-4" type="submit">
|
|
{#if isGenerating}
|
|
Generiere...
|
|
{:else}
|
|
Bild generieren
|
|
{/if}
|
|
</button>
|
|
<button class="btn variant-filled-secondary" on:click|preventDefault={improvePrompt}>
|
|
{#if improvePromptRunning}
|
|
Verbessere...
|
|
{:else}
|
|
Prompt verbessern
|
|
{/if}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|