INITIAL COMMIT

This commit is contained in:
2024-05-01 16:50:42 +02:00
commit 3b1a72d7f9
36 changed files with 6354 additions and 0 deletions

13
web/src/app.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

12
web/src/app.html Normal file
View File

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover" data-theme="skeleton">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

4
web/src/app.pcss Normal file
View File

@ -0,0 +1,4 @@
/* Write your global styles here, in PostCSS syntax */
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 -3 30 30">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5229 6.47715 22 12 22C17.5229 22 22 17.5229 22 12C22 6.47715 17.5229 2 12 2ZM0 12C0 5.3726 5.3726 0 12 0C18.6274 0 24 5.3726 24 12C24 18.6274 18.6274 24 12 24C5.3726 24 0 18.6274 0 12Z"
fill="rgba(0,0,0,0.7)"
stroke="none"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M9.59162 22.7357C9.49492 22.6109 9.49492 21.4986 9.59162 19.399C8.55572 19.4347 7.90122 19.3628 7.62812 19.1833C7.21852 18.9139 6.80842 18.0833 6.44457 17.4979C6.08072 16.9125 5.27312 16.8199 4.94702 16.6891C4.62091 16.5582 4.53905 16.0247 5.84562 16.4282C7.15222 16.8316 7.21592 17.9303 7.62812 18.1872C8.04032 18.4441 9.02572 18.3317 9.47242 18.1259C9.91907 17.9201 9.88622 17.1538 9.96587 16.8503C10.0666 16.5669 9.71162 16.5041 9.70382 16.5018C9.26777 16.5018 6.97697 16.0036 6.34772 13.7852C5.71852 11.5669 6.52907 10.117 6.96147 9.49369C7.24972 9.07814 7.22422 8.19254 6.88497 6.83679C8.11677 6.67939 9.06732 7.06709 9.73672 7.99999C9.73737 8.00534 10.6143 7.47854 12.0001 7.47854C13.386 7.47854 13.8777 7.90764 14.2571 7.99999C14.6365 8.09234 14.94 6.36699 17.2834 6.83679C16.7942 7.79839 16.3844 8.99999 16.6972 9.49369C17.0099 9.98739 18.2372 11.5573 17.4833 13.7852C16.9807 15.2706 15.9927 16.1761 14.5192 16.5018C14.3502 16.5557 14.2658 16.6427 14.2658 16.7627C14.2658 16.9427 14.4942 16.9624 14.8233 17.8058C15.0426 18.368 15.0585 19.9739 14.8708 22.6234C14.3953 22.7445 14.0254 22.8257 13.7611 22.8673C13.2924 22.9409 12.7835 22.9822 12.2834 22.9982C11.7834 23.0141 11.6098 23.0123 10.9185 22.948C10.4577 22.9051 10.0154 22.8343 9.59162 22.7357Z"
fill="rgba(0,0,0,0.7)"
stroke="none"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.1566,22.8189c-10.4-14.8851-30.94-19.2971-45.7914-9.8348L22.2825,29.6078A29.9234,29.9234,0,0,0,8.7639,49.6506a31.5136,31.5136,0,0,0,3.1076,20.2318A30.0061,30.0061,0,0,0,7.3953,81.0653a31.8886,31.8886,0,0,0,5.4473,24.1157c10.4022,14.8865,30.9423,19.2966,45.7914,9.8348L84.7167,98.3921A29.9177,29.9177,0,0,0,98.2353,78.3493,31.5263,31.5263,0,0,0,95.13,58.117a30,30,0,0,0,4.4743-11.1824,31.88,31.88,0,0,0-5.4473-24.1157" style="fill:#ff3e00"/><path d="M45.8171,106.5815A20.7182,20.7182,0,0,1,23.58,98.3389a19.1739,19.1739,0,0,1-3.2766-14.5025,18.1886,18.1886,0,0,1,.6233-2.4357l.4912-1.4978,1.3363.9815a33.6443,33.6443,0,0,0,10.203,5.0978l.9694.2941-.0893.9675a5.8474,5.8474,0,0,0,1.052,3.8781,6.2389,6.2389,0,0,0,6.6952,2.485,5.7449,5.7449,0,0,0,1.6021-.7041L69.27,76.281a5.4306,5.4306,0,0,0,2.4506-3.631,5.7948,5.7948,0,0,0-.9875-4.3712,6.2436,6.2436,0,0,0-6.6978-2.4864,5.7427,5.7427,0,0,0-1.6.7036l-9.9532,6.3449a19.0329,19.0329,0,0,1-5.2965,2.3259,20.7181,20.7181,0,0,1-22.2368-8.2427,19.1725,19.1725,0,0,1-3.2766-14.5024,17.9885,17.9885,0,0,1,8.13-12.0513L55.8833,23.7472a19.0038,19.0038,0,0,1,5.3-2.3287A20.7182,20.7182,0,0,1,83.42,29.6611a19.1739,19.1739,0,0,1,3.2766,14.5025,18.4,18.4,0,0,1-.6233,2.4357l-.4912,1.4978-1.3356-.98a33.6175,33.6175,0,0,0-10.2037-5.1l-.9694-.2942.0893-.9675a5.8588,5.8588,0,0,0-1.052-3.878,6.2389,6.2389,0,0,0-6.6952-2.485,5.7449,5.7449,0,0,0-1.6021.7041L37.73,51.719a5.4218,5.4218,0,0,0-2.4487,3.63,5.7862,5.7862,0,0,0,.9856,4.3717,6.2437,6.2437,0,0,0,6.6978,2.4864,5.7652,5.7652,0,0,0,1.602-.7041l9.9519-6.3425a18.978,18.978,0,0,1,5.2959-2.3278,20.7181,20.7181,0,0,1,22.2368,8.2427,19.1725,19.1725,0,0,1,3.2766,14.5024,17.9977,17.9977,0,0,1-8.13,12.0532L51.1167,104.2528a19.0038,19.0038,0,0,1-5.3,2.3287" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@ -0,0 +1,55 @@
<script>
import '../app.pcss';
import './styles.css';
import { initializeStores, Toast } from '@skeletonlabs/skeleton';
import { autoModeWatcher } from '@skeletonlabs/skeleton';
initializeStores();
</script>
<svelte:head>{@html '<script>(' + autoModeWatcher.toString() + ')();</script>'}</svelte:head>
<Toast></Toast>
<div class="app">
<main>
<slot></slot>
</main>
</div>
<style>
.app {
display: flex;
flex-direction: column;
min-height: 100vh;
}
main {
flex: 1;
display: flex;
flex-direction: column;
padding: 1rem;
width: 100%;
max-width: 64rem;
margin: 0 auto;
box-sizing: border-box;
}
footer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 12px;
}
footer a {
font-weight: bold;
}
@media (min-width: 480px) {
footer {
padding: 12px 0;
}
}
</style>

178
web/src/routes/+page.svelte Normal file
View File

@ -0,0 +1,178 @@
<script lang="ts">
import { ProgressRadial, getToastStore } from '@skeletonlabs/skeleton';
let inputText = '';
let imageUrl = '';
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)}`,
{ 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="mt-8">
<img src={url} alt="Generiertes Bild" class="h-auto max-w-full" />
</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>
</center>
<form on:submit|preventDefault={handleSubmit} class="flex 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>

View File

@ -0,0 +1,27 @@
import { error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { env } from '$env/dynamic/private';
export const GET: RequestHandler = async ({ url, fetch }) => {
const imagenHost = env.IMAGEN_HOST;
if (!imagenHost) {
throw error(500, 'IMAGEN_HOST environment variable is not set');
}
const path = url.pathname.replace('/api/forward', '');
const queryString = url.search;
const forwardUrl = `http://${imagenHost}${path}${queryString}`;
try {
const response = await fetch(forwardUrl);
const data = await response.text();
return new Response(data, {
status: response.status,
headers: { 'Content-Type': 'application/json' },
});
} catch (err) {
console.error('Error forwarding request:', err);
throw error(500, 'Error forwarding request');
}
};

94
web/src/routes/styles.css Normal file
View File

@ -0,0 +1,94 @@
@import '@fontsource/fira-mono';
:root {
}
body {
min-height: 100vh;
margin: 0;
background-attachment: fixed;
background-color: var(--color-bg-1);
background-size: 100vw 100vh;
background-image: radial-gradient(
50% 50% at 50% 50%,
rgba(255, 255, 255, 0.75) 0%,
rgba(255, 255, 255, 0) 100%
),
linear-gradient(180deg, var(--color-bg-0) 0%, var(--color-bg-1) 15%, var(--color-bg-2) 50%);
}
h1,
h2,
p {
font-weight: 400;
}
p {
line-height: 1.5;
}
a {
color: var(--color-theme-1);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
h1 {
font-size: 2rem;
text-align: center;
}
h2 {
font-size: 1rem;
}
pre {
font-size: 16px;
font-family: var(--font-mono);
background-color: rgba(255, 255, 255, 0.45);
border-radius: 3px;
box-shadow: 2px 2px 6px rgb(255 255 255 / 25%);
padding: 0.5em;
overflow-x: auto;
color: var(--color-text);
}
.text-column {
display: flex;
max-width: 48rem;
flex: 0.6;
flex-direction: column;
justify-content: center;
margin: 0 auto;
}
input,
button {
font-size: inherit;
font-family: inherit;
}
button:focus:not(:focus-visible) {
outline: none;
}
@media (min-width: 720px) {
h1 {
font-size: 2.4rem;
}
}
.visually-hidden {
border: 0;
clip: rect(0 0 0 0);
height: auto;
margin: 0;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
white-space: nowrap;
}