mirror of
https://github.com/easydiffusion/easydiffusion.git
synced 2024-11-22 00:03:20 +01:00
commit
1e3b4f9969
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -1 +0,0 @@
|
||||
* text=auto eol=lf
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -2,10 +2,4 @@ __pycache__
|
||||
installer
|
||||
installer.tar
|
||||
dist
|
||||
|
||||
# built code for the front end
|
||||
!/ui/frontend/dist
|
||||
ui/frontend/.idea/*
|
||||
ui/frontend/build_src/.idea/*
|
||||
|
||||
.idea/*
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
[![Discord Server](https://img.shields.io/discord/1014774730907209781?label=Discord)](https://discord.com/invite/u9yhsFmEkB) (for support, and development discussion) | [Troubleshooting guide for common problems](Troubleshooting.md)
|
||||
|
||||
️🔥🎉 **New!** Task Queue, Negative Prompt, Live Preview, More Samplers, In-Painting, Face Correction (GFPGAN) and Upscaling (RealESRGAN) have been added!
|
||||
️🔥🎉 **New!** Use Custom Weights, Task Queue, Negative Prompt, Live Preview, More Samplers, In-Painting, Face Correction (GFPGAN) and Upscaling (RealESRGAN) have been added!
|
||||
|
||||
This distribution currently uses Stable Diffusion 1.4. Once the model for 1.5 becomes publicly available, the model in this distribution will be updated.
|
||||
|
||||
@ -18,6 +18,7 @@ This distribution currently uses Stable Diffusion 1.4. Once the model for 1.5 be
|
||||
- **In-Painting**
|
||||
- **Live Preview**: See the image as the AI is drawing it
|
||||
- **Task Queue**: Queue up all your ideas, without waiting for the current task to finish
|
||||
- **Custom Weights**: Use your own `.ckpt` file, by placing it inside the `stable-diffusion` folder (rename it to `custom-model.ckpt`)
|
||||
- **Negative Prompt**: Specify aspects of the image to *remove*.
|
||||
- **Lots of Samplers:** ddim, plms, heun, euler, euler_a, dpm2, dpm2_a, lms
|
||||
- **Image Modifiers**: A library of *modifier tags* like *"Realistic"*, *"Pencil Sketch"*, *"ArtStation"* etc. Experiment with various styles quickly.
|
||||
|
@ -64,7 +64,7 @@ Users don't need to have the Anaconda Prompt installed to do this anymore, since
|
||||
5. Type `conda activate .\env` and press enter
|
||||
6. Type `python --version` and press enter. You should see 3.8.5.
|
||||
|
||||
**Windows:**
|
||||
**Linux:**
|
||||
1. Open the terminal
|
||||
2. Type `cd /path/to/stable-diffusion-ui` and press enter
|
||||
3. Type `installer/bin/activate` and press enter
|
||||
|
14
scripts/Developer Console.cmd
Normal file
14
scripts/Developer Console.cmd
Normal file
@ -0,0 +1,14 @@
|
||||
@echo off
|
||||
|
||||
echo "Opening Stable Diffusion UI - Developer Console.." & echo.
|
||||
|
||||
@call installer\Scripts\activate.bat
|
||||
|
||||
@call conda-unpack
|
||||
|
||||
@call conda --version
|
||||
@call git --version
|
||||
|
||||
@call conda activate .\stable-diffusion\env
|
||||
|
||||
cmd /k
|
17
scripts/developer_console.sh
Normal file
17
scripts/developer_console.sh
Normal file
@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ "$0" == "bash" ]; then
|
||||
echo "Opening Stable Diffusion UI - Developer Console.."
|
||||
echo ""
|
||||
|
||||
source installer/bin/activate
|
||||
|
||||
conda-unpack
|
||||
|
||||
conda --version
|
||||
git --version
|
||||
|
||||
conda activate ./stable-diffusion/env
|
||||
else
|
||||
bash --init-file open_dev_console.sh
|
||||
fi
|
@ -55,6 +55,7 @@ if "%update_branch%"=="" (
|
||||
@xcopy sd-ui-files\ui ui /s /i /Y
|
||||
@copy sd-ui-files\scripts\on_sd_start.bat scripts\ /Y
|
||||
@copy "sd-ui-files\scripts\Start Stable Diffusion UI.cmd" . /Y
|
||||
@copy "sd-ui-files\scripts\Developer Console.cmd" . /Y
|
||||
|
||||
@call scripts\on_sd_start.bat
|
||||
|
||||
|
@ -37,6 +37,7 @@ rm -rf ui
|
||||
cp -Rf sd-ui-files/ui .
|
||||
cp sd-ui-files/scripts/on_sd_start.sh scripts/
|
||||
cp sd-ui-files/scripts/start.sh .
|
||||
cp sd-ui-files/scripts/developer_console.sh .
|
||||
|
||||
./scripts/on_sd_start.sh
|
||||
|
||||
|
@ -5,6 +5,9 @@
|
||||
@REM Caution, this file will make your eyes and brain bleed. It's such an unholy mess.
|
||||
@REM Note to self: Please rewrite this in Python. For the sake of your own sanity.
|
||||
|
||||
@copy "sd-ui-files\scripts\Developer Console.cmd" . /Y
|
||||
if exist "Open Developer Console.cmd" del "Open Developer Console.cmd"
|
||||
|
||||
@call python -c "import os; import shutil; frm = 'sd-ui-files\\ui\\hotfix\\9c24e6cd9f499d02c4f21a033736dabd365962dc80fe3aeb57a8f85ea45a20a3.26fead7ea4f0f843f6eb4055dfd25693f1a71f3c6871b184042d4b126244e142'; dst = os.path.join(os.path.expanduser('~'), '.cache', 'huggingface', 'transformers', '9c24e6cd9f499d02c4f21a033736dabd365962dc80fe3aeb57a8f85ea45a20a3.26fead7ea4f0f843f6eb4055dfd25693f1a71f3c6871b184042d4b126244e142'); shutil.copyfile(frm, dst) if os.path.exists(dst) else print(''); print('Hotfixed broken JSON file from OpenAI');"
|
||||
|
||||
@>nul grep -c "sd_git_cloned" scripts\install_status.txt
|
||||
@ -163,6 +166,9 @@ call WHERE uvicorn > .tmp
|
||||
|
||||
|
||||
|
||||
if not exist "..\models\stable-diffusion" mkdir "..\models\stable-diffusion"
|
||||
echo. > "..\models\stable-diffusion\Put your custom ckpt files here.txt"
|
||||
|
||||
@if exist "sd-v1-4.ckpt" (
|
||||
for %%I in ("sd-v1-4.ckpt") do if "%%~zI" EQU "4265380512" (
|
||||
echo "Data files (weights) necessary for Stable Diffusion were already downloaded. Using the HuggingFace 4 GB Model."
|
||||
|
@ -4,6 +4,11 @@ cp sd-ui-files/scripts/on_env_start.sh scripts/
|
||||
|
||||
source installer/etc/profile.d/conda.sh
|
||||
|
||||
cp sd-ui-files/scripts/developer_console.sh .
|
||||
if [ -e "open_dev_console.sh" ]; then
|
||||
rm "open_dev_console.sh"
|
||||
fi
|
||||
|
||||
python -c "import os; import shutil; frm = 'sd-ui-files/ui/hotfix/9c24e6cd9f499d02c4f21a033736dabd365962dc80fe3aeb57a8f85ea45a20a3.26fead7ea4f0f843f6eb4055dfd25693f1a71f3c6871b184042d4b126244e142'; dst = os.path.join(os.path.expanduser('~'), '.cache', 'huggingface', 'transformers', '9c24e6cd9f499d02c4f21a033736dabd365962dc80fe3aeb57a8f85ea45a20a3.26fead7ea4f0f843f6eb4055dfd25693f1a71f3c6871b184042d4b126244e142'); shutil.copyfile(frm, dst) if os.path.exists(dst) else print(''); print('Hotfixed broken JSON file from OpenAI');"
|
||||
|
||||
# Caution, this file will make your eyes and brain bleed. It's such an unholy mess.
|
||||
@ -159,8 +164,11 @@ fi
|
||||
|
||||
|
||||
|
||||
mkdir -p "../models/stable-diffusion"
|
||||
echo "" > "../models/stable-diffusion/Put your custom ckpt files here.txt"
|
||||
|
||||
if [ -f "sd-v1-4.ckpt" ]; then
|
||||
model_size=`ls -l sd-v1-4.ckpt | awk '{print $5}'`
|
||||
model_size=`find "sd-v1-4.ckpt" -printf "%s"`
|
||||
|
||||
if [ "$model_size" -eq "4265380512" ] || [ "$model_size" -eq "7703807346" ] || [ "$model_size" -eq "7703810927" ]; then
|
||||
echo "Data files (weights) necessary for Stable Diffusion were already downloaded"
|
||||
@ -176,7 +184,7 @@ if [ ! -f "sd-v1-4.ckpt" ]; then
|
||||
curl -L -k https://me.cmdr2.org/stable-diffusion-ui/sd-v1-4.ckpt > sd-v1-4.ckpt
|
||||
|
||||
if [ -f "sd-v1-4.ckpt" ]; then
|
||||
model_size=`ls -l sd-v1-4.ckpt | awk '{print $5}'`
|
||||
model_size=`find "sd-v1-4.ckpt" -printf "%s"`
|
||||
if [ ! "$model_size" == "4265380512" ]; then
|
||||
printf "\n\nError: The downloaded model file was invalid! Bytes downloaded: $model_size\n\n"
|
||||
printf "\n\nError downloading the data files (weights) for Stable Diffusion. Sorry about that, please try to:\n 1. Run this installer again.\n 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/blob/main/Troubleshooting.md\n 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB\n 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues\nThanks!\n\n"
|
||||
@ -192,7 +200,7 @@ fi
|
||||
|
||||
|
||||
if [ -f "GFPGANv1.3.pth" ]; then
|
||||
model_size=`ls -l GFPGANv1.3.pth | awk '{print $5}'`
|
||||
model_size=`find "GFPGANv1.3.pth" -printf "%s"`
|
||||
|
||||
if [ "$model_size" -eq "348632874" ]; then
|
||||
echo "Data files (weights) necessary for GFPGAN (Face Correction) were already downloaded"
|
||||
@ -208,7 +216,7 @@ if [ ! -f "GFPGANv1.3.pth" ]; then
|
||||
curl -L -k https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth > GFPGANv1.3.pth
|
||||
|
||||
if [ -f "GFPGANv1.3.pth" ]; then
|
||||
model_size=`ls -l GFPGANv1.3.pth | awk '{print $5}'`
|
||||
model_size=`find "GFPGANv1.3.pth" -printf "%s"`
|
||||
if [ ! "$model_size" -eq "348632874" ]; then
|
||||
printf "\n\nError: The downloaded GFPGAN model file was invalid! Bytes downloaded: $model_size\n\n"
|
||||
printf "\n\nError downloading the data files (weights) for GFPGAN (Face Correction). Sorry about that, please try to:\n 1. Run this installer again.\n 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/blob/main/Troubleshooting.md\n 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB\n 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues\nThanks!\n\n"
|
||||
@ -224,7 +232,7 @@ fi
|
||||
|
||||
|
||||
if [ -f "RealESRGAN_x4plus.pth" ]; then
|
||||
model_size=`ls -l RealESRGAN_x4plus.pth | awk '{print $5}'`
|
||||
model_size=`find "RealESRGAN_x4plus.pth" -printf "%s"`
|
||||
|
||||
if [ "$model_size" -eq "67040989" ]; then
|
||||
echo "Data files (weights) necessary for ESRGAN (Resolution Upscaling) x4plus were already downloaded"
|
||||
@ -240,7 +248,7 @@ if [ ! -f "RealESRGAN_x4plus.pth" ]; then
|
||||
curl -L -k https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth > RealESRGAN_x4plus.pth
|
||||
|
||||
if [ -f "RealESRGAN_x4plus.pth" ]; then
|
||||
model_size=`ls -l RealESRGAN_x4plus.pth | awk '{print $5}'`
|
||||
model_size=`find "RealESRGAN_x4plus.pth" -printf "%s"`
|
||||
if [ ! "$model_size" -eq "67040989" ]; then
|
||||
printf "\n\nError: The downloaded ESRGAN x4plus model file was invalid! Bytes downloaded: $model_size\n\n"
|
||||
printf "\n\nError downloading the data files (weights) for ESRGAN (Resolution Upscaling) x4plus. Sorry about that, please try to:\n 1. Run this installer again.\n 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/blob/main/Troubleshooting.md\n 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB\n 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues\nThanks!\n\n"
|
||||
@ -256,7 +264,7 @@ fi
|
||||
|
||||
|
||||
if [ -f "RealESRGAN_x4plus_anime_6B.pth" ]; then
|
||||
model_size=`ls -l RealESRGAN_x4plus_anime_6B.pth | awk '{print $5}'`
|
||||
model_size=`find "RealESRGAN_x4plus_anime_6B.pth" -printf "%s"`
|
||||
|
||||
if [ "$model_size" -eq "17938799" ]; then
|
||||
echo "Data files (weights) necessary for ESRGAN (Resolution Upscaling) x4plus_anime were already downloaded"
|
||||
@ -272,7 +280,7 @@ if [ ! -f "RealESRGAN_x4plus_anime_6B.pth" ]; then
|
||||
curl -L -k https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth > RealESRGAN_x4plus_anime_6B.pth
|
||||
|
||||
if [ -f "RealESRGAN_x4plus_anime_6B.pth" ]; then
|
||||
model_size=`ls -l RealESRGAN_x4plus_anime_6B.pth | awk '{print $5}'`
|
||||
model_size=`find "RealESRGAN_x4plus_anime_6B.pth" -printf "%s"`
|
||||
if [ ! "$model_size" -eq "17938799" ]; then
|
||||
printf "\n\nError: The downloaded ESRGAN x4plus_anime model file was invalid! Bytes downloaded: $model_size\n\n"
|
||||
printf "\n\nError downloading the data files (weights) for ESRGAN (Resolution Upscaling) x4plus_anime. Sorry about that, please try to:\n 1. Run this installer again.\n 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/blob/main/Troubleshooting.md\n 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB\n 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues\nThanks!\n\n"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,87 +0,0 @@
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
},
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
|
||||
plugins: ["react"],
|
||||
|
||||
extends: [
|
||||
"prettier",
|
||||
"plugin:react/recommended",
|
||||
"standard-with-typescript",
|
||||
"plugin:i18next/recommended",
|
||||
"plugin:i18n-json/recommended",
|
||||
],
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// general things turned off for now
|
||||
"no-debugger": "warn",
|
||||
"eol-last": "off",
|
||||
"comma-dangle": ["off", "always-multiline"],
|
||||
"no-void": ["off"],
|
||||
"array-callback-return": ["off"],
|
||||
"spaced-comment": ["off"],
|
||||
"padded-blocks": ["off"],
|
||||
"no-multiple-empty-lines": ["off", { max: 2, maxEOF: 1 }],
|
||||
quotes: ["off", "double"],
|
||||
semi: ["off", "always"],
|
||||
yoda: ["off"],
|
||||
eqeqeq: ["off"],
|
||||
"react/display-name": "warn",
|
||||
|
||||
// TS THINGS WE DONT WANT
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
|
||||
// these are things that fight with prettier
|
||||
"@typescript-eslint/comma-dangle": "off",
|
||||
"@typescript-eslint/space-before-function-paren": "off",
|
||||
"@typescript-eslint/quotes": "off",
|
||||
"@typescript-eslint/semi": "off",
|
||||
"@typescript-eslint/brace-style": "off",
|
||||
"@typescript-eslint/indent": "off",
|
||||
"@typescript-eslint/member-delimiter-style": "off",
|
||||
|
||||
// TS WARNINGS WE WANT
|
||||
"@typescript-eslint/no-unused-vars": "warn",
|
||||
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||
|
||||
// i18n stuff no string literal works but turned off for now
|
||||
"i18next/no-literal-string": "off",
|
||||
// still need to figure out how to get this to work
|
||||
// it should error if we dont haev all the keys in the translation file
|
||||
"i18n-json/identical-keys": [
|
||||
"error",
|
||||
{
|
||||
filePath: {
|
||||
"home.json/": path.resolve("./Translation/locales/en/home.json"),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.ts", "*.tsx"],
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.json"], // Specify it only for TypeScript files
|
||||
},
|
||||
},
|
||||
],
|
||||
// eslint-disable-next-line semi
|
||||
};
|
18
ui/frontend/build_src/.gitignore
vendored
18
ui/frontend/build_src/.gitignore
vendored
@ -1,18 +0,0 @@
|
||||
# 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
|
||||
|
@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/png" href="/media/favicon-16x16.png" sizes="16x16">
|
||||
<link rel="icon" type="image/png" href="/media/favicon-32x32.png" sizes="32x32">
|
||||
<meta name="color-scheme" content="dark light" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css">
|
||||
<title>Stable Diffusion UI</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- The react app entry point. Currently no ui just poc importing and logging -->
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
8225
ui/frontend/build_src/package-lock.json
generated
8225
ui/frontend/build_src/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,56 +0,0 @@
|
||||
{
|
||||
"name": "react-ts",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"pretty": "prettier --write .",
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build --emptyOutDir",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.7.2",
|
||||
"@tanstack/react-location": "^3.7.4",
|
||||
"@tanstack/react-query": "^4.2.3",
|
||||
"@tanstack/react-query-devtools": "^4.2.3",
|
||||
"@vanilla-extract/css": "^1.9.0",
|
||||
"@vanilla-extract/css-utils": "^0.1.2",
|
||||
"@vanilla-extract/recipes": "^0.2.5",
|
||||
"@vanilla-extract/vite-plugin": "^3.5.0",
|
||||
"i18next": "^21.9.2",
|
||||
"immer": "^9.0.15",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^11.18.6",
|
||||
"uuid": "^9.0.0",
|
||||
"zustand": "^4.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.7.18",
|
||||
"@types/react": "^18.0.17",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.37.0",
|
||||
"@typescript-eslint/parser": "^5.37.0",
|
||||
"@vitejs/plugin-react": "^2.0.1",
|
||||
"eslint": "^8.23.1",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-config-standard-with-typescript": "^23.0.0",
|
||||
"eslint-plugin-i18n-json": "^4.0.0",
|
||||
"eslint-plugin-i18next": "^6.0.0-4",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.2.5",
|
||||
"eslint-plugin-promise": "^6.0.1",
|
||||
"eslint-plugin-react": "^7.31.8",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.8.3",
|
||||
"vite": "^3.0.7",
|
||||
"vite-plugin-eslint": "^1.8.1"
|
||||
},
|
||||
"overrides": {
|
||||
"@vanilla-extract/vite-plugin": {
|
||||
"vite": "^3"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
singleQuote: true,
|
||||
tabWidth: 2,
|
||||
semi: true,
|
||||
trailingComma: "es5",
|
||||
endOfLine: "lf",
|
||||
};
|
@ -1,21 +0,0 @@
|
||||
import React from "react";
|
||||
import { ReactLocation, Router } from "@tanstack/react-location";
|
||||
import Home from "./pages/Home";
|
||||
import Settings from "./pages/Settings";
|
||||
import "./Translation/config";
|
||||
const location = new ReactLocation();
|
||||
|
||||
function App() {
|
||||
|
||||
return (
|
||||
<Router
|
||||
location={location}
|
||||
routes={[
|
||||
{ path: "/", element: <Home /> },
|
||||
{ path: "/settings", element: <Settings /> },
|
||||
]}
|
||||
></Router>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
@ -1,32 +0,0 @@
|
||||
import i18n from "i18next";
|
||||
// this should be updated to an interface
|
||||
import ENTranslation from "./locales/en/home.json";
|
||||
import ESTranslation from "./locales/es/home.json";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
|
||||
export const resources = {
|
||||
en: {
|
||||
translation: ENTranslation,
|
||||
},
|
||||
es: {
|
||||
translation: ESTranslation,
|
||||
},
|
||||
} as const;
|
||||
i18n
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
lng: "en",
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
resources,
|
||||
})
|
||||
.then(() => {
|
||||
console.log("i18n initialized");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("i18n initialization failed", err);
|
||||
})
|
||||
.finally(() => {
|
||||
console.log("i18n initialization finished");
|
||||
});
|
@ -1,110 +0,0 @@
|
||||
{
|
||||
"title": "Stable Diffusion UI",
|
||||
"description": "",
|
||||
"navbar": {
|
||||
"home": "Home",
|
||||
"history": "History",
|
||||
"community": "Community",
|
||||
"settings": "Settings"
|
||||
},
|
||||
"land-cre": {
|
||||
"cp": "Create Profile",
|
||||
"cp-place": "Profile name",
|
||||
"pp": "Profile Picture",
|
||||
"pp-disc": "",
|
||||
"ast": "Automatically save to",
|
||||
"ast-disc": "File path to auto save your creations",
|
||||
"place": "File path",
|
||||
"cre": "Create"
|
||||
},
|
||||
"land-pre": {
|
||||
"user": "Username",
|
||||
"add": "Add Profile"
|
||||
},
|
||||
"home": {
|
||||
"status-starting": "Stable Diffusion is starting...",
|
||||
"status-ready": "Stable Diffusion is ready to use!",
|
||||
"status-error": "Stable Diffusion is not running!",
|
||||
"editor-title": "Prompt",
|
||||
"initial-img-txt": "Initial Image: (optional)",
|
||||
"initial-img-btn": "Browse...",
|
||||
"initial-img-text2": "No file selected.",
|
||||
"make-img-btn": "Make Image",
|
||||
"make-img-btn-stop": "Stop"
|
||||
},
|
||||
"in-paint": {
|
||||
"txt": "In-Painting (select the area which the AI will paint into)",
|
||||
"clear": "Clear"
|
||||
},
|
||||
"settings": {
|
||||
"base-img": "Use base image:",
|
||||
"seed": "Seed:",
|
||||
"amount-of-img": "Amount of images to make:",
|
||||
"how-many": "How many at once:",
|
||||
"stream-img": "Stream images (this will slow down image generation):",
|
||||
"width": "Width:",
|
||||
"height": "Height:",
|
||||
"sampler": "Sampler:",
|
||||
"steps": "Number of inference steps:",
|
||||
"guide-scale": "Guidance Scale:",
|
||||
"prompt-str": "Prompt Strength:",
|
||||
"live-preview": "Show a live preview of the image (disable this for faster image generation)",
|
||||
"fix-face": "Fix incorrect faces and eyes (uses GFPGAN)",
|
||||
"ups": "Upscale the image to 4x resolution using:",
|
||||
"no-ups": "No Upscaling",
|
||||
"corrected": "Show only the corrected/upscaled image"
|
||||
},
|
||||
"tags": {
|
||||
"txt": "Image Modifiers (art styles, tags etc)"
|
||||
},
|
||||
"preview-prompt": {
|
||||
"part1": "Type a prompt and press the \"Make Image\" button.",
|
||||
"part2": "You can set an \"Initial Image\" if you want to guide the AI.\n",
|
||||
"part3": "You can also add modifiers like \"Realistic\", \"Pencil Sketch\", \"ArtStation\" etc by browsing through the \"Image Modifiers\" section and selecting the desired modifiers.\n",
|
||||
"part4": "Click \"Advanced Settings\" for additional settings like seed, image size, number of images to generate etc.",
|
||||
"part5": "Enjoy! :)"
|
||||
},
|
||||
"current-task": "Current task",
|
||||
"recent-create": "Recently Created",
|
||||
"popup": {
|
||||
"use-btn": "Use Image",
|
||||
"use-btn2": "Use Image and Tags"
|
||||
},
|
||||
"history": {
|
||||
"fave": "Favorites Only",
|
||||
"search": "Search"
|
||||
},
|
||||
"advanced-settings": {
|
||||
"sound": "Play sound on task completion",
|
||||
"sound-disc": "Will play a sound so user can hear when image is done.",
|
||||
"turbo": "Turbo mode",
|
||||
"turbo-disc": "Generates images faster, but uses an additional 1 GB of GPU memory",
|
||||
"cpu": "Use CPU instead of GPU",
|
||||
"cpu-disc": "Warning: this will be *very* slow",
|
||||
"gpu": "Use full precision",
|
||||
"gpu-disc": "(for GPU-only. warning: this will consume more VRAM)",
|
||||
"beta": "Beta Features",
|
||||
"beta-disc": "Get the latest features immediately (but could be less stable). \nPlease restart the program after changing this.",
|
||||
"save": "SAVE"
|
||||
},
|
||||
"storage": {
|
||||
"ast": "Automatically save to",
|
||||
"ast-disc": "File path to auto save your creations",
|
||||
"place": "File path",
|
||||
"cps": "Cross profile sharing",
|
||||
"cps-disc": "Profiles will see suggestions from each other.",
|
||||
"acb": "Allow cloud backup",
|
||||
"acb-disc": "A button will show up for images on hover",
|
||||
"acb-place": "Choose your",
|
||||
"acc-api": "Api key",
|
||||
"acb-api-place": "Your API key",
|
||||
"save": "SAVE"
|
||||
},
|
||||
"import": {
|
||||
"imp-btn": "IMPORT",
|
||||
"exp-btn": "EXPORT",
|
||||
"disc": "It is a good idea to leave the exported file as it is. Otherwise it may not import correctly",
|
||||
"disc:2": "When importing, only profiles that are not already present on the will be added."
|
||||
},
|
||||
"about": "If you found this project useful and want to help keep it alive, please to help cover the cost of development and maintenance! Thank you for your support!\n\nPlease feel free to join the discord community or file an issue if you have any problems or suggestions in using this interface.\n\nDisclaimer: The authors of this project are not responsible for any content generated using this interface.\n\nThis 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,\nspread misinformation and target vulnerable groups. For the full list of restrictions please read the license.\n\nBy using this software, you consent to the terms and conditions of the license.\n"
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
{
|
||||
"title": "Stable Diffusion UI",
|
||||
"description": "",
|
||||
"navbar": {
|
||||
"home": "Home",
|
||||
"history": "History",
|
||||
"community": "Community",
|
||||
"settings": "Settings"
|
||||
},
|
||||
"land-cre": {
|
||||
"cp": "Create Profile",
|
||||
"cp-place": "Profile name",
|
||||
"pp": "Profile Picture",
|
||||
"pp-disc": "",
|
||||
"ast": "Automatically save to",
|
||||
"ast-disc": "File path to auto save your creations",
|
||||
"place": "File path",
|
||||
"cre": "Create"
|
||||
},
|
||||
"land-pre": {
|
||||
"user": "Username",
|
||||
"add": "Add Profile"
|
||||
},
|
||||
"home": {
|
||||
"status-starting": "Stable Diffusion is starting...",
|
||||
"status-ready": "Stable Diffusion is ready to use!",
|
||||
"status-error": "Stable Diffusion is not running!",
|
||||
"editor-title": "Prompt",
|
||||
"initial-img-txt": "Initial Image: (optional)",
|
||||
"initial-img-btn": "Browse...",
|
||||
"initial-img-text2": "No file selected.",
|
||||
"make-img-btn": "Make Image",
|
||||
"make-img-btn-stop": "Stop"
|
||||
},
|
||||
"in-paint": {
|
||||
"txt": "In-Painting (select the area which the AI will paint into)",
|
||||
"clear": "Clear"
|
||||
},
|
||||
"settings": {
|
||||
"base-img": "Use base image:",
|
||||
"seed": "Seed:",
|
||||
"amount-of-img": "Amount of images to make:",
|
||||
"how-many": "How many at once:",
|
||||
"width": "Width:",
|
||||
"height": "Height:",
|
||||
"steps": "Number of inference steps:",
|
||||
"guide-scale": "Guidance Scale:",
|
||||
"prompt-str": "Prompt Strength:",
|
||||
"live-preview": "Show a live preview of the image (disable this for faster image generation)",
|
||||
"fix-face": "Fix incorrect faces and eyes (uses GFPGAN)",
|
||||
"ups": "Upscale the image to 4x resolution using:",
|
||||
"no-ups": "No Upscaling",
|
||||
"corrected": "Show only the corrected/upscaled image"
|
||||
},
|
||||
"tags": {
|
||||
"txt": "Image Modifiers (art styles, tags etc)"
|
||||
},
|
||||
"preview-prompt": {
|
||||
"part1": "Type a prompt and press the \"Make Image\" button.",
|
||||
"part2": "You can set an \"Initial Image\" if you want to guide the AI.\n",
|
||||
"part3": "You can also add modifiers like \"Realistic\", \"Pencil Sketch\", \"ArtStation\" etc by browsing through the \"Image Modifiers\" section and selecting the desired modifiers.\n",
|
||||
"part4": "Click \"Advanced Settings\" for additional settings like seed, image size, number of images to generate etc.",
|
||||
"part5": "Enjoy! :)"
|
||||
},
|
||||
"current-task": "Current task",
|
||||
"recent-create": "Recently Created",
|
||||
"popup": {
|
||||
"use-btn": "Use Image",
|
||||
"use-btn2": "Use Image and Tags"
|
||||
},
|
||||
"history": {
|
||||
"fave": "Favorites Only",
|
||||
"search": "Search"
|
||||
},
|
||||
"advanced-settings": {
|
||||
"sound": "Play sound on task completion",
|
||||
"sound-disc": "Will play a sound so user can hear when image is done.",
|
||||
"turbo": "Turbo mode",
|
||||
"turbo-disc": "Generates images faster, but uses an additional 1 GB of GPU memory",
|
||||
"cpu": "Use CPU instead of GPU",
|
||||
"cpu-disc": "Warning: this will be *very* slow",
|
||||
"gpu": "Use full precision",
|
||||
"gpu-disc": "(for GPU-only. warning: this will consume more VRAM)",
|
||||
"beta": "Beta Features",
|
||||
"beta-disc": "Get the latest features immediately (but could be less stable). \nPlease restart the program after changing this.",
|
||||
"save": "SAVE"
|
||||
},
|
||||
"storage": {
|
||||
"ast": "Automatically save to",
|
||||
"ast-disc": "File path to auto save your creations",
|
||||
"place": "File path",
|
||||
"cps": "Cross profile sharing",
|
||||
"cps-disc": "Profiles will see suggestions from each other.",
|
||||
"acb": "Allow cloud backup",
|
||||
"acb-disc": "A button will show up for images on hover",
|
||||
"acb-place": "Choose your",
|
||||
"acc-api": "Api key",
|
||||
"acb-api-place": "Your API key",
|
||||
"save": "SAVE"
|
||||
},
|
||||
"import": {
|
||||
"imp-btn": "IMPORT",
|
||||
"exp-btn": "EXPORT",
|
||||
"disc": "It is a good idea to leave the exported file as it is. Otherwise it may not import correctly",
|
||||
"disc:2": "When importing, only profiles that are not already present on the will be added."
|
||||
},
|
||||
"about": "If you found this project useful and want to help keep it alive, please to help cover the cost of development and maintenance! Thank you for your support!\n\nPlease feel free to join the discord community or file an issue if you have any problems or suggestions in using this interface.\n\nDisclaimer: The authors of this project are not responsible for any content generated using this interface.\n\nThis 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,\nspread misinformation and target vulnerable groups. For the full list of restrictions please read the license.\n\nBy using this software, you consent to the terms and conditions of the license.\n"
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
/**
|
||||
* basic server health
|
||||
*/
|
||||
|
||||
import type { SAMPLER_OPTIONS } from "../stores/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
|
||||
export 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`;
|
||||
const 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];
|
||||
};
|
||||
|
||||
export const KEY_CONFIG = "config";
|
||||
export const getConfig = async () => {
|
||||
const response = await fetch(`${API_URL}/app_config`);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
|
||||
export const KEY_TOGGLE_CONFIG = "toggle_config";
|
||||
export const toggleBetaConfig = async (branch: string) => {
|
||||
const response = await fetch(`${API_URL}/app_config`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
update_branch: branch,
|
||||
}),
|
||||
});
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* post a new request for an image
|
||||
*/
|
||||
// TODO; put hese some place better
|
||||
export interface ImageRequest {
|
||||
session_id: string;
|
||||
prompt: string;
|
||||
negative_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;
|
||||
mask: undefined | string;
|
||||
sampler: typeof SAMPLER_OPTIONS[number];
|
||||
stream_progress_updates: true;
|
||||
stream_image_progress: boolean;
|
||||
|
||||
}
|
||||
|
||||
export interface ImageOutput {
|
||||
data: string;
|
||||
path_abs: string | null;
|
||||
seed: number;
|
||||
}
|
||||
|
||||
export interface ImageReturnType {
|
||||
output: ImageOutput[];
|
||||
request: ImageRequest;
|
||||
status: string;
|
||||
}
|
||||
|
||||
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),
|
||||
});
|
||||
return res;
|
||||
};
|
||||
|
||||
export const doStopImage = async () => {
|
||||
|
||||
const response = await fetch(`${API_URL}/image/stop`);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
@ -1,140 +0,0 @@
|
||||
// would prefer to use a var here, but it doesn't work
|
||||
// vars: {
|
||||
// '--button-base-saturation': vars.colorMod.saturation.normal,
|
||||
// '--button-base-lightness': vars.colorMod.lightness.normal,
|
||||
// },
|
||||
|
||||
|
||||
import { recipe } from "@vanilla-extract/recipes";
|
||||
import { vars } from "../../styles/theme/index.css";
|
||||
|
||||
export const buttonStyle = recipe({
|
||||
|
||||
base: {
|
||||
fontSize: vars.fonts.sizes.Subheadline,
|
||||
fontWeight: "bold",
|
||||
color: vars.colors.text.normal,
|
||||
padding: vars.spacing.small,
|
||||
border: "0",
|
||||
borderRadius: vars.trim.smallBorderRadius,
|
||||
},
|
||||
|
||||
variants: {
|
||||
color: {
|
||||
primary: {
|
||||
// @ts-expect-error
|
||||
'--button-hue': vars.brandHue,
|
||||
'--button-base-saturation': vars.colorMod.saturation.normal,
|
||||
'--button-base-lightness': vars.colorMod.lightness.normal,
|
||||
},
|
||||
secondary: {
|
||||
// @ts-expect-error
|
||||
'--button-hue': vars.secondaryHue,
|
||||
'--button-base-saturation': vars.colorMod.saturation.normal,
|
||||
'--button-base-lightness': vars.colorMod.lightness.normal,
|
||||
},
|
||||
tertiary: {
|
||||
// @ts-expect-error
|
||||
'--button-hue': vars.tertiaryHue,
|
||||
'--button-base-saturation': vars.colorMod.saturation.normal,
|
||||
'--button-base-lightness': vars.colorMod.lightness.normal,
|
||||
},
|
||||
cancel: {
|
||||
// @ts-expect-error
|
||||
'--button-hue': vars.errorHue,
|
||||
'--button-base-saturation': vars.colorMod.saturation.normal,
|
||||
'--button-base-lightness': vars.colorMod.lightness.normal,
|
||||
},
|
||||
accent: {
|
||||
// @ts-expect-error
|
||||
'--button-hue': vars.backgroundAccentHue,
|
||||
'--button-base-saturation': vars.backgroundAccentSaturation,
|
||||
'--button-base-lightness': vars.backgroundAccentLightness,
|
||||
},
|
||||
clear: {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
},
|
||||
|
||||
type: {
|
||||
fill: {
|
||||
backgroundColor: `hsl(var(--button-hue),var(--button-base-saturation),${vars.colorMod.lightness.normal})`,
|
||||
border: `1px solid hsl(var(--button-hue),var(--button-base-saturation),${vars.colorMod.lightness.normal})`,
|
||||
":hover": {
|
||||
backgroundColor: `hsl(var(--button-hue),${vars.colorMod.saturation.bright},${vars.colorMod.lightness.normal})`,
|
||||
border: `1px solid hsl(var(--button-hue),${vars.colorMod.saturation.bright},${vars.colorMod.lightness.normal})`,
|
||||
},
|
||||
":active": {
|
||||
backgroundColor: `hsl(var(--button-hue),${vars.colorMod.saturation.bright},${vars.colorMod.lightness.dim})`,
|
||||
border: `1px solid hsl(var(--button-hue),${vars.colorMod.saturation.bright},${vars.colorMod.lightness.dim})`,
|
||||
},
|
||||
|
||||
":focus": {
|
||||
backgroundColor: `hsl(var(--button-hue),${vars.colorMod.saturation.bright},${vars.colorMod.lightness.dim})`,
|
||||
border: `1px solid hsl(var(--button-hue),${vars.colorMod.saturation.bright},${vars.colorMod.lightness.dim})`,
|
||||
},
|
||||
|
||||
":disabled": {
|
||||
backgroundColor: `hsl(var(--button-hue),${vars.colorMod.saturation.dim},${vars.colorMod.lightness.dim})`,
|
||||
border: `1px solid hsl(var(--button-hue),${vars.colorMod.saturation.dim},${vars.colorMod.lightness.dim})`,
|
||||
},
|
||||
},
|
||||
outline: {
|
||||
backgroundColor: "transparent",
|
||||
border: `1px solid hsl(var(--button-hue),var(--button-base-saturation),${vars.colorMod.lightness.normal})`,
|
||||
":hover": {
|
||||
borderColor: `hsl(var(--button-hue),${vars.colorMod.saturation.bright},${vars.colorMod.lightness.normal})`,
|
||||
},
|
||||
|
||||
":active": {
|
||||
borderColor: `hsl(var(--button-hue),${vars.colorMod.saturation.bright},${vars.colorMod.lightness.dim})`,
|
||||
},
|
||||
|
||||
":focus": {
|
||||
borderColor: `hsl(var(--button-hue),${vars.colorMod.saturation.bright},${vars.colorMod.lightness.dim})`,
|
||||
},
|
||||
|
||||
":disabled": {
|
||||
borderColor: `hsl(var(--button-hue),${vars.colorMod.saturation.dim},${vars.colorMod.lightness.dim})`,
|
||||
},
|
||||
},
|
||||
action: {
|
||||
backgroundColor: "transparent",
|
||||
color: `hsl(var(--button-hue),var(--button-base-saturation),${vars.colorMod.lightness.normal})`,
|
||||
textDecoration: "underline",
|
||||
":hover": {
|
||||
color: `hsl(var(--button-hue),${vars.colorMod.saturation.bright},${vars.colorMod.lightness.normal})`,
|
||||
},
|
||||
|
||||
":active": {
|
||||
color: `hsl(var(--button-hue),${vars.colorMod.saturation.bright},${vars.colorMod.lightness.dim})`,
|
||||
},
|
||||
|
||||
":focus": {
|
||||
color: `hsl(var(--button-hue),${vars.colorMod.saturation.bright},${vars.colorMod.lightness.dim})`,
|
||||
},
|
||||
|
||||
":disabled": {
|
||||
color: `hsl(var(--button-hue),${vars.colorMod.saturation.dim},${vars.colorMod.lightness.dim})`,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
size: {
|
||||
slim: {
|
||||
padding: vars.spacing.min,
|
||||
fontSize: vars.fonts.sizes.Caption,
|
||||
},
|
||||
large: {
|
||||
width: "100%",
|
||||
fontSize: vars.fonts.sizes.Headline,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
defaultVariants: {
|
||||
color: "primary",
|
||||
type: "fill",
|
||||
},
|
||||
|
||||
});
|
@ -1,51 +0,0 @@
|
||||
import { recipe } from "@vanilla-extract/recipes";
|
||||
import { vars } from "../../styles/theme/index.css";
|
||||
|
||||
export const card = recipe({
|
||||
|
||||
base: {
|
||||
color: vars.colors.text.normal,
|
||||
padding: vars.spacing.medium,
|
||||
},
|
||||
|
||||
variants: {
|
||||
backing: {
|
||||
normal: {
|
||||
background: vars.backgroundMain,
|
||||
},
|
||||
light: {
|
||||
background: vars.backgroundLight,
|
||||
},
|
||||
dark: {
|
||||
background: vars.backgroundDark,
|
||||
},
|
||||
},
|
||||
|
||||
rounded: {
|
||||
true: {
|
||||
borderRadius: vars.trim.smallBorderRadius,
|
||||
},
|
||||
},
|
||||
|
||||
info: {
|
||||
true: {
|
||||
background: vars.backgroundDark,
|
||||
border: `1px solid ${vars.backgroundAccentMain}`,
|
||||
},
|
||||
},
|
||||
|
||||
level: {
|
||||
flat: {},
|
||||
1: { boxShadow: "0 4px 8px 0 rgba(0, 0, 0, 0.15), 0 6px 20px 0 rgba(0, 0, 0, 0.15)" },
|
||||
2: { boxShadow: "0 4px 8px 0 rgba(0, 0, 0, 0.15), 0 6px 20px 0 rgba(0, 0, 0, 0.15)" },
|
||||
3: { boxShadow: "0 4px 8px 0 rgba(0, 0, 0, 0.15), 0 6px 20px 0 rgba(0, 0, 0, 0.15)" },
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
backing: "light",
|
||||
level: 'flat',
|
||||
rounded: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -1,24 +0,0 @@
|
||||
import { recipe } from "@vanilla-extract/recipes";
|
||||
import { vars } from "../../styles/theme/index.css";
|
||||
|
||||
|
||||
export const card = recipe({
|
||||
base: {
|
||||
background: vars.backgroundMain,
|
||||
color: vars.colors.text.normal,
|
||||
padding: vars.spacing.medium,
|
||||
borderRadius: vars.trim.smallBorderRadius,
|
||||
},
|
||||
variants: {
|
||||
level: {
|
||||
1: { boxShadow: "0 4px 8px 0 rgba(0, 0, 0, 0.15), 0 6px 20px 0 rgba(0, 0, 0, 0.15)" },
|
||||
2: { boxShadow: "0 4px 8px 0 rgba(0, 0, 0, 0.15), 0 6px 20px 0 rgba(0, 0, 0, 0.15)" },
|
||||
3: { boxShadow: "0 4px 8px 0 rgba(0, 0, 0, 0.15), 0 6px 20px 0 rgba(0, 0, 0, 0.15)" },
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
level: 1,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -1,39 +0,0 @@
|
||||
|
||||
import { style, globalStyle } from "@vanilla-extract/css";
|
||||
|
||||
// import { recipe } from "@vanilla-extract/recipes";
|
||||
import { vars } from "../../styles/theme/index.css";
|
||||
|
||||
import {
|
||||
card
|
||||
} from "./card.css";
|
||||
|
||||
export const PopoverMain = style({
|
||||
position: 'relative'
|
||||
});
|
||||
|
||||
export const PopoverButtonStyle = style({
|
||||
backgroundColor: "transparent",
|
||||
border: "0 none",
|
||||
cursor: "pointer",
|
||||
padding: vars.spacing.none,
|
||||
fontSize: vars.fonts.sizes.Subheadline,
|
||||
});
|
||||
|
||||
globalStyle(`${PopoverButtonStyle} > i`, {
|
||||
marginRight: vars.spacing.small,
|
||||
});
|
||||
|
||||
export const PopoverPanelMain = style([card(
|
||||
{
|
||||
backing: 'dark',
|
||||
level: 2,
|
||||
}
|
||||
), {
|
||||
position: 'absolute',
|
||||
top: '100%',
|
||||
right: '0',
|
||||
zIndex: '1',
|
||||
}]);
|
||||
|
||||
|
@ -1,19 +0,0 @@
|
||||
import { style, } from "@vanilla-extract/css";
|
||||
|
||||
// import { recipe } from "@vanilla-extract/recipes";
|
||||
import { vars } from "../../styles/theme/index.css";
|
||||
|
||||
export const SwitchGroupMain = style({
|
||||
});
|
||||
|
||||
export const SwitchMain = style({
|
||||
});
|
||||
|
||||
export const SwitchLabel = style({
|
||||
});
|
||||
|
||||
export const SwitchEnabled = style({
|
||||
});
|
||||
|
||||
export const SwitchPill = style({
|
||||
});
|
@ -1,60 +0,0 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
import { recipe } from "@vanilla-extract/recipes";
|
||||
import { vars } from "../../styles/theme/index.css";
|
||||
|
||||
export const tabStyles = recipe({
|
||||
base: {
|
||||
background: vars.backgroundMain,
|
||||
color: vars.colors.text.normal,
|
||||
padding: vars.spacing.small,
|
||||
borderRadius: `${vars.trim.smallBorderRadius} ${vars.trim.smallBorderRadius} 0 0`,
|
||||
border: `1px solid ${vars.backgroundLight}`,
|
||||
borderBottom: 'none',
|
||||
marginLeft: vars.spacing.small,
|
||||
boxShadow: `0px -1px 4px -2px ${vars.backgroundAccentMain} inset`,
|
||||
width: 'fit-content',
|
||||
':focus': {
|
||||
outline: 'none',
|
||||
},
|
||||
},
|
||||
|
||||
variants: {
|
||||
selected: {
|
||||
true: {
|
||||
background: vars.backgroundLight,
|
||||
color: vars.colors.text.normal,
|
||||
boxShadow: `0px -1px 4px -2px ${vars.backgroundDark} inset`,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const tabPanelStyles = recipe({
|
||||
base: {
|
||||
color: vars.colors.text.normal,
|
||||
// borderRadius: `0 0 ${vars.trim.smallBorderRadius} ${vars.trim.smallBorderRadius}`,
|
||||
background: vars.backgroundLight,
|
||||
padding: vars.spacing.medium,
|
||||
flexGrow: 1,
|
||||
overflow: 'auto',
|
||||
// "::-webkit-scrollbar": {
|
||||
// width: "4px",
|
||||
// },
|
||||
|
||||
},
|
||||
variants: {
|
||||
|
||||
backing: {
|
||||
normal: {
|
||||
background: vars.backgroundMain,
|
||||
},
|
||||
light: {
|
||||
background: vars.backgroundLight,
|
||||
},
|
||||
dark: {
|
||||
background: vars.backgroundDark,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -1,34 +0,0 @@
|
||||
import { style, globalStyle } from "@vanilla-extract/css";
|
||||
import { vars } from "../../../styles/theme/index.css";
|
||||
import {
|
||||
tabPanelStyles,
|
||||
} from "../../_recipes/tabs_headless.css";
|
||||
|
||||
|
||||
export const TabpanelScrollFlop = style([tabPanelStyles(), {
|
||||
direction: 'rtl',
|
||||
// position: 'relative',
|
||||
overflow: 'overlay',
|
||||
"::-webkit-scrollbar": {
|
||||
position: 'absolute',
|
||||
width: "6px",
|
||||
backgroundColor: vars.backgroundAccentMain,
|
||||
},
|
||||
|
||||
"::-webkit-scrollbar-thumb": {
|
||||
backgroundColor: vars.backgroundDark,
|
||||
borderRadius: "4px",
|
||||
},
|
||||
|
||||
|
||||
// "::-webkit-scrollbar-button: {
|
||||
// backgroundColor: vars.backgroundDark,
|
||||
// }
|
||||
|
||||
|
||||
}]);
|
||||
|
||||
globalStyle(`${TabpanelScrollFlop} > *`, {
|
||||
direction: 'ltr',
|
||||
|
||||
});
|
@ -1,61 +0,0 @@
|
||||
import React, { Fragment } from "react";
|
||||
import { Tab } from '@headlessui/react';
|
||||
|
||||
|
||||
|
||||
import CreationPanel from "../../organisms/creationPanel";
|
||||
import QueueDisplay from "../../organisms/queueDisplay";
|
||||
|
||||
import ProcessingStatus from "../../molecules/queueStatusTab";
|
||||
|
||||
import {
|
||||
tabStyles,
|
||||
} from "../../_recipes/tabs_headless.css";
|
||||
|
||||
import {
|
||||
TabpanelScrollFlop
|
||||
} from "./creationTabs.css";
|
||||
|
||||
export default function CreationTabs() {
|
||||
|
||||
return (
|
||||
<Tab.Group>
|
||||
<Tab.List>
|
||||
<Tab as={Fragment}>
|
||||
{({ selected }) => (
|
||||
<button
|
||||
className={tabStyles({
|
||||
selected,
|
||||
})}
|
||||
>
|
||||
Create
|
||||
</button>
|
||||
)}
|
||||
</Tab>
|
||||
|
||||
<Tab as={Fragment}>
|
||||
{({ selected }) => (
|
||||
|
||||
<button
|
||||
className={tabStyles({
|
||||
selected,
|
||||
})}
|
||||
>
|
||||
<ProcessingStatus></ProcessingStatus>
|
||||
</button>
|
||||
)}
|
||||
</Tab>
|
||||
|
||||
|
||||
</Tab.List>
|
||||
<Tab.Panels className={TabpanelScrollFlop}>
|
||||
<Tab.Panel>
|
||||
<CreationPanel></CreationPanel>
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<QueueDisplay></QueueDisplay>
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
);
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
import { style, globalStyle } from "@vanilla-extract/css";
|
||||
import { vars } from "../../../styles/theme/index.css";
|
||||
|
||||
import {
|
||||
tabStyles
|
||||
} from "../../_recipes/tabs_headless.css";
|
||||
|
||||
|
||||
export const basicDisplayLayout = style({
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 250px",
|
||||
gridTemplateRows: "1fr 250px",
|
||||
gridTemplateAreas: `
|
||||
"content info"
|
||||
"history history"`,
|
||||
|
||||
overflow: "hidden",
|
||||
|
||||
selectors: {
|
||||
'&[data-hide-info]': {
|
||||
gridTemplateColumns: "1fr",
|
||||
gridTemplateRows: "1fr 250px",
|
||||
// gridTemplateAreas: `
|
||||
// "content"
|
||||
// "history"`,
|
||||
},
|
||||
'&[data-hide-history]': {
|
||||
gridTemplateColumns: "1fr 250px",
|
||||
gridTemplateRows: "1fr 0px",
|
||||
// gridTemplateAreas: `
|
||||
// "content info"`,
|
||||
},
|
||||
'&[data-hide-info][data-hide-history]': {
|
||||
gridTemplateColumns: "1fr 0px",
|
||||
gridTemplateRows: "1fr 0px",
|
||||
// gridTemplateAreas: `
|
||||
// "content"`,
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
// "@media": {
|
||||
// "screen and (max-width: 800px)": {
|
||||
// gridTemplateColumns: "1fr",
|
||||
// gridTemplateRows: "100px 300px 1fr",
|
||||
// gridTemplateAreas: `
|
||||
// "header"
|
||||
// "create"
|
||||
// "display"
|
||||
// `,
|
||||
// },
|
||||
// },
|
||||
|
||||
});
|
||||
|
||||
// globalStyle(`${basicDisplayLayout}.hideHistory`, {
|
||||
|
||||
// });
|
||||
|
||||
export const contentLayout = style({
|
||||
gridArea: "content",
|
||||
});
|
||||
|
||||
export const infoLayout = style({
|
||||
gridArea: "info",
|
||||
position: "relative",
|
||||
});
|
||||
|
||||
export const historyLayout = style({
|
||||
gridArea: "history",
|
||||
position: "relative",
|
||||
});
|
||||
|
||||
globalStyle(`${historyLayout} > button`, {
|
||||
position: "absolute",
|
||||
top: '-29px',
|
||||
});
|
||||
|
||||
export const displayContainer = style({
|
||||
flexGrow: 1,
|
||||
overflow: 'auto',
|
||||
display: "flex",
|
||||
});
|
||||
|
||||
export const displayData = style({
|
||||
width: '250px',
|
||||
height: '100%',
|
||||
backgroundColor: 'rebeccapurple',
|
||||
position: 'relative',
|
||||
});
|
||||
|
||||
export const DataTab = style([tabStyles(), {
|
||||
position: 'absolute',
|
||||
top: '0',
|
||||
left: '0',
|
||||
|
||||
// pretty sure this is a magic number
|
||||
transformOrigin: '37% 275%',
|
||||
transform: 'rotate(-90deg)',
|
||||
}]);
|
||||
|
||||
|
||||
// export const previousImages = style({
|
||||
// minHeight: '250px',
|
||||
// });
|
@ -1,65 +0,0 @@
|
||||
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { Transition } from '@headlessui/react'
|
||||
|
||||
import CurrentDisplay from "../../organisms/currentDisplay";
|
||||
import CompletedImages from "../../organisms/completedImages";
|
||||
import CurrentInfo from "../../organisms/currentInfo";
|
||||
|
||||
|
||||
import {
|
||||
tabStyles
|
||||
} from "../../_recipes/tabs_headless.css";
|
||||
|
||||
import {
|
||||
basicDisplayLayout,
|
||||
contentLayout,
|
||||
infoLayout,
|
||||
historyLayout
|
||||
} from "./basicDisplay.css";
|
||||
|
||||
export default function BasicDisplay() {
|
||||
|
||||
const [isShowingHistory, setIsShowingHistory] = useState(true)
|
||||
|
||||
const layoutRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (layoutRef.current != undefined) {
|
||||
// set the hide-history data attribute
|
||||
if (isShowingHistory) {
|
||||
layoutRef.current.removeAttribute('data-hide-history');
|
||||
}
|
||||
else {
|
||||
// layoutRef.current.dataset.hideHistory = "true";
|
||||
layoutRef.current.setAttribute('data-hide-history', '');
|
||||
}
|
||||
}
|
||||
}, [layoutRef, isShowingHistory]);
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={layoutRef}
|
||||
className={basicDisplayLayout}
|
||||
>
|
||||
<div className={contentLayout}>
|
||||
<CurrentDisplay></CurrentDisplay>
|
||||
</div>
|
||||
|
||||
{/* <div className={infoLayout}>
|
||||
<CurrentInfo ></CurrentInfo>
|
||||
</div> */}
|
||||
|
||||
<div className={historyLayout}>
|
||||
<button
|
||||
className={tabStyles({})}
|
||||
onClick={() => setIsShowingHistory((isShowingHistory) => !isShowingHistory)}>
|
||||
{isShowingHistory ? "Hide History" : "Show History"}
|
||||
</button>
|
||||
<CompletedImages></CompletedImages>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import React from "react";
|
||||
import { API_URL } from "../../../api";
|
||||
|
||||
const url = `${API_URL}/ding.mp3`;
|
||||
|
||||
const AudioDing = React.forwardRef((props, ref) => (
|
||||
// @ts-expect-error
|
||||
<audio ref={ref} style={{ display: "none" }}>
|
||||
<source src={url} type="audio/mp3" />
|
||||
</audio>
|
||||
));
|
||||
|
||||
AudioDing.displayName = "AudioDing";
|
||||
|
||||
export default AudioDing;
|
||||
|
@ -1,73 +0,0 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import {
|
||||
KEY_CONFIG,
|
||||
getConfig,
|
||||
KEY_TOGGLE_CONFIG,
|
||||
toggleBetaConfig,
|
||||
} from "../../../api";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function BetaMode() {
|
||||
const { t } = useTranslation();
|
||||
// gate for the toggle
|
||||
const [shouldSetCofig, setShouldSetConfig] = useState(false);
|
||||
// next branch to get
|
||||
const [branchToGetNext, setBranchToGetNext] = useState("beta");
|
||||
|
||||
// our basic config
|
||||
const { status: configStatus, data: configData } = useQuery(
|
||||
[KEY_CONFIG],
|
||||
getConfig
|
||||
);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// the toggle config
|
||||
const { status: toggleStatus, data: toggleData } = useQuery(
|
||||
[KEY_TOGGLE_CONFIG],
|
||||
async () => await toggleBetaConfig(branchToGetNext),
|
||||
{
|
||||
enabled: shouldSetCofig,
|
||||
}
|
||||
);
|
||||
|
||||
// this is also in the Header Display
|
||||
// TODO: make this a custom hook
|
||||
useEffect(() => {
|
||||
if (configStatus === "success") {
|
||||
const { update_branch: updateBranch } = configData;
|
||||
|
||||
if (updateBranch === "main") {
|
||||
setBranchToGetNext("beta");
|
||||
} else {
|
||||
// setIsBeta(true);
|
||||
setBranchToGetNext("main");
|
||||
}
|
||||
}
|
||||
}, [configStatus, configData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (toggleStatus === "success") {
|
||||
if (toggleData[0] === "OK") {
|
||||
// force a refetch of the config
|
||||
void queryClient.invalidateQueries([KEY_CONFIG])
|
||||
}
|
||||
setShouldSetConfig(false);
|
||||
}
|
||||
}, [toggleStatus, toggleData, setShouldSetConfig]);
|
||||
|
||||
return (
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={branchToGetNext === "main"}
|
||||
onChange={(e) => {
|
||||
setShouldSetConfig(true);
|
||||
}}
|
||||
/>🔥
|
||||
{t("advanced-settings.beta")} {t("advanced-settings.beta-disc")}
|
||||
</label>
|
||||
);
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
import React from "react";
|
||||
import { doStopImage } from "../../../api";
|
||||
import { useRequestQueue } from "../../../stores/requestQueueStore";
|
||||
|
||||
import {
|
||||
buttonStyle
|
||||
} from "../../_recipes/button.css";
|
||||
|
||||
|
||||
export default function ClearQueue() {
|
||||
|
||||
const hasQueue = useRequestQueue((state) => state.hasAnyQueue());
|
||||
const clearQueue = useRequestQueue((state) => state.clearQueue);
|
||||
|
||||
const stopAll = async () => {
|
||||
try {
|
||||
clearQueue();
|
||||
const res = await doStopImage();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button className={buttonStyle(
|
||||
{
|
||||
color: "cancel",
|
||||
size: "large",
|
||||
}
|
||||
)}
|
||||
disabled={!hasQueue} onClick={() => void stopAll()}>
|
||||
STOP ALL
|
||||
</button>
|
||||
);
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import { style, globalStyle } from "@vanilla-extract/css";
|
||||
|
||||
export const DrawImageMain = style({
|
||||
position: "relative",
|
||||
});
|
||||
|
||||
globalStyle(`${DrawImageMain} > canvas`, {
|
||||
position: "absolute",
|
||||
top: "0",
|
||||
left: "0",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
});
|
||||
|
||||
globalStyle(`${DrawImageMain} > canvas:first-of-type`, {
|
||||
opacity: ".7",
|
||||
});
|
||||
|
||||
globalStyle(`${DrawImageMain} > img`, {
|
||||
top: "0",
|
||||
left: "0",
|
||||
});
|
@ -1,194 +0,0 @@
|
||||
import React, { useRef, useState, useEffect } from "react";
|
||||
|
||||
import {
|
||||
DrawImageMain,
|
||||
} from "./drawImage.css";
|
||||
|
||||
// https://github.com/embiem/react-canvas-draw
|
||||
|
||||
interface DrawImageProps {
|
||||
imageData: string;
|
||||
brushSize: number;
|
||||
|
||||
brushShape: string;
|
||||
brushColor: string;
|
||||
isErasing: boolean;
|
||||
setData: (data: string) => void;
|
||||
}
|
||||
|
||||
export default function DrawImage({
|
||||
imageData,
|
||||
brushSize,
|
||||
brushShape,
|
||||
brushColor,
|
||||
isErasing,
|
||||
setData,
|
||||
}: DrawImageProps) {
|
||||
const drawingRef = useRef<HTMLCanvasElement>(null);
|
||||
const cursorRef = useRef<HTMLCanvasElement>(null);
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
|
||||
const [canvasWidth, setCanvasWidth] = useState(512);
|
||||
const [canvasHeight, setCanvasHeight] = useState(512);
|
||||
|
||||
useEffect(() => {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
setCanvasWidth(img.width);
|
||||
setCanvasHeight(img.height);
|
||||
};
|
||||
img.src = imageData;
|
||||
}, [imageData]);
|
||||
|
||||
useEffect(() => {
|
||||
// when the brush color changes, change the color of all the
|
||||
// drawn pixels to the new color
|
||||
if (drawingRef.current != null) {
|
||||
const ctx = drawingRef.current.getContext("2d");
|
||||
if (ctx != null) {
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
|
||||
const data = imageData.data;
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
if (data[i + 3] > 0) {
|
||||
data[i] = parseInt(brushColor, 16);
|
||||
data[i + 1] = parseInt(brushColor, 16);
|
||||
data[i + 2] = parseInt(brushColor, 16);
|
||||
}
|
||||
}
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
}
|
||||
}
|
||||
}, [brushColor]);
|
||||
|
||||
const _handleMouseDown = (
|
||||
e: React.MouseEvent<HTMLCanvasElement, MouseEvent>
|
||||
) => {
|
||||
const {
|
||||
nativeEvent: { offsetX, offsetY },
|
||||
} = e;
|
||||
|
||||
setIsUpdating(true);
|
||||
};
|
||||
|
||||
const _handleMouseUp = (
|
||||
e: React.MouseEvent<HTMLCanvasElement, MouseEvent>
|
||||
) => {
|
||||
setIsUpdating(false);
|
||||
const canvas = drawingRef.current;
|
||||
if (canvas != null) {
|
||||
const data = canvas.toDataURL();
|
||||
setData(data);
|
||||
}
|
||||
};
|
||||
|
||||
const _drawCanvas = (x: number, y: number, brushSize: number, brushShape: string, brushColor: string | CanvasGradient | CanvasPattern) => {
|
||||
const canvas = drawingRef.current;
|
||||
if (canvas != null) {
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (ctx != null) {
|
||||
if (isErasing) {
|
||||
// stack overflow https://stackoverflow.com/questions/10396991/clearing-circular-regions-from-html5-canvas
|
||||
const offset = brushSize / 2;
|
||||
ctx.clearRect(x - offset, y - offset, brushSize, brushSize);
|
||||
} else {
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = brushSize;
|
||||
// @ts-expect-error
|
||||
ctx.lineCap = brushShape;
|
||||
ctx.strokeStyle = brushColor;
|
||||
ctx.moveTo(x, y);
|
||||
ctx.lineTo(x, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const _drawCursor = (
|
||||
x: number,
|
||||
y: number,
|
||||
brushSize: number,
|
||||
brushShape: string,
|
||||
brushColor: string
|
||||
) => {
|
||||
const canvas = cursorRef.current;
|
||||
if (canvas != null) {
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (ctx != null) {
|
||||
ctx.beginPath();
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
if (isErasing) {
|
||||
const offset = brushSize / 2;
|
||||
// draw a quare
|
||||
ctx.lineWidth = 2;
|
||||
ctx.lineCap = "butt";
|
||||
ctx.strokeStyle = brushColor;
|
||||
ctx.moveTo(x - offset, y - offset);
|
||||
ctx.lineTo(x + offset, y - offset);
|
||||
ctx.lineTo(x + offset, y + offset);
|
||||
ctx.lineTo(x - offset, y + offset);
|
||||
ctx.lineTo(x - offset, y - offset);
|
||||
ctx.stroke();
|
||||
} else {
|
||||
ctx.lineWidth = brushSize;
|
||||
// @ts-expect-error
|
||||
ctx.lineCap = brushShape;
|
||||
ctx.strokeStyle = brushColor;
|
||||
ctx.moveTo(x, y);
|
||||
ctx.lineTo(x, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const _handleMouseMove = (
|
||||
e: React.MouseEvent<HTMLCanvasElement, MouseEvent>
|
||||
) => {
|
||||
const {
|
||||
nativeEvent: { offsetX: x, offsetY: y },
|
||||
} = e;
|
||||
|
||||
_drawCursor(x, y, brushSize, brushShape, brushColor);
|
||||
|
||||
if (isUpdating) {
|
||||
_drawCanvas(x, y, brushSize, brushShape, brushColor);
|
||||
}
|
||||
};
|
||||
|
||||
// function for external use
|
||||
const fillCanvas = () => {
|
||||
const canvas = drawingRef.current;
|
||||
if (canvas != null) {
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (ctx != null) {
|
||||
ctx.fillStyle = brushColor;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={DrawImageMain}>
|
||||
<img src={imageData} />
|
||||
<canvas
|
||||
ref={drawingRef}
|
||||
width={canvasWidth}
|
||||
height={canvasHeight}
|
||||
></canvas>
|
||||
<canvas
|
||||
ref={cursorRef}
|
||||
width={canvasWidth}
|
||||
height={canvasHeight}
|
||||
onMouseDown={_handleMouseDown}
|
||||
onMouseUp={_handleMouseUp}
|
||||
onMouseMove={_handleMouseMove}
|
||||
></canvas>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import { style, globalStyle } from "@vanilla-extract/css";
|
||||
|
||||
export const generatedImageMain = style({
|
||||
position: "relative",
|
||||
});
|
||||
|
||||
globalStyle(`${generatedImageMain} img`, {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "contain",
|
||||
});
|
@ -1,25 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import { ImageRequest } from "../../../api";
|
||||
|
||||
import {
|
||||
generatedImageMain,
|
||||
} from "./generatedImage.css";
|
||||
|
||||
interface GeneretaedImageProps {
|
||||
imageData: string | undefined;
|
||||
metadata: ImageRequest | undefined;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function GeneratedImage({
|
||||
imageData,
|
||||
metadata,
|
||||
className,
|
||||
}: GeneretaedImageProps) {
|
||||
return (
|
||||
<div className={[generatedImageMain, className].join(" ")}>
|
||||
<img src={imageData} alt={metadata!.prompt} />
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,276 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import React, { useEffect, useRef } from "react";
|
||||
|
||||
import { useImageCreate } from "../../../stores/imageCreateStore";
|
||||
import {
|
||||
QueueStatus,
|
||||
useRequestQueue
|
||||
} from "../../../stores/requestQueueStore";
|
||||
|
||||
import {
|
||||
FetchingStates,
|
||||
useImageFetching
|
||||
} from "../../../stores/imageFetchingStore";
|
||||
|
||||
|
||||
import { useImageDisplay } from "../../../stores/imageDisplayStore";
|
||||
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import { useRandomSeed } from "../../../utils";
|
||||
import {
|
||||
ImageRequest,
|
||||
ImageReturnType,
|
||||
ImageOutput,
|
||||
doMakeImage,
|
||||
} from "../../../api";
|
||||
|
||||
import {
|
||||
buttonStyle
|
||||
} from "../../_recipes/button.css";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import AudioDing from "../../molecules/audioDing";
|
||||
|
||||
const idDelim = "_batch";
|
||||
|
||||
export default function MakeButton() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dingRef = useRef<HTMLAudioElement>();
|
||||
|
||||
const parallelCount = useImageCreate((state) => state.parallelCount);
|
||||
const builtRequest = useImageCreate((state) => state.builtRequest);
|
||||
const isRandomSeed = useImageCreate((state) => state.isRandomSeed());
|
||||
const setRequestOption = useImageCreate((state) => state.setRequestOptions);
|
||||
|
||||
const isSoundEnabled = useImageCreate((state) => state.isSoundEnabled());
|
||||
|
||||
const addtoQueue = useRequestQueue((state) => state.addtoQueue);
|
||||
const hasQueue = useRequestQueue((state) => state.hasPendingQueue());
|
||||
const { id, options } = useRequestQueue((state) => state.firstInQueue());
|
||||
const updateQueueStatus = useRequestQueue((state) => state.updateStatus);
|
||||
|
||||
const status = useImageFetching((state) => state.status);
|
||||
const setStatus = useImageFetching((state) => state.setStatus);
|
||||
const setStep = useImageFetching((state) => state.setStep);
|
||||
const setTotalSteps = useImageFetching((state) => state.setTotalSteps);
|
||||
const addProgressImage = useImageFetching((state) => state.addProgressImage);
|
||||
const setStartTime = useImageFetching((state) => state.setStartTime);
|
||||
const setNowTime = useImageFetching((state) => state.setNowTime);
|
||||
const resetForFetching = useImageFetching((state) => state.resetForFetching);
|
||||
const appendData = useImageFetching((state) => state.appendData);
|
||||
|
||||
const updateDisplay = useImageDisplay((state) => state.updateDisplay);
|
||||
|
||||
const hackJson = (jsonStr: string, id: string) => {
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(jsonStr);
|
||||
const { status, request, output: outputs } = parsed as ImageReturnType;
|
||||
|
||||
|
||||
if (status === 'succeeded') {
|
||||
|
||||
updateQueueStatus(id, QueueStatus.complete);
|
||||
outputs.forEach((output: any, index: number) => {
|
||||
|
||||
const { data, seed } = output as ImageOutput;
|
||||
const seedReq = {
|
||||
...request,
|
||||
seed,
|
||||
};
|
||||
const batchId = `${id}${idDelim}-${seed}-${index}`;
|
||||
updateDisplay(batchId, data, seedReq);
|
||||
});
|
||||
}
|
||||
|
||||
else {
|
||||
console.warn(`Unexpected status: ${status}`);
|
||||
updateQueueStatus(id, QueueStatus.error);
|
||||
}
|
||||
|
||||
}
|
||||
catch (e) {
|
||||
updateQueueStatus(id, QueueStatus.error);
|
||||
console.warn("Error HACKING JSON: ", e)
|
||||
}
|
||||
}
|
||||
|
||||
const parseRequest = async (id: string, reader: ReadableStreamDefaultReader<Uint8Array>) => {
|
||||
const decoder = new TextDecoder();
|
||||
let finalJSON = '';
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
const jsonStr = decoder.decode(value);
|
||||
if (done) {
|
||||
setStatus(FetchingStates.COMPLETE);
|
||||
hackJson(finalJSON, id);
|
||||
if (isSoundEnabled) {
|
||||
void dingRef.current?.play();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
const update = JSON.parse(jsonStr);
|
||||
const { status } = update;
|
||||
|
||||
if (status === "progress") {
|
||||
setStatus(FetchingStates.PROGRESSING);
|
||||
const { progress: { step, total_steps }, output: outputs } = update;
|
||||
setStep(step);
|
||||
setTotalSteps(total_steps);
|
||||
|
||||
if (step === 0) {
|
||||
setStartTime();
|
||||
}
|
||||
else {
|
||||
setNowTime();
|
||||
}
|
||||
|
||||
if (void 0 !== outputs) {
|
||||
outputs.forEach((output: any) => {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
const timePath = `${output.path}?t=${new Date().getTime()}`
|
||||
addProgressImage(timePath);
|
||||
});
|
||||
}
|
||||
|
||||
} else if (status === "succeeded") {
|
||||
// TODO this should be the the new out instead of the try catch
|
||||
// wait for the path to come back instead of the data
|
||||
setStatus(FetchingStates.SUCCEEDED);
|
||||
}
|
||||
else if (status === 'failed') {
|
||||
console.warn('failed');
|
||||
console.warn(update);
|
||||
}
|
||||
else {
|
||||
console.warn("UNKNOWN ?", update);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
// console.log('EXPECTED PARSE ERRROR')
|
||||
finalJSON += jsonStr;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const startStream = async (id: string, req: ImageRequest) => {
|
||||
|
||||
try {
|
||||
updateQueueStatus(id, QueueStatus.processing);
|
||||
resetForFetching();
|
||||
const res = await doMakeImage(req);
|
||||
const reader = res.body?.getReader();
|
||||
|
||||
if (void 0 !== reader) {
|
||||
void parseRequest(id, reader);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.log('TOP LINE STREAM ERROR')
|
||||
updateQueueStatus(id, QueueStatus.error);
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const queueImageRequest = (req: ImageRequest) => {
|
||||
// the actual number of request we will make
|
||||
const requests = [];
|
||||
// the number of images we will make
|
||||
let { num_outputs } = req;
|
||||
if (parallelCount > num_outputs) {
|
||||
requests.push(num_outputs);
|
||||
} else {
|
||||
// while we have at least 1 image to make
|
||||
while (num_outputs >= 1) {
|
||||
// subtract the parallel count from the number of images to make
|
||||
num_outputs -= parallelCount;
|
||||
|
||||
// if we are still 0 or greater we can make the full parallel count
|
||||
if (num_outputs <= 0) {
|
||||
requests.push(parallelCount);
|
||||
}
|
||||
// otherwise we can only make the remaining images
|
||||
else {
|
||||
requests.push(Math.abs(num_outputs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
requests.forEach((num, index) => {
|
||||
// get the seed we want to use
|
||||
let seed = req.seed;
|
||||
if (index !== 0) {
|
||||
// we want to use a random seed for subsequent requests
|
||||
seed = useRandomSeed();
|
||||
}
|
||||
// add the request to the queue
|
||||
addtoQueue(uuidv4(), {
|
||||
...req,
|
||||
// updated the number of images to make
|
||||
num_outputs: num,
|
||||
// update the seed
|
||||
seed,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const makeImageQueue = async () => {
|
||||
// potentially update the seed
|
||||
if (isRandomSeed) {
|
||||
// update the seed for the next time we click the button
|
||||
setRequestOption("seed", useRandomSeed());
|
||||
}
|
||||
// the request that we have built
|
||||
const req = builtRequest();
|
||||
queueImageRequest(req);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const makeImages = async (options: ImageRequest) => {
|
||||
// removeFirstInQueue();
|
||||
await startStream(id ?? "", options);
|
||||
}
|
||||
|
||||
if (status === FetchingStates.PROGRESSING || status === FetchingStates.FETCHING) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasQueue) {
|
||||
|
||||
if (options === undefined) {
|
||||
console.log('req is undefined');
|
||||
return;
|
||||
}
|
||||
|
||||
makeImages(options).catch((e) => {
|
||||
console.log('HAS QUEUE ERROR');
|
||||
console.log(e);
|
||||
});
|
||||
}
|
||||
|
||||
}, [hasQueue, status, id, options, startStream]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className={buttonStyle({
|
||||
size: "large",
|
||||
})}
|
||||
onClick={() => {
|
||||
void makeImageQueue();
|
||||
}}
|
||||
>
|
||||
{t("home.make-img-btn")}
|
||||
</button>
|
||||
<AudioDing ref={dingRef}></AudioDing>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import {
|
||||
ModifierPreview,
|
||||
useImageCreate
|
||||
} from "../../../stores/imageCreateStore";
|
||||
|
||||
import { API_URL } from "../../../api";
|
||||
|
||||
import {
|
||||
IconFont,
|
||||
} from "../../../styles/shared.css";
|
||||
|
||||
import {
|
||||
ModifierTagMain,
|
||||
ModifierActions,
|
||||
tagPreview,
|
||||
TagText,
|
||||
TagToggle,
|
||||
} from "./modifierTags.css";
|
||||
|
||||
interface ModifierTagProps {
|
||||
name: string;
|
||||
category: string;
|
||||
previews: ModifierPreview[];
|
||||
}
|
||||
|
||||
export default function ModifierTag({ name, category, previews }: ModifierTagProps) {
|
||||
|
||||
const previewType: 'portrait' | 'landscape' = "portrait";
|
||||
|
||||
const [showActions, setShowActions] = useState(false);
|
||||
|
||||
const handleHover = () => {
|
||||
setShowActions(true);
|
||||
};
|
||||
|
||||
const handleLeave = () => {
|
||||
setShowActions(false);
|
||||
};
|
||||
|
||||
const addCreateTag = useImageCreate((state) => state.addCreateTag);
|
||||
const setPositivePrompt = () => {
|
||||
addCreateTag({ id: uuidv4(), name, type: 'positive' });
|
||||
}
|
||||
const setNegativePrompt = () => {
|
||||
addCreateTag({ id: uuidv4(), name, type: 'negative' });
|
||||
}
|
||||
|
||||
|
||||
const hasTag = useImageCreate((state) => state.hasTag(category, name))
|
||||
? "selected"
|
||||
: "";
|
||||
const toggleTag = useImageCreate((state) => state.toggleTag);
|
||||
|
||||
const _toggleTag = () => {
|
||||
toggleTag(category, name);
|
||||
};
|
||||
|
||||
// , hasTag].join(" ")
|
||||
return (
|
||||
<div className={ModifierTagMain}
|
||||
onMouseEnter={handleHover}
|
||||
onMouseLeave={handleLeave}>
|
||||
<p className={!showActions ? TagText : TagToggle}>{name}</p>
|
||||
{showActions && (
|
||||
<div className={ModifierActions}>
|
||||
<button onClick={setPositivePrompt}>
|
||||
<i className={[IconFont, 'fa-solid', 'fa-plus'].join(" ")}></i>
|
||||
</button>
|
||||
<button onClick={setNegativePrompt}>
|
||||
<i className={[IconFont, 'fa-solid', 'fa-minus'].join(" ")}></i>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{/* <div className={tagPreview}>
|
||||
{previews.map((preview) => {
|
||||
if (preview.name !== previewType) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<img
|
||||
key={preview.name}
|
||||
src={`${API_URL}/media/modifier-thumbnails/${preview.path}`}
|
||||
alt={preview.name}
|
||||
title={preview.name}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
import { style, globalStyle } from "@vanilla-extract/css";
|
||||
import { vars } from '../../../styles/theme/index.css';
|
||||
|
||||
import { card } from '../../_recipes/card.css';
|
||||
|
||||
export const ModifierTagMain = style([
|
||||
card({
|
||||
backing: 'normal',
|
||||
level: 1,
|
||||
info: true
|
||||
}), {
|
||||
position: "relative",
|
||||
width: "fit-content",
|
||||
borderColor: `hsl(${vars.brandHue}, ${vars.colorMod.saturation.normal}, ${vars.colorMod.lightness.normal})`,
|
||||
padding: vars.spacing.small,
|
||||
}
|
||||
]);
|
||||
|
||||
globalStyle(`${ModifierTagMain}.selected`, {
|
||||
backgroundColor: "rgb(131, 11, 121)",
|
||||
})
|
||||
|
||||
globalStyle(`${ModifierTagMain} p`, {
|
||||
margin: 0,
|
||||
textAlign: "center",
|
||||
marginBottom: "2px",
|
||||
});
|
||||
|
||||
|
||||
export const TagText = style({
|
||||
opacity: 1,
|
||||
});
|
||||
|
||||
export const TagToggle = style({
|
||||
opacity: 0.3,
|
||||
});
|
||||
|
||||
|
||||
export const ModifierActions = style({
|
||||
position: "absolute",
|
||||
top: "0",
|
||||
left: "0",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
});
|
||||
|
||||
globalStyle(`${ModifierActions} button`, {
|
||||
flexGrow: 1,
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
boxShadow: `inset 0 0 24px 0px rgb(255 255 255 / 50%)`,
|
||||
borderRadius: "5px",
|
||||
padding: "0",
|
||||
});
|
||||
|
||||
export const tagPreview = style({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
});
|
||||
|
||||
globalStyle(`${tagPreview} img`, {
|
||||
width: "90px",
|
||||
height: "100%",
|
||||
objectFit: "cover",
|
||||
objectPosition: "center",
|
||||
});
|
||||
|
@ -1,68 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { useImageCreate } from "../../../stores/imageCreateStore";
|
||||
|
||||
import {
|
||||
IconFont,
|
||||
} from "../../../styles/shared.css";
|
||||
|
||||
import {
|
||||
PromptTagMain,
|
||||
TagToggle,
|
||||
TagRemoveButton,
|
||||
PromptTagText,
|
||||
PromptTagToggle
|
||||
} from "./promptTag.css";
|
||||
|
||||
interface PromptTagProps {
|
||||
id: string;
|
||||
name: string;
|
||||
category?: string;
|
||||
previews?: string[];
|
||||
type: string;
|
||||
};
|
||||
|
||||
export default function PromptTag({ id, name, category, previews, type }: PromptTagProps) {
|
||||
|
||||
const [showToggle, setShowToggle] = useState(false);
|
||||
|
||||
const removeCreateTag = useImageCreate((state) => state.removeCreateTag);
|
||||
const changeCreateTagType = useImageCreate((state) => state.changeCreateTagType);
|
||||
|
||||
const handleHover = () => {
|
||||
setShowToggle(true);
|
||||
};
|
||||
|
||||
const handleLeave = () => {
|
||||
setShowToggle(false);
|
||||
};
|
||||
|
||||
const toggleType = () => {
|
||||
if (type === 'positive') {
|
||||
changeCreateTagType(id, 'negative');
|
||||
}
|
||||
else {
|
||||
changeCreateTagType(id, 'positive');
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemove = () => {
|
||||
console.log('remove');
|
||||
removeCreateTag(id);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={handleHover}
|
||||
onMouseLeave={handleLeave}
|
||||
className={[PromptTagMain, type].join(' ')}>
|
||||
<p className={!showToggle ? PromptTagText : PromptTagToggle}>{name}</p>
|
||||
{showToggle && <button className={TagToggle} onClick={toggleType}>
|
||||
{type === 'positive' ? <i className={[IconFont, 'fa-solid', 'fa-minus'].join(" ")}></i> : <i className={[IconFont, 'fa-solid', 'fa-plus'].join(" ")}></i>}
|
||||
</button>}
|
||||
{showToggle && <button className={TagRemoveButton} onClick={handleRemove}>
|
||||
<i className={[IconFont, 'fa-solid', 'fa-close'].join(" ")}></i>
|
||||
</button>}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,55 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import { style, globalStyle } from '@vanilla-extract/css';
|
||||
|
||||
import { XButton } from "../../../styles/shared.css";
|
||||
import { vars } from '../../../styles/theme/index.css';
|
||||
import { card } from '../../_recipes/card.css';
|
||||
|
||||
|
||||
export const PromptTagMain = style([
|
||||
card({
|
||||
backing: 'normal',
|
||||
level: 1,
|
||||
info: true
|
||||
}), {
|
||||
position: "relative",
|
||||
width: "fit-content",
|
||||
backgroundColor: `hsl(${vars.backgroundLight}, ${vars.colorMod.saturation.normal}, ${vars.colorMod.lightness.normal})`,
|
||||
padding: vars.spacing.small,
|
||||
}
|
||||
]);
|
||||
|
||||
export const PromptTagText = style({
|
||||
opacity: 1,
|
||||
fontSize: vars.fonts.sizes.Plain,
|
||||
});
|
||||
|
||||
export const PromptTagToggle = style({
|
||||
opacity: 0.3,
|
||||
fontSize: vars.fonts.sizes.Plain,
|
||||
});
|
||||
|
||||
globalStyle(`${PromptTagMain}.positive`, {
|
||||
borderColor: `hsl(${vars.brandHue}, ${vars.colorMod.saturation.normal}, ${vars.colorMod.lightness.normal})`,
|
||||
});
|
||||
|
||||
globalStyle(`${PromptTagMain}.negative`, {
|
||||
borderColor: `hsl(${vars.errorHue}, ${vars.colorMod.saturation.normal}, ${vars.colorMod.lightness.normal})`,
|
||||
});
|
||||
|
||||
export const TagToggle = style({
|
||||
position: "absolute",
|
||||
top: "0",
|
||||
right: "0",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
border: "none",
|
||||
backgroundColor: "transparent",
|
||||
boxShadow: `inset 0 0 24px 0px rgb(255 255 255 / 50%)`,
|
||||
});
|
||||
|
||||
export const TagRemoveButton = style([XButton, {
|
||||
top: '-4px',
|
||||
left: '4px',
|
||||
padding: '0',
|
||||
}]);
|
@ -1,70 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { FetchingStates, useImageFetching } from "../../../stores/imageFetchingStore";
|
||||
import { useRequestQueue } from "../../../stores/requestQueueStore";
|
||||
|
||||
export default function QueueStatusTab() {
|
||||
|
||||
|
||||
const [showBasicQueue, setShowBasicQueue] = useState(true);
|
||||
|
||||
const hasPendingQueue = useRequestQueue((state) => state.hasPendingQueue());
|
||||
const pendingRequests = useRequestQueue((state) => state.pendingRequests());
|
||||
|
||||
const status = useImageFetching((state) => state.status);
|
||||
|
||||
const step = useImageFetching((state) => state.step);
|
||||
const totalSteps = useImageFetching((state) => state.totalSteps);
|
||||
|
||||
const startTime = useImageFetching((state) => state.timeStarted);
|
||||
const timeNow = useImageFetching((state) => state.timeNow);
|
||||
const [timeRemaining, setTimeRemaining] = useState(0);
|
||||
|
||||
const [percent, setPercent] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (totalSteps > 0) {
|
||||
setPercent(Math.round((step / totalSteps) * 100));
|
||||
} else {
|
||||
setPercent(0);
|
||||
}
|
||||
}, [step, totalSteps]);
|
||||
|
||||
useEffect(() => {
|
||||
// find the remaining time
|
||||
const timeTaken = +timeNow - +startTime;
|
||||
const timePerStep = step == 0 ? 0 : timeTaken / step;
|
||||
const totalTime = timePerStep * totalSteps;
|
||||
const timeRemaining = (totalTime - timeTaken) / 1000;
|
||||
// @ts-expect-error
|
||||
setTimeRemaining(timeRemaining.toPrecision(3));
|
||||
|
||||
}, [step, totalSteps, startTime, timeNow, setTimeRemaining]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasPendingQueue) {
|
||||
setShowBasicQueue(false);
|
||||
}
|
||||
}, [status, hasPendingQueue]);
|
||||
|
||||
// {/* {showBasicQueue
|
||||
// ? <> */}
|
||||
// Queue
|
||||
// {/* </>
|
||||
// : <>
|
||||
// <span>Percent: {percent}%</span>
|
||||
// </>npm
|
||||
// } */}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<span>Queue </span>
|
||||
{hasPendingQueue && <span> Items Remaining: {pendingRequests.length} </span>}
|
||||
</>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,25 +0,0 @@
|
||||
import React from "react";
|
||||
import { doStopImage } from "../../../api";
|
||||
|
||||
|
||||
import {
|
||||
buttonStyle
|
||||
} from "../../_recipes/button.css";
|
||||
|
||||
export default function StopButton() {
|
||||
|
||||
const stopMake = async () => {
|
||||
try {
|
||||
const res = await doStopImage();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
return <button className={buttonStyle(
|
||||
{
|
||||
color: "cancel",
|
||||
size: "large",
|
||||
}
|
||||
)} onClick={() => void stopMake()}>Stop</button>;
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
import { style, globalStyle } from "@vanilla-extract/css";
|
||||
|
||||
import { vars } from "../../../styles/theme/index.css";
|
||||
import {
|
||||
card as cardStyle,
|
||||
} from '../../_recipes/card.css'
|
||||
|
||||
export const completedImagesMain = style({
|
||||
position: "relative",
|
||||
});
|
||||
|
||||
// globalStyle(`${completedImagesMain} > button`, {
|
||||
// position: "absolute",
|
||||
// top: '-29px',
|
||||
// });
|
||||
|
||||
export const completedImagesContent = style([cardStyle(), {
|
||||
height: "250px",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
padding: vars.spacing.medium,
|
||||
borderRadius: 0,
|
||||
}]);
|
||||
|
||||
export const completedImagesList = style({
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
flexWrap: "nowrap",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
overflow: "auto",
|
||||
paddingLeft: vars.spacing.none,
|
||||
});
|
||||
|
||||
globalStyle(`${completedImagesContent} li`, {
|
||||
position: "relative",
|
||||
});
|
||||
|
||||
globalStyle(`${completedImagesContent} > li:first-of-type`, {
|
||||
marginLeft: vars.spacing.medium,
|
||||
});
|
||||
|
||||
globalStyle(`${completedImagesContent} > li:last-of-type`, {
|
||||
marginRight: 0,
|
||||
});
|
||||
|
||||
export const imageContain = style({
|
||||
width: "206px",
|
||||
backgroundColor: "black",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
flexShrink: 0,
|
||||
border: "0 none",
|
||||
padding: "0",
|
||||
marginLeft: vars.spacing.medium,
|
||||
cursor: "pointer",
|
||||
});
|
||||
|
||||
globalStyle(`${imageContain} img`, {
|
||||
width: "100%",
|
||||
objectFit: "contain",
|
||||
});
|
@ -1,87 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
|
||||
import { useImageDisplay } from "../../../stores/imageDisplayStore";
|
||||
|
||||
import {
|
||||
completedImagesMain,
|
||||
completedImagesContent,
|
||||
completedImagesList,
|
||||
imageContain,
|
||||
} from "./completedImages.css";
|
||||
|
||||
import {
|
||||
buttonStyle
|
||||
} from "../../_recipes/button.css";
|
||||
|
||||
// import { Transition } from '@headlessui/react'
|
||||
|
||||
|
||||
// import {
|
||||
// tabStyles
|
||||
// } from "../../_recipes/tabs_headless.css";
|
||||
|
||||
export default function CompletedImages(
|
||||
|
||||
) {
|
||||
|
||||
const [isShowing, setIsShowing] = useState(false)
|
||||
|
||||
|
||||
const images = useImageDisplay((state) => state.images);
|
||||
const setCurrentImage = useImageDisplay((state) => state.setCurrentImage);
|
||||
const clearDisplay = useImageDisplay((state) => state.clearDisplay);
|
||||
|
||||
const removeImagesAll = () => {
|
||||
clearDisplay();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={completedImagesMain}>
|
||||
{/* <button
|
||||
className={tabStyles({})}
|
||||
onClick={() => setIsShowing((isShowing) => !isShowing)}>
|
||||
{isShowing ? "Hide History" : "Show History"}
|
||||
</button> */}
|
||||
{/* <Transition
|
||||
show={isShowing}
|
||||
> */}
|
||||
|
||||
<div className={completedImagesContent}>
|
||||
{/* Adjust the dom do we dont do this check twice */}
|
||||
{images != null && images.length > 0 && (
|
||||
<button
|
||||
className={buttonStyle()}
|
||||
onClick={() => {
|
||||
removeImagesAll();
|
||||
}}
|
||||
>
|
||||
REMOVE ALL
|
||||
</button>
|
||||
)}
|
||||
<ul className={completedImagesList}>
|
||||
{images?.map((image, index) => {
|
||||
if (void 0 === image) {
|
||||
console.warn(`image ${index} is undefined`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={image.id}>
|
||||
<button
|
||||
className={imageContain}
|
||||
onClick={() => {
|
||||
setCurrentImage(image);
|
||||
}}
|
||||
>
|
||||
<img src={image.data} alt={image.info.prompt} />
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
{/* </Transition> */}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import { style, globalStyle } from "@vanilla-extract/css";
|
||||
|
||||
import { vars } from "../../../../styles/theme/index.css";
|
||||
|
||||
export const AdvancedSettingsList = style({
|
||||
paddingLeft: 0,
|
||||
listStyleType: "none",
|
||||
});
|
||||
|
||||
export const AdvancedSettingGrouping = style({
|
||||
marginTop: vars.spacing.small,
|
||||
});
|
@ -1,119 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useImageCreate } from "../../../../../stores/imageCreateStore";
|
||||
|
||||
import { useCreateUI } from "../../creationPanelUIStore";
|
||||
|
||||
import {
|
||||
SettingItem,
|
||||
} from "../../../../../styles/shared.css";
|
||||
|
||||
import {
|
||||
buttonStyle,
|
||||
} from "../../../../_recipes/button.css";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function ImprovementSettings() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// these are conditionals that should be retired and inferred from the store
|
||||
const isUsingFaceCorrection = useImageCreate((state) =>
|
||||
state.isUsingFaceCorrection()
|
||||
);
|
||||
|
||||
const isUsingUpscaling = useImageCreate((state) => state.isUsingUpscaling());
|
||||
|
||||
const useUpscale = useImageCreate((state) =>
|
||||
state.getValueForRequestKey("use_upscale")
|
||||
);
|
||||
|
||||
const filteredOnly = useImageCreate((state) =>
|
||||
state.getValueForRequestKey("show_only_filtered_image")
|
||||
);
|
||||
|
||||
const toggleUseFaceCorrection = useImageCreate(
|
||||
(state) => state.toggleUseFaceCorrection
|
||||
);
|
||||
|
||||
const setRequestOption = useImageCreate((state) => state.setRequestOptions);
|
||||
|
||||
const improvementOpen = useCreateUI(
|
||||
(state) => state.isOpenAdvImprovementSettings
|
||||
);
|
||||
|
||||
const toggleImprovementOpen = useCreateUI(
|
||||
(state) => state.toggleAdvImprovementSettings
|
||||
);
|
||||
|
||||
const [isFilteringDisabled, setIsFilteringDisabled] = useState(false);
|
||||
// should probably be a store selector
|
||||
useEffect(() => {
|
||||
// if either are true we arent disabled
|
||||
if (isUsingFaceCorrection || useUpscale != "") {
|
||||
setIsFilteringDisabled(false);
|
||||
} else {
|
||||
setIsFilteringDisabled(true);
|
||||
}
|
||||
}, [isUsingFaceCorrection, isUsingUpscaling, setIsFilteringDisabled]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
className={buttonStyle({
|
||||
type: 'action',
|
||||
color: 'accent',
|
||||
})}
|
||||
onClick={toggleImprovementOpen}
|
||||
>
|
||||
Improvement Settings
|
||||
</button>
|
||||
{improvementOpen && (
|
||||
<>
|
||||
<div className={SettingItem}>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isUsingFaceCorrection}
|
||||
onChange={(e) => toggleUseFaceCorrection()}
|
||||
/>
|
||||
Fix incorrect faces and eyes (uses GFPGAN)
|
||||
</label>
|
||||
</div>
|
||||
<div className={SettingItem}>
|
||||
<label>
|
||||
{t("settings.ups")}
|
||||
<select
|
||||
id="upscale_model"
|
||||
name="upscale_model"
|
||||
value={useUpscale}
|
||||
onChange={(e) => {
|
||||
setRequestOption("use_upscale", e.target.value);
|
||||
}}
|
||||
>
|
||||
<option value="">{t("settings.no-ups")}</option>
|
||||
<option value="RealESRGAN_x4plus">RealESRGAN_x4plus</option>
|
||||
<option value="RealESRGAN_x4plus_anime_6B">
|
||||
RealESRGAN_x4plus_anime_6B
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div className={SettingItem}>
|
||||
<label>
|
||||
<input
|
||||
disabled={isFilteringDisabled}
|
||||
type="checkbox"
|
||||
checked={filteredOnly}
|
||||
onChange={(e) =>
|
||||
setRequestOption("show_only_filtered_image", e.target.checked)
|
||||
}
|
||||
/>
|
||||
{t("settings.corrected")}
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useCreateUI } from "../creationPanelUIStore";
|
||||
|
||||
|
||||
|
||||
import {
|
||||
card
|
||||
} from '../../../_recipes/card.css';
|
||||
|
||||
import {
|
||||
buttonStyle,
|
||||
} from "../../../_recipes/button.css";
|
||||
|
||||
import {
|
||||
AdvancedSettingsList,
|
||||
AdvancedSettingGrouping,
|
||||
} from "./advancedsettings.css";
|
||||
|
||||
import ImprovementSettings from "./improvementSettings";
|
||||
import PropertySettings from "./propertySettings";
|
||||
import WorkflowSettings from "./workflowSettings";
|
||||
|
||||
function SettingsList() {
|
||||
return (
|
||||
<ul className={AdvancedSettingsList}>
|
||||
<li className={AdvancedSettingGrouping}>
|
||||
<ImprovementSettings />
|
||||
</li>
|
||||
<li className={AdvancedSettingGrouping}>
|
||||
<PropertySettings />
|
||||
</li>
|
||||
<li className={AdvancedSettingGrouping}>
|
||||
<WorkflowSettings />
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AdvancedSettings() {
|
||||
const advancedSettingsIsOpen = useCreateUI(
|
||||
(state) => state.isOpenAdvancedSettings
|
||||
);
|
||||
|
||||
const toggleAdvancedSettingsIsOpen = useCreateUI(
|
||||
(state) => state.toggleAdvancedSettings
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={card(
|
||||
{
|
||||
level: 1,
|
||||
backing: 'normal'
|
||||
}
|
||||
)}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleAdvancedSettingsIsOpen}
|
||||
className={buttonStyle({
|
||||
type: 'action',
|
||||
color: 'secondary',
|
||||
size: 'large'
|
||||
})}
|
||||
>
|
||||
Advanced Settings
|
||||
</button>
|
||||
{advancedSettingsIsOpen && <SettingsList />}
|
||||
</div >
|
||||
);
|
||||
}
|
@ -1,211 +0,0 @@
|
||||
import React from "react";
|
||||
import { useImageCreate, SAMPLER_OPTIONS } from "../../../../../stores/imageCreateStore";
|
||||
import { useCreateUI } from "../../creationPanelUIStore";
|
||||
|
||||
import {
|
||||
SettingItem,
|
||||
} from "../../../../../styles/shared.css";
|
||||
|
||||
|
||||
import {
|
||||
buttonStyle,
|
||||
} from "../../../../_recipes/button.css";
|
||||
|
||||
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// 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 (*)" },
|
||||
];
|
||||
|
||||
export default function PropertySettings() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const setRequestOption = useImageCreate((state) => state.setRequestOptions);
|
||||
const toggleUseRandomSeed = useImageCreate(
|
||||
(state) => state.toggleUseRandomSeed
|
||||
);
|
||||
const isRandomSeed = useImageCreate((state) => state.isRandomSeed());
|
||||
|
||||
const seed = useImageCreate((state) => state.getValueForRequestKey("seed"));
|
||||
const steps = useImageCreate((state) =>
|
||||
state.getValueForRequestKey("num_inference_steps")
|
||||
);
|
||||
const guidanceScale = useImageCreate((state) =>
|
||||
state.getValueForRequestKey("guidance_scale")
|
||||
);
|
||||
|
||||
const initImage = useImageCreate((state) =>
|
||||
state.getValueForRequestKey("init_image")
|
||||
);
|
||||
|
||||
const promptStrength = useImageCreate((state) =>
|
||||
state.getValueForRequestKey("prompt_strength")
|
||||
);
|
||||
|
||||
const width = useImageCreate((state) => state.getValueForRequestKey("width"));
|
||||
const height = useImageCreate((state) =>
|
||||
state.getValueForRequestKey("height")
|
||||
);
|
||||
|
||||
const sampler = useImageCreate((state) =>
|
||||
state.getValueForRequestKey("sampler")
|
||||
);
|
||||
|
||||
const propertyOpen = useCreateUI((state) => state.isOpenAdvPropertySettings);
|
||||
const togglePropertyOpen = useCreateUI(
|
||||
(state) => state.toggleAdvPropertySettings
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button type="button" className={buttonStyle({
|
||||
type: 'action',
|
||||
color: 'accent',
|
||||
})} onClick={togglePropertyOpen}>
|
||||
Property Settings
|
||||
</button>
|
||||
{propertyOpen && (
|
||||
<>
|
||||
<div className={SettingItem}>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div className={SettingItem}>
|
||||
<label>
|
||||
{t("settings.steps")}{" "}
|
||||
<input
|
||||
value={steps}
|
||||
onChange={(e) => {
|
||||
setRequestOption("num_inference_steps", e.target.value);
|
||||
}}
|
||||
size={4}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className={SettingItem}>
|
||||
<label>
|
||||
{t("settings.guide-scale")}
|
||||
<input
|
||||
value={guidanceScale}
|
||||
onChange={(e) =>
|
||||
setRequestOption("guidance_scale", e.target.value)
|
||||
}
|
||||
type="range"
|
||||
min="0"
|
||||
max="20"
|
||||
step=".1"
|
||||
/>
|
||||
</label>
|
||||
<span>{guidanceScale}</span>
|
||||
</div>
|
||||
|
||||
{void 0 !== initImage && (
|
||||
<div className={SettingItem}>
|
||||
<label>
|
||||
{t("settings.prompt-str")}{" "}
|
||||
<input
|
||||
value={promptStrength}
|
||||
onChange={(e) =>
|
||||
setRequestOption("prompt_strength", e.target.value)
|
||||
}
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step=".05"
|
||||
/>
|
||||
</label>
|
||||
<span>{promptStrength}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={SettingItem}>
|
||||
<label>
|
||||
{t("settings.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>
|
||||
|
||||
<label>
|
||||
{t("settings.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>
|
||||
</div>
|
||||
|
||||
<div className={SettingItem}>
|
||||
<label>
|
||||
{t("settings.sampler")}
|
||||
<select
|
||||
value={sampler}
|
||||
onChange={(e) => setRequestOption("sampler", e.target.value)}
|
||||
>
|
||||
{SAMPLER_OPTIONS.map((sampler) => (
|
||||
<option key={`sampler-option_${sampler}`} value={sampler}>
|
||||
{sampler}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
import React from "react";
|
||||
import { useImageCreate } from "../../../../../stores/imageCreateStore";
|
||||
|
||||
import { useCreateUI } from "../../creationPanelUIStore";
|
||||
|
||||
import {
|
||||
SettingItem,
|
||||
} from "../../../../../styles/shared.css";
|
||||
|
||||
import {
|
||||
buttonStyle,
|
||||
} from "../../../../_recipes/button.css";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function WorkflowSettings() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const numOutputs = useImageCreate((state) =>
|
||||
state.getValueForRequestKey("num_outputs")
|
||||
);
|
||||
const parallelCount = useImageCreate((state) => state.parallelCount);
|
||||
|
||||
const setRequestOption = useImageCreate((state) => state.setRequestOptions);
|
||||
const setParallelCount = useImageCreate((state) => state.setParallelCount);
|
||||
const shouldStreamImages = useImageCreate((state) => state.getValueForRequestKey("stream_image_progress"));
|
||||
|
||||
const workflowOpen = useCreateUI((state) => state.isOpenAdvWorkflowSettings);
|
||||
const toggleWorkflowOpen = useCreateUI(
|
||||
(state) => state.toggleAdvWorkflowSettings
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button type="button" className={buttonStyle({
|
||||
type: 'action',
|
||||
color: 'accent',
|
||||
})} onClick={toggleWorkflowOpen}>
|
||||
Workflow Settings
|
||||
</button>
|
||||
{workflowOpen && (
|
||||
<>
|
||||
<div className={SettingItem}>
|
||||
<label>
|
||||
{t("settings.amount-of-img")}{" "}
|
||||
<input
|
||||
type="number"
|
||||
value={numOutputs}
|
||||
onChange={(e) =>
|
||||
setRequestOption("num_outputs", parseInt(e.target.value, 10))
|
||||
}
|
||||
size={4}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className={SettingItem}>
|
||||
<label>
|
||||
{t("settings.how-many")}
|
||||
<input
|
||||
type="number"
|
||||
value={parallelCount}
|
||||
onChange={(e) => setParallelCount(parseInt(e.target.value, 10))}
|
||||
size={4}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className={SettingItem}>
|
||||
<label>
|
||||
{t("settings.stream-img")}
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={shouldStreamImages}
|
||||
onChange={(e) =>
|
||||
setRequestOption("stream_image_progress", e.target.checked)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
import { style, globalStyle } from "@vanilla-extract/css";
|
||||
|
||||
import { card } from "../../../_recipes/card.css";
|
||||
|
||||
|
||||
export const CreationBasicMain = style([
|
||||
card({
|
||||
backing: 'normal',
|
||||
level: 1
|
||||
}), {
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
}]
|
||||
);
|
||||
|
||||
export const PromptDisplay = style({});
|
||||
|
||||
globalStyle(`${CreationBasicMain} > *`, {
|
||||
marginBottom: '10px'
|
||||
});
|
@ -1,37 +0,0 @@
|
||||
import React, { ChangeEvent } from "react";
|
||||
import { useImageCreate } from "../../../../stores/imageCreateStore";
|
||||
|
||||
import {
|
||||
CreationBasicMain,
|
||||
PromptDisplay,
|
||||
} from "./basicCreation.css";
|
||||
|
||||
import MakeButton from "../../../molecules/makeButton";
|
||||
|
||||
import PromptCreator from "./promptCreator";
|
||||
// import CreationActions from "./creationActions";
|
||||
import SeedImage from "./seedImage";
|
||||
import ActiveTags from "./promptCreator/activeTags";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function BasicCreation() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const promptText = useImageCreate((state) =>
|
||||
state.getValueForRequestKey("prompt")
|
||||
);
|
||||
const setRequestOption = useImageCreate((state) => state.setRequestOptions);
|
||||
|
||||
const handlePromptChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setRequestOption("prompt", event.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={CreationBasicMain}>
|
||||
<MakeButton></MakeButton>
|
||||
<PromptCreator></PromptCreator>
|
||||
<SeedImage></SeedImage>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
import { vars } from '../../../../../../styles/theme/index.css';
|
||||
export const ActiveTagListMain = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
gap: '10px',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'visible',
|
||||
scrollbarWidth: 'none',
|
||||
msOverflowStyle: 'none',
|
||||
'::-webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -1,39 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import { useImageCreate } from "../../../../../../stores/imageCreateStore";
|
||||
import ModifierTag from "../../../../../molecules/modifierTag";
|
||||
|
||||
// import {
|
||||
// card
|
||||
// } from '../../../../../_recipes/card.css';
|
||||
|
||||
|
||||
|
||||
import PromptTag from "../../../../../molecules/promptTag";
|
||||
|
||||
import {
|
||||
ActiveTagListMain
|
||||
} from "./activeTags.css";
|
||||
|
||||
|
||||
export default function ActiveTags() {
|
||||
const selectedtags = useImageCreate((state) => state.selectedTags());
|
||||
|
||||
const createTags = useImageCreate((state) => state.createTags);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ul className={ActiveTagListMain}>
|
||||
{createTags.map((tag) => {
|
||||
console.log(tag);
|
||||
return (
|
||||
<li key={tag.id}>
|
||||
{/* @ts-expect-error */}
|
||||
<PromptTag id={tag.id} name={tag.name} category={tag?.category} previews={tag?.previews} type={tag.type} />
|
||||
</li>)
|
||||
}
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
import React, { useState, ChangeEvent, KeyboardEventHandler, Fragment } from "react";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { Switch } from '@headlessui/react'
|
||||
|
||||
import { useImageCreate } from "../../../../../stores/imageCreateStore";
|
||||
|
||||
import ActiveTags from "./activeTags";
|
||||
|
||||
import {
|
||||
IconFont,
|
||||
} from "../../../../../styles/shared.css";
|
||||
|
||||
import {
|
||||
buttonStyle,
|
||||
} from "../../../../_recipes/button.css";
|
||||
|
||||
import {
|
||||
PromptCreatorMain,
|
||||
ToggleGroupMain,
|
||||
ToggleMain,
|
||||
ToggleLabel,
|
||||
ToggleEnabled,
|
||||
TogglePill,
|
||||
buttonRow,
|
||||
} from "./promptCreator.css";
|
||||
|
||||
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { type } from "os";
|
||||
|
||||
interface TagTypeProps {
|
||||
positive: boolean;
|
||||
setPositive: (positive: boolean) => void;
|
||||
};
|
||||
|
||||
function TagTypeToggle({ positive, setPositive }: TagTypeProps) {
|
||||
return (
|
||||
<Switch.Group as={Fragment}>
|
||||
<div className={ToggleGroupMain}>
|
||||
<Switch.Label> Type </Switch.Label>
|
||||
<Switch className={ToggleMain} checked={positive} onChange={setPositive}>
|
||||
<span
|
||||
className={TogglePill}
|
||||
>
|
||||
{positive
|
||||
? <i className={[IconFont, 'fa-solid', 'fa-plus'].join(" ")}></i>
|
||||
: <i className={[IconFont, 'fa-solid', 'fa-minus'].join(" ")}></i>}
|
||||
</span>
|
||||
</Switch>
|
||||
</div>
|
||||
</Switch.Group>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PromptCreator() {
|
||||
|
||||
const [positive, setPositive] = useState(true)
|
||||
const [tagText, setTagText] = useState('An astronaut riding a horse');
|
||||
|
||||
const addCreateTag = useImageCreate((state) => state.addCreateTag);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const checkForEnter = (event: KeyboardEventHandler<HTMLInputElement>) => {
|
||||
// @ts-expect-error
|
||||
if (event.key === "Enter") {
|
||||
if (tagText !== '') {
|
||||
const type = positive ? "positive" : "negative";
|
||||
|
||||
tagText.split(',').map((tag) => tag.trim()).forEach((tag) => {
|
||||
addCreateTag({ id: uuidv4(), name: tag, type });
|
||||
});
|
||||
//debugger;
|
||||
|
||||
setTagText('');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={PromptCreatorMain}>
|
||||
<div>
|
||||
<p>{t("home.editor-title")}</p>
|
||||
{/* @ts-expect-error */}
|
||||
<input value={tagText} onKeyDown={checkForEnter} onChange={(event) => {
|
||||
setTagText(event.target.value)
|
||||
}}></input>
|
||||
</div>
|
||||
<div className={buttonRow}>
|
||||
<button
|
||||
className={buttonStyle(
|
||||
{
|
||||
size: 'slim'
|
||||
}
|
||||
)}
|
||||
onClick={() => {
|
||||
}}
|
||||
>
|
||||
Add Prompt
|
||||
</button>
|
||||
|
||||
<TagTypeToggle positive={positive} setPositive={setPositive}></TagTypeToggle>
|
||||
</div>
|
||||
<ActiveTags></ActiveTags>
|
||||
</div >
|
||||
);
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
import { style, globalStyle } from '@vanilla-extract/css';
|
||||
|
||||
import { vars } from "../../../../../styles/theme/index.css";
|
||||
|
||||
export const PromptCreatorMain = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
marginBottom: 0,
|
||||
});
|
||||
|
||||
globalStyle(`${PromptCreatorMain} input`, {
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
globalStyle(`${PromptCreatorMain} > div`, {
|
||||
marginBottom: vars.spacing.small,
|
||||
});
|
||||
|
||||
export const ToggleGroupMain = style({
|
||||
// '--toggle-size': '30px',
|
||||
});
|
||||
|
||||
export const ToggleMain = style({
|
||||
background: vars.backgroundDark,
|
||||
height: '22px',
|
||||
borderRadius: '15px',
|
||||
width: '34px',
|
||||
border: 0,
|
||||
position: 'relative',
|
||||
display: 'inline-flex',
|
||||
padding: 0,
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: `0 0 2px 0 ${vars.backgroundDark}`,
|
||||
});
|
||||
|
||||
export const ToggleLabel = style({
|
||||
});
|
||||
|
||||
export const ToggleEnabled = style({
|
||||
});
|
||||
|
||||
globalStyle(`${ToggleMain}[data-headlessui-state="checked"]`, {
|
||||
background: vars.backgroundLight,
|
||||
});
|
||||
|
||||
export const TogglePill = style({
|
||||
display: 'inline-flex',
|
||||
height: '18px',
|
||||
width: '30px',
|
||||
borderRadius: '15px',
|
||||
background: vars.backgroundDark,
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
});
|
||||
|
||||
globalStyle(`${ToggleMain}[data-headlessui-state="checked"] ${TogglePill}`, {
|
||||
background: vars.backgroundAccentMain,
|
||||
});
|
||||
|
||||
globalStyle(`${TogglePill} p`, {
|
||||
color: vars.colors.text.normal,
|
||||
});
|
||||
|
||||
|
||||
export const buttonRow = style({
|
||||
marginTop: vars.spacing.small,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
});
|
||||
|
||||
globalStyle(`${buttonRow} > button`, {
|
||||
flexGrow: 1,
|
||||
marginRight: vars.spacing.medium,
|
||||
});
|
@ -1,108 +0,0 @@
|
||||
import React, { useRef, ChangeEvent } from "react";
|
||||
|
||||
|
||||
|
||||
import { XButton } from "../../../../../styles/shared.css";
|
||||
|
||||
import {
|
||||
ImageInputDisplay,
|
||||
InputLabel,
|
||||
ImageInput,
|
||||
ImageFixer,
|
||||
} from "./seedImage.css";
|
||||
|
||||
import {
|
||||
buttonStyle
|
||||
} from "../../../../_recipes/button.css";
|
||||
|
||||
import { useImageCreate } from "../../../../../stores/imageCreateStore";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// TODO : figure out why this needs props to be passed in.. fixes a type error
|
||||
// when the component is used in the parent component
|
||||
export default function SeedImage(_props: any) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const imageInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const initImage = useImageCreate((state) =>
|
||||
state.getValueForRequestKey("init_image")
|
||||
);
|
||||
|
||||
const isInPaintingMode = useImageCreate((state) => state.isInpainting);
|
||||
|
||||
const setRequestOption = useImageCreate((state) => state.setRequestOptions);
|
||||
|
||||
const _startFileSelect = () => {
|
||||
imageInputRef.current?.click();
|
||||
};
|
||||
const _handleFileSelect = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
// @ts-expect-error
|
||||
const file = event.target.files[0];
|
||||
|
||||
if (void 0 !== file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
if (e.target != null) {
|
||||
setRequestOption("init_image", e.target.result);
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleInpainting = useImageCreate((state) => state.toggleInpainting);
|
||||
|
||||
const _handleClearImage = () => {
|
||||
setRequestOption("init_image", undefined);
|
||||
|
||||
setRequestOption("mask", undefined);
|
||||
if (isInPaintingMode) {
|
||||
toggleInpainting();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={ImageInputDisplay}>
|
||||
<div>
|
||||
<label className={InputLabel}>
|
||||
<b>{t("home.initial-img-txt")}</b>
|
||||
</label>
|
||||
<input
|
||||
ref={imageInputRef}
|
||||
className={ImageInput}
|
||||
name="init_image"
|
||||
type="file"
|
||||
onChange={_handleFileSelect}
|
||||
/>
|
||||
<button className={buttonStyle()} onClick={_startFileSelect}>
|
||||
{t("home.initial-img-btn")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={ImageFixer}>
|
||||
{void 0 !== initImage && (
|
||||
<>
|
||||
<div>
|
||||
<img src={initImage} width="100" height="100" />
|
||||
<button className={XButton} onClick={_handleClearImage}>
|
||||
X
|
||||
</button>
|
||||
</div>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
onChange={(e) => {
|
||||
toggleInpainting();
|
||||
}}
|
||||
checked={isInPaintingMode}
|
||||
></input>
|
||||
{t("in-paint.txt")}
|
||||
</label>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import { style } from "@vanilla-extract/css";
|
||||
|
||||
import { vars } from "../../../../../styles/theme/index.css";
|
||||
|
||||
export const ImageInputDisplay = style({
|
||||
display: "flex",
|
||||
});
|
||||
|
||||
export const InputLabel = style({
|
||||
marginBottom: vars.spacing.small,
|
||||
display: "block",
|
||||
});
|
||||
|
||||
export const ImageInput = style({
|
||||
display: "none",
|
||||
});
|
||||
|
||||
|
||||
// this is needed to fix an issue with the image input text
|
||||
// when that is a drag an drop we can remove this
|
||||
export const ImageFixer = style({
|
||||
marginLeft: "20px",
|
||||
});
|
@ -1,21 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import { useCreateUI } from "../../creationPanelUIStore";
|
||||
|
||||
export default function ShowQueue() {
|
||||
|
||||
const showQueue = useCreateUI((state) => state.showQueue);
|
||||
const toggleQueue = useCreateUI((state) => state.toggleQueue);
|
||||
|
||||
return (
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showQueue}
|
||||
onChange={() => toggleQueue()}
|
||||
>
|
||||
</input>
|
||||
Display Queue
|
||||
</label>
|
||||
);
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import { style, globalStyle } from "@vanilla-extract/css";
|
||||
import { vars } from "../../../styles/theme/index.css";
|
||||
export const CreationPaneMain = style({
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
overflowY: "auto",
|
||||
overflowX: "hidden",
|
||||
});
|
||||
|
||||
globalStyle(`${CreationPaneMain} > div`, {
|
||||
marginBottom: vars.spacing.medium,
|
||||
});
|
||||
|
||||
export const InpaintingSlider = style({
|
||||
position: "absolute",
|
||||
top: "10px",
|
||||
left: "400px",
|
||||
zIndex: 1,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||
});
|
@ -1,89 +0,0 @@
|
||||
import create from "zustand";
|
||||
import produce from "immer";
|
||||
import { persist } from "zustand/middleware";
|
||||
|
||||
export interface ImageCreationUIOptions {
|
||||
isOpenAdvancedSettings: boolean;
|
||||
isOpenAdvImprovementSettings: boolean;
|
||||
isOpenAdvPropertySettings: boolean;
|
||||
isOpenAdvWorkflowSettings: boolean;
|
||||
isOpenImageModifier: boolean;
|
||||
showQueue: boolean;
|
||||
|
||||
toggleAdvancedSettings: () => void;
|
||||
toggleAdvImprovementSettings: () => void;
|
||||
toggleAdvPropertySettings: () => void;
|
||||
toggleAdvWorkflowSettings: () => void;
|
||||
|
||||
toggleImageModifier: () => void;
|
||||
toggleQueue: () => void;
|
||||
|
||||
}
|
||||
|
||||
export const useCreateUI = create<ImageCreationUIOptions>(
|
||||
//@ts-expect-error
|
||||
persist(
|
||||
(set, get) => ({
|
||||
isOpenAdvancedSettings: false,
|
||||
isOpenAdvImprovementSettings: false,
|
||||
isOpenAdvPropertySettings: false,
|
||||
isOpenAdvWorkflowSettings: false,
|
||||
isOpenImageModifier: false,
|
||||
showQueue: false,
|
||||
|
||||
toggleAdvancedSettings: () => {
|
||||
set(
|
||||
produce((state: ImageCreationUIOptions) => {
|
||||
state.isOpenAdvancedSettings = !state.isOpenAdvancedSettings;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
toggleAdvImprovementSettings: () => {
|
||||
set(
|
||||
produce((state: ImageCreationUIOptions) => {
|
||||
state.isOpenAdvImprovementSettings =
|
||||
!state.isOpenAdvImprovementSettings;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
toggleAdvPropertySettings: () => {
|
||||
set(
|
||||
produce((state: ImageCreationUIOptions) => {
|
||||
state.isOpenAdvPropertySettings = !state.isOpenAdvPropertySettings;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
toggleAdvWorkflowSettings: () => {
|
||||
set(
|
||||
produce((state: ImageCreationUIOptions) => {
|
||||
state.isOpenAdvWorkflowSettings = !state.isOpenAdvWorkflowSettings;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
toggleImageModifier: () => {
|
||||
set(
|
||||
produce((state: ImageCreationUIOptions) => {
|
||||
state.isOpenImageModifier = !state.isOpenImageModifier;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
toggleQueue: () => {
|
||||
set(
|
||||
produce((state: ImageCreationUIOptions) => {
|
||||
state.showQueue = !state.showQueue;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
}),
|
||||
{
|
||||
name: "createUI",
|
||||
// getStorage: () => localStorage,
|
||||
}
|
||||
)
|
||||
);
|
@ -1,33 +0,0 @@
|
||||
import { style, globalStyle } from "@vanilla-extract/css";
|
||||
import { vars } from "../../../../styles/theme/index.css";
|
||||
|
||||
export const ImagerModifierGroups = style({
|
||||
// marginBottom: vars.spacing.small,
|
||||
paddingLeft: 0,
|
||||
listStyleType: "none",
|
||||
});
|
||||
|
||||
globalStyle(`${ImagerModifierGroups} li`, {
|
||||
marginTop: vars.spacing.medium,
|
||||
});
|
||||
|
||||
export const ImageModifierGrouping = style({
|
||||
marginTop: vars.spacing.medium,
|
||||
});
|
||||
|
||||
globalStyle(`${ImageModifierGrouping} h4`, {
|
||||
fontSize: vars.fonts.sizes.Plain,
|
||||
});
|
||||
|
||||
|
||||
export const ModifierListStyle = style({
|
||||
paddingLeft: 0,
|
||||
listStyleType: "none",
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: vars.spacing.small,
|
||||
});
|
||||
|
||||
globalStyle(`${ModifierListStyle} li`, {
|
||||
margin: 0,
|
||||
});
|
@ -1,115 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import {
|
||||
MenuButton,
|
||||
} from "../../../../styles/shared.css";
|
||||
|
||||
import {
|
||||
card
|
||||
} from '../../../_recipes/card.css';
|
||||
|
||||
import {
|
||||
buttonStyle,
|
||||
} from "../../../_recipes/button.css";
|
||||
|
||||
import {
|
||||
ImagerModifierGroups,
|
||||
ImageModifierGrouping,
|
||||
ModifierListStyle,
|
||||
} from "./imageModifiers.css";
|
||||
|
||||
import { ModifierObject, useImageCreate } from "../../../../stores/imageCreateStore";
|
||||
import { useCreateUI } from "../creationPanelUIStore";
|
||||
|
||||
import ModifierTag from "../../../molecules/modifierTag";
|
||||
|
||||
interface ModifierListProps {
|
||||
category: string;
|
||||
tags: ModifierObject[];
|
||||
}
|
||||
|
||||
function ModifierList({ tags, category }: ModifierListProps) {
|
||||
return (
|
||||
<ul className={ModifierListStyle}>
|
||||
{tags.map((tag) => (
|
||||
<li key={tag.modifier}>
|
||||
<ModifierTag category={category} name={tag.modifier} previews={tag.previews} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
interface ModifierGroupingProps {
|
||||
title: string;
|
||||
category: string;
|
||||
tags: ModifierObject[];
|
||||
}
|
||||
|
||||
function ModifierGrouping({ title, category, tags }: ModifierGroupingProps) {
|
||||
// doing this localy for now, but could move to a store
|
||||
// and persist if we wanted to
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
const _toggleExpand = () => {
|
||||
setIsExpanded(!isExpanded);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={ImageModifierGrouping}>
|
||||
<button type="button" className={buttonStyle({
|
||||
type: 'action',
|
||||
color: 'accent',
|
||||
})} onClick={_toggleExpand}>
|
||||
<h4>{title}</h4>
|
||||
</button>
|
||||
{isExpanded && <ModifierList category={category} tags={tags} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ImageModifers() {
|
||||
const allModifiers = useImageCreate((state) => state.allModifiers);
|
||||
|
||||
const imageModifierIsOpen = useCreateUI((state) => state.isOpenImageModifier);
|
||||
const toggleImageModifiersIsOpen = useCreateUI(
|
||||
(state) => state.toggleImageModifier
|
||||
);
|
||||
|
||||
const handleClick = () => {
|
||||
toggleImageModifiersIsOpen();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={card(
|
||||
{
|
||||
level: 1,
|
||||
backing: 'normal'
|
||||
}
|
||||
)}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClick}
|
||||
className={buttonStyle({
|
||||
type: 'action',
|
||||
color: 'secondary',
|
||||
size: 'large'
|
||||
})}
|
||||
>
|
||||
Image Modifiers
|
||||
</button>
|
||||
|
||||
{imageModifierIsOpen && (
|
||||
<ul className={ImagerModifierGroups}>
|
||||
{allModifiers.map((item, index) => {
|
||||
return (
|
||||
<li key={item.category}>
|
||||
<ModifierGrouping title={item.category} category={item.category} tags={item.modifiers} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
const Mockifiers = [
|
||||
[
|
||||
"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"]],
|
||||
];
|
||||
|
||||
export default Mockifiers;
|
@ -1,43 +0,0 @@
|
||||
import React, { ChangeEvent } from "react";
|
||||
|
||||
import BasicCreation from "./basicCreation";
|
||||
import AdvancedSettings from "./advancedSettings";
|
||||
import ImageModifiers from "./imageModifiers";
|
||||
import InpaintingPanel from "./inpaintingPanel";
|
||||
|
||||
import QueueDisplay from "../queueDisplay";
|
||||
|
||||
// this works but causes type errors so its not worth it for now
|
||||
// import { useImageCreate } from "@stores/imageCreateStore.ts";
|
||||
|
||||
import { useImageCreate } from "../../../stores/imageCreateStore";
|
||||
import { useRequestQueue } from "../../../stores/requestQueueStore";
|
||||
|
||||
|
||||
import {
|
||||
CreationPaneMain,
|
||||
InpaintingSlider,
|
||||
} from "./creationPanel.css";
|
||||
|
||||
|
||||
export default function CreationPanel() {
|
||||
const isInPaintingMode = useImageCreate((state) => state.isInpainting);
|
||||
|
||||
const hasQueue = useRequestQueue((state) => state.hasAnyQueue());
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={CreationPaneMain}>
|
||||
<BasicCreation></BasicCreation>
|
||||
<AdvancedSettings></AdvancedSettings>
|
||||
<ImageModifiers></ImageModifiers>
|
||||
</div>
|
||||
|
||||
{isInPaintingMode && (
|
||||
<div className={InpaintingSlider}>
|
||||
<InpaintingPanel></InpaintingPanel>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
import React, { useRef, useState, ChangeEvent } from "react";
|
||||
import DrawImage from "../../../molecules/drawImage";
|
||||
|
||||
import { useImageCreate } from "../../../../stores/imageCreateStore";
|
||||
|
||||
import {
|
||||
InpaintingPanelMain,
|
||||
InpaintingControls,
|
||||
InpaintingControlRow,
|
||||
} from "./inpaintingPanel.css";
|
||||
|
||||
export default function InpaintingPanel() {
|
||||
// no idea if this is the right typing
|
||||
// const drawingRef = useRef(null);
|
||||
|
||||
const [brushSize, setBrushSize] = useState("20");
|
||||
const [brushShape, setBrushShape] = useState("round");
|
||||
const [brushColor, setBrushColor] = useState("#fff");
|
||||
const [isErasing, setIsErasing] = useState(false);
|
||||
|
||||
const initImage = useImageCreate((state) =>
|
||||
state.getValueForRequestKey("init_image")
|
||||
);
|
||||
|
||||
const setRequestOption = useImageCreate((state) => state.setRequestOptions);
|
||||
|
||||
const setMask = (data: string) => {
|
||||
setRequestOption("mask", data);
|
||||
}
|
||||
|
||||
|
||||
const _handleBrushMask = () => {
|
||||
setIsErasing(false);
|
||||
};
|
||||
|
||||
const _handleBrushErase = () => {
|
||||
setIsErasing(true);
|
||||
};
|
||||
|
||||
|
||||
const _handleBrushSize = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setBrushSize(event.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={InpaintingPanelMain}>
|
||||
<DrawImage
|
||||
// ref={drawingRef}
|
||||
imageData={initImage}
|
||||
// @ts-expect-error
|
||||
brushSize={brushSize}
|
||||
brushShape={brushShape}
|
||||
brushColor={brushColor}
|
||||
isErasing={isErasing}
|
||||
setData={setMask}
|
||||
/>
|
||||
<div className={InpaintingControls}>
|
||||
<div className={InpaintingControlRow}>
|
||||
<button onClick={_handleBrushMask}>Mask</button>
|
||||
<button onClick={_handleBrushErase}>Erase</button>
|
||||
{/* <button disabled onClick={_handleFillMask}>
|
||||
Fill
|
||||
</button>
|
||||
<button disabled onClick={_handleClearAll}>
|
||||
Clear
|
||||
</button> */}
|
||||
|
||||
<label>
|
||||
Brush Size
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="100"
|
||||
value={brushSize}
|
||||
onChange={_handleBrushSize}
|
||||
></input>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className={InpaintingControlRow}>
|
||||
<button
|
||||
onClick={() => {
|
||||
setBrushShape("round");
|
||||
}}
|
||||
>
|
||||
Cirle Brush
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setBrushShape("square");
|
||||
}}
|
||||
>
|
||||
Square Brush
|
||||
</button>
|
||||
|
||||
{/* <button
|
||||
onClick={() => {
|
||||
setBrushColor("#000");
|
||||
}}
|
||||
>
|
||||
Dark Brush
|
||||
</button> */}
|
||||
{/* <button
|
||||
onClick={() => {
|
||||
setBrushColor("#fff");
|
||||
}}
|
||||
>
|
||||
Light Brush
|
||||
</button> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
import { style } from "@vanilla-extract/css";
|
||||
|
||||
export const InpaintingPanelMain = style({
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
padding: "10px 10px",
|
||||
});
|
||||
|
||||
export const InpaintingControls = style({
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
flexWrap: "wrap",
|
||||
});
|
||||
|
||||
export const InpaintingControlRow = style({
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-evenly",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
|
||||
":first-of-type": {
|
||||
margin: "10px 0",
|
||||
},
|
||||
});
|
@ -1,12 +0,0 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
|
||||
// handles all 3
|
||||
export const currentDisplayMain = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
padding: '0 0 0 0',
|
||||
})
|
@ -1,40 +0,0 @@
|
||||
import { style, globalStyle } from '@vanilla-extract/css'
|
||||
|
||||
import { vars } from '../../../../styles/theme/index.css'
|
||||
|
||||
export const imageDisplayMain = style({
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
});
|
||||
|
||||
export const imageDisplayContainer = style({
|
||||
height: '100%',
|
||||
width: '80%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
});
|
||||
|
||||
export const imageDisplayCenter = style({
|
||||
width: '100%',
|
||||
maxWidth: '1000px',
|
||||
position: 'relative',
|
||||
});
|
||||
|
||||
export const imageDisplayContent = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
});
|
||||
|
||||
globalStyle(`${imageDisplayContent} > div`, {
|
||||
marginBottom: vars.spacing.medium,
|
||||
});
|
||||
|
||||
globalStyle(`${imageDisplayContent} p`, {
|
||||
marginBottom: vars.spacing.small,
|
||||
});
|
||||
|
||||
globalStyle(`${imageDisplayContent} button`, {
|
||||
marginRight: vars.spacing.medium,
|
||||
});
|
@ -1,100 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import React from "react";
|
||||
import { useImageCreate } from "../../../../stores/imageCreateStore";
|
||||
import { CompletedImagesType } from "../../../../stores/imageDisplayStore";
|
||||
|
||||
import GeneratedImage from "../../../molecules/generatedImage";
|
||||
|
||||
import {
|
||||
imageDisplayMain,
|
||||
imageDisplayContainer,
|
||||
imageDisplayCenter,
|
||||
imageDisplayContent,
|
||||
} from './imageDisplay.css';
|
||||
|
||||
|
||||
import {
|
||||
buttonStyle
|
||||
} from "../../../_recipes/button.css";
|
||||
|
||||
export default function ImageDisplay({ info, data }: CompletedImagesType) {
|
||||
|
||||
const createFileName = () => {
|
||||
const {
|
||||
prompt,
|
||||
negative_prompt,
|
||||
seed,
|
||||
num_inference_steps,
|
||||
guidance_scale,
|
||||
use_face_correction,
|
||||
use_upscale,
|
||||
width,
|
||||
height,
|
||||
} = info;
|
||||
|
||||
// Most important information is the prompt
|
||||
let underscoreName = prompt.replace(/[^a-zA-Z0-9]/g, "_");
|
||||
underscoreName = underscoreName.substring(0, 100);
|
||||
// name and the top level metadata
|
||||
let fileName = `${underscoreName}_Seed-${seed}_Steps-${num_inference_steps}_Guidance-${guidance_scale}`;
|
||||
// Add the face correction and upscale
|
||||
if (typeof use_face_correction == "string") {
|
||||
fileName += `_FaceCorrection-${use_face_correction}`;
|
||||
}
|
||||
if (typeof use_upscale == "string") {
|
||||
fileName += `_Upscale-${use_upscale}`;
|
||||
}
|
||||
// Add the width and height
|
||||
fileName += `_${width}x${height}`;
|
||||
// add the file extension
|
||||
fileName += ".png";
|
||||
// return fileName
|
||||
return fileName;
|
||||
};
|
||||
|
||||
const setRequestOption = useImageCreate((state) => state.setRequestOptions);
|
||||
|
||||
const _handleSave = () => {
|
||||
const link = document.createElement("a");
|
||||
link.download = createFileName();
|
||||
link.href = data ?? "";
|
||||
link.click();
|
||||
};
|
||||
|
||||
const _handleUseAsInput = () => {
|
||||
setRequestOption("init_image", data);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={imageDisplayMain}>
|
||||
|
||||
<div className={imageDisplayContainer}>
|
||||
|
||||
<div className={imageDisplayCenter}>
|
||||
|
||||
<div className={imageDisplayContent}>
|
||||
<div>
|
||||
<p> {info?.prompt}</p>
|
||||
<p> {info?.negative_prompt}</p>
|
||||
<div>
|
||||
<button className={buttonStyle(
|
||||
|
||||
)} onClick={_handleSave}>Save</button>
|
||||
<button className={buttonStyle(
|
||||
{
|
||||
color: "secondary",
|
||||
type: "outline",
|
||||
}
|
||||
)} onClick={_handleUseAsInput}>Use as Input</button>
|
||||
</div>
|
||||
</div>
|
||||
<GeneratedImage imageData={data} metadata={info}></GeneratedImage>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,58 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { FetchingStates, useImageFetching } from "../../../stores/imageFetchingStore";
|
||||
import { useImageDisplay } from "../../../stores/imageDisplayStore";
|
||||
|
||||
import { API_URL } from "../../../api";
|
||||
|
||||
import {
|
||||
currentDisplayMain,
|
||||
} from './currentDisplay.css';
|
||||
|
||||
import ImageDisplay from "./imageDisplay";
|
||||
|
||||
const IdleDisplay = () => {
|
||||
return (
|
||||
<h4 className="no-image">Try Making a new image!</h4>
|
||||
);
|
||||
};
|
||||
|
||||
const LoadingDisplay = ({ images }: { images: string[] }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{images.map((image, index) => {
|
||||
if (index == images.length - 1) {
|
||||
return (
|
||||
<img src={`${API_URL}${image}`} key={index} />
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default function CurrentDisplay() {
|
||||
|
||||
const status = useImageFetching((state) => state.status);
|
||||
const currentImage = useImageDisplay((state) => state.currentImage);
|
||||
|
||||
const progressImages = useImageFetching((state) => state.progressImages);
|
||||
|
||||
return (
|
||||
<div className={currentDisplayMain}>
|
||||
|
||||
{(currentImage == null) && <IdleDisplay />}
|
||||
{/* {(status === FetchingStates.FETCHING || status === FetchingStates.PROGRESSING) && <LoadingDisplay />}
|
||||
{(currentImage != null) && <ImageDisplay info={currentImage?.info} data={currentImage?.data} />} */}
|
||||
|
||||
{
|
||||
(progressImages.length > 0)
|
||||
? <LoadingDisplay images={progressImages} />
|
||||
: (currentImage != null) && <ImageDisplay info={currentImage?.info} data={currentImage?.data} />
|
||||
}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
|
||||
import {
|
||||
card as cardStyles,
|
||||
} from '../../_recipes/card.css'
|
||||
|
||||
export const currentInfoMain = style([
|
||||
cardStyles(
|
||||
{
|
||||
backing: 'dark',
|
||||
}
|
||||
),
|
||||
{
|
||||
// display: 'flex',
|
||||
// flexDirection: 'column',
|
||||
// justifyContent: 'center',
|
||||
// alignItems: 'center',
|
||||
// height: '100%',
|
||||
width: '250px',
|
||||
padding: '0 0 0 0',
|
||||
},
|
||||
])
|
@ -1,7 +0,0 @@
|
||||
import React from "react";
|
||||
import { currentInfoMain } from "./currentInfo.css";
|
||||
export default function CurrentInfo() {
|
||||
return <div className={
|
||||
currentInfoMain
|
||||
}> current info</div>;
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
import { style, globalStyle } from "@vanilla-extract/css";
|
||||
|
||||
import { vars } from "../../../styles/theme/index.css";
|
||||
|
||||
export const FooterDisplayMain = style({
|
||||
color: vars.colors.text.normal,
|
||||
fontSize: vars.fonts.sizes.Caption,
|
||||
|
||||
display: "inline-block",
|
||||
// marginTop: vars.spacing.medium,
|
||||
// marginBottom: vars.spacing.medium,
|
||||
// TODO move this to the theme
|
||||
padding: vars.spacing.small,
|
||||
boxShadow:
|
||||
"0 4px 8px 0 rgba(0, 0, 0, 0.15), 0 6px 20px 0 rgba(0, 0, 0, 0.15)",
|
||||
});
|
||||
|
||||
export const CoffeeButton = style({
|
||||
height: "23px",
|
||||
transform: "translateY(25%)",
|
||||
});
|
||||
|
||||
globalStyle(`${FooterDisplayMain} a`, {
|
||||
color: vars.colors.link,
|
||||
textDecoration: "none",
|
||||
});
|
||||
|
||||
globalStyle(`${FooterDisplayMain} a:hover`, {
|
||||
textDecoration: "underline",
|
||||
});
|
||||
|
||||
globalStyle(`${FooterDisplayMain} a:visited`, {
|
||||
color: vars.colors.link,
|
||||
});
|
||||
|
||||
globalStyle(`${FooterDisplayMain} a:active`, {
|
||||
color: vars.colors.link,
|
||||
});
|
||||
|
||||
globalStyle(`${FooterDisplayMain} a:focus`, {
|
||||
color: vars.colors.link,
|
||||
});
|
||||
|
||||
globalStyle(`${FooterDisplayMain} p`, {
|
||||
margin: vars.spacing.min,
|
||||
});
|
||||
|
||||
// .footer-display {
|
||||
// color: #ffffff;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
// }
|
||||
|
||||
// #coffeeButton {
|
||||
// height: 23px;
|
||||
// transform: translateY(25%);
|
||||
// }
|
@ -1,71 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
FooterDisplayMain,
|
||||
CoffeeButton,
|
||||
} from "./footerDisplay.css";
|
||||
|
||||
import { API_URL } from "../../../api";
|
||||
|
||||
export default function FooterDisplay() {
|
||||
return (
|
||||
<div className={FooterDisplayMain}>
|
||||
<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"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img src={`${API_URL}/kofi.png`} className={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"
|
||||
rel="noreferrer"
|
||||
>
|
||||
discord community
|
||||
</a>{" "}
|
||||
or{" "}
|
||||
<a
|
||||
href="https://github.com/cmdr2/stable-diffusion-ui/issues"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
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"
|
||||
rel="noreferrer"
|
||||
>
|
||||
the license
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<p>
|
||||
By using this software, you consent to the terms and conditions of the
|
||||
license.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
import { style, globalStyle } from "@vanilla-extract/css";
|
||||
import { vars } from "../../../styles/theme/index.css";
|
||||
|
||||
export const HeaderDisplayMain = style({
|
||||
color: vars.colors.text.normal,
|
||||
display: "flex",
|
||||
justifyContent: "space-between"
|
||||
});
|
||||
|
||||
globalStyle(`${HeaderDisplayMain} > h1`, {
|
||||
fontSize: vars.fonts.sizes.Title,
|
||||
fontWeight: "bold",
|
||||
marginRight: vars.spacing.medium,
|
||||
});
|
||||
|
||||
|
||||
export const HeaderTitle = style({
|
||||
marginLeft: vars.spacing.large,
|
||||
});
|
||||
|
||||
export const HeaderLinks = style({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexGrow: 1,
|
||||
justifyContent: "space-between",
|
||||
maxWidth: "300px",
|
||||
marginRight: vars.spacing.large,
|
||||
});
|
@ -1,5 +0,0 @@
|
||||
import { style } from "@vanilla-extract/css";
|
||||
|
||||
export const HelpContent = style({
|
||||
width: '300px',
|
||||
});
|
@ -1,61 +0,0 @@
|
||||
import React from "react";
|
||||
import { Popover } from '@headlessui/react';
|
||||
// import { useTranslation } from "react-i18next";
|
||||
|
||||
import {
|
||||
PopoverMain,
|
||||
PopoverButtonStyle,
|
||||
PopoverPanelMain,
|
||||
} from "../../../_recipes/popover_headless.css";
|
||||
|
||||
import {
|
||||
card
|
||||
} from '../../../_recipes/card.css';
|
||||
|
||||
import {
|
||||
IconFont,
|
||||
SettingItem
|
||||
} from "../../../../styles/shared.css";
|
||||
|
||||
import {
|
||||
HelpContent
|
||||
} from "./helpOptions.css";
|
||||
|
||||
export default function HelpOptions() {
|
||||
|
||||
return (
|
||||
<Popover className={PopoverMain}>
|
||||
<Popover.Button className={PopoverButtonStyle}>
|
||||
<i className={[IconFont, 'fa-solid', 'fa-comments'].join(" ")}></i>
|
||||
Help & Community
|
||||
</Popover.Button>
|
||||
|
||||
<Popover.Panel className={PopoverPanelMain}>
|
||||
<div className={HelpContent}>
|
||||
<ul>
|
||||
<li className={SettingItem}>
|
||||
<a href="https://github.com/cmdr2/stable-diffusion-ui/blob/main/Troubleshooting.md" target="_blank" rel="noreferrer">
|
||||
<i className={[IconFont, 'fa-solid', 'fa-circle-question'].join(" ")}></i> Usual Problems and Solutions
|
||||
</a>
|
||||
</li>
|
||||
<li className={SettingItem}>
|
||||
<a href="https://discord.com/invite/u9yhsFmEkB" target="_blank" rel="noreferrer">
|
||||
<i className={[IconFont, 'fa-brands', 'fa-discord'].join(" ")}></i> Discord user Community
|
||||
</a>
|
||||
</li>
|
||||
<li className={SettingItem}>
|
||||
<a href="https://old.reddit.com/r/StableDiffusionUI/" target="_blank" rel="noreferrer">
|
||||
<i className={[IconFont, 'fa-brands', 'fa-reddit'].join(" ")}></i> Reddit Community
|
||||
</a>
|
||||
</li>
|
||||
<li className={SettingItem}>
|
||||
<a href="https://github.com/cmdr2/stable-diffusion-ui " target="_blank" rel="noreferrer">
|
||||
<i className={[IconFont, 'fa-brands', 'fa-github'].join(" ")}></i> Source Code on Github
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
);
|
||||
};
|
@ -1,62 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { KEY_CONFIG, getConfig } from "../../../api";
|
||||
|
||||
import StatusDisplay from "./statusDisplay";
|
||||
|
||||
import HelpOptions from "./helpOptions";
|
||||
import SystemSettings from "./systemSettings";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import {
|
||||
HeaderDisplayMain,
|
||||
HeaderTitle,
|
||||
HeaderLinks,
|
||||
} from "./headerDisplay.css";
|
||||
|
||||
// import LanguageDropdown from "./languageDropdown";
|
||||
|
||||
export default function HeaderDisplay() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { status, data } = useQuery([KEY_CONFIG], getConfig);
|
||||
|
||||
const [version, setVersion] = useState("2.1.0");
|
||||
const [release, setRelease] = useState("");
|
||||
|
||||
// this is also in the Beta Mode
|
||||
// TODO: make this a custom hook
|
||||
useEffect(() => {
|
||||
if (status === "success") {
|
||||
// TODO also pass down the actual version
|
||||
const { update_branch: updateBranch } = data;
|
||||
|
||||
// just hard coded for now
|
||||
setVersion("v2.1");
|
||||
|
||||
if (updateBranch === "main") {
|
||||
setRelease("(stable)");
|
||||
} else {
|
||||
setRelease("(beta)");
|
||||
}
|
||||
}
|
||||
}, [status, data, setVersion, setVersion]);
|
||||
|
||||
return (
|
||||
<div className={HeaderDisplayMain}>
|
||||
<div className={HeaderTitle}>
|
||||
<h1>
|
||||
{t("title")} {version} {release}{" "}
|
||||
</h1>
|
||||
<StatusDisplay className="status-display"></StatusDisplay>
|
||||
</div>
|
||||
<div className={HeaderLinks}>
|
||||
<HelpOptions></HelpOptions>
|
||||
<SystemSettings></SystemSettings>
|
||||
</div>
|
||||
{/* <LanguageDropdown></LanguageDropdown> */}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const LanguageDropdown = () => {
|
||||
const { i18n } = useTranslation();
|
||||
const [language, setLanguage] = useState("id");
|
||||
|
||||
const handleLangChange = (evt: any) => {
|
||||
const lang = evt.target.value;
|
||||
console.log(lang);
|
||||
setLanguage(lang);
|
||||
i18n.changeLanguage(lang);
|
||||
};
|
||||
|
||||
return (
|
||||
<select onChange={handleLangChange} value={language}>
|
||||
<option value="en">EN</option>
|
||||
<option value="es">ES</option>
|
||||
</select>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguageDropdown;
|
@ -1,57 +0,0 @@
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { healthPing, HEALTH_PING_INTERVAL } from "../../../../api";
|
||||
|
||||
import AudioDing from "../../../molecules/audioDing";
|
||||
|
||||
import {
|
||||
StartingStatus,
|
||||
ErrorStatus,
|
||||
SuccessStatus,
|
||||
} from "./statusDisplay.css";
|
||||
|
||||
const startingMessage = "Stable Diffusion is starting...";
|
||||
const successMessage = "Stable Diffusion is ready to use!";
|
||||
const errorMessage = "Stable Diffusion is not running!";
|
||||
|
||||
export default function StatusDisplay({ className }: { className?: string }) {
|
||||
const [statusMessage, setStatusMessage] = useState(startingMessage);
|
||||
const [statusClass, setStatusClass] = useState(StartingStatus);
|
||||
|
||||
const dingRef = useRef<HTMLAudioElement>();
|
||||
|
||||
// but this will be moved to the status display when it is created
|
||||
const { status, data } = useQuery(["health"], healthPing, {
|
||||
refetchInterval: HEALTH_PING_INTERVAL,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (status === "loading") {
|
||||
setStatusMessage(startingMessage);
|
||||
setStatusClass(StartingStatus);
|
||||
} else if (status === "error") {
|
||||
setStatusMessage(errorMessage);
|
||||
setStatusClass(ErrorStatus);
|
||||
} else if (status === "success") {
|
||||
if (data[0] === "OK") {
|
||||
setStatusMessage(successMessage);
|
||||
setStatusClass(SuccessStatus);
|
||||
// catch an auto play error
|
||||
dingRef.current?.play().catch((e) => {
|
||||
console.log('DING!')
|
||||
});
|
||||
} else {
|
||||
setStatusMessage(errorMessage);
|
||||
setStatusClass(ErrorStatus);
|
||||
}
|
||||
}
|
||||
}, [status, data, dingRef]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AudioDing ref={dingRef}></AudioDing>
|
||||
<p className={[statusClass, className].join(" ")}>{statusMessage}</p>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
import { style } from "@vanilla-extract/css";
|
||||
|
||||
import { vars } from "../../../../styles/theme/index.css";
|
||||
|
||||
export const StartingStatus = style({
|
||||
color: `hsl(${vars.warningHue}, ${vars.colorMod.saturation.normal}, ${vars.colorMod.lightness.normal})`,
|
||||
});
|
||||
|
||||
export const ErrorStatus = style({
|
||||
color: `hsl(${vars.errorHue}, ${vars.colorMod.saturation.normal}, ${vars.colorMod.lightness.normal})`,
|
||||
});
|
||||
|
||||
export const SuccessStatus = style({
|
||||
color: `hsl(${vars.successHue}, ${vars.colorMod.saturation.normal}, ${vars.colorMod.lightness.normal})`,
|
||||
});
|
@ -1,162 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
|
||||
import React from "react";
|
||||
import { Popover } from '@headlessui/react';
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useImageCreate } from "../../../../stores/imageCreateStore";
|
||||
|
||||
import BetaMode from "../../../molecules/betaMode";
|
||||
|
||||
|
||||
import {
|
||||
IconFont,
|
||||
SettingItem
|
||||
} from "../../../../styles/shared.css";
|
||||
|
||||
import {
|
||||
PopoverMain,
|
||||
PopoverButtonStyle,
|
||||
PopoverPanelMain,
|
||||
} from "../../../_recipes/popover_headless.css";
|
||||
|
||||
import {
|
||||
SettingContent
|
||||
} from "./systemSettings.css";
|
||||
|
||||
// import {
|
||||
// SwitchGroupMain,
|
||||
// SwitchMain,
|
||||
// SwitchLabel,
|
||||
// SwitchEnabled,
|
||||
// SwitchPill,
|
||||
// } from "../../../_headless/switch/index.css";
|
||||
|
||||
|
||||
export default function SystemSettings() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isUseAutoSave = useImageCreate((state) => state.isUseAutoSave());
|
||||
const diskPath = useImageCreate((state) =>
|
||||
state.getValueForRequestKey("save_to_disk_path")
|
||||
);
|
||||
|
||||
const turbo = useImageCreate((state) => state.getValueForRequestKey("turbo"));
|
||||
const useCpu = useImageCreate((state) =>
|
||||
state.getValueForRequestKey("use_cpu")
|
||||
);
|
||||
const useFullPrecision = useImageCreate((state) =>
|
||||
state.getValueForRequestKey("use_full_precision")
|
||||
);
|
||||
|
||||
const isSoundEnabled = useImageCreate((state) => state.isSoundEnabled());
|
||||
|
||||
const setRequestOption = useImageCreate((state) => state.setRequestOptions);
|
||||
const toggleUseAutoSave = useImageCreate((state) => state.toggleUseAutoSave);
|
||||
const toggleSoundEnabled = useImageCreate(
|
||||
(state) => state.toggleSoundEnabled
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover className={PopoverMain}>
|
||||
<Popover.Button className={PopoverButtonStyle}>
|
||||
<i className={[IconFont, 'fa-solid', 'fa-gear'].join(" ")}></i>
|
||||
Settings
|
||||
</Popover.Button>
|
||||
|
||||
<Popover.Panel className={PopoverPanelMain}>
|
||||
<div className={SettingContent}>
|
||||
<h4>System Settings</h4>
|
||||
<ul>
|
||||
<li className={SettingItem}>
|
||||
<label>
|
||||
<input
|
||||
checked={isUseAutoSave}
|
||||
onChange={(e) => toggleUseAutoSave()}
|
||||
type="checkbox"
|
||||
/>
|
||||
{t("storage.ast")}{" "}
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
value={diskPath}
|
||||
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>
|
||||
<li className={SettingItem}>
|
||||
<label>
|
||||
<input
|
||||
checked={isSoundEnabled}
|
||||
onChange={(e) => toggleSoundEnabled()}
|
||||
type="checkbox"
|
||||
/>
|
||||
{t("advanced-settings.sound")}
|
||||
</label>
|
||||
|
||||
|
||||
{/* <Switch.Group>
|
||||
<Switch.Label passive> <>{t("advanced-settings.sound")}</> </Switch.Label>
|
||||
<Switch checked={isSoundEnabled} onChange={toggleSoundEnabled} className={SwitchMain}>
|
||||
<span
|
||||
className={SwitchPill}
|
||||
/>
|
||||
</Switch>
|
||||
</Switch.Group> */}
|
||||
</li>
|
||||
|
||||
|
||||
<li className={SettingItem}>
|
||||
<label>
|
||||
<input
|
||||
checked={turbo}
|
||||
onChange={(e) => setRequestOption("turbo", e.target.checked)}
|
||||
type="checkbox"
|
||||
/>
|
||||
{t("advanced-settings.turbo")} {t("advanced-settings.turbo-disc")}
|
||||
</label>
|
||||
</li>
|
||||
<li className={SettingItem}>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={useCpu}
|
||||
onChange={(e) => setRequestOption("use_cpu", e.target.checked)}
|
||||
/>
|
||||
{t("advanced-settings.cpu")} {t("advanced-settings.cpu-disc")}
|
||||
</label>
|
||||
</li>
|
||||
<li className={SettingItem}>
|
||||
<label>
|
||||
<input
|
||||
checked={useFullPrecision}
|
||||
onChange={(e) =>
|
||||
setRequestOption("use_full_precision", e.target.checked)
|
||||
}
|
||||
type="checkbox"
|
||||
/>
|
||||
{t("advanced-settings.gpu")} {t("advanced-settings.gpu-disc")}
|
||||
</label>
|
||||
</li>
|
||||
|
||||
<li className={SettingItem}>
|
||||
<BetaMode />
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
|
||||
);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
import { style } from "@vanilla-extract/css";
|
||||
|
||||
export const SettingContent = style({
|
||||
width: '480px',
|
||||
});
|
@ -1,52 +0,0 @@
|
||||
import React from "react";
|
||||
import { ImageRequest } from "../../../api";
|
||||
|
||||
import { QueuedRequest, useRequestQueue } from "../../../stores/requestQueueStore";
|
||||
|
||||
import {
|
||||
QueueDisplayMain,
|
||||
QueueListButtons,
|
||||
} from "./queueDisplay.css";
|
||||
|
||||
import {
|
||||
buttonStyle
|
||||
} from "../../_recipes/button.css";
|
||||
|
||||
import ClearQueue from "../../molecules/clearQueue";
|
||||
import QueueItem from "./queueItem";
|
||||
|
||||
export default function QueueDisplay() {
|
||||
|
||||
const requests: QueuedRequest[] = useRequestQueue((state) => state.requests);
|
||||
const removeCompleted = useRequestQueue((state) => state.removeCompleted);
|
||||
const removeErrored = useRequestQueue((state) => state.removeErrored);
|
||||
|
||||
const clearCompleted = () => {
|
||||
removeCompleted();
|
||||
}
|
||||
|
||||
const clearErrored = () => {
|
||||
removeErrored();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={QueueDisplayMain}>
|
||||
<ClearQueue />
|
||||
<div className={QueueListButtons}>
|
||||
<button
|
||||
className={buttonStyle({
|
||||
type: 'outline',
|
||||
})}
|
||||
onClick={clearCompleted}>Clear Completed</button>
|
||||
<button
|
||||
className={buttonStyle({
|
||||
type: 'outline',
|
||||
})}
|
||||
onClick={clearErrored}>Clear Errored</button>
|
||||
</div>
|
||||
{requests.map((request) => {
|
||||
return <QueueItem key={request.id} request={request}></QueueItem>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,28 +0,0 @@
|
||||
import { style, globalStyle } from "@vanilla-extract/css";
|
||||
|
||||
|
||||
import { vars } from "../../../styles/theme/index.css";
|
||||
|
||||
export const QueueDisplayMain = style({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
});
|
||||
|
||||
export const QueueListButtons = style({
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: vars.spacing.medium,
|
||||
marginTop: vars.spacing.medium,
|
||||
});
|
||||
|
||||
globalStyle(`${QueueListButtons} button`, {
|
||||
flexGrow: 1,
|
||||
});
|
||||
|
||||
globalStyle(`${QueueListButtons} button:first-child`, {
|
||||
marginRight: vars.spacing.medium,
|
||||
});
|
@ -1,125 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
import React from "react";
|
||||
|
||||
|
||||
import { QueueStatus, QueuedRequest, useRequestQueue } from '../../../../stores/requestQueueStore';
|
||||
|
||||
import StopButton from '../../../molecules/stopButton';
|
||||
|
||||
import {
|
||||
QueueItemMain,
|
||||
QueueItemInfo,
|
||||
QueueButtons,
|
||||
} from "./queueItem.css";
|
||||
|
||||
|
||||
import {
|
||||
buttonStyle
|
||||
} from "../../../_recipes/button.css";
|
||||
|
||||
|
||||
interface QueueItemProps {
|
||||
request: QueuedRequest;
|
||||
}
|
||||
|
||||
export default function QueueItem({ request }: QueueItemProps) {
|
||||
|
||||
const removeItem = useRequestQueue((state) => state.removeItem);
|
||||
const updateStatus = useRequestQueue((state) => state.updateStatus);
|
||||
const sendPendingToTop = useRequestQueue((state) => state.sendPendingToTop);
|
||||
|
||||
const {
|
||||
id,
|
||||
options: {
|
||||
prompt,
|
||||
num_outputs,
|
||||
seed,
|
||||
sampler,
|
||||
guidance_scale,
|
||||
num_inference_steps,
|
||||
|
||||
},
|
||||
status,
|
||||
} = request;
|
||||
|
||||
const removeFromQueue = () => {
|
||||
removeItem(id);
|
||||
}
|
||||
|
||||
const pauseItem = () => {
|
||||
updateStatus(id, QueueStatus.paused);
|
||||
}
|
||||
|
||||
const retryRequest = () => {
|
||||
updateStatus(id, QueueStatus.pending);
|
||||
}
|
||||
|
||||
const sendToTop = () => {
|
||||
sendPendingToTop(id);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={[QueueItemMain, status].join(' ')}>
|
||||
|
||||
<div className={QueueItemInfo}>
|
||||
<p>{prompt}</p>
|
||||
<p>Making {num_outputs} concurrent images</p>
|
||||
<p>
|
||||
<span>Seed: {seed} </span>
|
||||
<span>Sampler: {sampler} </span>
|
||||
<span>Guidance Scale: {guidance_scale} </span>
|
||||
<span>Num Inference Steps: {num_inference_steps} </span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={QueueButtons}>
|
||||
|
||||
{status === QueueStatus.processing && (
|
||||
<StopButton></StopButton>
|
||||
)}
|
||||
|
||||
{status === QueueStatus.complete && (
|
||||
<button
|
||||
className={buttonStyle({
|
||||
size: "large",
|
||||
})}
|
||||
onClick={removeFromQueue}>
|
||||
Clear
|
||||
</button>
|
||||
)}
|
||||
|
||||
{status === QueueStatus.pending && (
|
||||
<>
|
||||
<button className={buttonStyle({
|
||||
color: "cancel",
|
||||
})} onClick={removeFromQueue}>Remove</button>
|
||||
<button className={buttonStyle({
|
||||
color: "secondary",
|
||||
type: "outline",
|
||||
})} onClick={pauseItem}>Pause</button>
|
||||
<button className={buttonStyle({
|
||||
color: "tertiary",
|
||||
type: "action",
|
||||
})} onClick={sendToTop}>Send to top</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{status === QueueStatus.paused && (
|
||||
<button
|
||||
className={buttonStyle({
|
||||
size: "large",
|
||||
})} onClick={retryRequest}>Resume</button>
|
||||
)}
|
||||
|
||||
{status === QueueStatus.error && (
|
||||
<button
|
||||
className={buttonStyle({
|
||||
size: "large",
|
||||
})} onClick={retryRequest}>Retry</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
import { style, globalStyle } from "@vanilla-extract/css";
|
||||
|
||||
|
||||
import { vars } from "../../../../styles/theme/index.css";
|
||||
|
||||
|
||||
import { QueueStatus } from "../../../../stores/requestQueueStore";
|
||||
|
||||
|
||||
import {
|
||||
card
|
||||
} from '../../../_recipes/card.css';
|
||||
|
||||
export const QueueItemMain = style([card(
|
||||
{
|
||||
|
||||
info: true,
|
||||
level: 1
|
||||
}
|
||||
), {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
marginBottom: vars.spacing.medium,
|
||||
}]);
|
||||
|
||||
export const QueueItemInfo = style({
|
||||
|
||||
});
|
||||
|
||||
globalStyle(`${QueueItemInfo} p`, {
|
||||
marginBottom: vars.spacing.small,
|
||||
});
|
||||
|
||||
globalStyle(`${QueueItemMain}.${QueueStatus.complete}`, {
|
||||
borderColor: `hsl(${vars.secondaryHue}, ${vars.colorMod.saturation.normal}, ${vars.colorMod.lightness.normal})`,
|
||||
});
|
||||
|
||||
globalStyle(`${QueueItemMain}.${QueueStatus.processing}`, {
|
||||
borderColor: `hsl(${vars.tertiaryHue}, ${vars.colorMod.saturation.bright}, ${vars.colorMod.lightness.bright})`,
|
||||
});
|
||||
|
||||
globalStyle(`${QueueItemMain}.${QueueStatus.pending}`, {
|
||||
borderColor: `hsl(${vars.backgroundAccentMain}, ${vars.colorMod.saturation.bright}, ${vars.colorMod.lightness.normal})`,
|
||||
});
|
||||
|
||||
globalStyle(`${QueueItemMain}.${QueueStatus.paused}`, {
|
||||
borderColor: `hsl(${vars.backgroundAccentMain}, ${vars.colorMod.saturation.dim}, ${vars.colorMod.lightness.dim})`,
|
||||
backgroundColor: `hsl(${vars.backgroundAccentMain}, ${vars.colorMod.saturation.dim}, ${vars.colorMod.lightness.dim})`,
|
||||
});
|
||||
|
||||
globalStyle(`${QueueItemMain}.${QueueStatus.error}`, {
|
||||
borderColor: `hsl(${vars.errorHue}, ${vars.colorMod.saturation.normal}, ${vars.colorMod.lightness.normal})`,
|
||||
});
|
||||
|
||||
export const QueueButtons = style({
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
});
|
@ -1,35 +0,0 @@
|
||||
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 "./styles/index.css.ts";
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
staleTime: Infinity,
|
||||
cacheTime: 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>
|
||||
);
|
@ -1,144 +0,0 @@
|
||||
[
|
||||
[
|
||||
"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"
|
||||
]
|
||||
]
|
||||
]
|
@ -1,9 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export default function Beta() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Beta</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
import { style, globalStyle } from "@vanilla-extract/css";
|
||||
import { vars } from "../../styles/theme/index.css";
|
||||
|
||||
export const AppLayout = style({
|
||||
position: "relative",
|
||||
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
pointerEvents: "auto",
|
||||
display: "grid",
|
||||
backgroundColor: vars.backgroundMain,
|
||||
gridTemplateColumns: "400px 1fr",
|
||||
gridTemplateRows: "70px 1fr 115px",
|
||||
gridTemplateAreas: `
|
||||
"header header header"
|
||||
"create display display"
|
||||
"create display display"
|
||||
`,
|
||||
|
||||
|
||||
// "@media": {
|
||||
// "screen and (max-width: 800px)": {
|
||||
// gridTemplateColumns: "1fr",
|
||||
// gridTemplateRows: "100px 300px 1fr",
|
||||
// gridTemplateAreas: `
|
||||
// "header"
|
||||
// "create"
|
||||
// "display"
|
||||
// `,
|
||||
// },
|
||||
// },
|
||||
});
|
||||
|
||||
export const HeaderLayout = style({
|
||||
gridArea: "header",
|
||||
});
|
||||
|
||||
export const CreateLayout = style({
|
||||
gridArea: "create",
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
});
|
||||
|
||||
export const DisplayLayout = style({
|
||||
gridArea: "display",
|
||||
overflow: "auto",
|
||||
});
|
||||
|
||||
export const FooterLayout = style({
|
||||
gridArea: "footer",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
});
|
@ -1,73 +0,0 @@
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
import {
|
||||
AppLayout,
|
||||
HeaderLayout,
|
||||
CreateLayout,
|
||||
DisplayLayout,
|
||||
FooterLayout,
|
||||
} from "./home.css";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getSaveDirectory, loadModifications } from "../../api";
|
||||
import Mockifiers from "../../components/organisms/creationPanel/imageModifiers/modifiers.mock";
|
||||
|
||||
import { useImageCreate } from "../../stores/imageCreateStore";
|
||||
|
||||
// Todo - import components here
|
||||
import HeaderDisplay from "../../components/organisms/headerDisplay";
|
||||
import BasicDisplay from "../../components/layouts/basicDisplay";
|
||||
import FooterDisplay from "../../components/organisms/footerDisplay";
|
||||
import CreationTabs from "../../components/layouts/creationTabs";
|
||||
|
||||
function Home() {
|
||||
// Get the original save directory
|
||||
const setRequestOption = useImageCreate((state) => state.setRequestOptions);
|
||||
|
||||
const { status: statusSave, data: dataSave } = useQuery(
|
||||
["SaveDir"],
|
||||
getSaveDirectory
|
||||
);
|
||||
const { status: statusMods, data: dataMoads } = useQuery(
|
||||
["modifications"],
|
||||
loadModifications
|
||||
);
|
||||
|
||||
const setAllModifiers = useImageCreate((state) => state.setAllModifiers);
|
||||
|
||||
useEffect(() => {
|
||||
if (statusSave === "success") {
|
||||
setRequestOption("save_to_disk_path", dataSave);
|
||||
}
|
||||
}, [setRequestOption, statusSave, dataSave]);
|
||||
|
||||
useEffect(() => {
|
||||
if (statusMods === "success") {
|
||||
setAllModifiers(dataMoads);
|
||||
} else if (statusMods === "error") {
|
||||
// @ts-expect-error
|
||||
setAllModifiers(Mockifiers);
|
||||
}
|
||||
}, [setRequestOption, statusMods, dataMoads]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={[AppLayout].join(" ")}>
|
||||
<header className={HeaderLayout}>
|
||||
<HeaderDisplay></HeaderDisplay>
|
||||
</header>
|
||||
<nav className={CreateLayout}>
|
||||
<CreationTabs></CreationTabs>
|
||||
</nav>
|
||||
<main className={DisplayLayout}>
|
||||
<BasicDisplay></BasicDisplay>
|
||||
</main>
|
||||
</div>
|
||||
<footer className={FooterLayout}>
|
||||
<FooterDisplay></FooterDisplay>
|
||||
</footer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Home;
|
@ -1,9 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export default function Settings() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Settings</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,397 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
|
||||
import create from "zustand";
|
||||
import produce from "immer";
|
||||
import { devtools } from "zustand/middleware";
|
||||
|
||||
import { useRandomSeed } from "../utils";
|
||||
|
||||
import { ImageRequest } from "../api";
|
||||
|
||||
export interface ImageCreationUiOptions {
|
||||
isUseRandomSeed: boolean;
|
||||
isUseAutoSave: boolean;
|
||||
isSoundEnabled: boolean;
|
||||
}
|
||||
|
||||
export const SAMPLER_OPTIONS = [
|
||||
'plms',
|
||||
'ddim',
|
||||
'heun',
|
||||
'euler',
|
||||
'euler_a',
|
||||
'dpm2',
|
||||
'dpm2_a',
|
||||
'lms',
|
||||
] as const;
|
||||
|
||||
export interface ModifierPreview {
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface ModifierObject {
|
||||
category?: string;
|
||||
modifier: string;
|
||||
previews: ModifierPreview[];
|
||||
}
|
||||
|
||||
interface ModifiersList {
|
||||
category: string;
|
||||
modifiers: ModifierObject[];
|
||||
}
|
||||
|
||||
type ModifiersOptionList = ModifiersList[];
|
||||
|
||||
export interface promptTag {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'positive' | 'negative';
|
||||
}
|
||||
|
||||
interface ImageCreateState {
|
||||
parallelCount: number;
|
||||
requestOptions: ImageRequest;
|
||||
allModifiers: ModifiersOptionList;
|
||||
|
||||
createTags: promptTag[];
|
||||
// negativeTags: promptTag[];
|
||||
|
||||
tagMap: Record<string, string[]>;
|
||||
isInpainting: boolean;
|
||||
|
||||
setParallelCount: (count: number) => void;
|
||||
setRequestOptions: (key: keyof ImageRequest, value: any) => void;
|
||||
getValueForRequestKey: (key: keyof ImageRequest) => any;
|
||||
setAllModifiers: (modifiers: ModifiersOptionList) => void;
|
||||
|
||||
setModifierOptions: (key: string, value: any) => void;
|
||||
toggleTag: (category: string, tag: string) => void;
|
||||
hasTag: (category: string, tag: string) => boolean;
|
||||
selectedTags: () => ModifierObject[];
|
||||
addCreateTag: (tag: promptTag) => void;
|
||||
removeCreateTag: (id: string) => void;
|
||||
changeCreateTagType: (id: string, type: 'positive' | 'negative') => void;
|
||||
reorderCreateTag: (tag: promptTag, index: number) => void;
|
||||
|
||||
builtRequest: () => ImageRequest;
|
||||
|
||||
uiOptions: ImageCreationUiOptions;
|
||||
toggleUseUpscaling: () => void;
|
||||
// isUsingUpscaling: () => boolean
|
||||
toggleUseFaceCorrection: () => void;
|
||||
isUsingFaceCorrection: () => boolean;
|
||||
isUsingUpscaling: () => boolean;
|
||||
toggleUseRandomSeed: () => void;
|
||||
isRandomSeed: () => boolean;
|
||||
toggleUseAutoSave: () => void;
|
||||
isUseAutoSave: () => boolean;
|
||||
toggleSoundEnabled: () => void;
|
||||
isSoundEnabled: () => boolean;
|
||||
toggleInpainting: () => void;
|
||||
}
|
||||
|
||||
// devtools breaks TS
|
||||
export const useImageCreate = create<ImageCreateState>(
|
||||
// @ts-expect-error
|
||||
devtools((set, get) => ({
|
||||
parallelCount: 1,
|
||||
|
||||
requestOptions: {
|
||||
session_id: new Date().getTime().toString(),
|
||||
prompt: "a photograph of an astronaut riding a horse",
|
||||
negative_prompt: "",
|
||||
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: "GFPGANv1.3",
|
||||
use_upscale: "RealESRGAN_x4plus",
|
||||
show_only_filtered_image: true,
|
||||
init_image: undefined,
|
||||
sampler: "plms",
|
||||
stream_progress_updates: true,
|
||||
stream_image_progress: false,
|
||||
mask: undefined,
|
||||
},
|
||||
|
||||
// selected tags
|
||||
createTags: [] as promptTag[],
|
||||
// negativeTags: [] as promptTag[],
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
tagMap: {} as Record<string, string[]>,
|
||||
|
||||
uiOptions: {
|
||||
// TODO proper persistence of all UI / user settings centrally somewhere?
|
||||
// localStorage.getItem('ui:advancedSettingsIsOpen') === 'true',
|
||||
isUseRandomSeed: true,
|
||||
isUseAutoSave: false,
|
||||
isSoundEnabled: false,
|
||||
},
|
||||
|
||||
allModifiers: [] as ModifiersOptionList,
|
||||
|
||||
isInpainting: false,
|
||||
|
||||
setParallelCount: (count: number) =>
|
||||
set(
|
||||
produce((state) => {
|
||||
state.parallelCount = count;
|
||||
})
|
||||
),
|
||||
|
||||
setRequestOptions: (key: keyof ImageRequest, value: any) => {
|
||||
set(
|
||||
produce((state) => {
|
||||
state.requestOptions[key] = value;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
getValueForRequestKey: (key: keyof ImageRequest) => {
|
||||
return get().requestOptions[key];
|
||||
},
|
||||
|
||||
setAllModifiers: (modifiers: ModifiersOptionList) => {
|
||||
set(
|
||||
produce((state) => {
|
||||
state.allModifiers = modifiers;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
toggleTag: (category: string, tag: string) => {
|
||||
set(
|
||||
produce((state) => {
|
||||
|
||||
if (Object.keys(state.tagMap).includes(category)) {
|
||||
if (state.tagMap[category].includes(tag)) {
|
||||
state.tagMap[category] = state.tagMap[category].filter((t: string) => t !== tag);
|
||||
} else {
|
||||
state.tagMap[category].push(tag);
|
||||
}
|
||||
} else {
|
||||
state.tagMap[category] = [tag];
|
||||
}
|
||||
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
hasTag: (category: string, tag: string) => {
|
||||
return get().tagMap[category]?.includes(tag);
|
||||
},
|
||||
|
||||
selectedTags: () => {
|
||||
// get all the modifiers and all the tags
|
||||
const allModifiers = get().allModifiers;
|
||||
const selectedTags = get().tagMap;
|
||||
let selected: ModifierObject[] = [];
|
||||
|
||||
// for each mappped tag
|
||||
for (const [category, tags] of Object.entries(selectedTags)) {
|
||||
// find the modifier
|
||||
const modifier = allModifiers.find((m) => m.category === category);
|
||||
if (modifier) {
|
||||
// for each tag in the modifier
|
||||
for (const tag of tags) {
|
||||
// find the tag
|
||||
const tagObject = modifier.modifiers.find((m) => m.modifier === tag);
|
||||
if (tagObject) {
|
||||
// add the previews to the selected list
|
||||
selected = selected.concat({
|
||||
...tagObject,
|
||||
category: modifier.category
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return selected;
|
||||
},
|
||||
|
||||
addCreateTag: (tag: promptTag) => {
|
||||
set(
|
||||
produce((state) => {
|
||||
state.createTags.push(tag);
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
removeCreateTag: (id: string) => {
|
||||
set(
|
||||
produce((state) => {
|
||||
// @ts-expect-error
|
||||
state.createTags = state.createTags.filter((t) => t.id !== id);
|
||||
})
|
||||
|
||||
);
|
||||
},
|
||||
|
||||
changeCreateTagType: (id: string, type: 'positive' | 'negative') => {
|
||||
set(
|
||||
produce((state) => {
|
||||
// @ts-expect-error
|
||||
const tag = state.createTags.find((t) => t.id === id);
|
||||
if (tag) {
|
||||
tag.type = type;
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
reorderCreateTag: (tag: promptTag, index: number) => {
|
||||
set(
|
||||
produce((state) => {
|
||||
const tagIndex = state.createTags.indexOf(tag);
|
||||
if (tagIndex !== -1) {
|
||||
state.createTags.splice(tagIndex, 1);
|
||||
state.createTags.splice(index, 0, tag);
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
// the request body to send to the server
|
||||
// this is a computed value, just adding the tags to the request
|
||||
builtRequest: () => {
|
||||
const state = get();
|
||||
const requestOptions = state.requestOptions;
|
||||
const selectedTags = get().selectedTags();
|
||||
const tags = selectedTags.map((t: ModifierObject) => t.modifier);
|
||||
|
||||
const positivePrompt = state.createTags.filter((t) => t.type === "positive").map((t) => t.name).join(",");
|
||||
const negativePrompt = state.createTags.filter((t) => t.type === "negative").map((t) => t.name).join(",");
|
||||
|
||||
// join all the tags with a comma and add it to the prompt
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
// const prompt = `${requestOptions.prompt}, ${tags.join(",")}`;
|
||||
|
||||
const request = {
|
||||
...requestOptions,
|
||||
prompt: positivePrompt,
|
||||
negative_prompt: negativePrompt,
|
||||
};
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (void 0 === request.init_image) {
|
||||
request.prompt_strength = undefined;
|
||||
}
|
||||
|
||||
// a bit of a hack. figure out what a clean value to pass to the server is
|
||||
// if we arent using upscaling clear the upscaling
|
||||
if (request.use_upscale === "") {
|
||||
request.use_upscale = null;
|
||||
}
|
||||
|
||||
// make sure you look above for the "null" value
|
||||
// this patches over a a backend issue if you dont ask for a filtered image
|
||||
// you get nothing back
|
||||
|
||||
if (
|
||||
null === request.use_upscale &&
|
||||
null === request.use_face_correction
|
||||
) {
|
||||
request.show_only_filtered_image = false;
|
||||
}
|
||||
return request;
|
||||
},
|
||||
|
||||
toggleUseFaceCorrection: () => {
|
||||
set(
|
||||
produce((state) => {
|
||||
const isSeting =
|
||||
typeof state.getValueForRequestKey("use_face_correction") ===
|
||||
"string"
|
||||
? null
|
||||
: "GFPGANv1.3";
|
||||
state.requestOptions.use_face_correction = isSeting;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
isUsingFaceCorrection: () => {
|
||||
const isUsing =
|
||||
typeof get().getValueForRequestKey("use_face_correction") === "string";
|
||||
return isUsing;
|
||||
},
|
||||
|
||||
isUsingUpscaling: () => {
|
||||
const isUsing = get().getValueForRequestKey("use_upscale") !== "";
|
||||
return isUsing;
|
||||
},
|
||||
|
||||
toggleUseRandomSeed: () => {
|
||||
set(
|
||||
produce((state: ImageCreateState) => {
|
||||
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: ImageCreateState) => {
|
||||
state.uiOptions.isUseAutoSave = !state.uiOptions.isUseAutoSave;
|
||||
|
||||
// localStorage.setItem(
|
||||
// "ui:isUseAutoSave",
|
||||
// state.uiOptions.isUseAutoSave
|
||||
// );
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
isUseAutoSave: () => {
|
||||
return get().uiOptions.isUseAutoSave;
|
||||
},
|
||||
|
||||
toggleSoundEnabled: () => {
|
||||
set(
|
||||
produce((state: ImageCreateState) => {
|
||||
state.uiOptions.isSoundEnabled = !state.uiOptions.isSoundEnabled;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
isSoundEnabled: () => {
|
||||
return get().uiOptions.isSoundEnabled;
|
||||
},
|
||||
|
||||
toggleInpainting: () => {
|
||||
set(
|
||||
produce((state: ImageCreateState) => {
|
||||
state.isInpainting = !state.isInpainting;
|
||||
})
|
||||
);
|
||||
},
|
||||
}))
|
||||
);
|
@ -1,54 +0,0 @@
|
||||
import create from "zustand";
|
||||
import produce from "immer";
|
||||
|
||||
import { ImageRequest } from "../api";
|
||||
|
||||
export interface CompletedImagesType {
|
||||
id?: string;
|
||||
data: string | undefined;
|
||||
info: ImageRequest;
|
||||
}
|
||||
|
||||
interface ImageDisplayState {
|
||||
// imageOptions: Map<string, any>;
|
||||
images: CompletedImagesType[]
|
||||
currentImage: CompletedImagesType | null
|
||||
updateDisplay: (id: string, ImageData: string, imageOptions: any) => void;
|
||||
setCurrentImage: (image: CompletedImagesType) => void;
|
||||
clearDisplay: () => void;
|
||||
}
|
||||
|
||||
export const useImageDisplay = create<ImageDisplayState>((set, get) => ({
|
||||
imageMap: new Map<string, any>(),
|
||||
images: [],
|
||||
currentImage: null,
|
||||
// use produce to make sure we don't mutate state
|
||||
// imageOptions: any
|
||||
updateDisplay: (id: string, ImageData: string, imageOptions) => {
|
||||
set(
|
||||
produce((state) => {
|
||||
state.currentImage = { id, display: ImageData, info: imageOptions };
|
||||
state.images.unshift({ id, data: ImageData, info: imageOptions });
|
||||
state.currentImage = state.images[0];
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
setCurrentImage: (image) => {
|
||||
set(
|
||||
produce((state) => {
|
||||
state.currentImage = image;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
clearDisplay: () => {
|
||||
set(
|
||||
produce((state) => {
|
||||
state.images = [];
|
||||
state.currentImage = null;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
}));
|
@ -1,113 +0,0 @@
|
||||
import create from "zustand";
|
||||
import produce from "immer";
|
||||
|
||||
export const FetchingStates = {
|
||||
IDLE: "IDLE",
|
||||
FETCHING: "FETCHING",
|
||||
PROGRESSING: "PROGRESSING",
|
||||
SUCCEEDED: "SUCCEEDED",
|
||||
COMPLETE: "COMPLETE",
|
||||
ERROR: "ERROR",
|
||||
} as const;
|
||||
|
||||
interface ImageFetchingState {
|
||||
status: typeof FetchingStates[keyof typeof FetchingStates];
|
||||
step: number;
|
||||
totalSteps: number;
|
||||
data: string;
|
||||
progressImages: string[]
|
||||
timeStarted: Date;
|
||||
timeNow: Date;
|
||||
appendData: (data: string) => void;
|
||||
reset: () => void;
|
||||
setStatus: (status: typeof FetchingStates[keyof typeof FetchingStates]) => void;
|
||||
setStep: (step: number) => void;
|
||||
setTotalSteps: (totalSteps: number) => void;
|
||||
addProgressImage: (imageLink: string) => void;
|
||||
setStartTime: () => void;
|
||||
setNowTime: () => void;
|
||||
resetForFetching: () => void;
|
||||
}
|
||||
|
||||
export const useImageFetching = create<ImageFetchingState>((set) => ({
|
||||
status: FetchingStates.IDLE,
|
||||
step: 0,
|
||||
totalSteps: 0,
|
||||
data: '',
|
||||
progressImages: [],
|
||||
timeStarted: new Date(),
|
||||
timeNow: new Date(),
|
||||
// use produce to make sure we don't mutate state
|
||||
appendData: (data: string) => {
|
||||
set(
|
||||
produce((state: ImageFetchingState) => {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
||||
state.data += data;
|
||||
})
|
||||
);
|
||||
},
|
||||
reset: () => {
|
||||
set(
|
||||
produce((state: ImageFetchingState) => {
|
||||
state.status = FetchingStates.IDLE;
|
||||
state.step = 0;
|
||||
state.totalSteps = 0;
|
||||
state.data = '';
|
||||
})
|
||||
);
|
||||
},
|
||||
setStatus: (status: typeof FetchingStates[keyof typeof FetchingStates]) => {
|
||||
set(
|
||||
produce((state: ImageFetchingState) => {
|
||||
state.status = status;
|
||||
})
|
||||
);
|
||||
},
|
||||
setStep: (step: number) => {
|
||||
set(
|
||||
produce((state: ImageFetchingState) => {
|
||||
state.step = step;
|
||||
})
|
||||
);
|
||||
},
|
||||
setTotalSteps: (totalSteps: number) => {
|
||||
set(
|
||||
produce((state: ImageFetchingState) => {
|
||||
state.totalSteps = totalSteps;
|
||||
})
|
||||
);
|
||||
},
|
||||
addProgressImage: (imageLink: string) => {
|
||||
set(
|
||||
produce((state: ImageFetchingState) => {
|
||||
state.progressImages.push(imageLink);
|
||||
})
|
||||
);
|
||||
},
|
||||
setStartTime: () => {
|
||||
set(
|
||||
produce((state: ImageFetchingState) => {
|
||||
state.timeStarted = new Date();
|
||||
})
|
||||
);
|
||||
},
|
||||
setNowTime: () => {
|
||||
set(
|
||||
produce((state: ImageFetchingState) => {
|
||||
state.timeNow = new Date();
|
||||
})
|
||||
);
|
||||
},
|
||||
resetForFetching: () => {
|
||||
set(
|
||||
produce((state: ImageFetchingState) => {
|
||||
state.status = FetchingStates.FETCHING;
|
||||
state.progressImages = [];
|
||||
state.step = 0;
|
||||
state.totalSteps = 0;
|
||||
state.timeNow = new Date();
|
||||
state.timeStarted = new Date();
|
||||
})
|
||||
);
|
||||
}
|
||||
}));
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user