Repository: modal-labs/turbo-art Branch: main Commit: c75ee541ed6f Files: 19 Total size: 34.0 KB Directory structure: gitextract_sj_tsbtw/ ├── .gitignore ├── README.md ├── index.html ├── package.json ├── postcss.config.js ├── src/ │ ├── App.svelte │ ├── app.css │ ├── lib/ │ │ ├── ImageOutput.svelte │ │ ├── Paint.svelte │ │ ├── PreviewImages.svelte │ │ └── Tools.svelte │ ├── main.ts │ └── vite-env.d.ts ├── svelte.config.js ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── turbo_art.py └── vite.config.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ node_modules/ dist/ dist-ssr/ __pycache__/ .venv/ venv/ .DS_Store ================================================ FILE: README.md ================================================ # turbo.art A playground for creative exploration that uses [SDXL Turbo](https://huggingface.co/stabilityai/sdxl-turbo) for real-time image editing. Try it now at [https://turbo.art](https://turbo.art)! ![turbo-art](https://github.com/modal-labs/turbo-art/assets/5786378/bb185f24-9946-4c26-a7ca-7c8732ea77f0) The entire app is serverless and hosted on [Modal](https://modal.com/). ## Developing locally ### File structure - [turbo_art.py](./turbo_art.py) - model endpoint and FastAPI web server (<150 lines of code!) - [src/](./src) - Svelte frontend ### Requirements To run this for yourself, you will need: 1. Modal installed and set up locally, as well as FastAPI ```shell pip install modal fastapi modal setup ``` 2. [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) installed ### Iterate During development, it's useful to have both the frontend and the Modal application automatically react to changes in the code. To do this, you'll need to run two processes. First, in one shell session, run: ```shell npm install npm run build:watch ``` Then, in another shell session, run: ```shell modal serve turbo_art.py ``` In the terminal output, you'll find a URL that you can visit to use your app. While the [`modal serve`](<(https://modal.com/docs/guide/webhooks#developing-with-modal-serve)>) process is running, changes to any of the project files will be automatically applied. `Ctrl+C` will stop the app. ### Deploy Once you're happy with your changes, [deploy](https://modal.com/docs/guide/managing-deployments#creating-deployments) your app: ```shell npm run build modal deploy turbo_art.py ``` In the terminal output, you'll find a different URL that you can visit to use your app. We chose to use Modal's [custom domains](https://modal.com/docs/guide/webhooks#custom-domains) feature to make the URL more memorable. Without a custom domain, you can still [select](https://modal.com/docs/guide/webhook-urls#user-specified-urls) part of the the `modal.run` subdomain you're assigned. Note that leaving the app deployed on Modal doesn't cost you anything! Modal apps are serverless and scale to 0 when not in use. ================================================ FILE: index.html ================================================ Turbo.Art
================================================ FILE: package.json ================================================ { "name": "turbo-art", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "vite build", "build:watch": "vite build --watch", "preview": "vite preview", "check": "svelte-check --tsconfig ./tsconfig.json" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.3", "@tsconfig/svelte": "^5.0.2", "@types/node": "^22.15.19", "@types/throttle-debounce": "^5.0.2", "autoprefixer": "^10.4.16", "color-picker-svelte": "^1.4.0", "svelte": "^5.30.2", "svelte-check": "^4.2.1", "tailwindcss": "^3.4.17", "tslib": "^2.6.2", "typescript": "^5.2.2", "vite": "^6.3.5" }, "dependencies": { "@types/three": "^0.176.0", "lucide-svelte": "^0.511.0", "paper": "^0.12.17", "postprocessing": "^6.33.4", "three": "^0.159.0", "throttle-debounce": "^5.0.0" } } ================================================ FILE: postcss.config.js ================================================ import tailwind from "tailwindcss"; import tailwindConfig from "./tailwind.config.js"; import autoprefixer from "autoprefixer"; export default { plugins: [tailwind(tailwindConfig), autoprefixer], }; ================================================ FILE: src/App.svelte ================================================
The image generation is powered by Stability's SDXL Turbo

Prompt

{#each promptOptions as item} {/each}
Canvas {#if isLoading} {/if}
Draw on the image to generate a new one
input
{#if isMobile} {/if}
{ paper.project.activeLayer.removeChildren(); paper.view.update(); generateOutputImage(); }} on:setPaint={setPaint} on:setBrushSize={setBrushSize} />
(currentImageName = name)} setPrompt={(v) => (value = v)} />
{#if !isMobile} {/if}
Built with
Get Started
================================================ FILE: src/app.css ================================================ /* #RefactorExamplesComponentsAndStyles */ @tailwind base; @tailwind components; @tailwind utilities; @layer base { body { @apply bg-black; color-scheme: dark; text-rendering: optimizelegibility; -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; } * { @apply outline-transparent; } *:focus { outline: none; } } ================================================ FILE: src/lib/ImageOutput.svelte ================================================ loading... ================================================ FILE: src/lib/Paint.svelte ================================================
{#each palette as { hex, name }} {/each}

================================================ FILE: src/lib/PreviewImages.svelte ================================================ ================================================ FILE: src/lib/Tools.svelte ================================================
================================================ FILE: src/main.ts ================================================ import { mount } from "svelte"; import App from "./App.svelte"; import "./app.css"; const app = mount(App, { target: document.getElementById("app")!, }); export default app; ================================================ FILE: src/vite-env.d.ts ================================================ /// /// ================================================ FILE: svelte.config.js ================================================ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' export default { // Consult https://svelte.dev/docs#compile-time-svelte-preprocess // for more information about preprocessors preprocess: vitePreprocess(), } ================================================ FILE: tailwind.config.js ================================================ import defaultTheme from "tailwindcss/defaultTheme"; /** @type {import('tailwindcss').Config} */ export default { content: ["./index.html", "./src/**/*.{html,js,svelte,ts}"], theme: { extend: { screens: { md: "840px", xl: "1200px", }, colors: { // Theme colors primary: "#7FEE64", "light-green": "#DDFFDC", "muted-yellow": "#FFEA71", }, fontFamily: { mono: ["Fira Mono", ...defaultTheme.fontFamily.mono], sans: ["Inter Variable", ...defaultTheme.fontFamily.sans], inter: ["Inter Variable", ...defaultTheme.fontFamily.sans], tosh: ["Tosh Modal", ...defaultTheme.fontFamily.sans], degular: ["degular", ...defaultTheme.fontFamily.sans], }, // Global font modifications fontSize: { sm: [ "0.875rem", { lineHeight: "1.25rem", letterSpacing: "0.01em", }, ], }, }, }, plugins: [], }; ================================================ FILE: tsconfig.json ================================================ { "extends": "@tsconfig/svelte/tsconfig.json", "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "resolveJsonModule": true, /** * Typecheck JS in `.svelte` and `.js` files by default. * Disable checkJs if you'd like to use dynamic types in JS. * Note that setting allowJs false does not prevent the use * of JS in `.svelte` files. */ "allowJs": true, "checkJs": true, "isolatedModules": true, "paths": { "$lib": ["./src/lib"], // aliased by SvelteKit "$lib/*": ["./src/lib/*"], } }, "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], "references": [{ "path": "./tsconfig.node.json" }] } ================================================ FILE: tsconfig.node.json ================================================ { "compilerOptions": { "composite": true, "skipLibCheck": true, "module": "ESNext", "moduleResolution": "bundler" }, "include": ["vite.config.ts"] } ================================================ FILE: turbo_art.py ================================================ from pathlib import Path import modal from fastapi import FastAPI, File, Form, Response, UploadFile from fastapi.staticfiles import StaticFiles app = modal.App("stable-diffusion-xl-turbo") assets_path = Path(__file__).parent / "dist" web_image = ( modal.Image.debian_slim() .pip_install("jinja2", "fastapi[standard]") .add_local_dir(assets_path, "/assets") ) def download_weights(): from huggingface_hub import snapshot_download # Ignore files that we don't need to speed up download time. ignore = [ "*.bin", "*.onnx_data", "*/diffusion_pytorch_model.safetensors", ] snapshot_download("stabilityai/sdxl-turbo", ignore_patterns=ignore) # https://huggingface.co/docs/diffusers/main/en/using-diffusers/sdxl_turbo#speed-up-sdxl-turbo-even-more # vae is used for a inference speedup snapshot_download("madebyollin/sdxl-vae-fp16-fix", ignore_patterns=ignore) inference_image = ( modal.Image.debian_slim() .pip_install( "Pillow~=10.1.0", "diffusers~=0.24", "transformers~=4.35", "accelerate~=0.25", "safetensors~=0.4", "fastapi[standard]", ) .run_function(download_weights) ) with inference_image.imports(): from io import BytesIO import torch from diffusers import AutoencoderKL, AutoPipelineForImage2Image from diffusers.utils import load_image from PIL import Image @app.cls( gpu="A100", image=inference_image, scaledown_window=240, max_containers=10, ) class Model: @modal.enter() def enter(self): self.pipe = AutoPipelineForImage2Image.from_pretrained( "stabilityai/sdxl-turbo", torch_dtype=torch.float16, device_map="balanced", variant="fp16", vae=AutoencoderKL.from_pretrained( "madebyollin/sdxl-vae-fp16-fix", torch_dtype=torch.float16, device_map="balanced", ), ) # We execute a blank inference since there are objects that are lazily loaded that # we want to start loading before an actual user query self.pipe( "blank", image=Image.new("RGB", (800, 1280), (255, 255, 255)), num_inference_steps=1, strength=1, guidance_scale=0.0, seed=42, ) @modal.fastapi_endpoint(method="POST") async def inference( self, image: UploadFile = File(...), prompt: str = Form(...), num_iterations: str = Form(...), ): img_data_in = await image.read() init_image = load_image(Image.open(BytesIO(img_data_in))).resize((512, 512)) # based on trial and error we saw the best results with 3 inference steps # it had better generation results than 4,5,6 even though it's faster num_inference_steps = int(num_iterations) # note: anything under 0.5 strength gives blurry results strength = 0.999 if num_iterations == 2 else 0.65 assert num_inference_steps * strength >= 1 image = self.pipe( prompt, image=init_image, num_inference_steps=num_inference_steps, strength=strength, guidance_scale=0.0, seed=42, ).images[0] byte_stream = BytesIO() image.save(byte_stream, format="jpeg") img_data_out = byte_stream.getvalue() return Response(content=img_data_out, media_type="image/jpeg") @app.function(image=web_image) @modal.concurrent(max_inputs=10) @modal.asgi_app() def fastapi_app(): import jinja2 web_app = FastAPI() with open("/assets/index.html", "r") as f: template_html = f.read() template = jinja2.Template(template_html) with open("/assets/index.html", "w") as f: # Mount the {{ inference_url }} with the Modal inference endpoint. html = template.render(inference_url=Model.inference.web_url) f.write(html) web_app.mount("/", StaticFiles(directory="/assets", html=True)) return web_app ================================================ FILE: vite.config.ts ================================================ import postcss from "./postcss.config.js"; import { defineConfig } from "vite"; import { svelte } from "@sveltejs/vite-plugin-svelte"; import path from "node:path"; export default defineConfig({ plugins: [svelte()], css: { postcss, }, build: { minify: true, }, server: { open: true, }, resolve: { alias: { $lib: path.resolve("./src/lib"), }, }, });