Merge branch 'main' into better-plugin-dx
This commit is contained in:
commit
62c9d968d6
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
|
@ -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.
|
54
README.md
54
README.md
|
@ -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/)
|
||||
|
||||
**👋 Welcome to the community repository for StableStudio, the open-source version of [DreamStudio](https://www.dreamstudio.ai)**
|
||||
<h3>👋 Welcome to StableStudio, the open-source version of <a href="https://dreamstudio.ai" target="_blank">DreamStudio</a>!</h3>
|
||||
|
||||
**🗺 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>**
|
||||
|
||||
</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>
|
||||
|
||||
# <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.
|
||||
|
||||
Once that's done, you can run the following commands...
|
||||
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.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Stability-AI/StableStudio.git
|
||||
```
|
||||
|
||||
```bash
|
||||
cd StableStudio
|
||||
```
|
||||
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
|
@ -39,25 +41,19 @@ _**That's it! 🎉**_
|
|||
|
||||
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;">
|
||||
<img src="./misc/PainterWithRobot.png" style="flex-grow: 1; flex-shrink: 1;" />
|
||||
</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!
|
||||
|
||||
# <a id="faq" href="#faq">🙋 FAQ</a>
|
||||
|
||||
## What's the difference between StableStudio and [DreamStudio](https://dreamstudio.ai)?
|
||||
### 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:
|
||||
|
||||
|
@ -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.
|
||||
|
||||
- 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.
|
||||
|
||||
- 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).
|
||||
|
||||
It will continue to get updates and stay up-to-date with StableStudio whenever possible.
|
||||
_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.
|
||||
|
||||
# <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!**_
|
||||
|
||||
_**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.
|
||||
**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.
|
||||
|
||||
Here are some useful links...
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 607 KiB |
|
@ -455,82 +455,82 @@ export const createPlugin = StableStudio.createPlugin<{
|
|||
{
|
||||
id: "enhance",
|
||||
name: "Enhance",
|
||||
imageURL: "https://dreamstudio.ai/presets/enhance.png",
|
||||
image: "https://dreamstudio.ai/presets/enhance.png",
|
||||
},
|
||||
{
|
||||
id: "anime",
|
||||
name: "Anime",
|
||||
imageURL: "https://dreamstudio.ai/presets/anime.png",
|
||||
image: "https://dreamstudio.ai/presets/anime.png",
|
||||
},
|
||||
{
|
||||
id: "photographic",
|
||||
name: "Photographic",
|
||||
imageURL: "https://dreamstudio.ai/presets/photographic.png",
|
||||
image: "https://dreamstudio.ai/presets/photographic.png",
|
||||
},
|
||||
{
|
||||
id: "digital-art",
|
||||
name: "Digital art",
|
||||
imageURL: "https://dreamstudio.ai/presets/digital-art.png",
|
||||
image: "https://dreamstudio.ai/presets/digital-art.png",
|
||||
},
|
||||
{
|
||||
id: "comic-book",
|
||||
name: "Comic book",
|
||||
imageURL: "https://dreamstudio.ai/presets/comic-book.png",
|
||||
image: "https://dreamstudio.ai/presets/comic-book.png",
|
||||
},
|
||||
{
|
||||
id: "fantasy-art",
|
||||
name: "Fantasy art",
|
||||
imageURL: "https://dreamstudio.ai/presets/fantasy-art.png",
|
||||
image: "https://dreamstudio.ai/presets/fantasy-art.png",
|
||||
},
|
||||
{
|
||||
id: "analog-film",
|
||||
name: "Analog film",
|
||||
imageURL: "https://dreamstudio.ai/presets/analog-film.png",
|
||||
image: "https://dreamstudio.ai/presets/analog-film.png",
|
||||
},
|
||||
{
|
||||
id: "neon-punk",
|
||||
name: "Neon punk",
|
||||
imageURL: "https://dreamstudio.ai/presets/neon-punk.png",
|
||||
image: "https://dreamstudio.ai/presets/neon-punk.png",
|
||||
},
|
||||
{
|
||||
id: "isometric",
|
||||
name: "Isometric",
|
||||
imageURL: "https://dreamstudio.ai/presets/isometric.png",
|
||||
image: "https://dreamstudio.ai/presets/isometric.png",
|
||||
},
|
||||
{
|
||||
id: "low-poly",
|
||||
name: "Low poly",
|
||||
imageURL: "https://dreamstudio.ai/presets/low-poly.png",
|
||||
image: "https://dreamstudio.ai/presets/low-poly.png",
|
||||
},
|
||||
{
|
||||
id: "origami",
|
||||
name: "Origami",
|
||||
imageURL: "https://dreamstudio.ai/presets/origami.png",
|
||||
image: "https://dreamstudio.ai/presets/origami.png",
|
||||
},
|
||||
{
|
||||
id: "line-art",
|
||||
name: "Line art",
|
||||
imageURL: "https://dreamstudio.ai/presets/line-art.png",
|
||||
image: "https://dreamstudio.ai/presets/line-art.png",
|
||||
},
|
||||
{
|
||||
id: "modeling-compound",
|
||||
name: "Craft clay",
|
||||
imageURL: "https://dreamstudio.ai/presets/modeling-compound.png",
|
||||
image: "https://dreamstudio.ai/presets/modeling-compound.png",
|
||||
},
|
||||
{
|
||||
id: "cinematic",
|
||||
name: "Cinematic",
|
||||
imageURL: "https://dreamstudio.ai/presets/cinematic.png",
|
||||
image: "https://dreamstudio.ai/presets/cinematic.png",
|
||||
},
|
||||
{
|
||||
id: "3d-model",
|
||||
name: "3D model",
|
||||
imageURL: "https://dreamstudio.ai/presets/3d-model.png",
|
||||
image: "https://dreamstudio.ai/presets/3d-model.png",
|
||||
},
|
||||
{
|
||||
id: "pixel-art",
|
||||
name: "Pixel art",
|
||||
imageURL: "https://dreamstudio.ai/presets/pixel-art.png",
|
||||
image: "https://dreamstudio.ai/presets/pixel-art.png",
|
||||
},
|
||||
],
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -1,3 +1,392 @@
|
|||
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,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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.
|
||||
|
||||
## ⭐️ 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...
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ declare global {
|
|||
interface ImportMetaEnv {
|
||||
readonly VITE_GIT_HASH: string;
|
||||
readonly VITE_USE_EXAMPLE_PLUGIN: string;
|
||||
readonly VITE_USE_WEBUI_PLUGIN: string;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +21,7 @@ export namespace Environment {
|
|||
const variables = {
|
||||
VITE_GIT_HASH: import.meta.env.VITE_GIT_HASH,
|
||||
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;
|
||||
|
||||
export function get(name: VariableName): string {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as StableStudio from "@stability/stablestudio-plugin";
|
||||
import * as StableStudioPluginStability from "@stability/stablestudio-plugin-stability";
|
||||
import * as StableStudioPluginWebUI from "@stability/stablestudio-plugin-webui";
|
||||
|
||||
import { Environment } from "~/Environment";
|
||||
import { Generation } from "~/Generation";
|
||||
|
|
Loading…
Reference in New Issue