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

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;
}