Merge branch 'main' into better-plugin-dx

This commit is contained in:
kaj 2023-05-24 10:13:05 -08:00
commit 62c9d968d6
13 changed files with 838 additions and 49 deletions

View File

@ -0,0 +1,27 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"name": "StableStudio dev",
"image": "mcr.microsoft.com/devcontainers/typescript-node:0-18",
// avoid git "dubious ownership" errors, which break yarn
"postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder} && yarn install",
"customizations": {
"vscode": {
"settings": {},
"extensions": [
"vscode.typescript-language-features"
]
}
},
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {
}
},
"portsAttributes": {
"3000": {
"label": "Application",
"onAutoForward": "openBrowserOnce"
}
}
}

View File

@ -0,0 +1,51 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"name": "StableStudio+WebUI GPU dev",
"image": "mcr.microsoft.com/devcontainers/typescript-node:0-18",
"postCreateCommand": "cd /home/node && git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git webui && cd webui && python -m venv ~/.webui-venv && bash -c 'source ~/.webui-venv/bin/activate' && pip install torch==2.0.1+cu118 torchvision==0.15.2+cu118 --extra-index-url https://download.pytorch.org/whl/cu118 && pip install -r requirements_versions.txt",
// avoid git "dubious ownership" errors, which break yarn
"postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder} && yarn",
"postAttachCommand": "echo 'To start the backend, run: cd ~/webui; ./webui.sh'",
"customizations": {
"vscode": {
"settings": {
"python.defaultInterpreterPath": "/home/node/.webui-venv/stability-generator/bin/python"
},
"extensions": [
"vscode.typescript-language-features"
]
}
},
"features": {
"ghcr.io/devcontainers/features/nvidia-cuda:1": {
"cudaVersion": "11.8",
"installCudnn": true,
"installNvtx": true
},
"ghcr.io/devcontainers/features/python:1": {
"version": "3.10.6"
},
"ghcr.io/devcontainers-contrib/features/apt-get-packages:1": {
"packages": "libgl1-mesa-glx"
},
"ghcr.io/devcontainers/features/github-cli:1": {
}
},
"remoteEnv": {
"COMMANDLINE_ARGS": "--nowebui --cors-allow-origins=http://localhost:3000",
"venv_dir": "/home/node/.webui-venv"
},
"runArgs": ["--gpus", "all"],
"portsAttributes": {
"3000": {
"label": "Application",
"onAutoForward": "openBrowserOnce"
},
"7861": {
"label": "Backend",
"onAutoForward": "ignore"
}
}
}

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: new feature
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -1,10 +1,10 @@
<div align="center"> <div align="center" style="display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 1em; margin: 4em 0;">
![StableStudio](./misc/Banner.png) <img src="./misc/Banner.png" />
<img src="./misc/GenerateScreenshot.png" style="width: 400px; max-width: 600px; flex-grow: 1;" />
<img src="./misc/EditScreenshot.png" style="width: 400px; max-width: 600px; flex-grow: 1;" />
# StableStudio by [Stability AI](https://stability.ai/) <h3>👋 Welcome to StableStudio, the open-source version of <a href="https://dreamstudio.ai" target="_blank">DreamStudio</a>!</h3>
**👋 Welcome to the community repository for StableStudio, the open-source version of [DreamStudio](https://www.dreamstudio.ai)**
**🗺 Contents [🚀 Quick Start](#quick-start) · [ About](#about) · [🙋 FAQ](#faq) · [🧑‍💻 Contributing](#contributing)** **🗺 Contents [🚀 Quick Start](#quick-start) · [ About](#about) · [🙋 FAQ](#faq) · [🧑‍💻 Contributing](#contributing)**
@ -12,26 +12,28 @@
**🔗 Links <a href="https://discord.com/channels/1002292111942635562/1108055793674227782" target="_blank">🎮 Discord</a> · <a href="https://dreamstudio.ai" target="_blank">🌈 DreamStudio</a> · <a href="https://github.com/Stability-AI/StableStudio/issues">🛟 Bugs & Support</a> · <a href="https://github.com/Stability-AI/StableStudio/discussions">💬 Discussion</a>** **🔗 Links <a href="https://discord.com/channels/1002292111942635562/1108055793674227782" target="_blank">🎮 Discord</a> · <a href="https://dreamstudio.ai" target="_blank">🌈 DreamStudio</a> · <a href="https://github.com/Stability-AI/StableStudio/issues">🛟 Bugs & Support</a> · <a href="https://github.com/Stability-AI/StableStudio/discussions">💬 Discussion</a>**
</div> <br />
<br />
<div align="center" style="display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 1em; margin: 4em 0;">
<img src="./misc/GenerateScreenshot.png" style="width: 400px; max-width: 600px; flex-grow: 1;" />
<img src="./misc/EditScreenshot.png" style="width: 400px; max-width: 600px; flex-grow: 1;" />
</div> </div>
# <a id="quick-start" href="#quick-start">🚀 Quick Start</a> # <a id="quick-start" href="#quick-start">🚀 Quick Start</a>
You'll need to have [Node.js](https://nodejs.org/en/) and [Yarn](https://yarnpkg.com/) installed. You'll need to have [Node.js](https://nodejs.org/en/) and [Yarn](https://yarnpkg.com/) installed. Then run the following commands to install dependencies and launch StableStudio.
Once that's done, you can run the following commands...
```bash ```bash
git clone https://github.com/Stability-AI/StableStudio.git git clone https://github.com/Stability-AI/StableStudio.git
```
```bash
cd StableStudio cd StableStudio
```
```bash
yarn yarn
```
```bash
yarn dev yarn dev
``` ```
@ -39,25 +41,19 @@ _**That's it! 🎉**_
StableStudio will be running at [localhost:3000](http://localhost:3000) by default. StableStudio will be running at [localhost:3000](http://localhost:3000) by default.
You'll need to have your [API key](https://platform.stability.ai/docs/getting-started/authentication) handy to use the default [Stability API](https://platform.stability.ai/docs/getting-started) plugin. > If you are using the default Stability API plugin, You'll need to have your [API key](https://platform.stability.ai/docs/getting-started/authentication) handy. Otherwise, you should be good to go!
If you don't have one, you can create an account on [DreamStudio](https://dreamstudio.ai) and get a key from the [account page](https://dreamstudio.ai/account). # <a id="about" href="#about">About</a>
# <a id="about" href="#about"> About</a>
<div style="display: flex; justify-content: center; align-items: center; gap: 1em; margin: 0 0 2em 0;"> <div style="display: flex; justify-content: center; align-items: center; gap: 1em; margin: 0 0 2em 0;">
<img src="./misc/PainterWithRobot.png" style="flex-grow: 1; flex-shrink: 1;" /> <img src="./misc/PainterWithRobot.png" style="flex-grow: 1; flex-shrink: 1;" />
</div> </div>
StableStudio is [Stability AI](https://stability.ai)'s official open-source variant of [DreamStudio](https://www.dreamstudio.ai), our user interface for generative AI. StableStudio is [Stability AI](https://stability.ai)'s official open-source variant of [DreamStudio](https://www.dreamstudio.ai), our user interface for generative AI. It is a web-based application that allows users to create and edit generated images. We're not entirely sure where this project is going just yet, but we're excited to see what the community does with it!
It is a web-based application that allows users to create and edit generated images. # <a id="faq" href="#faq">FAQ</a>
We're not entirely sure where this project is going just yet, but we're excited to see what the community does with it! ### What's the difference between StableStudio and [DreamStudio](https://dreamstudio.ai)?
# <a id="faq" href="#faq">🙋 FAQ</a>
## What's the difference between StableStudio and [DreamStudio](https://dreamstudio.ai)?
_Not much!_ There are a few tweaks we made to make the project more community-friendly: _Not much!_ There are a few tweaks we made to make the project more community-friendly:
@ -65,17 +61,15 @@ _Not much!_ There are a few tweaks we made to make the project more community-fr
- All "over-the-wire" API calls have been replaced by a [plugin system](./packages/stablestudio-plugin/README.md) which allows you to easily swap out the back-end. - All "over-the-wire" API calls have been replaced by a [plugin system](./packages/stablestudio-plugin/README.md) which allows you to easily swap out the back-end.
- On release, we'll only be providing a plugin for the Stability API, but with a little bit of TypeScript, you can [create your own](./packages/stablestudio-plugin/README.md). - With a little bit of TypeScript, you can [create your own plugin](./packages/stablestudio-plugin/README.md) and use StableStudio with any back-end you want!
- We removed Stability-specific account features such as billing, API key management, etc. - We removed Stability-specific account features such as billing, API key management, etc.
- These features are still available at [DreamStudio's account page](https://dreamstudio.ai/account). - These features are still available at [DreamStudio's account page](https://dreamstudio.ai/account).
## Will [DreamStudio](https://dreamstudio.ai) still be supported? ### Will [DreamStudio](https://dreamstudio.ai) still be supported?
_Yes!_ Stability's hosted deployment of StableStudio will remain [DreamStudio](https://dreamstudio.ai). _Yes!_ Stability's hosted deployment of StableStudio will remain [DreamStudio](https://dreamstudio.ai). It will continue to get updates and stay up-to-date with StableStudio whenever possible.
It will continue to get updates and stay up-to-date with StableStudio whenever possible.
# <a id="contributing" href="#contributing">🧑‍💻 Contributing</a> # <a id="contributing" href="#contributing">🧑‍💻 Contributing</a>
@ -85,9 +79,7 @@ It will continue to get updates and stay up-to-date with StableStudio whenever p
_**Community contributions are encouraged!**_ _**Community contributions are encouraged!**_
_**The UI package's [README](./packages/stablestudio-ui/README.md) is a great place to start.**_ **The UI package's [README](./packages/stablestudio-ui/README.md) is a great place to start.** Bug fixes, documentation, general clean-up, new features, etc. are all welcome.
Bug fixes, documentation, general clean-up, new features, etc. are all welcome.
Here are some useful links... Here are some useful links...

BIN
misc/Electric1111.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 KiB

View File

@ -455,82 +455,82 @@ export const createPlugin = StableStudio.createPlugin<{
{ {
id: "enhance", id: "enhance",
name: "Enhance", name: "Enhance",
imageURL: "https://dreamstudio.ai/presets/enhance.png", image: "https://dreamstudio.ai/presets/enhance.png",
}, },
{ {
id: "anime", id: "anime",
name: "Anime", name: "Anime",
imageURL: "https://dreamstudio.ai/presets/anime.png", image: "https://dreamstudio.ai/presets/anime.png",
}, },
{ {
id: "photographic", id: "photographic",
name: "Photographic", name: "Photographic",
imageURL: "https://dreamstudio.ai/presets/photographic.png", image: "https://dreamstudio.ai/presets/photographic.png",
}, },
{ {
id: "digital-art", id: "digital-art",
name: "Digital art", name: "Digital art",
imageURL: "https://dreamstudio.ai/presets/digital-art.png", image: "https://dreamstudio.ai/presets/digital-art.png",
}, },
{ {
id: "comic-book", id: "comic-book",
name: "Comic book", name: "Comic book",
imageURL: "https://dreamstudio.ai/presets/comic-book.png", image: "https://dreamstudio.ai/presets/comic-book.png",
}, },
{ {
id: "fantasy-art", id: "fantasy-art",
name: "Fantasy art", name: "Fantasy art",
imageURL: "https://dreamstudio.ai/presets/fantasy-art.png", image: "https://dreamstudio.ai/presets/fantasy-art.png",
}, },
{ {
id: "analog-film", id: "analog-film",
name: "Analog film", name: "Analog film",
imageURL: "https://dreamstudio.ai/presets/analog-film.png", image: "https://dreamstudio.ai/presets/analog-film.png",
}, },
{ {
id: "neon-punk", id: "neon-punk",
name: "Neon punk", name: "Neon punk",
imageURL: "https://dreamstudio.ai/presets/neon-punk.png", image: "https://dreamstudio.ai/presets/neon-punk.png",
}, },
{ {
id: "isometric", id: "isometric",
name: "Isometric", name: "Isometric",
imageURL: "https://dreamstudio.ai/presets/isometric.png", image: "https://dreamstudio.ai/presets/isometric.png",
}, },
{ {
id: "low-poly", id: "low-poly",
name: "Low poly", name: "Low poly",
imageURL: "https://dreamstudio.ai/presets/low-poly.png", image: "https://dreamstudio.ai/presets/low-poly.png",
}, },
{ {
id: "origami", id: "origami",
name: "Origami", name: "Origami",
imageURL: "https://dreamstudio.ai/presets/origami.png", image: "https://dreamstudio.ai/presets/origami.png",
}, },
{ {
id: "line-art", id: "line-art",
name: "Line art", name: "Line art",
imageURL: "https://dreamstudio.ai/presets/line-art.png", image: "https://dreamstudio.ai/presets/line-art.png",
}, },
{ {
id: "modeling-compound", id: "modeling-compound",
name: "Craft clay", name: "Craft clay",
imageURL: "https://dreamstudio.ai/presets/modeling-compound.png", image: "https://dreamstudio.ai/presets/modeling-compound.png",
}, },
{ {
id: "cinematic", id: "cinematic",
name: "Cinematic", name: "Cinematic",
imageURL: "https://dreamstudio.ai/presets/cinematic.png", image: "https://dreamstudio.ai/presets/cinematic.png",
}, },
{ {
id: "3d-model", id: "3d-model",
name: "3D model", name: "3D model",
imageURL: "https://dreamstudio.ai/presets/3d-model.png", image: "https://dreamstudio.ai/presets/3d-model.png",
}, },
{ {
id: "pixel-art", id: "pixel-art",
name: "Pixel art", name: "Pixel art",
imageURL: "https://dreamstudio.ai/presets/pixel-art.png", image: "https://dreamstudio.ai/presets/pixel-art.png",
}, },
], ],

View File

@ -0,0 +1,67 @@
<div align="center">
# 🔌 [`stable-diffusion-webui`](https://github.com/AUTOMATIC1111/stable-diffusion-webui) Plugin
**🗺 Contents [ About](#about) · [⚙️ Usage](#usage) · [⭐️ Features](#features)**
**[⬆️ Top-Level README](../../README.md)**
![Electric1111](../../misc/Electric1111.png)
</div>
# <a id="about" href="#about"> About</a>
This plugin enables StableStudio to run using [`stable-diffusion-webui`](https://github.com/AUTOMATIC1111/stable-diffusion-webui), which means you can generate images entirely on your own machine!
Thanks goes to [Terry Jia](https://github.com/jtydhr88) for the original work on this plugin.
# <a id="usage" href="#usage">⚙️ Usage</a>
1. First, you'll need to configure your local installation of `stable-diffusion-webui` to run without the UI and with CORS enabled.
**Windows**
Edit the command line arguments within `webui-user.bat`:
```
set COMMANDLINE_ARGS=--nowebui --cors-allow-origins=http://localhost:3000
```
**Mac**
Edit the command line arguments within `webui-macos-env.sh`:
```
export COMMANDLINE_ARGS="--nowebui --cors-allow-origins=http://localhost:3000"
```
2. Start `stable-diffusion-webui` and look for `INFO: Uvicorn running on http://127.0.0.1:7861`.
You can make sure everything is running correctly by checking to see if [`http://127.0.0.1:7861/docs`](http://127.0.0.1:7861/docs) displays API documentation.
3. Within your installation of StableStudio, run `yarn dev:use-webui-plugin`.
_**That's it!**_ 🎉 You should now be able to generate images using your local machine.
## <a id="image-history" href="#image-history">💾 Image History</a>
To persist your image history, you'll need to install the [`sd-webui-StableStudio`](https://github.com/jtydhr88/sd-webui-StableStudio) extension for `stable-diffusion-webui`.
> 🛑 Be wary installing third-party extensions for `stable-diffusion-webui`, it's always a good idea to check before running untrusted code.
# <a id="features" href="#features">⭐️ Features</a>
Missing something? Please [let us know](https://github.com/Stability-AI/StableStudio/issues/new/choose)!
- [x] Text-to-image
- [x] Image-to-image
- [x] Basic features (prompt, negative prompt, steps, batch size, image size)
- [x] Model selection
- [x] Sampler selection
- [x] Masking, in-painting, and out-painting
- [x] Settings storage
- [x] Accurate plugin status
- [x] [Loading existing images]("#image-history)
- [x] Upscaling
- [ ] Lora support

View File

@ -0,0 +1,184 @@
import { StableDiffusionInput } from "@stability/stablestudio-plugin";
export function base64ToBlob(base64: string, contentType = ""): Promise<Blob> {
return fetch(`data:${contentType};base64,${base64}`).then((res) =>
res.blob()
);
}
export function blobToBase64(blob: Blob): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result as string);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
export async function fetchOptions(baseUrl: string | undefined) {
const optionsResponse = await fetch(`${baseUrl}/sdapi/v1/options`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
return await optionsResponse.json();
}
export async function setOptions(baseUrl: string | undefined, options: any) {
const optionsResponse = await fetch(`${baseUrl}/sdapi/v1/options`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(options),
});
return await optionsResponse.json();
}
export async function getImageInfo(
baseUrl: string | undefined,
base64image: any
) {
const imageInfoResponse = await fetch(`${baseUrl}/sdapi/v1/png-info`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ image: base64image }),
});
const imageInfoJson = await imageInfoResponse.json();
const info = imageInfoJson.info.split("\n");
const data: any = {};
if (info.length === 0) {
return data;
}
data.prompt = info[0];
let detailIndex = 1;
if (info.length === 3) {
data.nagtivePrompt = info[1].split(":")[1].trim();
detailIndex = 2;
}
const details = info[detailIndex].split(",");
details.map((detail: any) => {
const detailInfo = detail.trim().split(":");
data[detailInfo[0]] = detailInfo[1].trim();
});
return data;
}
export async function testForHistoryPlugin(webuiHostUrl: string) {
// timeout after 1 second
const finished = Promise.race([
fetch(`${webuiHostUrl}/StableStudio/get-generated-images`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
limit: 1,
}),
}),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("Request timed out")), 1000)
),
]);
try {
await finished;
return (finished as any).ok;
} catch (error) {
return false;
}
}
export async function constructPayload(
options: {
input?: StableDiffusionInput | undefined;
count?: number | undefined;
},
isUpscale = false,
upscaler: string | undefined
) {
const { sampler, prompts, initialImage, maskImage, width, height, steps } =
options?.input ?? {};
// Construct payload
const data: any = {
seed: options?.input?.seed === 0 ? -1 : options?.input?.seed,
cfgScale: options?.input?.cfgScale ?? 7,
};
if (isUpscale) {
/*
Upscaling values
*/
data.upscaling_resize_w = width ?? 512;
data.upscaling_resize_h = height ?? 512;
data.upscaler_1 = upscaler;
} else {
/*
regular image generation values
*/
data.width = width ?? 512;
data.height = height ?? 512;
data.sampler_name = sampler?.name ?? "";
data.sampler_index = sampler?.name ?? "";
data.prompt =
prompts?.find((p) => (p.text && (p.weight ?? 0) > 0) ?? 0 > 0)?.text ??
"";
data.negative_prompt =
prompts?.find((p) => (p.text && (p.weight ?? 0) < 0) ?? 0 < 0)?.text ??
"";
data.steps = steps ?? 20;
data.batch_size = options?.count;
data.save_images = true;
}
if (initialImage?.weight && !isUpscale) {
data.denoising_strength = 1 - initialImage.weight;
}
if (initialImage?.blob) {
const initImgB64 = await blobToBase64(initialImage?.blob);
if (isUpscale) {
data.image = initImgB64.split(",")[1];
} else {
data.init_images = [initImgB64.split(",")[1]];
}
}
if (maskImage?.blob) {
const maskImgB64 = await blobToBase64(maskImage?.blob);
data.mask = maskImgB64.split(",")[1];
data.inpainting_mask_invert = 1; // Mask mode
data.inpainting_fill = 1; // Masked content
data.inpaint_full_res = false; // Inpaint area
}
return data;
}

View File

@ -1,3 +1,392 @@
import * as StableStudio from "@stability/stablestudio-plugin"; import * as StableStudio from "@stability/stablestudio-plugin";
import { StableDiffusionImage } from "@stability/stablestudio-plugin";
export const createPlugin = StableStudio.createPlugin(() => ({})); import {
base64ToBlob,
constructPayload,
fetchOptions,
getImageInfo,
setOptions,
testForHistoryPlugin,
} from "./Utilities";
const manifest = {
name: "stable-diffusion-webui",
author: "Terry Jia",
link: "https://github.com/jtydhr88",
icon: `${window.location.origin}/DummyImage.png`,
version: "0.0.0",
license: "MIT",
description:
"This plugin uses [`stable-diffusion-webui`](https://github.com/AUTOMATIC1111/stable-diffusion-webui) as its back-end for inference",
};
const webuiUpscalers = [
{
label: "None",
value: "None",
},
{
label: "Lanczos",
value: "Lanczos",
},
{
label: "Nearest",
value: "Nearest",
},
{
label: "ESRGAN_4x",
value: "ESRGAN_4x",
},
{
label: "LDSR",
value: "LDSR",
},
{
label: "R-ESRGAN 4x+",
value: "R-ESRGAN 4x+",
},
{
label: "R-ESRGAN 4x+ Anime6B",
value: "R-ESRGAN 4x+ Anime6B",
},
{
label: "ScuNET GAN",
value: "ScuNET GAN",
},
{
label: "ScuNET PSNR",
value: "ScuNET PSNR",
},
{
label: "SwinIR_4x",
value: "SwinIR_4x",
},
];
const getNumber = (strValue: string | null, defaultValue: number) => {
let retValue = defaultValue;
if (strValue) {
retValue = Number(strValue);
}
return retValue;
};
const getStableDiffusionDefaultCount = () => 4;
export const createPlugin = StableStudio.createPlugin<{
settings: {
baseUrl: StableStudio.PluginSettingString;
upscaler: StableStudio.PluginSettingString;
historyImagesCount: StableStudio.PluginSettingNumber;
};
}>(({ set, get }) => {
const webuiLoad = (
webuiHostUrl?: string
): Pick<
StableStudio.Plugin,
| "createStableDiffusionImages"
| "getStatus"
| "getStableDiffusionModels"
| "getStableDiffusionSamplers"
| "getStableDiffusionDefaultCount"
| "getStableDiffusionDefaultInput"
| "getStableDiffusionExistingImages"
> => {
webuiHostUrl = webuiHostUrl ?? "http://127.0.0.1:7861";
return {
createStableDiffusionImages: async (options) => {
if (!options) {
throw new Error("options is required");
}
// fetch the current webui options (model/sampler/etc)
const webUIOptions = await fetchOptions(webuiHostUrl);
const { model, sampler, initialImage } = options?.input ?? {};
options.count = options?.count ?? getStableDiffusionDefaultCount();
// quickly save the sampler and model name to local storage
if (sampler?.name) {
localStorage.setItem("webui-saved-sampler", sampler.name);
}
if (model) {
localStorage.setItem("webui-saved-model", model);
}
// little hacky until StableStudio is better with upscaling
const isUpscale =
options?.input?.initialImage?.weight === 1 &&
model === "esrgan-v1-x2plus";
// WebUI doesn't have the right model loaded, switch the model
if (model && model !== webUIOptions.sd_model_checkpoint && !isUpscale) {
localStorage.setItem("webui-saved-model", model);
const modelResponse = await setOptions(webuiHostUrl, {
sd_model_checkpoint: model,
});
if (modelResponse.ok) {
console.log("applied model");
}
}
// Construct payload for webui
const data = await constructPayload(
options,
isUpscale,
get().settings.upscaler.value
);
// Send payload to webui
const response = await fetch(
initialImage
? isUpscale
? `${webuiHostUrl}/sdapi/v1/extra-single-image`
: `${webuiHostUrl}/sdapi/v1/img2img`
: `${webuiHostUrl}/sdapi/v1/txt2img`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}
);
const responseData = await response.json();
const images = [];
const createdAt = new Date();
if (isUpscale) {
// Upscaling only returns one image
const blob = await base64ToBlob(responseData.image, "image/jpeg");
const image = {
id: `${Math.random() * 10000000}`,
createdAt: createdAt,
blob: blob,
input: {
model: model ?? "",
},
};
images.push(image);
} else {
// Image generation returns an array of images
const startIndex =
responseData.images.length > data.batch_size ? 1 : 0;
for (let i = startIndex; i < responseData.images.length; i++) {
const blob = await base64ToBlob(
responseData.images[i],
"image/jpeg"
);
const image: StableDiffusionImage = {
id: `${Math.random() * 10000000}`,
createdAt,
blob,
input: {
prompts: options?.input?.prompts ?? [],
steps: options?.input?.steps ?? 0,
seed: responseData.images[i].seed,
model: model ?? "",
width: options?.input?.width ?? 512,
height: options?.input?.height ?? 512,
cfgScale: options?.input?.cfgScale ?? 7,
sampler: sampler ?? { id: "", name: "" },
},
};
images.push(image);
}
}
return {
id: `${Math.random() * 10000000}`,
images: images,
};
},
getStableDiffusionModels: async () => {
const response = await fetch(`${webuiHostUrl}/sdapi/v1/sd-models`);
const responseData = await response.json();
return responseData.map((model: any) => ({
id: model.title,
name: model.model_name,
}));
},
getStatus: async () => {
const optionsResponse = await fetch(`${webuiHostUrl}/sdapi/v1/options`);
const hasWebuiHistoryPlugin = await testForHistoryPlugin(
`${webuiHostUrl}`
);
return optionsResponse.ok
? {
indicator: hasWebuiHistoryPlugin ? "success" : "info",
text: `Ready ${
hasWebuiHistoryPlugin ? "with" : "without"
} history plugin`,
}
: {
indicator: "error",
text: "unable to connect webui on " + webuiHostUrl,
};
},
};
};
const webuiHostUrl =
localStorage.getItem("webui-host-url") ?? "http://127.0.0.1:7861";
return {
...webuiLoad(webuiHostUrl),
getStableDiffusionDefaultCount: () => 4,
getStableDiffusionDefaultInput: () => {
return {
steps: 20,
sampler: {
id: localStorage.getItem("webui-saved-sampler") ?? "",
name: localStorage.getItem("webui-saved-sampler") ?? "",
},
model: localStorage.getItem("webui-saved-model") ?? "",
};
},
getStableDiffusionSamplers: async () => {
const response = await fetch(`${webuiHostUrl}/sdapi/v1/samplers`);
const responseData = await response.json();
return responseData.map((sampler: any) => ({
id: sampler.name,
name: sampler.name,
}));
},
getStableDiffusionExistingImages: async () => {
const existingImagesResponse = await fetch(
`${webuiHostUrl}/StableStudio/get-generated-images`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
limit: get().settings.historyImagesCount.value,
}),
}
);
if (!existingImagesResponse.ok) {
console.warn("unable to get existing data from webui");
}
const responseData = await existingImagesResponse.json();
const images = [];
for (let i = 0; i < responseData.length; i++) {
const imageInfo = await getImageInfo(
webuiHostUrl,
responseData[i].content
);
const blob = await base64ToBlob(responseData[i].content, "image/jpeg");
const timestampInSeconds = responseData[i].create_date;
const timestampInMilliseconds = timestampInSeconds * 1000;
const createdAt = new Date(timestampInMilliseconds);
const stableDiffusionImage = {
id: responseData[i].image_name,
createdAt: createdAt,
blob: blob,
input: {
prompts: [
{
text: imageInfo["prompt"],
weight: imageInfo["CFG scale"],
},
],
style: "",
steps: Number(imageInfo["Steps"]) ?? -1,
seed: Number(imageInfo["Seed"]) ?? -1,
model: imageInfo["Model"] ?? "",
width: responseData[i].width,
height: responseData[i].height,
},
};
images.push(stableDiffusionImage);
}
return [
{
id: `${Math.random() * 10000000}`,
images: images,
},
];
},
settings: {
baseUrl: {
type: "string",
title: "Host URL",
placeholder: "http://127.0.0.1:7861",
value: localStorage.getItem("webui-host-url") ?? "",
description:
"The URL of the `stable-diffusion-webui` host, usually http://127.0.0.1:7861",
},
upscaler: {
type: "string",
title: "Upscaler 1",
options: webuiUpscalers,
value: localStorage.getItem("upscaler1") ?? webuiUpscalers[0].value,
description:
"Select the upscaler used when downloading images at more than 1x size",
},
historyImagesCount: {
type: "number",
title: "History image count",
description: "How many images should be fetched from local history?",
min: 0,
max: 50,
step: 1,
variant: "slider",
value: getNumber(localStorage.getItem("historyImagesCount"), 20),
},
},
setSetting: (key, value) => {
set(({ settings }) => ({
settings: {
...settings,
[key]: { ...settings[key], value: value as string },
},
}));
if (key === "baseUrl" && typeof value === "string") {
localStorage.setItem("webui-host-url", value);
set((plugin) => ({ ...plugin, ...webuiLoad(value) }));
} else if (key === "upscaler" && typeof value === "string") {
localStorage.setItem("upscaler1", value);
} else if (key === "historyImagesCount" && typeof value === "number") {
localStorage.setItem("historyImagesCount", value.toString());
}
},
manifest,
};
});

View File

@ -20,7 +20,25 @@ In order to make StableStudio easier to extend, we've ripped out the "back-end"
This means you can implement an entirely different inference stack, StableStudio doesn't care if it's local or a hosted API. This means you can implement an entirely different inference stack, StableStudio doesn't care if it's local or a hosted API.
## ⭐️ Features ## ⭐️ First-Party Plugins
There are currently three first-party plugins which are maintained in this repository:
- [`stablestudio-plugin-stability`](../stablestudio-plugin-stability/src/index.ts) The default plugin which uses [Stability's API](https://platform.stability.ai) for inference.
```bash
yarn dev
```
- [`stablestudio-plugin-webui`](../stablestudio-plugin-webui/README.md) This plugin uses [`stable-diffusion-webui`](https://github.com/AUTOMATIC1111/stable-diffusion-webui) for inference.
```bash
yarn dev:use-webui-plugin
```
We are still figuring out a more scalable strategy for third-party plugins, [let us know what you think](https://github.com/Stability-AI/StableStudio/issues/3)!
## ⚡️ Features
We're hoping this list expands [over time](#future), but here's what's available right now... We're hoping this list expands [over time](#future), but here's what's available right now...

View File

@ -8,6 +8,7 @@ declare global {
interface ImportMetaEnv { interface ImportMetaEnv {
readonly VITE_GIT_HASH: string; readonly VITE_GIT_HASH: string;
readonly VITE_USE_EXAMPLE_PLUGIN: string; readonly VITE_USE_EXAMPLE_PLUGIN: string;
readonly VITE_USE_WEBUI_PLUGIN: string;
} }
} }
@ -20,6 +21,7 @@ export namespace Environment {
const variables = { const variables = {
VITE_GIT_HASH: import.meta.env.VITE_GIT_HASH, VITE_GIT_HASH: import.meta.env.VITE_GIT_HASH,
VITE_USE_EXAMPLE_PLUGIN: import.meta.env.VITE_USE_EXAMPLE_PLUGIN ?? "false", VITE_USE_EXAMPLE_PLUGIN: import.meta.env.VITE_USE_EXAMPLE_PLUGIN ?? "false",
VITE_USE_WEBUI_PLUGIN: import.meta.env.VITE_USE_WEBUI_PLUGIN ?? "false",
} as const; } as const;
export function get(name: VariableName): string { export function get(name: VariableName): string {

View File

@ -1,5 +1,6 @@
import * as StableStudio from "@stability/stablestudio-plugin"; import * as StableStudio from "@stability/stablestudio-plugin";
import * as StableStudioPluginStability from "@stability/stablestudio-plugin-stability"; import * as StableStudioPluginStability from "@stability/stablestudio-plugin-stability";
import * as StableStudioPluginWebUI from "@stability/stablestudio-plugin-webui";
import { Environment } from "~/Environment"; import { Environment } from "~/Environment";
import { Generation } from "~/Generation"; import { Generation } from "~/Generation";