inital push

This commit is contained in:
caranicas 2022-09-14 10:48:46 -04:00
parent 1d88a5b42e
commit 34aa60d47b
49 changed files with 7269 additions and 7 deletions

5
.gitignore vendored
View File

@ -1,4 +1,7 @@
__pycache__
installer
installer.tar
dist
dist
# built code for the front end
!/ui/frontend/dist

18
ui/frontend/build_src/.gitignore vendored Normal file
View File

@ -0,0 +1,18 @@
# local ignores - We could move these to the global ignores,
# but I think it makes sense to keep them here
# env
*.local
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# installed dependencies
node_modules

File diff suppressed because it is too large Load Diff

2571
ui/frontend/build_src/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
{
"name": "react-ts",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build --emptyOutDir",
"preview": "vite preview"
},
"dependencies": {
"@tanstack/react-query": "^4.2.3",
"@tanstack/react-query-devtools": "^4.2.3",
"immer": "^9.0.15",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"uuid": "^9.0.0",
"zustand": "^4.1.1"
},
"devDependencies": {
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6",
"@types/uuid": "^8.3.4",
"@vitejs/plugin-react": "^2.0.1",
"typescript": "^4.6.4",
"vite": "^3.0.7"
}
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,151 @@
[
[
"Drawing Style",
[
"Cel Shading",
"Children's Drawing",
"Crosshatch",
"Detailed and Intricate",
"Doodle",
"Dot Art",
"Line Art",
"Sketch"
]
],
[
"Visual Style",
[
"2D",
"8-bit",
"16-bit",
"Anaglyph",
"Anime",
"CGI",
"Cartoon",
"Comic Book",
"Concept Art",
"Digital Art",
"Fantasy",
"Graphic Novel",
"Hard Edge Painting",
"Hydrodipped",
"Lithography",
"Manga",
"Modern Art",
"Mosaic",
"Mural",
"Photo",
"Realistic",
"Street Art",
"Visual Novel",
"Watercolor"
]
],
[
"Pen",
[
"Chalk",
"Colored Pencil",
"Graphite",
"Ink",
"Oil Paint",
"Pastel Art"
]
],
[
"Carving and Etching",
[
"Etching",
"Linocut",
"Paper Model",
"Paper-Mache",
"Papercutting",
"Pyrography",
"Wood-Carving"
]
],
[
"Camera",
[
"Aerial View",
"Canon50",
"Cinematic",
"Close-up",
"Color Grading",
"Dramatic",
"Film Grain",
"Fisheye Lens",
"Glamor Shot",
"Golden Hour",
"HD",
"Lens Flare",
"Macro",
"Polaroid",
"Vintage",
"War Photography",
"White Balance",
"Wildlife Photography"
]
],
[
"Color",
[
"Beautiful Lighting",
"Colorful",
"Dynamic Lighting",
"Electric Colors",
"Infrared",
"Synthwave",
"Warm Color Palette"
]
],
[
"Emotions",
[
"Angry",
"Disgusted",
"Embarrassed",
"Evil",
"Excited",
"Fear",
"Happy",
"Lonely",
"Sad",
"Surprised"
]
],
[
"Style of an artist or community",
[
"Artstation",
"by Agnes Lawrence Pelton",
"by Akihito Yoshida",
"by Andy Warhol",
"by Artgerm",
"by Asaf Hanuka",
"by Aubrey Beardsley",
"by Banksy",
"by Ben Enwonwu",
"by Caravaggio Michelangelo Merisi",
"by David Mann",
"by Frida Kahlo",
"by H.R. Giger",
"by Hayao Miyazaki",
"by Ivan Shishkin",
"by Johannes Vermeer",
"by John William Waterhouse",
"by Katsushika Hokusai",
"by Ko Young Hoon",
"by Leonardo da Vinci",
"by Lisa Frank",
"by Mahmoud Saïd",
"by Mark Brooks",
"by Pablo Picasso",
"by Richard Dadd",
"by Salvador Dalí",
"by Tivadar Csontváry Kosztka",
"by Yoshitaka Amano",
"by wlop"
]
]
]

View File

@ -0,0 +1,60 @@
.App {
position: relative;
width: 100%;
height: 100%;
pointer-events: auto;
display: grid;
background-color: rgb(32, 33, 36);
grid-template-columns: 360px 1fr;
grid-template-rows: 100px 1fr 300px;
grid-template-areas: "header header header"
"create display display"
"footer footer footer";
}
/* Very basic mobile stacked UI*/
@media screen and (max-width: 768px) {
.App {
grid-template-columns: 1fr;
grid-template-rows: 100px 1fr 1fr 300px;
grid-template-areas: "header"
"create"
"display"
"footer";
}
}
.header-layout {
grid-area: header;
}
.create-layout {
grid-area: create;
}
.display-layout {
grid-area: display;
}
.footer-layout {
grid-area: footer;
}
/* Copypasta from Bootstrap, makes content visually hidden but still accessible for screenreaders */
.visually-hidden, .visually-hidden-focusable:not(:focus):not(:focus-within) {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}
/* TODO proper utility classes */
.mb-4 {
margin-bottom: 1rem;
}

View File

@ -0,0 +1,46 @@
import React, {useEffect, useState} from 'react'
import './App.css'
import { useQuery } from "@tanstack/react-query";
import { getSaveDirectory } from './api'
import { useImageCreate } from "./store/imageCreateStore";
// Todo - import components here
import HeaderDisplay from './components/headerDisplay';
import CreationPanel from './components/creationPanel';
import DisplayPanel from './components/displayPanel';
import FooterDisplay from './components/footerDisplay';
function App() {
// Get the original save directory
const setRequestOption = useImageCreate((state) => state.setRequestOptions);
const { status, data } = useQuery(
['SaveDir'], getSaveDirectory,
);
useEffect(() => {
if(status === 'success') {
setRequestOption("save_to_disk_path", data);
}
}, [setRequestOption, status, data]);
return (
<div className="App">
<header className="header-layout">
<HeaderDisplay></HeaderDisplay>
</header>
<nav className="create-layout">
<CreationPanel></CreationPanel>
</nav>
<main className="display-layout">
<DisplayPanel></DisplayPanel>
</main>
<footer className="footer-layout">
<FooterDisplay></FooterDisplay>
</footer>
</div>
)
}
export default App

View File

@ -0,0 +1,55 @@
/**
* basic server health
*/
import type {ImageRequest} from '../store/imageCreateStore';
// when we are on dev we want to specifiy 9000 as the port for the backend
// when we are on prod we want be realtive to the current url
const API_URL = import.meta.env.DEV ? 'http://localhost:9000' : '';
export const HEALTH_PING_INTERVAL = 5000; // 5 seconds
export const healthPing = async () => {
const pingUrl = `${API_URL}/ping`;
let response = await fetch(pingUrl)
const data = await response.json();
return data;
}
/**
* the local list of modifications
*/
export const loadModifications = async () => {
const response = await fetch(`${API_URL}/modifiers.json`);
const data = await response.json();
return data;
}
export const getSaveDirectory = async () => {
const response = await fetch(`${API_URL}/output_dir`);
const data = await response.json();
return data[0];
};
/**
* post a new request for an image
*/
export const MakeImageKey = 'MakeImage';
export const doMakeImage = async (reqBody: ImageRequest) => {
const res = await fetch(`${API_URL}/image`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(reqBody)
});
const data = await res.json();
return data;
}

View File

@ -0,0 +1,3 @@
input[size="4"] {
width: 4.5rem;
}

View File

@ -0,0 +1,353 @@
import React, {useEffect} from "react";
import { useImageCreate } from "../../../store/imageCreateStore";
import "./advancedSettings.css";
// todo: move this someplace more global
const IMAGE_DIMENSIONS = [
{ value: 128, label: "128 (*)" },
{ value: 192, label: "192" },
{ value: 256, label: "256 (*)" },
{ value: 320, label: "320" },
{ value: 384, label: "384" },
{ value: 448, label: "448" },
{ value: 512, label: "512 (*)" },
{ value: 576, label: "576" },
{ value: 640, label: "640" },
{ value: 704, label: "704" },
{ value: 768, label: "768 (*)" },
{ value: 832, label: "832" },
{ value: 896, label: "896" },
{ value: 960, label: "960" },
{ value: 1024, label: "1024 (*)" },
];
function SettingsList() {
const requestCount = useImageCreate((state) => state.requestCount);
const setRequestCount = useImageCreate((state) => state.setRequestCount);
const setRequestOption = useImageCreate((state) => state.setRequestOptions);
const toggleUseFaceCorrection = useImageCreate((state) => state.toggleUseFaceCorrection);
const isUsingFaceCorrection = useImageCreate((state) => state.isUsingFaceCorrection());
const toggleUseUpscaling = useImageCreate((state) => state.toggleUseUpscaling);
const isUsingUpscaling = useImageCreate((state) => state.isUsingUpscaling());
const toggleUseRandomSeed = useImageCreate((state) => state.toggleUseRandomSeed);
const isRandomSeed = useImageCreate((state) => state.isRandomSeed());
const toggleUseAutoSave = useImageCreate((state) => state.toggleUseAutoSave);
const isUseAutoSave = useImageCreate((state) => state.isUseAutoSave());
const toggleSoundEnabled = useImageCreate((state) => state.toggleSoundEnabled);
const isSoundEnabled = useImageCreate((state) => state.isSoundEnabled());
const use_upscale = useImageCreate((state) => state.getValueForRequestKey(('use_upscale')));
const show_only_filtered_image = useImageCreate((state) => state.getValueForRequestKey(('show_only_filtered_image')));
const seed = useImageCreate((state) => state.getValueForRequestKey(('seed')));
const width = useImageCreate((state) => state.getValueForRequestKey(('width')));
const num_outputs = useImageCreate((state) => state.getValueForRequestKey(('num_outputs')));
const height = useImageCreate((state) => state.getValueForRequestKey(('height')));
const steps = useImageCreate((state) => state.getValueForRequestKey(('num_inference_steps')));
const guidance_scale = useImageCreate((state) => state.getValueForRequestKey(('guidance_scale')));
const prompt_strength = useImageCreate((state) => state.getValueForRequestKey(('prompt_strength')));
const save_to_disk_path = useImageCreate((state) => state.getValueForRequestKey(('save_to_disk_path')));
const turbo = useImageCreate((state) => state.getValueForRequestKey(('turbo')));
const use_cpu = useImageCreate((state) => state.getValueForRequestKey(('use_cpu')));
const use_full_precision = useImageCreate((state) => state.getValueForRequestKey(('use_full_precision')));
return (
<ul id="editor-settings-entries">
{/*IMAGE CORRECTION */}
<li>
<label>
<input
type="checkbox"
checked={isUsingFaceCorrection}
onChange={(e) =>
toggleUseFaceCorrection()
}
/>
Fix incorrect faces and eyes (uses GFPGAN)
</label>
</li>
<li>
<label>
<input
type="checkbox"
checked={isUsingUpscaling}
onChange={(e) =>
toggleUseUpscaling()
}
/>
Upscale the image to 4x resolution using
<select id="upscale_model" name="upscale_model" disabled={!isUsingUpscaling} value={use_upscale}>
<option value="RealESRGAN_x4plus">RealESRGAN_x4plus</option>
<option value="RealESRGAN_x4plus_anime_6B">RealESRGAN_x4plus_anime_6B</option>
</select>
</label>
</li>
<li>
<label>
<input
type="checkbox"
checked={show_only_filtered_image}
onChange={(e) =>
setRequestOption("show_only_filtered_image", e.target.checked)
}
/>
Show only filtered image
</label>
</li>
{/* SEED */}
<li>
<label>
Seed:
<input
size={10}
value={seed}
onChange={(e) =>
setRequestOption("seed", e.target.value)
}
disabled={isRandomSeed}
placeholder="random"
/>
</label>
<label>
<input
type="checkbox"
checked={isRandomSeed}
onChange={(e) =>
toggleUseRandomSeed()
}
/>{" "}
Random Image
</label>
</li>
{/* COUNT */}
<li>
<label>
Number of images to make:{" "}
<input
type="number"
value={requestCount}
onChange={(e) =>
setRequestCount(parseInt(e.target.value, 10))
}
size={4}
/>
</label>
<label>
Generate in parallel:
<input
type="number"
value={num_outputs}
onChange={(e) =>
setRequestOption("num_outputs", parseInt(e.target.value, 10))
}
size={4}
/>
</label>
</li>
{/* DIMENTIONS */}
<li>
<label>
Width:
<select
value={width}
onChange={(e) =>
setRequestOption("width", e.target.value)
}
>
{IMAGE_DIMENSIONS.map((dimension) => (
<option
key={"width-option_" + dimension.value}
value={dimension.value}
>
{dimension.label}
</option>
))}
</select>
</label>
</li>
<li>
<label>
Height:
<select
value={height}
onChange={(e) =>
setRequestOption("height", e.target.value)
}
>
{IMAGE_DIMENSIONS.map((dimension) => (
<option
key={"height-option_" + dimension.value}
value={dimension.value}
>
{dimension.label}
</option>
))}
</select>
</label>
</li>
{/* STEPS */}
<li>
<label>
Number of inference steps:{" "}
<input
value={steps}
onChange={(e) => {
console.log('ON CHNAGE num_inference_steps', e.target.value)
setRequestOption("num_inference_steps", e.target.value)
}}
size={4}
/>
</label>
</li>
{/* GUIDANCE SCALE */}
<li>
<label>
Guidance Scale:
<input
value={guidance_scale}
onChange={(e) =>
setRequestOption("guidance_scale", e.target.value)
}
type="range"
min="0"
max="20"
step=".1"
/>
</label>
<span>{guidance_scale}</span>
</li>
{/* PROMPT STRENGTH */}
<li className="mb-4">
<label>
Prompt Strength:{" "}
<input
value={prompt_strength}
onChange={(e) =>
// setImageOptions({ promptStrength: Number(e.target.value) })
setRequestOption("prompt_strength", e.target.value)
}
type="range"
min="0"
max="1"
step=".05"
/>
</label>
<span>{prompt_strength}</span>
</li>
{/* AUTO SAVE */}
<li>
<label>
<input
checked={isUseAutoSave}
onChange={(e) =>
toggleUseAutoSave()
}
type="checkbox"
/>
Automatically save to{" "}
</label>
<label>
<input
value={save_to_disk_path}
onChange={(e) =>
setRequestOption("save_to_disk_path", e.target.value)
}
size={40}
disabled={!isUseAutoSave}
/>
<span className="visually-hidden">
Path on disk where images will be saved
</span>
</label>
</li>
{/* SOUND */}
<li>
<label>
<input
checked={isSoundEnabled}
onChange={(e) =>
toggleSoundEnabled()
}
type="checkbox"
/>
Play sound on task completion
</label>
</li>
{/* GENERATE */}
<li>
<label>
<input
checked={turbo}
onChange={(e) =>
setRequestOption("turbo", e.target.checked)
}
type="checkbox"
/>
Turbo mode (generates images faster, but uses an additional 1 GB
of GPU memory)
</label>
</li>
<li>
<label>
<input
type="checkbox"
checked={use_cpu}
onChange={(e) =>
setRequestOption("use_cpu", e.target.checked)
}
/>
Use CPU instead of GPU (warning: this will be *very* slow)
</label>
</li>
<li>
<label>
<input
checked={use_full_precision}
onChange={(e) =>
setRequestOption("use_full_precision", e.target.checked)
}
type="checkbox"
/>
Use full precision (for GPU-only. warning: this will consume more
VRAM)
</label>
</li>
</ul>
)
}
// {/* <!-- <li><input id="allow_nsfw" name="allow_nsfw" type="checkbox"/> <label htmlFor="allow_nsfw">Allow NSFW Content (You confirm you are above 18 years of age)</label></li> --> */}
export default function AdvancedSettings() {
const advancedSettingsIsOpen = useImageCreate(
(state) => state.uiOptions.advancedSettingsIsOpen
);
const toggleAdvancedSettingsIsOpen = useImageCreate(
(state) => state.toggleAdvancedSettingsIsOpen
);
return (
<div className="panel-box">
<button
type="button"
onClick={toggleAdvancedSettingsIsOpen}
className="panel-box-toggle-btn"
>
<h4>Advanced Settings</h4>
</button>
{advancedSettingsIsOpen && <SettingsList/>}
</div>
);
}

View File

@ -0,0 +1,48 @@
.panel-box-toggle-btn {
display: block;
width: 100%;
text-align: left;
background-color: transparent;
color: #fff;
border: 0 none;
cursor: pointer;
}
.selected-tags {
margin: 10px 0;
}
.selected-tags ul {
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
}
li {
list-style: none;
}
.modifier-list{
display: flex;
flex-wrap: wrap;
margin: 0;
padding: 0;
}
.modifierTag {
display: inline-block;
padding: 6px;
background-color: rgb(38, 77, 141);
color: #fff;
border-radius: 5px;
margin: 5px;
}
.modifierTag.selected {
background-color: rgb(131, 11, 121);
}
.modifierTag p {
margin: 0;
}

View File

@ -0,0 +1,96 @@
import React, {useEffect, useState} from "react";
import { useQuery } from "@tanstack/react-query";
import { loadModifications } from "../../../api";
import { useImageCreate } from "../../../store/imageCreateStore";
import ModifierTag from "../modierTag";
type ModifierListProps = {
tags: string[];
}
function ModifierList({tags}: ModifierListProps) {
const setImageOptions = useImageCreate((state) => state.setImageOptions);
const imageOptions = useImageCreate((state) => state.imageOptions);
return(
<ul className="modifier-list">
{tags.map((tag) => (
<li key={tag} >
<ModifierTag name={tag} />
</li>
))}
</ul>
)
}
type ModifierGroupingProps = {
title: string;
tags: string[];
};
function ModifierGrouping({title, tags}: ModifierGroupingProps) {
// doing this localy for now, but could move to a store
// and persist if we wanted to
const [isExpanded, setIsExpanded] = useState(false);
// console.log('grouping', title, tags)
const _toggleExpand = () => {
// console.log('toggle expand')
setIsExpanded(!isExpanded);
};
return (
<div className="modifier-grouping">
<div className="modifier-grouping-header" onClick={_toggleExpand}>
<h5>{title}</h5>
</div>
{isExpanded && <ModifierList tags={tags} />}
</div>
);
}
export default function ImageModifers() {
const {status, data} = useQuery(["modifications"], loadModifications);
const imageModifierIsOpen = useImageCreate(
(state) => state.uiOptions.imageModifierIsOpen
);
const toggleImageModifiersIsOpen = useImageCreate(
(state) => state.toggleImageModifiersIsOpen
);
// useEffect(() => {
// console.log("imageModifers", status, data);
// }, [status, data]);
const handleClick = () => {
// debugger;
toggleImageModifiersIsOpen();
};
return (
<div className="panel-box">
<button
type="button"
onClick={handleClick}
className="panel-box-toggle-btn"
>
{/* TODO: swap this manual collapse stuff out for some UI component? */}
<h4>Image Modifiers (art styles, tags, ect)</h4>
</button>
{/* @ts-ignore */}
{imageModifierIsOpen && data.map((item, index) => {
return (
<ModifierGrouping key={item[0]} title={item[0]} tags={item[1]}/>
)
})}
</div>
);
}

View File

@ -0,0 +1,86 @@
import React, { ChangeEvent } from "react";
import MakeButton from "./makeButton";
import AdvancedSettings from "./advancedSettings";
import ImageModifiers from "./imageModifiers";
import ModifierTag from "./modierTag";
import { useImageCreate } from "../../store/imageCreateStore";
import './creationPanel.css';
export default function CreationPanel() {
const promptText = useImageCreate((state) => state.getValueForRequestKey("prompt"));
const init_image = useImageCreate((state) => state.getValueForRequestKey("init_image"));
const setRequestOption = useImageCreate((state) => state.setRequestOptions);
const selectedtags = useImageCreate((state) => state.selectedTags());
const handlePromptChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
setRequestOption("prompt", event.target.value);
};
const _handleFileSelect = (event: ChangeEvent<HTMLInputElement>) => {
//console.log("file select", event);
const file = event.target.files[0];
// console.log("file", file);
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
if (e.target) {
debugger;
setRequestOption("init_image", e.target.result);
}
};
reader.readAsDataURL(file);
}
};
return (
<div className="create-panel">
<div className="basic-create">
<div className="prompt">
<p>Prompt </p>
<textarea value={promptText} onChange={handlePromptChange}></textarea>
</div>
{/* <div className="seed-image">
<p>Seed Image</p>
<input type="file" accept="image/*" />
</div> */}
<div id="editor-inputs-init-image" className="row">
<label ><b>Initial Image:</b> (optional) </label>
<input id="init_image" name="init_image" type="file" onChange={_handleFileSelect}/><br/>
<div id="init_image_preview" className="image_preview">
{ init_image &&
<img id="init_image_preview" src={init_image} width="100" height="100" />
}
<button id="init_image_clear" className="image_clear_btn">X</button>
</div>
</div>
<MakeButton></MakeButton>
<div className="selected-tags">
<p>Active Tags</p>
<ul>
{selectedtags.map((tag) => (
<li key={tag}>
<ModifierTag name={tag}></ModifierTag>
</li>
))}
</ul>
</div>
</div>
<div className="advanced-create">
<AdvancedSettings></AdvancedSettings>
<ImageModifiers></ImageModifiers>
</div>
</div>
);
}

View File

@ -0,0 +1,24 @@
import React, {useEffect, useState}from "react";
import { useImageCreate } from "../../../store/imageCreateStore";
// import { useImageDisplay } from "../../../store/imageDisplayStore";
import { useImageQueue } from "../../../store/imageQueueStore";
// import { doMakeImage } from "../../../api";
import {v4 as uuidv4} from 'uuid';
export default function MakeButton() {
const builtRequest = useImageCreate((state) => state.builtRequest);
const addNewImage = useImageQueue((state) => state.addNewImage);
const makeImage = () => {
// todo turn this into a loop and adjust the parallel count
//
const req = builtRequest();
addNewImage(uuidv4(), req)
};
return (
<button onClick={makeImage}>Make</button>
);
}

View File

@ -0,0 +1,25 @@
import React from "react";
import { useImageCreate } from "../../../store/imageCreateStore";
type ModifierTagProps = {
name: string;
}
export default function ModifierTag({name}: ModifierTagProps) {
const hasTag = useImageCreate((state) => state.hasTag(name)) ? "selected" : "";
const toggleTag = useImageCreate((state) => state.toggleTag);
console.log('has tag', hasTag)
const _toggleTag = () => {
toggleTag(name);
};
return (
<div className={"modifierTag " + hasTag} onClick={_toggleTag}>
<p>{name}</p>
</div>
);
}

View File

@ -0,0 +1,18 @@
export const CompletedImages = () => {
return (
<div className="completed-images">
<h1>Completed Images</h1>
</div>
);
// const { data } = useQuery("completedImages", getCompletedImages);
// return (
// <div className="completed-images">
// <h2>Completed Images</h2>
// <div className="completed-images-list">
// {data?.map((image) => (
// <GeneratedImage imageData={image.data} key={image.id} />
// ))}
// </div>
// </div>
// );
}

View File

@ -0,0 +1,50 @@
import React, { useEffect, useState } from "react";
import { useImageQueue } from "../../../store/imageQueueStore";
import { doMakeImage, MakeImageKey } from "../../../api";
import { useQuery } from "@tanstack/react-query";
import GeneratedImage from "../generatedImage";
export default function CurrentImage() {
const [imageData, setImageData] = useState(null);
// @ts-ignore
const {id, options} = useImageQueue((state) => state.firstInQueue());
console.log('CurrentImage id', id)
const removeFirstInQueue = useImageQueue((state) => state.removeFirstInQueue);
const { status, data } = useQuery(
[MakeImageKey, id],
() => doMakeImage(options),
{
enabled: void 0 !== id,
}
);
useEffect(() => {
// query is done
if(status === 'success') {
console.log("success");
// check to make sure that the image was created
if(data.status === 'succeeded') {
console.log("succeeded");
setImageData(data.output[0].data);
removeFirstInQueue();
}
}
}, [status, data, removeFirstInQueue]);
return (
<div className="current-display">
<h1>Current Image</h1>
{imageData && <GeneratedImage imageData={imageData} />}
</div>
);
};

View File

@ -0,0 +1,53 @@
import React from "react";
import { useImageCreate } from "../../../store/imageCreateStore";
export default function GeneratedImage({ imageData }: { imageData: string }) {
const setRequestOption = useImageCreate((state) => state.setRequestOptions);
const _handleSave = () => {
const link = document.createElement("a");
link.download = "image.png";
link.href = imageData;
link.click();
};
const _handleUseAsInput = () => {
console.log(" TODO : use as input");
setRequestOption("init_image", imageData);
// initImageSelector.value = null
// initImagePreview.src = imgBody
// imgUseBtn.addEventListener('click', function() {
// initImageSelector.value = null
// initImagePreview.src = imgBody
// initImagePreviewContainer.style.display = 'block'
// promptStrengthContainer.style.display = 'block'
// // maskSetting.style.display = 'block'
// randomSeedField.checked = false
// seedField.value = seed
// seedField.disabled = false
// })
}
return (
<div className="generated-image">
<p>Your image</p>
<img src={imageData} alt="generated" />
<button onClick={_handleSave}>
Save
</button>
<button onClick={_handleUseAsInput}>
Use as Input
</button>
</div>
);
}

View File

@ -0,0 +1,78 @@
import React, {useEffect, useState} from "react";
import { useImageQueue } from "../../store/imageQueueStore";
import { ImageRequest } from '../../store/imageCreateStore';
import { useQueryClient } from '@tanstack/react-query'
import { MakeImageKey } from "../../api";
import CurrentImage from "./currentImage";
import GeneratedImage from "./generatedImage";
type CompletedImagesType = {
id: string;
data: string;
}
export default function DisplayPanel() {
const queryClient = useQueryClient();
const [completedImages, setCompletedImages] = useState<CompletedImagesType[]>([]);
const completedIds = useImageQueue((state) => state.completedImageIds);
useEffect(() => {
const testReq = {} as ImageRequest;
const completedQueries = completedIds.map((id) => {
const imageData = queryClient.getQueryData([MakeImageKey,id])
return imageData;
});
console.log('completedQueries', completedQueries);
if (completedQueries.length > 0) {
// map the completedImagesto a new array
// and then set the state
const temp = completedQueries.map((query, index ) => {
// debugger;
if(void 0 !== query) {
return query.output.map((data)=>{
return {id: `${completedIds[index]}-${data.seed}`, data: data.data}
})
}
}).flat().reverse();
setCompletedImages(temp);
}
else {
setCompletedImages([]);
}
},[setCompletedImages, queryClient, completedIds]);
return (
<div className="display-panel">
<h1>Display Panel</h1>
<div>
<CurrentImage />
{completedImages.map((image, index) => {
if(index == 0){
return null;
}
if(void 0 !== image) {
return <GeneratedImage key={image.id} imageData={image.data} />;
}
else {
console.warn('image is undefined', image, index);
return null;
}
})}
</div>
</div>
);
};

View File

@ -0,0 +1,8 @@
.footer-display {
color: #ffffff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

View File

@ -0,0 +1,18 @@
import React from "react";
import './footerDisplay.css';
export default function FooterDisplay() {
return (
<div id="footer" className="panel-box">
<p>If you found this project useful and want to help keep it alive, please <a href="https://ko-fi.com/cmdr2_stablediffusion_ui" target="_blank"><img src="./kofi.png" id="coffeeButton"/></a> to help cover the cost of development and maintenance! Thank you for your support!</p>
<p>Please feel free to join the <a href="https://discord.com/invite/u9yhsFmEkB" target="_blank">discord community</a> or <a href="https://github.com/cmdr2/stable-diffusion-ui/issues" target="_blank">file an issue</a> if you have any problems or suggestions in using this interface.</p>
<div id="footer-legal">
<p><b>Disclaimer:</b> The authors of this project are not responsible for any content generated using this interface.</p>
<p>This license of this software forbids you from sharing any content that violates any laws, produce any harm to a person, disseminate any personal information that would be meant for harm, <br/>spread misinformation and target vulnerable groups. For the full list of restrictions please read <a href="https://github.com/cmdr2/stable-diffusion-ui/blob/main/LICENSE" target="_blank">the license</a>.</p>
<p>By using this software, you consent to the terms and conditions of the license.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,10 @@
.header-display {
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
}
.status-display {
margin-left: 10px;
}

View File

@ -0,0 +1,14 @@
import React from "react";
import StatusDisplay from "./statusDisplay";
import './headerDisplay.css';
export default function HeaderDisplay() {
return (
<div className="header-display">
<h1>Stable Diffusion UI v2.1.0</h1>
<StatusDisplay className="status-display"></StatusDisplay>
</div>
);
};

View File

@ -0,0 +1,59 @@
import React, {useEffect, useState} from 'react'
import { useQuery } from '@tanstack/react-query';
import { healthPing, HEALTH_PING_INTERVAL } from '../../../api';
const startingMessage = 'Stable Diffusion is starting...';
const successMessage = 'Stable Diffusion is ready to use!';
const errorMessage = 'Stable Diffusion is not running!';
import './statusDisplay.css';
export default function StatusDisplay({className}: {className?: string}) {
const [statusMessage, setStatusMessage] = useState(startingMessage);
const [statusClass, setStatusClass] = useState('starting');
// doing this here for the time being, to show the data getting loaded
// but this will be moved to the status display when it is created
const {status, data} = useQuery(['health'], healthPing, {refetchInterval: HEALTH_PING_INTERVAL});
useEffect(() => {
console.log('health data', data);
}, [data]);
// const data = {};
useEffect(() => {
console.log('status', status);
if (status === 'loading') {
setStatusMessage(startingMessage);
setStatusClass('starting');
}
else if (status === 'error') {
setStatusMessage(errorMessage);
setStatusClass('error');
}
else if (status === 'success') {
if(data[0] === 'OK') {
setStatusMessage(successMessage);
setStatusClass('success');
}
else {
setStatusMessage(errorMessage);
setStatusClass('error');
}
}
}, [status, data]);
return (
<>
{/* alittle hacky but joins the class names, will probably need a better css in js solution or tailwinds*/}
<p className={[statusClass, className].join(' ')}>{statusMessage}</p>
</>
);
};

View File

@ -0,0 +1,11 @@
.starting {
color: #f0ad4e;
}
.error {
color: #d9534f;
}
.success {
color: #5cb85c;
}

View File

@ -0,0 +1,18 @@
body {
margin: 0;
min-width: 320px;
min-height: 100vh;
}
#root {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
/* pointer-events: none; */
/* this are used while we still have the original app code in the index.html */
/* display: none; */
z-index: 1;
}

View File

@ -0,0 +1,37 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import {
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { enableMapSet } from 'immer';
import App from './App'
import './index.css'
const queryClient = new QueryClient(
{
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
refetchOnReconnect : false,
refetchOnMount : false,
staleTime: Infinity,
},
},
}
);
enableMapSet();
// application entry point
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={true} />
</QueryClientProvider>
</React.StrictMode>
)

View File

@ -0,0 +1,151 @@
[
[
"Drawing Style",
[
"Cel Shading",
"Children's Drawing",
"Crosshatch",
"Detailed and Intricate",
"Doodle",
"Dot Art",
"Line Art",
"Sketch"
]
],
[
"Visual Style",
[
"2D",
"8-bit",
"16-bit",
"Anaglyph",
"Anime",
"CGI",
"Cartoon",
"Comic Book",
"Concept Art",
"Digital Art",
"Fantasy",
"Graphic Novel",
"Hard Edge Painting",
"Hydrodipped",
"Lithography",
"Manga",
"Modern Art",
"Mosaic",
"Mural",
"Photo",
"Realistic",
"Street Art",
"Visual Novel",
"Watercolor"
]
],
[
"Pen",
[
"Chalk",
"Colored Pencil",
"Graphite",
"Ink",
"Oil Paint",
"Pastel Art"
]
],
[
"Carving and Etching",
[
"Etching",
"Linocut",
"Paper Model",
"Paper-Mache",
"Papercutting",
"Pyrography",
"Wood-Carving"
]
],
[
"Camera",
[
"Aerial View",
"Canon50",
"Cinematic",
"Close-up",
"Color Grading",
"Dramatic",
"Film Grain",
"Fisheye Lens",
"Glamor Shot",
"Golden Hour",
"HD",
"Lens Flare",
"Macro",
"Polaroid",
"Vintage",
"War Photography",
"White Balance",
"Wildlife Photography"
]
],
[
"Color",
[
"Beautiful Lighting",
"Colorful",
"Dynamic Lighting",
"Electric Colors",
"Infrared",
"Synthwave",
"Warm Color Palette"
]
],
[
"Emotions",
[
"Angry",
"Disgusted",
"Embarrassed",
"Evil",
"Excited",
"Fear",
"Happy",
"Lonely",
"Sad",
"Surprised"
]
],
[
"Style of an artist or community",
[
"Artstation",
"by Agnes Lawrence Pelton",
"by Akihito Yoshida",
"by Andy Warhol",
"by Artgerm",
"by Asaf Hanuka",
"by Aubrey Beardsley",
"by Banksy",
"by Ben Enwonwu",
"by Caravaggio Michelangelo Merisi",
"by David Mann",
"by Frida Kahlo",
"by H.R. Giger",
"by Hayao Miyazaki",
"by Ivan Shishkin",
"by Johannes Vermeer",
"by John William Waterhouse",
"by Katsushika Hokusai",
"by Ko Young Hoon",
"by Leonardo da Vinci",
"by Lisa Frank",
"by Mahmoud Saïd",
"by Mark Brooks",
"by Pablo Picasso",
"by Richard Dadd",
"by Salvador Dalí",
"by Tivadar Csontváry Kosztka",
"by Yoshitaka Amano",
"by wlop"
]
]
]

View File

@ -0,0 +1,258 @@
import create from 'zustand';
import produce from 'immer';
import { devtools } from 'zustand/middleware'
import { useRandomSeed } from '../utils';
export type ImageCreationUiOptions = {
advancedSettingsIsOpen: boolean;
imageModifierIsOpen: boolean;
isCheckedUseUpscaling: boolean;
isCheckUseFaceCorrection: boolean;
isUseRandomSeed: boolean;
isUseAutoSave: boolean;
isSoundEnabled: boolean;
}
export type ImageRequest = {
prompt: string;
seed: number;
num_outputs: number;
num_inference_steps: number;
guidance_scale: number
width: 128 | 192 | 256 | 320 | 384 | 448 | 512 | 576 | 640 | 704 | 768 | 832 | 896 | 960 | 1024;
height: 128 | 192 | 256 | 320 | 384 | 448 | 512 | 576 | 640 | 704 | 768 | 832 | 896 | 960 | 1024;
// allow_nsfw: boolean;
turbo: boolean;
use_cpu: boolean;
use_full_precision: boolean;
save_to_disk_path: null | string;
use_face_correction: null | 'GFPGANv1.3';
use_upscale: null| 'RealESRGAN_x4plus' | 'RealESRGAN_x4plus_anime_6B';
show_only_filtered_image: boolean;
init_image: undefined | string;
prompt_strength: undefined | number;
};
interface ImageCreateState {
requestCount: number;
requestOptions: ImageRequest;
tags: string[];
setRequestCount: (count: number) => void;
setRequestOptions: (key: keyof ImageRequest, value: any) => void;
getValueForRequestKey: (key: keyof ImageRequest) => any;
toggleTag: (tag: string) => void;
hasTag: (tag: string) => boolean;
selectedTags:() => string[]
builtRequest: () => ImageRequest;
uiOptions: ImageCreationUiOptions;
toggleAdvancedSettingsIsOpen: () => void;
toggleImageModifiersIsOpen: () => void;
toggleUseUpscaling: () => void;
isUsingUpscaling: () => boolean;
toggleUseFaceCorrection: () => void;
isUsingFaceCorrection: () => boolean;
toggleUseRandomSeed: () => void;
isRandomSeed: () => boolean;
toggleUseAutoSave: () => void;
isUseAutoSave: () => boolean;
toggleSoundEnabled: () => void;
isSoundEnabled: () => boolean;
}
// devtools breaks TS
// @ts-ignore
export const useImageCreate = create<ImageCreateState>(devtools((set, get) => ({
requestCount: 1,
requestOptions:{
prompt: 'a photograph of an astronaut riding a horse',
seed: useRandomSeed(),
num_outputs: 1,
num_inference_steps: 50,
guidance_scale: 7.5,
width: 512,
height: 512,
prompt_strength: 0.8,
// allow_nsfw: false,
turbo: true,
use_cpu: false,
use_full_precision: true,
save_to_disk_path: 'null',
use_face_correction: null,
use_upscale: 'RealESRGAN_x4plus',
show_only_filtered_image: false,
} as ImageRequest,
tags: [] as string[],
setRequestCount: (count: number) => set(produce((state) => {
state.requestCount = count;
})),
setRequestOptions: (key: keyof ImageRequest, value: any) => {
set( produce((state) => {
state.requestOptions[key] = value;
}))
},
getValueForRequestKey: (key: keyof ImageRequest) => {
return get().requestOptions[key];
},
toggleTag: (tag: string) => {
set( produce((state) => {
const index = state.tags.indexOf(tag);
if (index > -1) {
state.tags.splice(index, 1);
} else {
state.tags.push(tag);
}
}))
},
hasTag: (tag:string) => {
return get().tags.indexOf(tag) > -1;
},
selectedTags: () => {
return get().tags;
},
// the request body to send to the server
// this is a computed value, just adding the tags to the request
builtRequest: () => {
console.log('builtRequest');
const state = get();
const requestOptions = state.requestOptions;
const tags = state.tags;
// join all the tags with a comma and add it to the prompt
const prompt = `${requestOptions.prompt} ${tags.join(',')}`;
console.log('builtRequest return1');
const request = {
...requestOptions,
prompt
}
// if we arent using auto save clear the save path
if(!state.uiOptions.isUseAutoSave){
// maybe this is "None" ?
// TODO check this
request.save_to_disk_path = null;
}
console.log('builtRequest return2');
// if we arent using face correction clear the face correction
if(!state.uiOptions.isCheckUseFaceCorrection){
request.use_face_correction = null;
}
console.log('builtRequest return3');
// if we arent using upscaling clear the upscaling
if(!state.uiOptions.isCheckedUseUpscaling){
request.use_upscale = null;
}
// const request = {
// ...requestOptions,
// prompt
// }
console.log('builtRequest return last');
return request;
},
uiOptions: {
// TODO proper persistence of all UI / user settings centrally somewhere?
// localStorage.getItem('ui:advancedSettingsIsOpen') === 'true',
advancedSettingsIsOpen:false,
imageModifierIsOpen: false,
isCheckedUseUpscaling: false,
isCheckUseFaceCorrection: true,
isUseRandomSeed: true,
isUseAutoSave: false,
isSoundEnabled: true,
},
toggleAdvancedSettingsIsOpen: () => {
set( produce((state) => {
state.uiOptions.advancedSettingsIsOpen = !state.uiOptions.advancedSettingsIsOpen;
localStorage.setItem('ui:advancedSettingsIsOpen', state.uiOptions.advancedSettingsIsOpen);
}))
},
toggleImageModifiersIsOpen: () => {
set( produce((state) => {
state.uiOptions.imageModifierIsOpen = !state.uiOptions.imageModifierIsOpen;
localStorage.setItem('ui:imageModifierIsOpen', state.uiOptions.imageModifierIsOpen);
}))
},
toggleUseUpscaling: () => {
set( produce((state) => {
state.uiOptions.isCheckedUseUpscaling = !state.uiOptions.isCheckedUseUpscaling;
state.requestOptions.use_upscale = state.uiOptions.isCheckedUseUpscaling ? 'RealESRGAN_x4plus' : null;
localStorage.setItem('ui:isCheckedUseUpscaling', state.uiOptions.isCheckedUseUpscaling);
}))
},
isUsingUpscaling: () => {
return get().uiOptions.isCheckedUseUpscaling;
},
toggleUseFaceCorrection: () => {
set( produce((state) => {
state.uiOptions.isCheckUseFaceCorrection = !state.uiOptions.isCheckUseFaceCorrection;
state.use_face_correction = state.uiOptions.isCheckUseFaceCorrection ? 'GFPGANv1.3' : null;
localStorage.setItem('ui:isCheckUseFaceCorrection', state.uiOptions.isCheckUseFaceCorrection);
}))
},
isUsingFaceCorrection: () => {
return get().uiOptions.isCheckUseFaceCorrection;
},
toggleUseRandomSeed: () => {
set( produce((state) => {
state.uiOptions.isUseRandomSeed = !state.uiOptions.isUseRandomSeed;
state.requestOptions.seed = state.uiOptions.isUseRandomSeed ? useRandomSeed() : state.requestOptions.seed;
localStorage.setItem('ui:isUseRandomSeed', state.uiOptions.isUseRandomSeed);
}))
},
isRandomSeed: () => {
return get().uiOptions.isUseRandomSeed;
},
toggleUseAutoSave: () => {
//isUseAutoSave
//save_to_disk_path
set( produce((state) => {
state.uiOptions.isUseAutoSave = !state.uiOptions.isUseAutoSave;
localStorage.setItem('ui:isUseAutoSave', state.uiOptions.isUseAutoSave);
}))
},
isUseAutoSave: () => {
return get().uiOptions.isUseAutoSave;
},
toggleSoundEnabled: () => {
set( produce((state) => {
state.uiOptions.isSoundEnabled = !state.uiOptions.isSoundEnabled;
//localStorage.setItem('ui:isSoundEnabled', state.uiOptions.isSoundEnabled);
}))
},
isSoundEnabled: () => {
return get().uiOptions.isSoundEnabled;
},
})));

View File

@ -0,0 +1,22 @@
import create from 'zustand';
import produce from 'immer';
// import { devtools } from 'zustand/middleware'
interface ImageDisplayState {
imageOptions: Map<string, any>;
currentImage: object | null;
addNewImage: (ImageData: string, imageOptions: any) => void
}
export const useImageDisplay = create<ImageDisplayState>((set) => ({
imageOptions: new Map<string, any>(),
currentImage: null,
// use produce to make sure we don't mutate state
addNewImage: (ImageData: string, imageOptions: any) => {
set( produce((state) => {
state.currentImage = { display: ImageData, options: imageOptions };
state.images.set(ImageData, imageOptions)
}));
}
}));

View File

@ -0,0 +1,48 @@
import create from 'zustand';
import produce from 'immer';
import { useRandomSeed } from '../utils';
import { imageOptions } from './imageCreateStore';
interface ImageQueueState {
images : imageOptions[];
completedImageIds: string[];
addNewImage: (id:string, imageOptions: imageOptions) => void
hasQueuedImages: () => boolean;
firstInQueue: () => imageOptions | [];
removeFirstInQueue: () => void;
}
// figure out why TS is complaining about this
// @ts-ignore
export const useImageQueue = create<ImageQueueState>((set, get) => ({
images: new Array(),
completedImageIds: new Array(),
// use produce to make sure we don't mutate state
addNewImage: (id: string, imageOptions: any) => {
set( produce((state) => {
let { seed } = imageOptions;
if (imageOptions.isSeedRandom) {
seed = useRandomSeed();
}
state.images.push({ id, options: {...imageOptions, seed} });
}));
},
hasQueuedImages: () => {
return get().images.length > 0;
},
firstInQueue: () => {
return get().images[0] as imageOptions || [];
},
removeFirstInQueue: () => {
set( produce((state) => {
const image = state.images.shift();
state.completedImageIds.push(image.id);
}));
}
}));

View File

@ -0,0 +1,3 @@
export function useRandomSeed(){
return Math.floor(Math.random() * 10000);
};

View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@ -0,0 +1,24 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
port: 9001,
},
build: {
// make sure everythign is in the same directory
outDir: '../dist',
rollupOptions: {
output: {
// dont hash the file names
// maybe once we update the python server?
entryFileNames: `[name].js`,
chunkFileNames: `[name].js`,
assetFileNames: `[name].[ext]`
}
}
},
})

BIN
ui/frontend/dist/ding.mp3 vendored Normal file

Binary file not shown.

1
ui/frontend/dist/index.css vendored Normal file
View File

@ -0,0 +1 @@
.App{position:relative;width:100%;height:100%;pointer-events:auto;display:grid;background-color:#202124;grid-template-columns:360px 1fr;grid-template-rows:100px 1fr 300px;grid-template-areas:"header header header" "create display display" "footer footer footer"}@media screen and (max-width: 768px){.App{grid-template-columns:1fr;grid-template-rows:100px 1fr 1fr 300px;grid-template-areas:"header" "create" "display" "footer"}}.header-layout{grid-area:header}.create-layout{grid-area:create}.display-layout{grid-area:display}.footer-layout{grid-area:footer}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.mb-4{margin-bottom:1rem}.starting{color:#f0ad4e}.error{color:#d9534f}.success{color:#5cb85c}.header-display{color:#fff;display:flex;align-items:center;justify-content:center}.status-display{margin-left:10px}input[size="4"]{width:4.5rem}.panel-box-toggle-btn{display:block;width:100%;text-align:left;background-color:transparent;color:#fff;border:0 none;cursor:pointer}.selected-tags{margin:10px 0}.selected-tags ul{margin:0;padding:0;display:flex;flex-wrap:wrap}li{list-style:none}.modifier-list{display:flex;flex-wrap:wrap;margin:0;padding:0}.modifierTag{display:inline-block;padding:6px;background-color:#264d8d;color:#fff;border-radius:5px;margin:5px}.modifierTag.selected{background-color:#830b79}.modifierTag p{margin:0}.footer-display{color:#fff;display:flex;flex-direction:column;align-items:center;justify-content:center}body{margin:0;min-width:320px;min-height:100vh}#root{position:absolute;top:0;left:0;width:100vw;height:100vh;z-index:1}

1265
ui/frontend/dist/index.html vendored Normal file

File diff suppressed because it is too large Load Diff

74
ui/frontend/dist/index.js vendored Normal file

File diff suppressed because one or more lines are too long

BIN
ui/frontend/dist/kofi.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

151
ui/frontend/dist/modifiers.json vendored Normal file
View File

@ -0,0 +1,151 @@
[
[
"Drawing Style",
[
"Cel Shading",
"Children's Drawing",
"Crosshatch",
"Detailed and Intricate",
"Doodle",
"Dot Art",
"Line Art",
"Sketch"
]
],
[
"Visual Style",
[
"2D",
"8-bit",
"16-bit",
"Anaglyph",
"Anime",
"CGI",
"Cartoon",
"Comic Book",
"Concept Art",
"Digital Art",
"Fantasy",
"Graphic Novel",
"Hard Edge Painting",
"Hydrodipped",
"Lithography",
"Manga",
"Modern Art",
"Mosaic",
"Mural",
"Photo",
"Realistic",
"Street Art",
"Visual Novel",
"Watercolor"
]
],
[
"Pen",
[
"Chalk",
"Colored Pencil",
"Graphite",
"Ink",
"Oil Paint",
"Pastel Art"
]
],
[
"Carving and Etching",
[
"Etching",
"Linocut",
"Paper Model",
"Paper-Mache",
"Papercutting",
"Pyrography",
"Wood-Carving"
]
],
[
"Camera",
[
"Aerial View",
"Canon50",
"Cinematic",
"Close-up",
"Color Grading",
"Dramatic",
"Film Grain",
"Fisheye Lens",
"Glamor Shot",
"Golden Hour",
"HD",
"Lens Flare",
"Macro",
"Polaroid",
"Vintage",
"War Photography",
"White Balance",
"Wildlife Photography"
]
],
[
"Color",
[
"Beautiful Lighting",
"Colorful",
"Dynamic Lighting",
"Electric Colors",
"Infrared",
"Synthwave",
"Warm Color Palette"
]
],
[
"Emotions",
[
"Angry",
"Disgusted",
"Embarrassed",
"Evil",
"Excited",
"Fear",
"Happy",
"Lonely",
"Sad",
"Surprised"
]
],
[
"Style of an artist or community",
[
"Artstation",
"by Agnes Lawrence Pelton",
"by Akihito Yoshida",
"by Andy Warhol",
"by Artgerm",
"by Asaf Hanuka",
"by Aubrey Beardsley",
"by Banksy",
"by Ben Enwonwu",
"by Caravaggio Michelangelo Merisi",
"by David Mann",
"by Frida Kahlo",
"by H.R. Giger",
"by Hayao Miyazaki",
"by Ivan Shishkin",
"by Johannes Vermeer",
"by John William Waterhouse",
"by Katsushika Hokusai",
"by Ko Young Hoon",
"by Leonardo da Vinci",
"by Lisa Frank",
"by Mahmoud Saïd",
"by Mark Brooks",
"by Pablo Picasso",
"by Richard Dadd",
"by Salvador Dalí",
"by Tivadar Csontváry Kosztka",
"by Yoshitaka Amano",
"by wlop"
]
]
]

View File

@ -18,11 +18,24 @@ from fastapi import FastAPI, HTTPException
from starlette.responses import FileResponse, StreamingResponse
from pydantic import BaseModel
import logging
# this is needed for development.
from fastapi.middleware.cors import CORSMiddleware
from sd_internal import Request, Response
app = FastAPI()
# we need to be able to run a local server for the UI (9001)
# and still be able to hit our python port (9000)
origins = ["*"]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
model_loaded = False
model_is_loading = False
@ -58,7 +71,18 @@ class SetAppConfigRequest(BaseModel):
@app.get('/')
def read_root():
headers = {"Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0"}
return FileResponse(os.path.join(SD_UI_DIR, 'index.html'), headers=headers)
#return FileResponse(os.path.join(SD_UI_DIR, 'index.html'), headers=headers)
return FileResponse(os.path.join(SD_UI_DIR,'frontend/dist/index.html'), headers=headers)
# then get the js files
@app.get('/index.js')
def read_scripts():
return FileResponse(os.path.join(SD_UI_DIR, 'frontend/dist/index.js'))
#then get the css files
@app.get('/index.css')
def read_styles():
return FileResponse(os.path.join(SD_UI_DIR, 'frontend/dist/index.css'))
@app.get('/ping')
async def ping():
@ -180,13 +204,15 @@ def getAppConfig():
print(traceback.format_exc())
return HTTPException(status_code=500, detail=str(e))
@app.get('/media/ding.mp3')
# moved these to the root for easier pathing
# TODO: change the vite config for public files
@app.get('/ding.mp3')
def read_ding():
return FileResponse(os.path.join(SD_UI_DIR, 'media/ding.mp3'))
return FileResponse(os.path.join(SD_UI_DIR, 'frontend/dist/ding.mp3'))
@app.get('/media/kofi.png')
@app.get('/kofi.png')
def read_modifiers():
return FileResponse(os.path.join(SD_UI_DIR, 'media/kofi.png'))
return FileResponse(os.path.join(SD_UI_DIR, 'frontend/dist/kofi.png'))
@app.get('/modifiers.json')
def read_modifiers():