INITIAL COMMIT
This commit is contained in:
		
							
								
								
									
										16
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
services:
 | 
			
		||||
  web:
 | 
			
		||||
    build: ./web
 | 
			
		||||
    ports:
 | 
			
		||||
      - 3000:3000
 | 
			
		||||
    environment:
 | 
			
		||||
      - IMAGEN_HOST=gen_service
 | 
			
		||||
    labels:
 | 
			
		||||
      - traefik.enable=true
 | 
			
		||||
      - traefik.http.routers.web.rule=Host(`imagen.yandrik.dev`)
 | 
			
		||||
      - traefik.http.routers.web.tls=true
 | 
			
		||||
      - traefik.http.routers.web.tls.certresolver=resolver
 | 
			
		||||
      - traefik.http.services.web.loadbalancer.server.port=3000
 | 
			
		||||
  
 | 
			
		||||
  gen_service:
 | 
			
		||||
    build: ./gen_service
 | 
			
		||||
							
								
								
									
										77
									
								
								gen_service/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								gen_service/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,77 @@
 | 
			
		||||
__pycache__/
 | 
			
		||||
*.py[cod]
 | 
			
		||||
*$py.class
 | 
			
		||||
*.so
 | 
			
		||||
.Python
 | 
			
		||||
build/
 | 
			
		||||
develop-eggs/
 | 
			
		||||
dist/
 | 
			
		||||
downloads/
 | 
			
		||||
eggs/
 | 
			
		||||
.eggs/
 | 
			
		||||
lib/
 | 
			
		||||
lib64/
 | 
			
		||||
parts/
 | 
			
		||||
sdist/
 | 
			
		||||
var/
 | 
			
		||||
wheels/
 | 
			
		||||
share/python-wheels/
 | 
			
		||||
*.egg-info/
 | 
			
		||||
.installed.cfg
 | 
			
		||||
*.egg
 | 
			
		||||
MANIFEST
 | 
			
		||||
*.manifest
 | 
			
		||||
*.spec
 | 
			
		||||
pip-log.txt
 | 
			
		||||
pip-delete-this-directory.txt
 | 
			
		||||
htmlcov/
 | 
			
		||||
.tox/
 | 
			
		||||
.nox/
 | 
			
		||||
.coverage
 | 
			
		||||
.coverage.*
 | 
			
		||||
.cache
 | 
			
		||||
nosetests.xml
 | 
			
		||||
coverage.xml
 | 
			
		||||
*.cover
 | 
			
		||||
*.py,cover
 | 
			
		||||
.hypothesis/
 | 
			
		||||
.pytest_cache/
 | 
			
		||||
cover/
 | 
			
		||||
*.mo
 | 
			
		||||
*.pot
 | 
			
		||||
*.log
 | 
			
		||||
local_settings.py
 | 
			
		||||
db.sqlite3
 | 
			
		||||
db.sqlite3-journal
 | 
			
		||||
instance/
 | 
			
		||||
.webassets-cache
 | 
			
		||||
.scrapy
 | 
			
		||||
docs/_build/
 | 
			
		||||
.pybuilder/
 | 
			
		||||
target/
 | 
			
		||||
.ipynb_checkpoints
 | 
			
		||||
profile_default/
 | 
			
		||||
ipython_config.py
 | 
			
		||||
__pypackages__/
 | 
			
		||||
celerybeat-schedule
 | 
			
		||||
celerybeat.pid
 | 
			
		||||
*.sage.py
 | 
			
		||||
.env
 | 
			
		||||
.venv
 | 
			
		||||
env/
 | 
			
		||||
venv/
 | 
			
		||||
ENV/
 | 
			
		||||
env.bak/
 | 
			
		||||
venv.bak/
 | 
			
		||||
.spyderproject
 | 
			
		||||
.spyproject
 | 
			
		||||
.ropeproject
 | 
			
		||||
/site
 | 
			
		||||
.mypy_cache/
 | 
			
		||||
.dmypy.json
 | 
			
		||||
dmypy.json
 | 
			
		||||
.pyre/
 | 
			
		||||
.pytype/
 | 
			
		||||
cython_debug/
 | 
			
		||||
poetry.toml
 | 
			
		||||
poetry.lock
 | 
			
		||||
							
								
								
									
										10
									
								
								gen_service/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								gen_service/Dockerfile
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
FROM python:3.12
 | 
			
		||||
 | 
			
		||||
RUN pip install poetry
 | 
			
		||||
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
COPY . /app
 | 
			
		||||
 | 
			
		||||
RUN poetry install
 | 
			
		||||
 | 
			
		||||
ENTRYPOINT ["poetry", "run", "gunicorn", "wsgi:app", "--bind", "0.0.0.0:5000"]
 | 
			
		||||
							
								
								
									
										121
									
								
								gen_service/main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								gen_service/main.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,121 @@
 | 
			
		||||
from openai import Client
 | 
			
		||||
from flask import Flask, request
 | 
			
		||||
import os
 | 
			
		||||
import g4f
 | 
			
		||||
 | 
			
		||||
app = Flask(__name__)
 | 
			
		||||
 | 
			
		||||
def get_client():
 | 
			
		||||
    return Client(
 | 
			
		||||
        api_key=os.environ.get("API_KEY"),
 | 
			
		||||
        base_url="https://zukijourney.xyzbot.net/v1"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
def get_g4f_client():
 | 
			
		||||
    return g4f.client.Client()
 | 
			
		||||
 | 
			
		||||
@app.route('/gen_text', methods=['GET'])
 | 
			
		||||
def gen_text():
 | 
			
		||||
    prompt = request.args.get('prompt')
 | 
			
		||||
 | 
			
		||||
    if not prompt:
 | 
			
		||||
        return "Error: No prompt provided", 400
 | 
			
		||||
    
 | 
			
		||||
    print(prompt)
 | 
			
		||||
    
 | 
			
		||||
    try:
 | 
			
		||||
        text = _gen_text(prompt)
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        print(f"Error generating text: {e}")
 | 
			
		||||
        return f"Error generating text: {str(e)}", 500
 | 
			
		||||
    
 | 
			
		||||
    return { 'text': str(text) }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
@app.route('/gen_prompt', methods=['GET'])
 | 
			
		||||
def gen_prompt():
 | 
			
		||||
    prompt = request.args.get('prompt')
 | 
			
		||||
    style = request.args.get('style')
 | 
			
		||||
 | 
			
		||||
    if not prompt:
 | 
			
		||||
        return "Error: No prompt provided", 400
 | 
			
		||||
    
 | 
			
		||||
    print(prompt)
 | 
			
		||||
    
 | 
			
		||||
    try:
 | 
			
		||||
        gen_prompt = _gen_text(f"""
 | 
			
		||||
Your job is to generate stable diffusion prompts. Here's how they're supposed to look:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
score_9, score_8_up, score_7_up, score_6_up, score_5_up, score_4_up, waterelements,beautiful expressive cute face, underwater, extremely detailed, beautiful underwater cute dragon creature, no humans, extremely detailed scales, seaweed mane, fishtail, beautiful eyes, moonlight, realistic, high quality, detailed, fantasy dark cave background, sparkling water, dark shadows, glowing in the dark, bright colors, high contrast, dark background, vivid lighting,
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
score_9, score_8_up, score_7_up, score_6_up, score_5_up, score_4_up, best quality, highres, detailed background, office BREAK <lora:Depresso:1> depresso, blue body, purple eyes, looking aside, eye bags, tired, bored, dress shirt, white shirt, glasses, table, sitting, coffee mug
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
anime art, surreal (biomorphic landscape:1.1) on a cloud, art made by complex AI art prompt machine and studio ghibli, crazy and beautiful, hyperdetailed, colorful, waterfall, strange style, meadow elements, whimsical (biomechanical kingdom shire:1.2), digital art and alcohol ink manga painting mixed into one wonderful uncanny masterwork
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Here's the request: {prompt} {f"\nKeep to the following style: {style}" if style else ""}
 | 
			
		||||
 | 
			
		||||
Output the prompt in a txt code block.""")
 | 
			
		||||
        
 | 
			
		||||
        print(gen_prompt)
 | 
			
		||||
        
 | 
			
		||||
        try:
 | 
			
		||||
            prompt = gen_prompt.split("```")[1].strip().lstrip('```\n').rstrip('\n```')
 | 
			
		||||
        except IndexError:
 | 
			
		||||
            return "Error: No prompt could be generated", 500
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        print(f"Error generating image prompt: {e}")
 | 
			
		||||
        return f"Error generating image prompt: {str(e)}", 500
 | 
			
		||||
    
 | 
			
		||||
    return { 'raw_generation': gen_prompt, 'text': prompt }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
def _gen_text(prompt):
 | 
			
		||||
    client = get_g4f_client()
 | 
			
		||||
    response = client.chat.completions.create(
 | 
			
		||||
            provider=g4f.Provider.PerplexityLabs,
 | 
			
		||||
            model="llama-3-70b-instruct",
 | 
			
		||||
            messages=[{"role": "user", "content": prompt}],
 | 
			
		||||
            max_tokens=800,
 | 
			
		||||
            temperature=0.7,
 | 
			
		||||
        )
 | 
			
		||||
    print(response)
 | 
			
		||||
    text = response.choices[0].message.content
 | 
			
		||||
    return text
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.route('/gen_image', methods=['GET'])
 | 
			
		||||
def gen_image():
 | 
			
		||||
    prompt = request.args.get('prompt')
 | 
			
		||||
 | 
			
		||||
    if not prompt:
 | 
			
		||||
        return "Error: No prompt provided", 400
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    print(prompt)
 | 
			
		||||
    
 | 
			
		||||
    try:
 | 
			
		||||
        client = get_client()
 | 
			
		||||
        response = client.images.generate(
 | 
			
		||||
            model="playground-v2.5",
 | 
			
		||||
            prompt=prompt,
 | 
			
		||||
        )
 | 
			
		||||
        image_url = response.data[0].url
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        print(f"Error generating image: {e}")
 | 
			
		||||
        return f"Error generating image: {str(e)}", 500
 | 
			
		||||
    
 | 
			
		||||
    return { 'url': str(image_url) }
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    app.run()
 | 
			
		||||
							
								
								
									
										21
									
								
								gen_service/pyproject.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								gen_service/pyproject.toml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
[tool.poetry]
 | 
			
		||||
name = "g4f-imagen-service"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
description = ""
 | 
			
		||||
authors = ["Your Name <you@example.com>"]
 | 
			
		||||
readme = "README.md"
 | 
			
		||||
 | 
			
		||||
[tool.poetry.dependencies]
 | 
			
		||||
python = "^3.12"
 | 
			
		||||
g4f = {extras = ["webdriver"], version = "^0.3.0.7"}
 | 
			
		||||
Flask = "^3.0.3"
 | 
			
		||||
openai = "^1.25.0"
 | 
			
		||||
gunicorn = "^22.0.0"
 | 
			
		||||
 | 
			
		||||
[tool.poetry.group.dev.dependencies]
 | 
			
		||||
pytest = "^8.2.0"
 | 
			
		||||
black = "^24.4.2"
 | 
			
		||||
 | 
			
		||||
[build-system]
 | 
			
		||||
requires = ["poetry-core"]
 | 
			
		||||
build-backend = "poetry.core.masonry.api"
 | 
			
		||||
							
								
								
									
										4
									
								
								gen_service/wsgi.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								gen_service/wsgi.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
			
		||||
from main import app
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    app.run()
 | 
			
		||||
							
								
								
									
										13
									
								
								web/.eslintignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								web/.eslintignore
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
.DS_Store
 | 
			
		||||
node_modules
 | 
			
		||||
/build
 | 
			
		||||
/.svelte-kit
 | 
			
		||||
/package
 | 
			
		||||
.env
 | 
			
		||||
.env.*
 | 
			
		||||
!.env.example
 | 
			
		||||
 | 
			
		||||
# Ignore files for PNPM, NPM and YARN
 | 
			
		||||
pnpm-lock.yaml
 | 
			
		||||
package-lock.json
 | 
			
		||||
yarn.lock
 | 
			
		||||
							
								
								
									
										31
									
								
								web/.eslintrc.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								web/.eslintrc.cjs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
/** @type { import("eslint").Linter.Config } */
 | 
			
		||||
module.exports = {
 | 
			
		||||
	root: true,
 | 
			
		||||
	extends: [
 | 
			
		||||
		'eslint:recommended',
 | 
			
		||||
		'plugin:@typescript-eslint/recommended',
 | 
			
		||||
		'plugin:svelte/recommended',
 | 
			
		||||
		'prettier'
 | 
			
		||||
	],
 | 
			
		||||
	parser: '@typescript-eslint/parser',
 | 
			
		||||
	plugins: ['@typescript-eslint'],
 | 
			
		||||
	parserOptions: {
 | 
			
		||||
		sourceType: 'module',
 | 
			
		||||
		ecmaVersion: 2020,
 | 
			
		||||
		extraFileExtensions: ['.svelte']
 | 
			
		||||
	},
 | 
			
		||||
	env: {
 | 
			
		||||
		browser: true,
 | 
			
		||||
		es2017: true,
 | 
			
		||||
		node: true
 | 
			
		||||
	},
 | 
			
		||||
	overrides: [
 | 
			
		||||
		{
 | 
			
		||||
			files: ['*.svelte'],
 | 
			
		||||
			parser: 'svelte-eslint-parser',
 | 
			
		||||
			parserOptions: {
 | 
			
		||||
				parser: '@typescript-eslint/parser'
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	]
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										12
									
								
								web/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								web/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
.DS_Store
 | 
			
		||||
node_modules
 | 
			
		||||
/build
 | 
			
		||||
/.svelte-kit
 | 
			
		||||
/package
 | 
			
		||||
.env
 | 
			
		||||
.env.*
 | 
			
		||||
!.env.example
 | 
			
		||||
.vercel
 | 
			
		||||
.output
 | 
			
		||||
vite.config.js.timestamp-*
 | 
			
		||||
vite.config.ts.timestamp-*
 | 
			
		||||
							
								
								
									
										1
									
								
								web/.npmrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								web/.npmrc
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
engine-strict=true
 | 
			
		||||
							
								
								
									
										4
									
								
								web/.prettierignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								web/.prettierignore
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
			
		||||
# Ignore files for PNPM, NPM and YARN
 | 
			
		||||
pnpm-lock.yaml
 | 
			
		||||
package-lock.json
 | 
			
		||||
yarn.lock
 | 
			
		||||
							
								
								
									
										15
									
								
								web/.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								web/.prettierrc
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
{
 | 
			
		||||
	"useTabs": true,
 | 
			
		||||
	"singleQuote": true,
 | 
			
		||||
	"trailingComma": "none",
 | 
			
		||||
	"printWidth": 100,
 | 
			
		||||
	"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
 | 
			
		||||
	"overrides": [
 | 
			
		||||
		{
 | 
			
		||||
			"files": "*.svelte",
 | 
			
		||||
			"options": {
 | 
			
		||||
				"parser": "svelte"
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								web/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								web/Dockerfile
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
FROM node:18-alpine AS build
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### RUN BUILD IN CONTAINER
 | 
			
		||||
 | 
			
		||||
# copy required files
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
COPY . .
 | 
			
		||||
RUN npm install
 | 
			
		||||
RUN npm run build
 | 
			
		||||
 | 
			
		||||
# copy to actual container (prod)
 | 
			
		||||
FROM node:18-alpine AS web
 | 
			
		||||
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
 | 
			
		||||
# copy required files
 | 
			
		||||
COPY --from=build /app/build /app/build
 | 
			
		||||
# COPY --from=build /app/migrations /app/migrations
 | 
			
		||||
COPY --from=build /app/package.json /app/package-lock.json .
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# get production node modules
 | 
			
		||||
RUN npm ci --omit dev
 | 
			
		||||
 | 
			
		||||
# expose port
 | 
			
		||||
EXPOSE 3000
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# run app
 | 
			
		||||
CMD ["node", "build"]
 | 
			
		||||
							
								
								
									
										38
									
								
								web/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								web/README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
# create-svelte
 | 
			
		||||
 | 
			
		||||
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
 | 
			
		||||
 | 
			
		||||
## Creating a project
 | 
			
		||||
 | 
			
		||||
If you're seeing this, you've probably already done this step. Congrats!
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# create a new project in the current directory
 | 
			
		||||
npm create svelte@latest
 | 
			
		||||
 | 
			
		||||
# create a new project in my-app
 | 
			
		||||
npm create svelte@latest my-app
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Developing
 | 
			
		||||
 | 
			
		||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
npm run dev
 | 
			
		||||
 | 
			
		||||
# or start the server and open the app in a new browser tab
 | 
			
		||||
npm run dev -- --open
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Building
 | 
			
		||||
 | 
			
		||||
To create a production version of your app:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
npm run build
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
You can preview the production build with `npm run preview`.
 | 
			
		||||
 | 
			
		||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
 | 
			
		||||
							
								
								
									
										5404
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										5404
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										48
									
								
								web/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								web/package.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "web",
 | 
			
		||||
	"version": "0.0.1",
 | 
			
		||||
	"scripts": {
 | 
			
		||||
		"dev": "vite dev",
 | 
			
		||||
		"build": "vite build",
 | 
			
		||||
		"preview": "vite preview",
 | 
			
		||||
		"test": "npm run test:integration && npm run test:unit",
 | 
			
		||||
		"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
 | 
			
		||||
		"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
 | 
			
		||||
		"lint": "prettier --check . && eslint .",
 | 
			
		||||
		"format": "prettier --write .",
 | 
			
		||||
		"test:integration": "playwright test",
 | 
			
		||||
		"test:unit": "vitest"
 | 
			
		||||
	},
 | 
			
		||||
	"devDependencies": {
 | 
			
		||||
		"@fontsource/fira-mono": "^4.5.10",
 | 
			
		||||
		"@neoconfetti/svelte": "^1.0.0",
 | 
			
		||||
		"@playwright/test": "^1.28.1",
 | 
			
		||||
		"@skeletonlabs/skeleton": "^2.9.2",
 | 
			
		||||
		"@skeletonlabs/tw-plugin": "^0.4.0",
 | 
			
		||||
		"@sveltejs/adapter-auto": "^3.0.0",
 | 
			
		||||
		"@sveltejs/adapter-node": "^5.0.1",
 | 
			
		||||
		"@sveltejs/kit": "^2.0.0",
 | 
			
		||||
		"@sveltejs/vite-plugin-svelte": "^3.0.0",
 | 
			
		||||
		"@types/eslint": "^8.56.0",
 | 
			
		||||
		"@types/node": "^20.12.7",
 | 
			
		||||
		"@typescript-eslint/eslint-plugin": "^7.0.0",
 | 
			
		||||
		"@typescript-eslint/parser": "^7.0.0",
 | 
			
		||||
		"autoprefixer": "^10.4.16",
 | 
			
		||||
		"eslint": "^8.56.0",
 | 
			
		||||
		"eslint-config-prettier": "^9.1.0",
 | 
			
		||||
		"eslint-plugin-svelte": "^2.35.1",
 | 
			
		||||
		"postcss": "^8.4.32",
 | 
			
		||||
		"postcss-load-config": "^5.0.2",
 | 
			
		||||
		"prettier": "^3.1.1",
 | 
			
		||||
		"prettier-plugin-svelte": "^3.1.2",
 | 
			
		||||
		"prettier-plugin-tailwindcss": "^0.5.9",
 | 
			
		||||
		"svelte": "^4.2.7",
 | 
			
		||||
		"svelte-check": "^3.6.0",
 | 
			
		||||
		"tailwindcss": "^3.3.6",
 | 
			
		||||
		"tslib": "^2.4.1",
 | 
			
		||||
		"typescript": "^5.0.0",
 | 
			
		||||
		"vite": "^5.0.3",
 | 
			
		||||
		"vitest": "^1.2.0"
 | 
			
		||||
	},
 | 
			
		||||
	"type": "module"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								web/playwright.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								web/playwright.config.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
import type { PlaywrightTestConfig } from '@playwright/test';
 | 
			
		||||
 | 
			
		||||
const config: PlaywrightTestConfig = {
 | 
			
		||||
	webServer: {
 | 
			
		||||
		command: 'npm run build && npm run preview',
 | 
			
		||||
		port: 4173
 | 
			
		||||
	},
 | 
			
		||||
	testDir: 'tests',
 | 
			
		||||
	testMatch: /(.+\.)?(test|spec)\.[jt]s/
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default config;
 | 
			
		||||
							
								
								
									
										13
									
								
								web/postcss.config.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								web/postcss.config.cjs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
const tailwindcss = require('tailwindcss');
 | 
			
		||||
const autoprefixer = require('autoprefixer');
 | 
			
		||||
 | 
			
		||||
const config = {
 | 
			
		||||
	plugins: [
 | 
			
		||||
		//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
 | 
			
		||||
		tailwindcss(),
 | 
			
		||||
		//But others, like autoprefixer, need to run after,
 | 
			
		||||
		autoprefixer
 | 
			
		||||
	]
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = config;
 | 
			
		||||
							
								
								
									
										13
									
								
								web/src/app.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								web/src/app.d.ts
									
									
									
									
										vendored
									
									
										Normal 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
									
								
							
							
						
						
									
										12
									
								
								web/src/app.html
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										4
									
								
								web/src/app.pcss
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
			
		||||
/* Write your global styles here, in PostCSS syntax */
 | 
			
		||||
@tailwind base;
 | 
			
		||||
@tailwind components;
 | 
			
		||||
@tailwind utilities;
 | 
			
		||||
							
								
								
									
										16
									
								
								web/src/lib/images/github.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								web/src/lib/images/github.svg
									
									
									
									
									
										Normal 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  | 
							
								
								
									
										1
									
								
								web/src/lib/images/svelte-logo.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								web/src/lib/images/svelte-logo.svg
									
									
									
									
									
										Normal 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  | 
							
								
								
									
										
											BIN
										
									
								
								web/src/lib/images/svelte-welcome.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/src/lib/images/svelte-welcome.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 352 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								web/src/lib/images/svelte-welcome.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/src/lib/images/svelte-welcome.webp
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 113 KiB  | 
							
								
								
									
										55
									
								
								web/src/routes/+layout.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								web/src/routes/+layout.svelte
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										178
									
								
								web/src/routes/+page.svelte
									
									
									
									
									
										Normal 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>
 | 
			
		||||
							
								
								
									
										27
									
								
								web/src/routes/api/forward/[[slug]]/+server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								web/src/routes/api/forward/[[slug]]/+server.ts
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										94
									
								
								web/src/routes/styles.css
									
									
									
									
									
										Normal 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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								web/static/favicon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/static/favicon.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 1.5 KiB  | 
							
								
								
									
										3
									
								
								web/static/robots.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								web/static/robots.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
# https://www.robotstxt.org/robotstxt.html
 | 
			
		||||
User-agent: *
 | 
			
		||||
Disallow:
 | 
			
		||||
							
								
								
									
										19
									
								
								web/svelte.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								web/svelte.config.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
// import adapter from '@sveltejs/adapter-auto';
 | 
			
		||||
import adapter from '@sveltejs/adapter-node';
 | 
			
		||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
 | 
			
		||||
 | 
			
		||||
/** @type {import('@sveltejs/kit').Config} */
 | 
			
		||||
const config = {
 | 
			
		||||
	// Consult https://kit.svelte.dev/docs/integrations#preprocessors
 | 
			
		||||
	// for more information about preprocessors
 | 
			
		||||
	preprocess: [vitePreprocess({})],
 | 
			
		||||
 | 
			
		||||
	kit: {
 | 
			
		||||
		// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
 | 
			
		||||
		// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
 | 
			
		||||
		// See https://kit.svelte.dev/docs/adapters for more information about adapters.
 | 
			
		||||
		adapter: adapter()
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default config;
 | 
			
		||||
							
								
								
									
										27
									
								
								web/tailwind.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								web/tailwind.config.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
import { join } from 'path';
 | 
			
		||||
import type { Config } from 'tailwindcss';
 | 
			
		||||
 | 
			
		||||
// Import the Skeleton plugin
 | 
			
		||||
import { skeleton } from '@skeletonlabs/tw-plugin';
 | 
			
		||||
 | 
			
		||||
const config = {
 | 
			
		||||
	// Opt for dark mode to be handled via the class method
 | 
			
		||||
	darkMode: 'class',
 | 
			
		||||
	content: [
 | 
			
		||||
		'./src/**/*.{html,js,svelte,ts}',
 | 
			
		||||
		join(require.resolve(
 | 
			
		||||
			'@skeletonlabs/skeleton'),
 | 
			
		||||
			'../**/*.{html,js,svelte,ts}'
 | 
			
		||||
		)
 | 
			
		||||
	],
 | 
			
		||||
	theme: {
 | 
			
		||||
		extend: {},
 | 
			
		||||
	},
 | 
			
		||||
	plugins: [
 | 
			
		||||
		skeleton({
 | 
			
		||||
			themes: { preset: [ "skeleton" ] }
 | 
			
		||||
		})
 | 
			
		||||
	]
 | 
			
		||||
} satisfies Config;
 | 
			
		||||
 | 
			
		||||
export default config;
 | 
			
		||||
							
								
								
									
										6
									
								
								web/tests/test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								web/tests/test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
import { expect, test } from '@playwright/test';
 | 
			
		||||
 | 
			
		||||
test('about page has expected h1', async ({ page }) => {
 | 
			
		||||
	await page.goto('/about');
 | 
			
		||||
	await expect(page.getByRole('heading', { name: 'About this app' })).toBeVisible();
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										19
									
								
								web/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								web/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
{
 | 
			
		||||
	"extends": "./.svelte-kit/tsconfig.json",
 | 
			
		||||
	"compilerOptions": {
 | 
			
		||||
		"allowJs": true,
 | 
			
		||||
		"checkJs": true,
 | 
			
		||||
		"esModuleInterop": true,
 | 
			
		||||
		"forceConsistentCasingInFileNames": true,
 | 
			
		||||
		"resolveJsonModule": true,
 | 
			
		||||
		"skipLibCheck": true,
 | 
			
		||||
		"sourceMap": true,
 | 
			
		||||
		"strict": true,
 | 
			
		||||
		"moduleResolution": "bundler"
 | 
			
		||||
	}
 | 
			
		||||
	// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
 | 
			
		||||
	// except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
 | 
			
		||||
	//
 | 
			
		||||
	// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
 | 
			
		||||
	// from the referenced tsconfig.json - TypeScript does not merge them in
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								web/vite.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								web/vite.config.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
import { sveltekit } from '@sveltejs/kit/vite';
 | 
			
		||||
import { defineConfig } from 'vitest/config';
 | 
			
		||||
 | 
			
		||||
export default defineConfig({
 | 
			
		||||
	plugins: [sveltekit()],
 | 
			
		||||
	test: {
 | 
			
		||||
		include: ['src/**/*.{test,spec}.{js,ts}']
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
		Reference in New Issue
	
	Block a user