Compare commits
55 Commits
plugin-res
...
tauri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3b7d2048e | ||
|
|
4469313106 | ||
|
|
d842066f51 | ||
|
|
e9657bb20f | ||
|
|
0a92760725 | ||
|
|
753eb570b7 | ||
|
|
44aac3087d | ||
|
|
3317206d3e | ||
|
|
0065f05d3b | ||
|
|
d44d5d1d8f | ||
|
|
b867894c19 | ||
|
|
f7fdd8202d | ||
|
|
12a086302c | ||
|
|
47b4fa31e0 | ||
|
|
61d872b37f | ||
|
|
5ed8642175 | ||
|
|
8bb42328d6 | ||
|
|
110c8dcf08 | ||
|
|
69d381aef6 | ||
|
|
d710f32f70 | ||
|
|
ecaf1a9ae3 | ||
|
|
2dadc6f5ec | ||
|
|
fbf24d80b4 | ||
|
|
c359a44be6 | ||
|
|
e7089b8a7b | ||
|
|
4d435a4d6d | ||
|
|
f4a9d4e4af | ||
|
|
bef5fe60f3 | ||
|
|
4881e96a22 | ||
|
|
2b9e34dcda | ||
|
|
1dc1fad664 | ||
|
|
896e91818f | ||
|
|
8d363baa58 | ||
|
|
54d703c38e | ||
|
|
f8adbd1e60 | ||
|
|
e111ca023c | ||
|
|
7174ae2ab3 | ||
|
|
e35446e9ef | ||
|
|
1fd566802d | ||
|
|
508173cbb3 | ||
|
|
5646d76c3e | ||
|
|
f07733c227 | ||
|
|
6fcdee3da0 | ||
|
|
fad621417d | ||
|
|
3800549123 | ||
|
|
d10af830f0 | ||
|
|
8c704abcb6 | ||
|
|
0785bc4751 | ||
|
|
75c7b7e51e | ||
|
|
14dc64e8b1 | ||
|
|
bfd60288d3 | ||
|
|
39da8e3bb5 | ||
|
|
58075b422e | ||
|
|
e0f787c4d7 | ||
|
|
158ccf298b |
47
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
name: "Build Tauri App"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- tauri
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: tauri-build
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
platform: [macos-latest, ubuntu-20.04, windows-latest]
|
||||||
|
permissions:
|
||||||
|
contents: "write"
|
||||||
|
packages: "write"
|
||||||
|
pull-requests: "read"
|
||||||
|
runs-on: ${{ matrix.platform }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: 16
|
||||||
|
- name: install Rust stable
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: install dependencies (ubuntu only)
|
||||||
|
if: matrix.platform == 'ubuntu-20.04'
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||||
|
- name: install frontend dependencies
|
||||||
|
run: yarn install
|
||||||
|
- uses: tauri-apps/tauri-action@v0
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tagName: stablestudio-v__VERSION__
|
||||||
|
releaseName: "StableStudio v__VERSION__"
|
||||||
|
releaseBody: "See the assets to download this version and install."
|
||||||
|
releaseDraft: false
|
||||||
|
prerelease: true
|
||||||
53
.github/workflows/comfy_windows.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
name: "Windows Release Repackaging with ZIP"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- tauri
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: comfyui_windows
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
repackage_comfyui:
|
||||||
|
permissions:
|
||||||
|
contents: "write"
|
||||||
|
packages: "write"
|
||||||
|
pull-requests: "read"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- shell: bash
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y p7zip-full
|
||||||
|
sudo apt-get install -y zip
|
||||||
|
|
||||||
|
wget -nv https://github.com/KAJdev/ComfyUI/releases/download/latest/new_ComfyUI_windows_portable_nvidia_cu118_or_cpu.7z -O ComfyUI_windows_portable_nvidia_cu118_or_cpu.7z
|
||||||
|
|
||||||
|
7z x ComfyUI_windows_portable_nvidia_cu118_or_cpu.7z
|
||||||
|
|
||||||
|
rm ComfyUI_windows_portable_nvidia_cu118_or_cpu.7z
|
||||||
|
|
||||||
|
mv ComfyUI_windows_portable ComfyUI
|
||||||
|
|
||||||
|
# remove ALL cache files recursively (.pyc, .pyo, __pycache__, etc.)
|
||||||
|
find ComfyUI -type f -name '*.pyc' -delete
|
||||||
|
find ComfyUI -type f -name '*.pyo' -delete
|
||||||
|
find ComfyUI -type d -name '__pycache__' -delete
|
||||||
|
|
||||||
|
zip -r -9 -q ComfyUI_windows_portable.zip ComfyUI -x "*.DS_Store" -x "__MACOSX" -x "*.pyc" -x "*.pyo" -x "__pycache__"
|
||||||
|
du -sh ComfyUI_windows_portable.zip
|
||||||
|
|
||||||
|
mkdir ComfyUI_bundle
|
||||||
|
mv ComfyUI_windows_portable.zip ComfyUI_bundle/ComfyUI_windows_portable.zip
|
||||||
|
|
||||||
|
- name: Upload binaries to S3
|
||||||
|
uses: shallwefootball/s3-upload-action@master
|
||||||
|
with:
|
||||||
|
aws_key_id: ${{ secrets.AWS_KEY_ID }}
|
||||||
|
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
aws_bucket: ${{ secrets.AWS_BUCKET }}
|
||||||
|
source_dir: ComfyUI_bundle
|
||||||
|
destination_dir: ComfyUI
|
||||||
|
endpoint: https://4f71c105ddc191f1fdc56ad4be97f44f.r2.cloudflarestorage.com/stability-downloads
|
||||||
2
.gitignore
vendored
@@ -11,3 +11,5 @@ node_modules
|
|||||||
!.yarn/releases
|
!.yarn/releases
|
||||||
!.yarn/sdks
|
!.yarn/sdks
|
||||||
!.yarn/versions
|
!.yarn/versions
|
||||||
|
|
||||||
|
**/src-tauri/comfyui/*
|
||||||
|
|||||||
5
.vscode/settings.json
vendored
@@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||||
|
"rust-analyzer.linkedProjects": [
|
||||||
|
".\\packages\\stablestudio-ui\\src-tauri\\Cargo.toml"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
105
README.md
@@ -1,57 +1,32 @@
|
|||||||
<div align="center" style="display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 1em; margin: 4em 0;">
|
<div align="center" style="display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 1em; margin: 4em 0;">
|
||||||
|
|
||||||
<img src="./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;" />
|
|
||||||
|
|
||||||
<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)**
|
**🗺 Contents – [🚀 Quick Start](#quick-start) · [ℹ️ About](#about) · [🙋 FAQ](#faq) · [🧑💻 Contributing](#contributing)**
|
||||||
|
|
||||||
**📚 Documentation – [🎨 UI](./packages/stablestudio-ui/README.md) · [🔌 Plugins](./packages/stablestudio-plugin/README.md) · <a href="https://platform.stability.ai" target="_blank">⚡️ platform.stability.ai</a>**
|
**📚 Documentation – [🎨 UI](./packages/stablestudio-ui/README.md) · <a href="https://platform.stability.ai" target="_blank">⚡️ platform.stability.ai</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>**
|
**🔗 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>**
|
||||||
|
|
||||||
<br />
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
# <a id="quick-start" href="#quick-start">🚀 Quick Start</a>
|
# 🚀 Installation
|
||||||
|
|
||||||
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.
|
You'll need to head over to the [Releases](https://github.com/Stability-AI/StableStudio/releases) page to download StableStudio for your operating system. StableStudio will download and install its own managed copy of [ComfyUI](https://github.com/comfyanonymous/ComfyUI) and the Stable Diffusion weights.
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/Stability-AI/StableStudio.git
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd StableStudio
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yarn
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yarn dev
|
|
||||||
```
|
|
||||||
|
|
||||||
_**That's it! 🎉**_
|
_**That's it! 🎉**_
|
||||||
|
|
||||||
StableStudio will be running at [localhost:3000](http://localhost:3000) by default.
|
# About
|
||||||
|
|
||||||
> 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!
|
|
||||||
|
|
||||||
# <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/aboutReadmeImage.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. 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!
|
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 desktop application for local inference of Stable Diffusion that allows users to create and edit generated images. We're excited to see what the community does with it!
|
||||||
|
|
||||||
# <a id="faq" href="#faq">FAQ</a>
|
# FAQ
|
||||||
|
|
||||||
### What's the difference between StableStudio and [DreamStudio](https://dreamstudio.ai)?
|
### What's the difference between StableStudio and [DreamStudio](https://dreamstudio.ai)?
|
||||||
|
|
||||||
@@ -59,9 +34,9 @@ _Not much!_ There are a few tweaks we made to make the project more community-fr
|
|||||||
|
|
||||||
- We removed [DreamStudio](https://dreamstudio.ai)-specific branding.
|
- We removed [DreamStudio](https://dreamstudio.ai)-specific branding.
|
||||||
|
|
||||||
- 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.
|
- StableStudio is now packaged as a tauri application
|
||||||
|
|
||||||
- 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!
|
- All "over-the-wire" API calls have been replaced by a [ComfyUI](https://github.com/comfyanonymous/ComfyUI) backend.
|
||||||
|
|
||||||
- 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.
|
||||||
|
|
||||||
@@ -69,12 +44,45 @@ _Not much!_ There are a few tweaks we made to make the project more community-fr
|
|||||||
|
|
||||||
### 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).
|
||||||
|
|
||||||
# <a id="contributing" href="#contributing">🧑💻 Contributing</a>
|
# Building From Source
|
||||||
|
|
||||||
|
> Make sure you have the rust toolchain, nodejs, and yarn installed
|
||||||
|
|
||||||
|
1. Clone the repo
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/Stability-AI/StableStudio
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install dependencies
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn install
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo install tauri-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Build
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo tauri build
|
||||||
|
```
|
||||||
|
> Or for development
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo tauri dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Installers/executables should be located in `packages/stablestudio-ui/src-tauri/target/release/bundle`
|
||||||
|
|
||||||
|
# Contributing
|
||||||
|
|
||||||
<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/ProgrammingRobots.png" style="flex-grow: 1; flex-shrink: 1;" />
|
<img src="./misc/contributingReadmeImage.png" style="flex-grow: 1; flex-shrink: 1;" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
_**Community contributions are encouraged!**_
|
_**Community contributions are encouraged!**_
|
||||||
@@ -87,3 +95,26 @@ Here are some useful links...
|
|||||||
- [Open Issues](https://github.com/Stability-AI/StableStudio/issues)
|
- [Open Issues](https://github.com/Stability-AI/StableStudio/issues)
|
||||||
- [Open Pull Requests](https://github.com/Stability-AI/StableStudio/pulls)
|
- [Open Pull Requests](https://github.com/Stability-AI/StableStudio/pulls)
|
||||||
- [Code of Conduct](./CODE_OF_CONDUCT.md)
|
- [Code of Conduct](./CODE_OF_CONDUCT.md)
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
### Model download
|
||||||
|
- [ ] Design & implement a community model browser w/ download flow
|
||||||
|
- [ ] A way to parse URLs into a URL that downloads a model (from say an input)
|
||||||
|
- [ ] A universal way to download models from URLs
|
||||||
|
- [ ] BONUS: A way to pick up model downloads if app is closed suddenly
|
||||||
|
|
||||||
|
### ComfyUI/SD install config
|
||||||
|
- [ ] Ability to change comfy location in settings (file choose dialog w/ tauri & write to settings.json)
|
||||||
|
- [ ] Ability to change comfy install location when/before downloading during first setup
|
||||||
|
- [ ] Ability to change the particular SD model & location when/before downloading during first setup
|
||||||
|
- [ ] Ability to change comfyui url in settings
|
||||||
|
- [ ] Ability to change comfyui cli flags in settings
|
||||||
|
|
||||||
|
### Custom inputs
|
||||||
|
- [ ] define & implement a strategy for arbitrary inputs in stablestudio UI
|
||||||
|
|
||||||
|
### Global job UI
|
||||||
|
- [ ] List model downloads
|
||||||
|
- [ ] List current/pending gens
|
||||||
|
- [ ] Ability to cancel jobs (global job API?)
|
||||||
|
- [ ] Floating UI for jobs - minimalistic
|
||||||
|
|||||||
BIN
misc/Banner.png
|
Before Width: | Height: | Size: 352 KiB After Width: | Height: | Size: 1.7 MiB |
BIN
misc/BannerOld.png
Normal file
|
After Width: | Height: | Size: 352 KiB |
|
Before Width: | Height: | Size: 356 KiB After Width: | Height: | Size: 1.7 MiB |
BIN
misc/ProjectCardOld.png
Normal file
|
After Width: | Height: | Size: 356 KiB |
BIN
misc/aboutReadmeImage.png
Normal file
|
After Width: | Height: | Size: 3.9 MiB |
BIN
misc/contributingReadmeImage.png
Normal file
|
After Width: | Height: | Size: 4.0 MiB |
10
package.json
@@ -9,13 +9,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "node -e \"try { require('husky').install() } catch (e) {if (e.code !== 'MODULE_NOT_FOUND') throw e}\" && yarn build",
|
"postinstall": "node -e \"try { require('husky').install() } catch (e) {if (e.code !== 'MODULE_NOT_FOUND') throw e}\" && yarn build",
|
||||||
"stablestudio-plugin": "yarn workspace @stability/stablestudio-plugin",
|
"stablestudio-plugin": "yarn workspace @stability/stablestudio-plugin",
|
||||||
"stablestudio-plugin-example": "yarn workspace @stability/stablestudio-plugin-example",
|
"stablestudio-plugin-comfy": "yarn workspace @stability/stablestudio-plugin-comfy",
|
||||||
"stablestudio-plugin-stability": "yarn workspace @stability/stablestudio-plugin-stability",
|
|
||||||
"stablestudio-plugin-webgpu": "yarn workspace @stability/stablestudio-plugin-webgpu",
|
|
||||||
"stablestudio-plugin-webui": "yarn workspace @stability/stablestudio-plugin-webui",
|
|
||||||
"stablestudio-ui": "yarn workspace @stability/stablestudio-ui",
|
"stablestudio-ui": "yarn workspace @stability/stablestudio-ui",
|
||||||
"dev:use-example-plugin": "cross-env VITE_USE_EXAMPLE_PLUGIN=true yarn dev",
|
|
||||||
"dev:use-webui-plugin": "cross-env VITE_USE_WEBUI_PLUGIN=true yarn dev",
|
|
||||||
"dev": "yarn workspaces foreach --all --interlaced --verbose --parallel --jobs unlimited run dev",
|
"dev": "yarn workspaces foreach --all --interlaced --verbose --parallel --jobs unlimited run dev",
|
||||||
"build": "yarn workspaces foreach --all --interlaced --verbose --jobs unlimited run build",
|
"build": "yarn workspaces foreach --all --interlaced --verbose --jobs unlimited run build",
|
||||||
"clean": "yarn workspaces foreach --all --interlaced --verbose --parallel --jobs unlimited run clean && rimraf node_modules"
|
"clean": "yarn workspaces foreach --all --interlaced --verbose --parallel --jobs unlimited run clean && rimraf node_modules"
|
||||||
@@ -32,5 +27,8 @@
|
|||||||
"prettier --write",
|
"prettier --write",
|
||||||
"eslint --fix"
|
"eslint --fix"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "^20.4.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@stability/stablestudio-plugin-webui",
|
"name": "@stability/stablestudio-plugin-comfy",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
54
packages/stablestudio-plugin-comfy/src/index.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import * as StableStudio from "@stability/stablestudio-plugin";
|
||||||
|
|
||||||
|
export const createPlugin = StableStudio.createPlugin(() => ({
|
||||||
|
manifest: {
|
||||||
|
name: "ComfyUI Backend",
|
||||||
|
author: "StabilityAI",
|
||||||
|
version: "0.0.1",
|
||||||
|
license: "MIT",
|
||||||
|
description: "An interface for generating images with ComfyUI",
|
||||||
|
},
|
||||||
|
|
||||||
|
createStableDiffusionImages: async () => {
|
||||||
|
(
|
||||||
|
(document.getElementById("comfyui-window") as any)?.contentWindow as any
|
||||||
|
)?.app.queuePrompt(1, 1);
|
||||||
|
|
||||||
|
const image = await fetch(`${window.location.origin}/DummyImage.png`);
|
||||||
|
const blob = await image.blob();
|
||||||
|
const createdAt = new Date();
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: `${Math.random() * 10000000}`,
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
id: `${Math.random() * 10000000}`,
|
||||||
|
createdAt,
|
||||||
|
blob,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: `${Math.random() * 10000000}`,
|
||||||
|
createdAt,
|
||||||
|
blob,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: `${Math.random() * 10000000}`,
|
||||||
|
createdAt,
|
||||||
|
blob,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: `${Math.random() * 10000000}`,
|
||||||
|
createdAt,
|
||||||
|
blob,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getStatus: () => {
|
||||||
|
return {
|
||||||
|
indicator: "success",
|
||||||
|
text: "Ready",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}));
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@stability/stablestudio-plugin-example",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"main": "./lib/index.js",
|
|
||||||
"types": "./lib/index.d.ts",
|
|
||||||
"files": [
|
|
||||||
"lib"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"clean": "rimraf lib && rimraf node_modules",
|
|
||||||
"build:types": "ttsc --project tsconfig.json",
|
|
||||||
"build:javascript": "tsx scripts/Build.ts",
|
|
||||||
"build": "yarn build:types && yarn build:javascript",
|
|
||||||
"dev": "nodemon --watch src --ext ts,tsx,json --exec \"yarn build\""
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@stability/stablestudio-plugin": "workspace:^"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.33.1",
|
|
||||||
"@typescript-eslint/parser": "^5.33.1",
|
|
||||||
"eslint": "8.22.0",
|
|
||||||
"eslint-config-prettier": "^8.5.0",
|
|
||||||
"eslint-plugin-import": "^2.26.0",
|
|
||||||
"eslint-plugin-markdown": "^3.0.0",
|
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
|
||||||
"eslint-plugin-react": "^7.30.1",
|
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
|
||||||
"nodemon": "^2.0.20",
|
|
||||||
"prettier": "^2.7.1",
|
|
||||||
"rimraf": "^3.0.2",
|
|
||||||
"ts-node": "^10.9.1",
|
|
||||||
"tsx": "^3.12.1",
|
|
||||||
"ttypescript": "^1.5.13",
|
|
||||||
"typescript": "4.8.4",
|
|
||||||
"typescript-transform-paths": "^3.4.4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
import * as StableStudio from "@stability/stablestudio-plugin";
|
|
||||||
|
|
||||||
export const createPlugin = StableStudio.createPlugin<{
|
|
||||||
imagesGeneratedSoFar: number;
|
|
||||||
settings: {
|
|
||||||
exampleSetting: StableStudio.PluginSettingString;
|
|
||||||
};
|
|
||||||
}>(({ set, get }) => ({
|
|
||||||
imagesGeneratedSoFar: 0,
|
|
||||||
|
|
||||||
manifest: {
|
|
||||||
name: "Example Plugin",
|
|
||||||
author: "Bobby Joe",
|
|
||||||
link: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
|
||||||
icon: `${window.location.origin}/DummyImage.png`,
|
|
||||||
version: "1.2.3",
|
|
||||||
license: "MIT",
|
|
||||||
description: "An example plugin for StableStudio",
|
|
||||||
},
|
|
||||||
|
|
||||||
createStableDiffusionImages: async () => {
|
|
||||||
const image = await fetch(`${window.location.origin}/DummyImage.png`);
|
|
||||||
const blob = await image.blob();
|
|
||||||
const createdAt = new Date();
|
|
||||||
|
|
||||||
set(({ imagesGeneratedSoFar }) => ({
|
|
||||||
imagesGeneratedSoFar: imagesGeneratedSoFar + 4,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: `${Math.random() * 10000000}`,
|
|
||||||
images: [
|
|
||||||
{
|
|
||||||
id: `${Math.random() * 10000000}`,
|
|
||||||
createdAt,
|
|
||||||
blob,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: `${Math.random() * 10000000}`,
|
|
||||||
createdAt,
|
|
||||||
blob,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: `${Math.random() * 10000000}`,
|
|
||||||
createdAt,
|
|
||||||
blob,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: `${Math.random() * 10000000}`,
|
|
||||||
createdAt,
|
|
||||||
blob,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getStatus: () => {
|
|
||||||
const { imagesGeneratedSoFar } = get();
|
|
||||||
return {
|
|
||||||
indicator: "success",
|
|
||||||
text:
|
|
||||||
imagesGeneratedSoFar > 0
|
|
||||||
? `${imagesGeneratedSoFar} images generated`
|
|
||||||
: "Ready",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
settings: {
|
|
||||||
exampleSetting: {
|
|
||||||
type: "string" as const,
|
|
||||||
default: "Hello, World!",
|
|
||||||
placeholder: "Example setting",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
setSetting: (key, value) =>
|
|
||||||
set(({ settings }) => ({
|
|
||||||
settings: {
|
|
||||||
[key]: { ...settings[key], value: value as string },
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
./src/Proto/Generated/**/*
|
|
||||||
./api-interfaces/**
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["../../.eslintrc.json"],
|
|
||||||
"ignorePatterns": ["./src/Proto/Generated/**"]
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
api-interfaces
|
|
||||||
lib
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
./src/Proto/Generated/**/*
|
|
||||||
./api-interfaces/**
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2023 Stability AI
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@stability/stablestudio-plugin-stability",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"main": "./lib/index.js",
|
|
||||||
"types": "./lib/index.d.ts",
|
|
||||||
"files": [
|
|
||||||
"lib"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"clean": "rimraf lib && rimraf api-interfaces && rimraf node_modules",
|
|
||||||
"generate:proto": "tsx scripts/GenerateProto.ts",
|
|
||||||
"build:types": "ttsc --project tsconfig.json",
|
|
||||||
"build:javascript": "tsx scripts/Build.ts",
|
|
||||||
"build": "yarn build:types && yarn build:javascript",
|
|
||||||
"dev": "nodemon --watch src --ext ts,json --exec \"yarn build\""
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@protobuf-ts/grpcweb-transport": "^2.8.2",
|
|
||||||
"@protobuf-ts/runtime": "^2.8.1",
|
|
||||||
"@protobuf-ts/runtime-rpc": "^2.8.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@protobuf-ts/plugin": "^2.8.1",
|
|
||||||
"@types/node": "^18.11.9",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.33.1",
|
|
||||||
"@typescript-eslint/parser": "^5.33.1",
|
|
||||||
"concurrently": "^7.6.0",
|
|
||||||
"eslint": "8.22.0",
|
|
||||||
"eslint-config-prettier": "^8.5.0",
|
|
||||||
"eslint-plugin-import": "^2.26.0",
|
|
||||||
"eslint-plugin-markdown": "^3.0.0",
|
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
|
||||||
"eslint-plugin-react": "^7.30.1",
|
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
|
||||||
"nodemon": "^2.0.20",
|
|
||||||
"prettier": "^2.7.1",
|
|
||||||
"rimraf": "^3.0.2",
|
|
||||||
"ts-node": "^10.9.1",
|
|
||||||
"ts-proto": "^1.133.0",
|
|
||||||
"tsx": "^3.12.1",
|
|
||||||
"ttypescript": "^1.5.13",
|
|
||||||
"typescript": "4.8.4",
|
|
||||||
"typescript-transform-paths": "^3.4.4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import * as ESBuild from "esbuild";
|
|
||||||
|
|
||||||
const main = async () => {
|
|
||||||
try {
|
|
||||||
await ESBuild.build({
|
|
||||||
entryPoints: ["src/index.ts"],
|
|
||||||
outdir: "lib",
|
|
||||||
bundle: true,
|
|
||||||
sourcemap: true,
|
|
||||||
minify: true,
|
|
||||||
splitting: true,
|
|
||||||
format: "esm",
|
|
||||||
target: ["esnext"],
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
main();
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import * as ChildProcess from "child_process";
|
|
||||||
import * as FileSystem from "fs";
|
|
||||||
import * as Path from "path";
|
|
||||||
|
|
||||||
// Set up input and output directories
|
|
||||||
|
|
||||||
const inputDirectoryPath = Path.join(__dirname, "../api-interfaces");
|
|
||||||
const outputDirectoryPath = Path.join(__dirname, "../src/Proto/Generated");
|
|
||||||
|
|
||||||
// Remove and recreate the output directory
|
|
||||||
|
|
||||||
FileSystem.existsSync(outputDirectoryPath) &&
|
|
||||||
FileSystem.rmSync(outputDirectoryPath, { recursive: true });
|
|
||||||
|
|
||||||
FileSystem.mkdirSync(outputDirectoryPath);
|
|
||||||
|
|
||||||
// Set up proto directories
|
|
||||||
|
|
||||||
const protoDirPath = `${inputDirectoryPath}/src/proto` as const;
|
|
||||||
const tensorProtoDirPath =
|
|
||||||
`${inputDirectoryPath}/src/tensorizer/proto` as const;
|
|
||||||
|
|
||||||
// Clone latest tensorizer git repository
|
|
||||||
|
|
||||||
const tensorizerGitPath = `${inputDirectoryPath}/src/tensorizer`;
|
|
||||||
|
|
||||||
// Clone and force update the api-interfaces Git repository
|
|
||||||
|
|
||||||
!FileSystem.existsSync(inputDirectoryPath) &&
|
|
||||||
ChildProcess.execSync(
|
|
||||||
`git clone https://github.com/Stability-AI/api-interfaces.git ${inputDirectoryPath}`
|
|
||||||
);
|
|
||||||
|
|
||||||
ChildProcess.execSync("git reset --hard origin/main", {
|
|
||||||
cwd: inputDirectoryPath,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clone and force update the tensorizer Git repository
|
|
||||||
|
|
||||||
!FileSystem.existsSync(`${tensorizerGitPath}/.git`) &&
|
|
||||||
ChildProcess.execSync(
|
|
||||||
`git clone https://github.com/coreweave/tensorizer.git ${tensorizerGitPath}`
|
|
||||||
);
|
|
||||||
|
|
||||||
ChildProcess.execSync("git reset --hard origin/main", {
|
|
||||||
cwd: tensorizerGitPath,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Copy tensors.proto from tensorizer to api-interfaces proto directory
|
|
||||||
|
|
||||||
FileSystem.copyFileSync(
|
|
||||||
`${tensorProtoDirPath}/tensors.proto`,
|
|
||||||
`${protoDirPath}/tensors.proto`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get all the proto paths and source files
|
|
||||||
|
|
||||||
const protoPaths = [
|
|
||||||
protoDirPath,
|
|
||||||
...FileSystem.readdirSync(protoDirPath)
|
|
||||||
.filter((file) => file.endsWith(".proto"))
|
|
||||||
.map((file) => Path.join(protoDirPath, file)),
|
|
||||||
].join(" ");
|
|
||||||
|
|
||||||
// Generate TypeScript files from the final list of proto files
|
|
||||||
|
|
||||||
ChildProcess.execSync(
|
|
||||||
`npx protoc --ts_out ${outputDirectoryPath} --proto_path ${protoPaths}`
|
|
||||||
);
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
// @generated by protobuf-ts 2.8.2
|
|
||||||
// @generated from protobuf file "dashboard.proto" (package "gooseai", syntax proto3)
|
|
||||||
// tslint:disable
|
|
||||||
import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import type { ServiceInfo } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import { DashboardService } from "./dashboard";
|
|
||||||
import type { GetAutoChargeRequest } from "./dashboard";
|
|
||||||
import type { AutoChargeIntent } from "./dashboard";
|
|
||||||
import type { CreateAutoChargeIntentRequest } from "./dashboard";
|
|
||||||
import type { Charges } from "./dashboard";
|
|
||||||
import type { GetChargesRequest } from "./dashboard";
|
|
||||||
import type { Charge } from "./dashboard";
|
|
||||||
import type { CreateChargeRequest } from "./dashboard";
|
|
||||||
import type { UserPasswordChangeTicket } from "./dashboard";
|
|
||||||
import type { UpdateUserInfoRequest } from "./dashboard";
|
|
||||||
import type { ClientSettings } from "./dashboard";
|
|
||||||
import type { UpdateDefaultOrganizationRequest } from "./dashboard";
|
|
||||||
import type { APIKeyFindRequest } from "./dashboard";
|
|
||||||
import type { APIKey } from "./dashboard";
|
|
||||||
import type { APIKeyRequest } from "./dashboard";
|
|
||||||
import type { Metrics } from "./dashboard";
|
|
||||||
import type { GetMetricsRequest } from "./dashboard";
|
|
||||||
import type { Organization } from "./dashboard";
|
|
||||||
import type { GetOrganizationRequest } from "./dashboard";
|
|
||||||
import { stackIntercept } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import type { User } from "./dashboard";
|
|
||||||
import type { EmptyRequest } from "./dashboard";
|
|
||||||
import type { UnaryCall } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import type { RpcOptions } from "@protobuf-ts/runtime-rpc";
|
|
||||||
/**
|
|
||||||
* @generated from protobuf service gooseai.DashboardService
|
|
||||||
*/
|
|
||||||
export interface IDashboardServiceClient {
|
|
||||||
/**
|
|
||||||
* Get info
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: GetMe(gooseai.EmptyRequest) returns (gooseai.User);
|
|
||||||
*/
|
|
||||||
getMe(input: EmptyRequest, options?: RpcOptions): UnaryCall<EmptyRequest, User>;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: GetOrganization(gooseai.GetOrganizationRequest) returns (gooseai.Organization);
|
|
||||||
*/
|
|
||||||
getOrganization(input: GetOrganizationRequest, options?: RpcOptions): UnaryCall<GetOrganizationRequest, Organization>;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: GetMetrics(gooseai.GetMetricsRequest) returns (gooseai.Metrics);
|
|
||||||
*/
|
|
||||||
getMetrics(input: GetMetricsRequest, options?: RpcOptions): UnaryCall<GetMetricsRequest, Metrics>;
|
|
||||||
/**
|
|
||||||
* API key management
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: CreateAPIKey(gooseai.APIKeyRequest) returns (gooseai.APIKey);
|
|
||||||
*/
|
|
||||||
createAPIKey(input: APIKeyRequest, options?: RpcOptions): UnaryCall<APIKeyRequest, APIKey>;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: DeleteAPIKey(gooseai.APIKeyFindRequest) returns (gooseai.APIKey);
|
|
||||||
*/
|
|
||||||
deleteAPIKey(input: APIKeyFindRequest, options?: RpcOptions): UnaryCall<APIKeyFindRequest, APIKey>;
|
|
||||||
/**
|
|
||||||
* User settings
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: UpdateDefaultOrganization(gooseai.UpdateDefaultOrganizationRequest) returns (gooseai.User);
|
|
||||||
*/
|
|
||||||
updateDefaultOrganization(input: UpdateDefaultOrganizationRequest, options?: RpcOptions): UnaryCall<UpdateDefaultOrganizationRequest, User>;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: GetClientSettings(gooseai.EmptyRequest) returns (gooseai.ClientSettings);
|
|
||||||
*/
|
|
||||||
getClientSettings(input: EmptyRequest, options?: RpcOptions): UnaryCall<EmptyRequest, ClientSettings>;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: SetClientSettings(gooseai.ClientSettings) returns (gooseai.ClientSettings);
|
|
||||||
*/
|
|
||||||
setClientSettings(input: ClientSettings, options?: RpcOptions): UnaryCall<ClientSettings, ClientSettings>;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: UpdateUserInfo(gooseai.UpdateUserInfoRequest) returns (gooseai.User);
|
|
||||||
*/
|
|
||||||
updateUserInfo(input: UpdateUserInfoRequest, options?: RpcOptions): UnaryCall<UpdateUserInfoRequest, User>;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: CreatePasswordChangeTicket(gooseai.EmptyRequest) returns (gooseai.UserPasswordChangeTicket);
|
|
||||||
*/
|
|
||||||
createPasswordChangeTicket(input: EmptyRequest, options?: RpcOptions): UnaryCall<EmptyRequest, UserPasswordChangeTicket>;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: DeleteAccount(gooseai.EmptyRequest) returns (gooseai.User);
|
|
||||||
*/
|
|
||||||
deleteAccount(input: EmptyRequest, options?: RpcOptions): UnaryCall<EmptyRequest, User>;
|
|
||||||
/**
|
|
||||||
* Payment functions
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: CreateCharge(gooseai.CreateChargeRequest) returns (gooseai.Charge);
|
|
||||||
*/
|
|
||||||
createCharge(input: CreateChargeRequest, options?: RpcOptions): UnaryCall<CreateChargeRequest, Charge>;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: GetCharges(gooseai.GetChargesRequest) returns (gooseai.Charges);
|
|
||||||
*/
|
|
||||||
getCharges(input: GetChargesRequest, options?: RpcOptions): UnaryCall<GetChargesRequest, Charges>;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: CreateAutoChargeIntent(gooseai.CreateAutoChargeIntentRequest) returns (gooseai.AutoChargeIntent);
|
|
||||||
*/
|
|
||||||
createAutoChargeIntent(input: CreateAutoChargeIntentRequest, options?: RpcOptions): UnaryCall<CreateAutoChargeIntentRequest, AutoChargeIntent>;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: UpdateAutoChargeIntent(gooseai.CreateAutoChargeIntentRequest) returns (gooseai.AutoChargeIntent);
|
|
||||||
*/
|
|
||||||
updateAutoChargeIntent(input: CreateAutoChargeIntentRequest, options?: RpcOptions): UnaryCall<CreateAutoChargeIntentRequest, AutoChargeIntent>;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: GetAutoChargeIntent(gooseai.GetAutoChargeRequest) returns (gooseai.AutoChargeIntent);
|
|
||||||
*/
|
|
||||||
getAutoChargeIntent(input: GetAutoChargeRequest, options?: RpcOptions): UnaryCall<GetAutoChargeRequest, AutoChargeIntent>;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf service gooseai.DashboardService
|
|
||||||
*/
|
|
||||||
export class DashboardServiceClient implements IDashboardServiceClient, ServiceInfo {
|
|
||||||
typeName = DashboardService.typeName;
|
|
||||||
methods = DashboardService.methods;
|
|
||||||
options = DashboardService.options;
|
|
||||||
constructor(private readonly _transport: RpcTransport) {
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Get info
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: GetMe(gooseai.EmptyRequest) returns (gooseai.User);
|
|
||||||
*/
|
|
||||||
getMe(input: EmptyRequest, options?: RpcOptions): UnaryCall<EmptyRequest, User> {
|
|
||||||
const method = this.methods[0], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<EmptyRequest, User>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: GetOrganization(gooseai.GetOrganizationRequest) returns (gooseai.Organization);
|
|
||||||
*/
|
|
||||||
getOrganization(input: GetOrganizationRequest, options?: RpcOptions): UnaryCall<GetOrganizationRequest, Organization> {
|
|
||||||
const method = this.methods[1], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<GetOrganizationRequest, Organization>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: GetMetrics(gooseai.GetMetricsRequest) returns (gooseai.Metrics);
|
|
||||||
*/
|
|
||||||
getMetrics(input: GetMetricsRequest, options?: RpcOptions): UnaryCall<GetMetricsRequest, Metrics> {
|
|
||||||
const method = this.methods[2], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<GetMetricsRequest, Metrics>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* API key management
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: CreateAPIKey(gooseai.APIKeyRequest) returns (gooseai.APIKey);
|
|
||||||
*/
|
|
||||||
createAPIKey(input: APIKeyRequest, options?: RpcOptions): UnaryCall<APIKeyRequest, APIKey> {
|
|
||||||
const method = this.methods[3], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<APIKeyRequest, APIKey>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: DeleteAPIKey(gooseai.APIKeyFindRequest) returns (gooseai.APIKey);
|
|
||||||
*/
|
|
||||||
deleteAPIKey(input: APIKeyFindRequest, options?: RpcOptions): UnaryCall<APIKeyFindRequest, APIKey> {
|
|
||||||
const method = this.methods[4], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<APIKeyFindRequest, APIKey>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* User settings
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: UpdateDefaultOrganization(gooseai.UpdateDefaultOrganizationRequest) returns (gooseai.User);
|
|
||||||
*/
|
|
||||||
updateDefaultOrganization(input: UpdateDefaultOrganizationRequest, options?: RpcOptions): UnaryCall<UpdateDefaultOrganizationRequest, User> {
|
|
||||||
const method = this.methods[5], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<UpdateDefaultOrganizationRequest, User>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: GetClientSettings(gooseai.EmptyRequest) returns (gooseai.ClientSettings);
|
|
||||||
*/
|
|
||||||
getClientSettings(input: EmptyRequest, options?: RpcOptions): UnaryCall<EmptyRequest, ClientSettings> {
|
|
||||||
const method = this.methods[6], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<EmptyRequest, ClientSettings>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: SetClientSettings(gooseai.ClientSettings) returns (gooseai.ClientSettings);
|
|
||||||
*/
|
|
||||||
setClientSettings(input: ClientSettings, options?: RpcOptions): UnaryCall<ClientSettings, ClientSettings> {
|
|
||||||
const method = this.methods[7], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<ClientSettings, ClientSettings>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: UpdateUserInfo(gooseai.UpdateUserInfoRequest) returns (gooseai.User);
|
|
||||||
*/
|
|
||||||
updateUserInfo(input: UpdateUserInfoRequest, options?: RpcOptions): UnaryCall<UpdateUserInfoRequest, User> {
|
|
||||||
const method = this.methods[8], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<UpdateUserInfoRequest, User>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: CreatePasswordChangeTicket(gooseai.EmptyRequest) returns (gooseai.UserPasswordChangeTicket);
|
|
||||||
*/
|
|
||||||
createPasswordChangeTicket(input: EmptyRequest, options?: RpcOptions): UnaryCall<EmptyRequest, UserPasswordChangeTicket> {
|
|
||||||
const method = this.methods[9], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<EmptyRequest, UserPasswordChangeTicket>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: DeleteAccount(gooseai.EmptyRequest) returns (gooseai.User);
|
|
||||||
*/
|
|
||||||
deleteAccount(input: EmptyRequest, options?: RpcOptions): UnaryCall<EmptyRequest, User> {
|
|
||||||
const method = this.methods[10], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<EmptyRequest, User>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Payment functions
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: CreateCharge(gooseai.CreateChargeRequest) returns (gooseai.Charge);
|
|
||||||
*/
|
|
||||||
createCharge(input: CreateChargeRequest, options?: RpcOptions): UnaryCall<CreateChargeRequest, Charge> {
|
|
||||||
const method = this.methods[11], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<CreateChargeRequest, Charge>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: GetCharges(gooseai.GetChargesRequest) returns (gooseai.Charges);
|
|
||||||
*/
|
|
||||||
getCharges(input: GetChargesRequest, options?: RpcOptions): UnaryCall<GetChargesRequest, Charges> {
|
|
||||||
const method = this.methods[12], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<GetChargesRequest, Charges>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: CreateAutoChargeIntent(gooseai.CreateAutoChargeIntentRequest) returns (gooseai.AutoChargeIntent);
|
|
||||||
*/
|
|
||||||
createAutoChargeIntent(input: CreateAutoChargeIntentRequest, options?: RpcOptions): UnaryCall<CreateAutoChargeIntentRequest, AutoChargeIntent> {
|
|
||||||
const method = this.methods[13], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<CreateAutoChargeIntentRequest, AutoChargeIntent>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: UpdateAutoChargeIntent(gooseai.CreateAutoChargeIntentRequest) returns (gooseai.AutoChargeIntent);
|
|
||||||
*/
|
|
||||||
updateAutoChargeIntent(input: CreateAutoChargeIntentRequest, options?: RpcOptions): UnaryCall<CreateAutoChargeIntentRequest, AutoChargeIntent> {
|
|
||||||
const method = this.methods[14], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<CreateAutoChargeIntentRequest, AutoChargeIntent>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: GetAutoChargeIntent(gooseai.GetAutoChargeRequest) returns (gooseai.AutoChargeIntent);
|
|
||||||
*/
|
|
||||||
getAutoChargeIntent(input: GetAutoChargeRequest, options?: RpcOptions): UnaryCall<GetAutoChargeRequest, AutoChargeIntent> {
|
|
||||||
const method = this.methods[15], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<GetAutoChargeRequest, AutoChargeIntent>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
// @generated by protobuf-ts 2.8.2
|
|
||||||
// @generated from protobuf file "engines.proto" (package "gooseai", syntax proto3)
|
|
||||||
// tslint:disable
|
|
||||||
import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import type { ServiceInfo } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import { EnginesService } from "./engines";
|
|
||||||
import { stackIntercept } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import type { Engines } from "./engines";
|
|
||||||
import type { ListEnginesRequest } from "./engines";
|
|
||||||
import type { UnaryCall } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import type { RpcOptions } from "@protobuf-ts/runtime-rpc";
|
|
||||||
/**
|
|
||||||
* @generated from protobuf service gooseai.EnginesService
|
|
||||||
*/
|
|
||||||
export interface IEnginesServiceClient {
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: ListEngines(gooseai.ListEnginesRequest) returns (gooseai.Engines);
|
|
||||||
*/
|
|
||||||
listEngines(input: ListEnginesRequest, options?: RpcOptions): UnaryCall<ListEnginesRequest, Engines>;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf service gooseai.EnginesService
|
|
||||||
*/
|
|
||||||
export class EnginesServiceClient implements IEnginesServiceClient, ServiceInfo {
|
|
||||||
typeName = EnginesService.typeName;
|
|
||||||
methods = EnginesService.methods;
|
|
||||||
options = EnginesService.options;
|
|
||||||
constructor(private readonly _transport: RpcTransport) {
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: ListEngines(gooseai.ListEnginesRequest) returns (gooseai.Engines);
|
|
||||||
*/
|
|
||||||
listEngines(input: ListEnginesRequest, options?: RpcOptions): UnaryCall<ListEnginesRequest, Engines> {
|
|
||||||
const method = this.methods[0], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<ListEnginesRequest, Engines>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,279 +0,0 @@
|
|||||||
// @generated by protobuf-ts 2.8.2
|
|
||||||
// @generated from protobuf file "engines.proto" (package "gooseai", syntax proto3)
|
|
||||||
// tslint:disable
|
|
||||||
import { ServiceType } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import type { BinaryWriteOptions } from "@protobuf-ts/runtime";
|
|
||||||
import type { IBinaryWriter } from "@protobuf-ts/runtime";
|
|
||||||
import { WireType } from "@protobuf-ts/runtime";
|
|
||||||
import type { BinaryReadOptions } from "@protobuf-ts/runtime";
|
|
||||||
import type { IBinaryReader } from "@protobuf-ts/runtime";
|
|
||||||
import { UnknownFieldHandler } from "@protobuf-ts/runtime";
|
|
||||||
import type { PartialMessage } from "@protobuf-ts/runtime";
|
|
||||||
import { reflectionMergePartial } from "@protobuf-ts/runtime";
|
|
||||||
import { MESSAGE_TYPE } from "@protobuf-ts/runtime";
|
|
||||||
import { MessageType } from "@protobuf-ts/runtime";
|
|
||||||
/**
|
|
||||||
* Engine info struct
|
|
||||||
*
|
|
||||||
* @generated from protobuf message gooseai.EngineInfo
|
|
||||||
*/
|
|
||||||
export interface EngineInfo {
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: string id = 1;
|
|
||||||
*/
|
|
||||||
id: string;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: string owner = 2;
|
|
||||||
*/
|
|
||||||
owner: string;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: bool ready = 3;
|
|
||||||
*/
|
|
||||||
ready: boolean;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: gooseai.EngineType type = 4;
|
|
||||||
*/
|
|
||||||
type: EngineType;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: gooseai.EngineTokenizer tokenizer = 5;
|
|
||||||
*/
|
|
||||||
tokenizer: EngineTokenizer;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: string name = 6;
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: string description = 7;
|
|
||||||
*/
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Empty
|
|
||||||
*
|
|
||||||
* @generated from protobuf message gooseai.ListEnginesRequest
|
|
||||||
*/
|
|
||||||
export interface ListEnginesRequest {
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Engine info list
|
|
||||||
*
|
|
||||||
* @generated from protobuf message gooseai.Engines
|
|
||||||
*/
|
|
||||||
export interface Engines {
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: repeated gooseai.EngineInfo engine = 1;
|
|
||||||
*/
|
|
||||||
engine: EngineInfo[];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Possible engine type
|
|
||||||
*
|
|
||||||
* @generated from protobuf enum gooseai.EngineType
|
|
||||||
*/
|
|
||||||
export enum EngineType {
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: TEXT = 0;
|
|
||||||
*/
|
|
||||||
TEXT = 0,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: PICTURE = 1;
|
|
||||||
*/
|
|
||||||
PICTURE = 1,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: AUDIO = 2;
|
|
||||||
*/
|
|
||||||
AUDIO = 2,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: VIDEO = 3;
|
|
||||||
*/
|
|
||||||
VIDEO = 3,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: CLASSIFICATION = 4;
|
|
||||||
*/
|
|
||||||
CLASSIFICATION = 4,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: STORAGE = 5;
|
|
||||||
*/
|
|
||||||
STORAGE = 5
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum gooseai.EngineTokenizer
|
|
||||||
*/
|
|
||||||
export enum EngineTokenizer {
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: GPT2 = 0;
|
|
||||||
*/
|
|
||||||
GPT2 = 0,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: PILE = 1;
|
|
||||||
*/
|
|
||||||
PILE = 1
|
|
||||||
}
|
|
||||||
// @generated message type with reflection information, may provide speed optimized methods
|
|
||||||
class EngineInfo$Type extends MessageType<EngineInfo> {
|
|
||||||
constructor() {
|
|
||||||
super("gooseai.EngineInfo", [
|
|
||||||
{ no: 1, name: "id", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
|
||||||
{ no: 2, name: "owner", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
|
||||||
{ no: 3, name: "ready", kind: "scalar", T: 8 /*ScalarType.BOOL*/ },
|
|
||||||
{ no: 4, name: "type", kind: "enum", T: () => ["gooseai.EngineType", EngineType] },
|
|
||||||
{ no: 5, name: "tokenizer", kind: "enum", T: () => ["gooseai.EngineTokenizer", EngineTokenizer] },
|
|
||||||
{ no: 6, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
|
||||||
{ no: 7, name: "description", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
create(value?: PartialMessage<EngineInfo>): EngineInfo {
|
|
||||||
const message = { id: "", owner: "", ready: false, type: 0, tokenizer: 0, name: "", description: "" };
|
|
||||||
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
|
||||||
if (value !== undefined)
|
|
||||||
reflectionMergePartial<EngineInfo>(this, message, value);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: EngineInfo): EngineInfo {
|
|
||||||
let message = target ?? this.create(), end = reader.pos + length;
|
|
||||||
while (reader.pos < end) {
|
|
||||||
let [fieldNo, wireType] = reader.tag();
|
|
||||||
switch (fieldNo) {
|
|
||||||
case /* string id */ 1:
|
|
||||||
message.id = reader.string();
|
|
||||||
break;
|
|
||||||
case /* string owner */ 2:
|
|
||||||
message.owner = reader.string();
|
|
||||||
break;
|
|
||||||
case /* bool ready */ 3:
|
|
||||||
message.ready = reader.bool();
|
|
||||||
break;
|
|
||||||
case /* gooseai.EngineType type */ 4:
|
|
||||||
message.type = reader.int32();
|
|
||||||
break;
|
|
||||||
case /* gooseai.EngineTokenizer tokenizer */ 5:
|
|
||||||
message.tokenizer = reader.int32();
|
|
||||||
break;
|
|
||||||
case /* string name */ 6:
|
|
||||||
message.name = reader.string();
|
|
||||||
break;
|
|
||||||
case /* string description */ 7:
|
|
||||||
message.description = reader.string();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
let u = options.readUnknownField;
|
|
||||||
if (u === "throw")
|
|
||||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
|
||||||
let d = reader.skip(wireType);
|
|
||||||
if (u !== false)
|
|
||||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
internalBinaryWrite(message: EngineInfo, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
|
||||||
/* string id = 1; */
|
|
||||||
if (message.id !== "")
|
|
||||||
writer.tag(1, WireType.LengthDelimited).string(message.id);
|
|
||||||
/* string owner = 2; */
|
|
||||||
if (message.owner !== "")
|
|
||||||
writer.tag(2, WireType.LengthDelimited).string(message.owner);
|
|
||||||
/* bool ready = 3; */
|
|
||||||
if (message.ready !== false)
|
|
||||||
writer.tag(3, WireType.Varint).bool(message.ready);
|
|
||||||
/* gooseai.EngineType type = 4; */
|
|
||||||
if (message.type !== 0)
|
|
||||||
writer.tag(4, WireType.Varint).int32(message.type);
|
|
||||||
/* gooseai.EngineTokenizer tokenizer = 5; */
|
|
||||||
if (message.tokenizer !== 0)
|
|
||||||
writer.tag(5, WireType.Varint).int32(message.tokenizer);
|
|
||||||
/* string name = 6; */
|
|
||||||
if (message.name !== "")
|
|
||||||
writer.tag(6, WireType.LengthDelimited).string(message.name);
|
|
||||||
/* string description = 7; */
|
|
||||||
if (message.description !== "")
|
|
||||||
writer.tag(7, WireType.LengthDelimited).string(message.description);
|
|
||||||
let u = options.writeUnknownFields;
|
|
||||||
if (u !== false)
|
|
||||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
|
||||||
return writer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated MessageType for protobuf message gooseai.EngineInfo
|
|
||||||
*/
|
|
||||||
export const EngineInfo = new EngineInfo$Type();
|
|
||||||
// @generated message type with reflection information, may provide speed optimized methods
|
|
||||||
class ListEnginesRequest$Type extends MessageType<ListEnginesRequest> {
|
|
||||||
constructor() {
|
|
||||||
super("gooseai.ListEnginesRequest", []);
|
|
||||||
}
|
|
||||||
create(value?: PartialMessage<ListEnginesRequest>): ListEnginesRequest {
|
|
||||||
const message = {};
|
|
||||||
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
|
||||||
if (value !== undefined)
|
|
||||||
reflectionMergePartial<ListEnginesRequest>(this, message, value);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ListEnginesRequest): ListEnginesRequest {
|
|
||||||
return target ?? this.create();
|
|
||||||
}
|
|
||||||
internalBinaryWrite(message: ListEnginesRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
|
||||||
let u = options.writeUnknownFields;
|
|
||||||
if (u !== false)
|
|
||||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
|
||||||
return writer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated MessageType for protobuf message gooseai.ListEnginesRequest
|
|
||||||
*/
|
|
||||||
export const ListEnginesRequest = new ListEnginesRequest$Type();
|
|
||||||
// @generated message type with reflection information, may provide speed optimized methods
|
|
||||||
class Engines$Type extends MessageType<Engines> {
|
|
||||||
constructor() {
|
|
||||||
super("gooseai.Engines", [
|
|
||||||
{ no: 1, name: "engine", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => EngineInfo }
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
create(value?: PartialMessage<Engines>): Engines {
|
|
||||||
const message = { engine: [] };
|
|
||||||
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
|
||||||
if (value !== undefined)
|
|
||||||
reflectionMergePartial<Engines>(this, message, value);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Engines): Engines {
|
|
||||||
let message = target ?? this.create(), end = reader.pos + length;
|
|
||||||
while (reader.pos < end) {
|
|
||||||
let [fieldNo, wireType] = reader.tag();
|
|
||||||
switch (fieldNo) {
|
|
||||||
case /* repeated gooseai.EngineInfo engine */ 1:
|
|
||||||
message.engine.push(EngineInfo.internalBinaryRead(reader, reader.uint32(), options));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
let u = options.readUnknownField;
|
|
||||||
if (u === "throw")
|
|
||||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
|
||||||
let d = reader.skip(wireType);
|
|
||||||
if (u !== false)
|
|
||||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
internalBinaryWrite(message: Engines, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
|
||||||
/* repeated gooseai.EngineInfo engine = 1; */
|
|
||||||
for (let i = 0; i < message.engine.length; i++)
|
|
||||||
EngineInfo.internalBinaryWrite(message.engine[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join();
|
|
||||||
let u = options.writeUnknownFields;
|
|
||||||
if (u !== false)
|
|
||||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
|
||||||
return writer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated MessageType for protobuf message gooseai.Engines
|
|
||||||
*/
|
|
||||||
export const Engines = new Engines$Type();
|
|
||||||
/**
|
|
||||||
* @generated ServiceType for protobuf service gooseai.EnginesService
|
|
||||||
*/
|
|
||||||
export const EnginesService = new ServiceType("gooseai.EnginesService", [
|
|
||||||
{ name: "ListEngines", options: {}, I: ListEnginesRequest, O: Engines }
|
|
||||||
]);
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
// @generated by protobuf-ts 2.8.2
|
|
||||||
// @generated from protobuf file "generation.proto" (package "gooseai", syntax proto3)
|
|
||||||
// tslint:disable
|
|
||||||
import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import type { ServiceInfo } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import { GenerationService } from "./generation";
|
|
||||||
import type { ChainRequest } from "./generation";
|
|
||||||
import { stackIntercept } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import type { Answer } from "./generation";
|
|
||||||
import type { Request } from "./generation";
|
|
||||||
import type { ServerStreamingCall } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import type { RpcOptions } from "@protobuf-ts/runtime-rpc";
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* gRPC services
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @generated from protobuf service gooseai.GenerationService
|
|
||||||
*/
|
|
||||||
export interface IGenerationServiceClient {
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: Generate(gooseai.Request) returns (stream gooseai.Answer);
|
|
||||||
*/
|
|
||||||
generate(input: Request, options?: RpcOptions): ServerStreamingCall<Request, Answer>;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: ChainGenerate(gooseai.ChainRequest) returns (stream gooseai.Answer);
|
|
||||||
*/
|
|
||||||
chainGenerate(input: ChainRequest, options?: RpcOptions): ServerStreamingCall<ChainRequest, Answer>;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* gRPC services
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @generated from protobuf service gooseai.GenerationService
|
|
||||||
*/
|
|
||||||
export class GenerationServiceClient implements IGenerationServiceClient, ServiceInfo {
|
|
||||||
typeName = GenerationService.typeName;
|
|
||||||
methods = GenerationService.methods;
|
|
||||||
options = GenerationService.options;
|
|
||||||
constructor(private readonly _transport: RpcTransport) {
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: Generate(gooseai.Request) returns (stream gooseai.Answer);
|
|
||||||
*/
|
|
||||||
generate(input: Request, options?: RpcOptions): ServerStreamingCall<Request, Answer> {
|
|
||||||
const method = this.methods[0], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<Request, Answer>("serverStreaming", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: ChainGenerate(gooseai.ChainRequest) returns (stream gooseai.Answer);
|
|
||||||
*/
|
|
||||||
chainGenerate(input: ChainRequest, options?: RpcOptions): ServerStreamingCall<ChainRequest, Answer> {
|
|
||||||
const method = this.methods[1], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<ChainRequest, Answer>("serverStreaming", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,480 +0,0 @@
|
|||||||
// @generated by protobuf-ts 2.8.2
|
|
||||||
// @generated from protobuf file "google/protobuf/struct.proto" (package "google.protobuf", syntax proto3)
|
|
||||||
// tslint:disable
|
|
||||||
//
|
|
||||||
// Protocol Buffers - Google's data interchange format
|
|
||||||
// Copyright 2008 Google Inc. All rights reserved.
|
|
||||||
// https://developers.google.com/protocol-buffers/
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions are
|
|
||||||
// met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright
|
|
||||||
// notice, this list of conditions and the following disclaimer.
|
|
||||||
// * Redistributions in binary form must reproduce the above
|
|
||||||
// copyright notice, this list of conditions and the following disclaimer
|
|
||||||
// in the documentation and/or other materials provided with the
|
|
||||||
// distribution.
|
|
||||||
// * Neither the name of Google Inc. nor the names of its
|
|
||||||
// contributors may be used to endorse or promote products derived from
|
|
||||||
// this software without specific prior written permission.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
//
|
|
||||||
import type { BinaryWriteOptions } from "@protobuf-ts/runtime";
|
|
||||||
import type { IBinaryWriter } from "@protobuf-ts/runtime";
|
|
||||||
import { WireType } from "@protobuf-ts/runtime";
|
|
||||||
import type { BinaryReadOptions } from "@protobuf-ts/runtime";
|
|
||||||
import type { IBinaryReader } from "@protobuf-ts/runtime";
|
|
||||||
import { UnknownFieldHandler } from "@protobuf-ts/runtime";
|
|
||||||
import type { PartialMessage } from "@protobuf-ts/runtime";
|
|
||||||
import { reflectionMergePartial } from "@protobuf-ts/runtime";
|
|
||||||
import { MESSAGE_TYPE } from "@protobuf-ts/runtime";
|
|
||||||
import { isJsonObject } from "@protobuf-ts/runtime";
|
|
||||||
import { typeofJsonValue } from "@protobuf-ts/runtime";
|
|
||||||
import type { JsonValue } from "@protobuf-ts/runtime";
|
|
||||||
import type { JsonReadOptions } from "@protobuf-ts/runtime";
|
|
||||||
import type { JsonWriteOptions } from "@protobuf-ts/runtime";
|
|
||||||
import type { JsonObject } from "@protobuf-ts/runtime";
|
|
||||||
import { MessageType } from "@protobuf-ts/runtime";
|
|
||||||
/**
|
|
||||||
* `Struct` represents a structured data value, consisting of fields
|
|
||||||
* which map to dynamically typed values. In some languages, `Struct`
|
|
||||||
* might be supported by a native representation. For example, in
|
|
||||||
* scripting languages like JS a struct is represented as an
|
|
||||||
* object. The details of that representation are described together
|
|
||||||
* with the proto support for the language.
|
|
||||||
*
|
|
||||||
* The JSON representation for `Struct` is JSON object.
|
|
||||||
*
|
|
||||||
* @generated from protobuf message google.protobuf.Struct
|
|
||||||
*/
|
|
||||||
export interface Struct {
|
|
||||||
/**
|
|
||||||
* Unordered map of dynamically typed values.
|
|
||||||
*
|
|
||||||
* @generated from protobuf field: map<string, google.protobuf.Value> fields = 1;
|
|
||||||
*/
|
|
||||||
fields: {
|
|
||||||
[key: string]: Value;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* `Value` represents a dynamically typed value which can be either
|
|
||||||
* null, a number, a string, a boolean, a recursive struct value, or a
|
|
||||||
* list of values. A producer of value is expected to set one of these
|
|
||||||
* variants. Absence of any variant indicates an error.
|
|
||||||
*
|
|
||||||
* The JSON representation for `Value` is JSON value.
|
|
||||||
*
|
|
||||||
* @generated from protobuf message google.protobuf.Value
|
|
||||||
*/
|
|
||||||
export interface Value {
|
|
||||||
/**
|
|
||||||
* @generated from protobuf oneof: kind
|
|
||||||
*/
|
|
||||||
kind: {
|
|
||||||
oneofKind: "nullValue";
|
|
||||||
/**
|
|
||||||
* Represents a null value.
|
|
||||||
*
|
|
||||||
* @generated from protobuf field: google.protobuf.NullValue null_value = 1;
|
|
||||||
*/
|
|
||||||
nullValue: NullValue;
|
|
||||||
} | {
|
|
||||||
oneofKind: "numberValue";
|
|
||||||
/**
|
|
||||||
* Represents a double value.
|
|
||||||
*
|
|
||||||
* @generated from protobuf field: double number_value = 2;
|
|
||||||
*/
|
|
||||||
numberValue: number;
|
|
||||||
} | {
|
|
||||||
oneofKind: "stringValue";
|
|
||||||
/**
|
|
||||||
* Represents a string value.
|
|
||||||
*
|
|
||||||
* @generated from protobuf field: string string_value = 3;
|
|
||||||
*/
|
|
||||||
stringValue: string;
|
|
||||||
} | {
|
|
||||||
oneofKind: "boolValue";
|
|
||||||
/**
|
|
||||||
* Represents a boolean value.
|
|
||||||
*
|
|
||||||
* @generated from protobuf field: bool bool_value = 4;
|
|
||||||
*/
|
|
||||||
boolValue: boolean;
|
|
||||||
} | {
|
|
||||||
oneofKind: "structValue";
|
|
||||||
/**
|
|
||||||
* Represents a structured value.
|
|
||||||
*
|
|
||||||
* @generated from protobuf field: google.protobuf.Struct struct_value = 5;
|
|
||||||
*/
|
|
||||||
structValue: Struct;
|
|
||||||
} | {
|
|
||||||
oneofKind: "listValue";
|
|
||||||
/**
|
|
||||||
* Represents a repeated `Value`.
|
|
||||||
*
|
|
||||||
* @generated from protobuf field: google.protobuf.ListValue list_value = 6;
|
|
||||||
*/
|
|
||||||
listValue: ListValue;
|
|
||||||
} | {
|
|
||||||
oneofKind: undefined;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* `ListValue` is a wrapper around a repeated field of values.
|
|
||||||
*
|
|
||||||
* The JSON representation for `ListValue` is JSON array.
|
|
||||||
*
|
|
||||||
* @generated from protobuf message google.protobuf.ListValue
|
|
||||||
*/
|
|
||||||
export interface ListValue {
|
|
||||||
/**
|
|
||||||
* Repeated field of dynamically typed values.
|
|
||||||
*
|
|
||||||
* @generated from protobuf field: repeated google.protobuf.Value values = 1;
|
|
||||||
*/
|
|
||||||
values: Value[];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* `NullValue` is a singleton enumeration to represent the null value for the
|
|
||||||
* `Value` type union.
|
|
||||||
*
|
|
||||||
* The JSON representation for `NullValue` is JSON `null`.
|
|
||||||
*
|
|
||||||
* @generated from protobuf enum google.protobuf.NullValue
|
|
||||||
*/
|
|
||||||
export enum NullValue {
|
|
||||||
/**
|
|
||||||
* Null value.
|
|
||||||
*
|
|
||||||
* @generated from protobuf enum value: NULL_VALUE = 0;
|
|
||||||
*/
|
|
||||||
NULL_VALUE = 0
|
|
||||||
}
|
|
||||||
// @generated message type with reflection information, may provide speed optimized methods
|
|
||||||
class Struct$Type extends MessageType<Struct> {
|
|
||||||
constructor() {
|
|
||||||
super("google.protobuf.Struct", [
|
|
||||||
{ no: 1, name: "fields", kind: "map", K: 9 /*ScalarType.STRING*/, V: { kind: "message", T: () => Value } }
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Encode `Struct` to JSON object.
|
|
||||||
*/
|
|
||||||
internalJsonWrite(message: Struct, options: JsonWriteOptions): JsonValue {
|
|
||||||
let json: JsonObject = {};
|
|
||||||
for (let [k, v] of Object.entries(message.fields)) {
|
|
||||||
json[k] = Value.toJson(v);
|
|
||||||
}
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Decode `Struct` from JSON object.
|
|
||||||
*/
|
|
||||||
internalJsonRead(json: JsonValue, options: JsonReadOptions, target?: Struct): Struct {
|
|
||||||
if (!isJsonObject(json))
|
|
||||||
throw new globalThis.Error("Unable to parse message " + this.typeName + " from JSON " + typeofJsonValue(json) + ".");
|
|
||||||
if (!target)
|
|
||||||
target = this.create();
|
|
||||||
for (let [k, v] of globalThis.Object.entries(json)) {
|
|
||||||
target.fields[k] = Value.fromJson(v);
|
|
||||||
}
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
create(value?: PartialMessage<Struct>): Struct {
|
|
||||||
const message = { fields: {} };
|
|
||||||
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
|
||||||
if (value !== undefined)
|
|
||||||
reflectionMergePartial<Struct>(this, message, value);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Struct): Struct {
|
|
||||||
let message = target ?? this.create(), end = reader.pos + length;
|
|
||||||
while (reader.pos < end) {
|
|
||||||
let [fieldNo, wireType] = reader.tag();
|
|
||||||
switch (fieldNo) {
|
|
||||||
case /* map<string, google.protobuf.Value> fields */ 1:
|
|
||||||
this.binaryReadMap1(message.fields, reader, options);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
let u = options.readUnknownField;
|
|
||||||
if (u === "throw")
|
|
||||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
|
||||||
let d = reader.skip(wireType);
|
|
||||||
if (u !== false)
|
|
||||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
private binaryReadMap1(map: Struct["fields"], reader: IBinaryReader, options: BinaryReadOptions): void {
|
|
||||||
let len = reader.uint32(), end = reader.pos + len, key: keyof Struct["fields"] | undefined, val: Struct["fields"][any] | undefined;
|
|
||||||
while (reader.pos < end) {
|
|
||||||
let [fieldNo, wireType] = reader.tag();
|
|
||||||
switch (fieldNo) {
|
|
||||||
case 1:
|
|
||||||
key = reader.string();
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
val = Value.internalBinaryRead(reader, reader.uint32(), options);
|
|
||||||
break;
|
|
||||||
default: throw new globalThis.Error("unknown map entry field for field google.protobuf.Struct.fields");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
map[key ?? ""] = val ?? Value.create();
|
|
||||||
}
|
|
||||||
internalBinaryWrite(message: Struct, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
|
||||||
/* map<string, google.protobuf.Value> fields = 1; */
|
|
||||||
for (let k of Object.keys(message.fields)) {
|
|
||||||
writer.tag(1, WireType.LengthDelimited).fork().tag(1, WireType.LengthDelimited).string(k);
|
|
||||||
writer.tag(2, WireType.LengthDelimited).fork();
|
|
||||||
Value.internalBinaryWrite(message.fields[k], writer, options);
|
|
||||||
writer.join().join();
|
|
||||||
}
|
|
||||||
let u = options.writeUnknownFields;
|
|
||||||
if (u !== false)
|
|
||||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
|
||||||
return writer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated MessageType for protobuf message google.protobuf.Struct
|
|
||||||
*/
|
|
||||||
export const Struct = new Struct$Type();
|
|
||||||
// @generated message type with reflection information, may provide speed optimized methods
|
|
||||||
class Value$Type extends MessageType<Value> {
|
|
||||||
constructor() {
|
|
||||||
super("google.protobuf.Value", [
|
|
||||||
{ no: 1, name: "null_value", kind: "enum", oneof: "kind", T: () => ["google.protobuf.NullValue", NullValue] },
|
|
||||||
{ no: 2, name: "number_value", kind: "scalar", oneof: "kind", T: 1 /*ScalarType.DOUBLE*/ },
|
|
||||||
{ no: 3, name: "string_value", kind: "scalar", oneof: "kind", T: 9 /*ScalarType.STRING*/ },
|
|
||||||
{ no: 4, name: "bool_value", kind: "scalar", oneof: "kind", T: 8 /*ScalarType.BOOL*/ },
|
|
||||||
{ no: 5, name: "struct_value", kind: "message", oneof: "kind", T: () => Struct },
|
|
||||||
{ no: 6, name: "list_value", kind: "message", oneof: "kind", T: () => ListValue }
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Encode `Value` to JSON value.
|
|
||||||
*/
|
|
||||||
internalJsonWrite(message: Value, options: JsonWriteOptions): JsonValue {
|
|
||||||
if (message.kind.oneofKind === undefined)
|
|
||||||
throw new globalThis.Error();
|
|
||||||
switch (message.kind.oneofKind) {
|
|
||||||
case undefined: throw new globalThis.Error();
|
|
||||||
case "boolValue": return message.kind.boolValue;
|
|
||||||
case "nullValue": return null;
|
|
||||||
case "numberValue": return message.kind.numberValue;
|
|
||||||
case "stringValue": return message.kind.stringValue;
|
|
||||||
case "listValue":
|
|
||||||
let listValueField = this.fields.find(f => f.no === 6);
|
|
||||||
if (listValueField?.kind !== "message")
|
|
||||||
throw new globalThis.Error();
|
|
||||||
return listValueField.T().toJson(message.kind.listValue);
|
|
||||||
case "structValue":
|
|
||||||
let structValueField = this.fields.find(f => f.no === 5);
|
|
||||||
if (structValueField?.kind !== "message")
|
|
||||||
throw new globalThis.Error();
|
|
||||||
return structValueField.T().toJson(message.kind.structValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Decode `Value` from JSON value.
|
|
||||||
*/
|
|
||||||
internalJsonRead(json: JsonValue, options: JsonReadOptions, target?: Value): Value {
|
|
||||||
if (!target)
|
|
||||||
target = this.create();
|
|
||||||
switch (typeof json) {
|
|
||||||
case "number":
|
|
||||||
target.kind = { oneofKind: "numberValue", numberValue: json };
|
|
||||||
break;
|
|
||||||
case "string":
|
|
||||||
target.kind = { oneofKind: "stringValue", stringValue: json };
|
|
||||||
break;
|
|
||||||
case "boolean":
|
|
||||||
target.kind = { oneofKind: "boolValue", boolValue: json };
|
|
||||||
break;
|
|
||||||
case "object":
|
|
||||||
if (json === null) {
|
|
||||||
target.kind = { oneofKind: "nullValue", nullValue: NullValue.NULL_VALUE };
|
|
||||||
}
|
|
||||||
else if (globalThis.Array.isArray(json)) {
|
|
||||||
target.kind = { oneofKind: "listValue", listValue: ListValue.fromJson(json) };
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
let val = Struct.fromJson(json);
|
|
||||||
target.kind = { oneofKind: "structValue", structValue: Struct.fromJson(json) };
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default: throw new globalThis.Error("Unable to parse " + this.typeName + " from JSON " + typeofJsonValue(json));
|
|
||||||
}
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
create(value?: PartialMessage<Value>): Value {
|
|
||||||
const message = { kind: { oneofKind: undefined } };
|
|
||||||
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
|
||||||
if (value !== undefined)
|
|
||||||
reflectionMergePartial<Value>(this, message, value);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Value): Value {
|
|
||||||
let message = target ?? this.create(), end = reader.pos + length;
|
|
||||||
while (reader.pos < end) {
|
|
||||||
let [fieldNo, wireType] = reader.tag();
|
|
||||||
switch (fieldNo) {
|
|
||||||
case /* google.protobuf.NullValue null_value */ 1:
|
|
||||||
message.kind = {
|
|
||||||
oneofKind: "nullValue",
|
|
||||||
nullValue: reader.int32()
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case /* double number_value */ 2:
|
|
||||||
message.kind = {
|
|
||||||
oneofKind: "numberValue",
|
|
||||||
numberValue: reader.double()
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case /* string string_value */ 3:
|
|
||||||
message.kind = {
|
|
||||||
oneofKind: "stringValue",
|
|
||||||
stringValue: reader.string()
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case /* bool bool_value */ 4:
|
|
||||||
message.kind = {
|
|
||||||
oneofKind: "boolValue",
|
|
||||||
boolValue: reader.bool()
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case /* google.protobuf.Struct struct_value */ 5:
|
|
||||||
message.kind = {
|
|
||||||
oneofKind: "structValue",
|
|
||||||
structValue: Struct.internalBinaryRead(reader, reader.uint32(), options, (message.kind as any).structValue)
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case /* google.protobuf.ListValue list_value */ 6:
|
|
||||||
message.kind = {
|
|
||||||
oneofKind: "listValue",
|
|
||||||
listValue: ListValue.internalBinaryRead(reader, reader.uint32(), options, (message.kind as any).listValue)
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
let u = options.readUnknownField;
|
|
||||||
if (u === "throw")
|
|
||||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
|
||||||
let d = reader.skip(wireType);
|
|
||||||
if (u !== false)
|
|
||||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
internalBinaryWrite(message: Value, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
|
||||||
/* google.protobuf.NullValue null_value = 1; */
|
|
||||||
if (message.kind.oneofKind === "nullValue")
|
|
||||||
writer.tag(1, WireType.Varint).int32(message.kind.nullValue);
|
|
||||||
/* double number_value = 2; */
|
|
||||||
if (message.kind.oneofKind === "numberValue")
|
|
||||||
writer.tag(2, WireType.Bit64).double(message.kind.numberValue);
|
|
||||||
/* string string_value = 3; */
|
|
||||||
if (message.kind.oneofKind === "stringValue")
|
|
||||||
writer.tag(3, WireType.LengthDelimited).string(message.kind.stringValue);
|
|
||||||
/* bool bool_value = 4; */
|
|
||||||
if (message.kind.oneofKind === "boolValue")
|
|
||||||
writer.tag(4, WireType.Varint).bool(message.kind.boolValue);
|
|
||||||
/* google.protobuf.Struct struct_value = 5; */
|
|
||||||
if (message.kind.oneofKind === "structValue")
|
|
||||||
Struct.internalBinaryWrite(message.kind.structValue, writer.tag(5, WireType.LengthDelimited).fork(), options).join();
|
|
||||||
/* google.protobuf.ListValue list_value = 6; */
|
|
||||||
if (message.kind.oneofKind === "listValue")
|
|
||||||
ListValue.internalBinaryWrite(message.kind.listValue, writer.tag(6, WireType.LengthDelimited).fork(), options).join();
|
|
||||||
let u = options.writeUnknownFields;
|
|
||||||
if (u !== false)
|
|
||||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
|
||||||
return writer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated MessageType for protobuf message google.protobuf.Value
|
|
||||||
*/
|
|
||||||
export const Value = new Value$Type();
|
|
||||||
// @generated message type with reflection information, may provide speed optimized methods
|
|
||||||
class ListValue$Type extends MessageType<ListValue> {
|
|
||||||
constructor() {
|
|
||||||
super("google.protobuf.ListValue", [
|
|
||||||
{ no: 1, name: "values", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => Value }
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Encode `ListValue` to JSON array.
|
|
||||||
*/
|
|
||||||
internalJsonWrite(message: ListValue, options: JsonWriteOptions): JsonValue {
|
|
||||||
return message.values.map(v => Value.toJson(v));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Decode `ListValue` from JSON array.
|
|
||||||
*/
|
|
||||||
internalJsonRead(json: JsonValue, options: JsonReadOptions, target?: ListValue): ListValue {
|
|
||||||
if (!globalThis.Array.isArray(json))
|
|
||||||
throw new globalThis.Error("Unable to parse " + this.typeName + " from JSON " + typeofJsonValue(json));
|
|
||||||
if (!target)
|
|
||||||
target = this.create();
|
|
||||||
let values = json.map(v => Value.fromJson(v));
|
|
||||||
target.values.push(...values);
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
create(value?: PartialMessage<ListValue>): ListValue {
|
|
||||||
const message = { values: [] };
|
|
||||||
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
|
||||||
if (value !== undefined)
|
|
||||||
reflectionMergePartial<ListValue>(this, message, value);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ListValue): ListValue {
|
|
||||||
let message = target ?? this.create(), end = reader.pos + length;
|
|
||||||
while (reader.pos < end) {
|
|
||||||
let [fieldNo, wireType] = reader.tag();
|
|
||||||
switch (fieldNo) {
|
|
||||||
case /* repeated google.protobuf.Value values */ 1:
|
|
||||||
message.values.push(Value.internalBinaryRead(reader, reader.uint32(), options));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
let u = options.readUnknownField;
|
|
||||||
if (u === "throw")
|
|
||||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
|
||||||
let d = reader.skip(wireType);
|
|
||||||
if (u !== false)
|
|
||||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
internalBinaryWrite(message: ListValue, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
|
||||||
/* repeated google.protobuf.Value values = 1; */
|
|
||||||
for (let i = 0; i < message.values.length; i++)
|
|
||||||
Value.internalBinaryWrite(message.values[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join();
|
|
||||||
let u = options.writeUnknownFields;
|
|
||||||
if (u !== false)
|
|
||||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
|
||||||
return writer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated MessageType for protobuf message google.protobuf.ListValue
|
|
||||||
*/
|
|
||||||
export const ListValue = new ListValue$Type();
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
// @generated by protobuf-ts 2.8.2
|
|
||||||
// @generated from protobuf file "project.proto" (package "gooseai", syntax proto3)
|
|
||||||
// tslint:disable
|
|
||||||
import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import type { ServiceInfo } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import { ProjectService } from "./project";
|
|
||||||
import type { DeleteAssetsResponse } from "./project";
|
|
||||||
import type { DeleteAssetsRequest } from "./project";
|
|
||||||
import type { QueryAssetsResponse } from "./project";
|
|
||||||
import type { QueryAssetsRequest } from "./project";
|
|
||||||
import type { UntagAssetsResponse } from "./project";
|
|
||||||
import type { UntagAssetsRequest } from "./project";
|
|
||||||
import type { TagAssetsResponse } from "./project";
|
|
||||||
import type { TagAssetsRequest } from "./project";
|
|
||||||
import type { DeleteProjectRequest } from "./project";
|
|
||||||
import type { GetProjectRequest } from "./project";
|
|
||||||
import type { ListProjectRequest } from "./project";
|
|
||||||
import type { ServerStreamingCall } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import type { UpdateProjectRequest } from "./project";
|
|
||||||
import { stackIntercept } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import type { Project } from "./project";
|
|
||||||
import type { CreateProjectRequest } from "./project";
|
|
||||||
import type { UnaryCall } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import type { RpcOptions } from "@protobuf-ts/runtime-rpc";
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* gRPC services
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @generated from protobuf service gooseai.ProjectService
|
|
||||||
*/
|
|
||||||
export interface IProjectServiceClient {
|
|
||||||
/**
|
|
||||||
* Create a new project if it does not exist
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: Create(gooseai.CreateProjectRequest) returns (gooseai.Project);
|
|
||||||
*/
|
|
||||||
create(input: CreateProjectRequest, options?: RpcOptions): UnaryCall<CreateProjectRequest, Project>;
|
|
||||||
/**
|
|
||||||
* Update an existing project
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: Update(gooseai.UpdateProjectRequest) returns (gooseai.Project);
|
|
||||||
*/
|
|
||||||
update(input: UpdateProjectRequest, options?: RpcOptions): UnaryCall<UpdateProjectRequest, Project>;
|
|
||||||
/**
|
|
||||||
* List all the projects for an organization
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: List(gooseai.ListProjectRequest) returns (stream gooseai.Project);
|
|
||||||
*/
|
|
||||||
list(input: ListProjectRequest, options?: RpcOptions): ServerStreamingCall<ListProjectRequest, Project>;
|
|
||||||
/**
|
|
||||||
* Get a project
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: Get(gooseai.GetProjectRequest) returns (gooseai.Project);
|
|
||||||
*/
|
|
||||||
get(input: GetProjectRequest, options?: RpcOptions): UnaryCall<GetProjectRequest, Project>;
|
|
||||||
/**
|
|
||||||
* Delete a project
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: Delete(gooseai.DeleteProjectRequest) returns (gooseai.Project);
|
|
||||||
*/
|
|
||||||
delete(input: DeleteProjectRequest, options?: RpcOptions): UnaryCall<DeleteProjectRequest, Project>;
|
|
||||||
/**
|
|
||||||
* Add or remove tags from an asset
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: TagAssets(gooseai.TagAssetsRequest) returns (gooseai.TagAssetsResponse);
|
|
||||||
*/
|
|
||||||
tagAssets(input: TagAssetsRequest, options?: RpcOptions): UnaryCall<TagAssetsRequest, TagAssetsResponse>;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: UntagAssets(gooseai.UntagAssetsRequest) returns (gooseai.UntagAssetsResponse);
|
|
||||||
*/
|
|
||||||
untagAssets(input: UntagAssetsRequest, options?: RpcOptions): UnaryCall<UntagAssetsRequest, UntagAssetsResponse>;
|
|
||||||
/**
|
|
||||||
* Query the assets of a project, with additional filtering
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: QueryAssets(gooseai.QueryAssetsRequest) returns (gooseai.QueryAssetsResponse);
|
|
||||||
*/
|
|
||||||
queryAssets(input: QueryAssetsRequest, options?: RpcOptions): UnaryCall<QueryAssetsRequest, QueryAssetsResponse>;
|
|
||||||
/**
|
|
||||||
* Delete one or more assets of a project
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: DeleteAssets(gooseai.DeleteAssetsRequest) returns (gooseai.DeleteAssetsResponse);
|
|
||||||
*/
|
|
||||||
deleteAssets(input: DeleteAssetsRequest, options?: RpcOptions): UnaryCall<DeleteAssetsRequest, DeleteAssetsResponse>;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* gRPC services
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @generated from protobuf service gooseai.ProjectService
|
|
||||||
*/
|
|
||||||
export class ProjectServiceClient implements IProjectServiceClient, ServiceInfo {
|
|
||||||
typeName = ProjectService.typeName;
|
|
||||||
methods = ProjectService.methods;
|
|
||||||
options = ProjectService.options;
|
|
||||||
constructor(private readonly _transport: RpcTransport) {
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Create a new project if it does not exist
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: Create(gooseai.CreateProjectRequest) returns (gooseai.Project);
|
|
||||||
*/
|
|
||||||
create(input: CreateProjectRequest, options?: RpcOptions): UnaryCall<CreateProjectRequest, Project> {
|
|
||||||
const method = this.methods[0], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<CreateProjectRequest, Project>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Update an existing project
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: Update(gooseai.UpdateProjectRequest) returns (gooseai.Project);
|
|
||||||
*/
|
|
||||||
update(input: UpdateProjectRequest, options?: RpcOptions): UnaryCall<UpdateProjectRequest, Project> {
|
|
||||||
const method = this.methods[1], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<UpdateProjectRequest, Project>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* List all the projects for an organization
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: List(gooseai.ListProjectRequest) returns (stream gooseai.Project);
|
|
||||||
*/
|
|
||||||
list(input: ListProjectRequest, options?: RpcOptions): ServerStreamingCall<ListProjectRequest, Project> {
|
|
||||||
const method = this.methods[2], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<ListProjectRequest, Project>("serverStreaming", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Get a project
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: Get(gooseai.GetProjectRequest) returns (gooseai.Project);
|
|
||||||
*/
|
|
||||||
get(input: GetProjectRequest, options?: RpcOptions): UnaryCall<GetProjectRequest, Project> {
|
|
||||||
const method = this.methods[3], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<GetProjectRequest, Project>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Delete a project
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: Delete(gooseai.DeleteProjectRequest) returns (gooseai.Project);
|
|
||||||
*/
|
|
||||||
delete(input: DeleteProjectRequest, options?: RpcOptions): UnaryCall<DeleteProjectRequest, Project> {
|
|
||||||
const method = this.methods[4], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<DeleteProjectRequest, Project>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Add or remove tags from an asset
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: TagAssets(gooseai.TagAssetsRequest) returns (gooseai.TagAssetsResponse);
|
|
||||||
*/
|
|
||||||
tagAssets(input: TagAssetsRequest, options?: RpcOptions): UnaryCall<TagAssetsRequest, TagAssetsResponse> {
|
|
||||||
const method = this.methods[5], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<TagAssetsRequest, TagAssetsResponse>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf rpc: UntagAssets(gooseai.UntagAssetsRequest) returns (gooseai.UntagAssetsResponse);
|
|
||||||
*/
|
|
||||||
untagAssets(input: UntagAssetsRequest, options?: RpcOptions): UnaryCall<UntagAssetsRequest, UntagAssetsResponse> {
|
|
||||||
const method = this.methods[6], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<UntagAssetsRequest, UntagAssetsResponse>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Query the assets of a project, with additional filtering
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: QueryAssets(gooseai.QueryAssetsRequest) returns (gooseai.QueryAssetsResponse);
|
|
||||||
*/
|
|
||||||
queryAssets(input: QueryAssetsRequest, options?: RpcOptions): UnaryCall<QueryAssetsRequest, QueryAssetsResponse> {
|
|
||||||
const method = this.methods[7], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<QueryAssetsRequest, QueryAssetsResponse>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Delete one or more assets of a project
|
|
||||||
*
|
|
||||||
* @generated from protobuf rpc: DeleteAssets(gooseai.DeleteAssetsRequest) returns (gooseai.DeleteAssetsResponse);
|
|
||||||
*/
|
|
||||||
deleteAssets(input: DeleteAssetsRequest, options?: RpcOptions): UnaryCall<DeleteAssetsRequest, DeleteAssetsResponse> {
|
|
||||||
const method = this.methods[8], opt = this._transport.mergeOptions(options);
|
|
||||||
return stackIntercept<DeleteAssetsRequest, DeleteAssetsResponse>("unary", this._transport, method, opt, input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,436 +0,0 @@
|
|||||||
// @generated by protobuf-ts 2.8.2
|
|
||||||
// @generated from protobuf file "tensors.proto" (package "tensors", syntax proto3)
|
|
||||||
// tslint:disable
|
|
||||||
import type { BinaryWriteOptions } from "@protobuf-ts/runtime";
|
|
||||||
import type { IBinaryWriter } from "@protobuf-ts/runtime";
|
|
||||||
import type { BinaryReadOptions } from "@protobuf-ts/runtime";
|
|
||||||
import type { IBinaryReader } from "@protobuf-ts/runtime";
|
|
||||||
import { UnknownFieldHandler } from "@protobuf-ts/runtime";
|
|
||||||
import { WireType } from "@protobuf-ts/runtime";
|
|
||||||
import type { PartialMessage } from "@protobuf-ts/runtime";
|
|
||||||
import { reflectionMergePartial } from "@protobuf-ts/runtime";
|
|
||||||
import { MESSAGE_TYPE } from "@protobuf-ts/runtime";
|
|
||||||
import { MessageType } from "@protobuf-ts/runtime";
|
|
||||||
/**
|
|
||||||
* @generated from protobuf message tensors.Tensor
|
|
||||||
*/
|
|
||||||
export interface Tensor {
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: tensors.Dtype dtype = 1;
|
|
||||||
*/
|
|
||||||
dtype: Dtype;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: repeated int64 shape = 2;
|
|
||||||
*/
|
|
||||||
shape: bigint[];
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: bytes data = 3;
|
|
||||||
*/
|
|
||||||
data: Uint8Array;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: optional tensors.AttributeType attr_type = 4;
|
|
||||||
*/
|
|
||||||
attrType?: AttributeType;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf message tensors.Attribute
|
|
||||||
*/
|
|
||||||
export interface Attribute {
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: string name = 1;
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf oneof: value
|
|
||||||
*/
|
|
||||||
value: {
|
|
||||||
oneofKind: "module";
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: tensors.Module module = 3;
|
|
||||||
*/
|
|
||||||
module: Module;
|
|
||||||
} | {
|
|
||||||
oneofKind: "tensor";
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: tensors.Tensor tensor = 4;
|
|
||||||
*/
|
|
||||||
tensor: Tensor;
|
|
||||||
} | {
|
|
||||||
oneofKind: "string";
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: string string = 5;
|
|
||||||
*/
|
|
||||||
string: string;
|
|
||||||
} | {
|
|
||||||
oneofKind: "int64";
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: int64 int64 = 6;
|
|
||||||
*/
|
|
||||||
int64: bigint;
|
|
||||||
} | {
|
|
||||||
oneofKind: "float";
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: float float = 7;
|
|
||||||
*/
|
|
||||||
float: number;
|
|
||||||
} | {
|
|
||||||
oneofKind: "bool";
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: bool bool = 8;
|
|
||||||
*/
|
|
||||||
bool: boolean;
|
|
||||||
} | {
|
|
||||||
oneofKind: undefined;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf message tensors.Module
|
|
||||||
*/
|
|
||||||
export interface Module {
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: string name = 1;
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: repeated string names = 2;
|
|
||||||
*/
|
|
||||||
names: string[];
|
|
||||||
/**
|
|
||||||
* @generated from protobuf field: repeated tensors.Attribute attributes = 3;
|
|
||||||
*/
|
|
||||||
attributes: Attribute[];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum tensors.Dtype
|
|
||||||
*/
|
|
||||||
export enum Dtype {
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: DT_INVALID = 0;
|
|
||||||
*/
|
|
||||||
DT_INVALID = 0,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: DT_FLOAT32 = 1;
|
|
||||||
*/
|
|
||||||
DT_FLOAT32 = 1,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: DT_FLOAT64 = 2;
|
|
||||||
*/
|
|
||||||
DT_FLOAT64 = 2,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: DT_FLOAT16 = 3;
|
|
||||||
*/
|
|
||||||
DT_FLOAT16 = 3,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: DT_BFLOAT16 = 4;
|
|
||||||
*/
|
|
||||||
DT_BFLOAT16 = 4,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: DT_COMPLEX32 = 5;
|
|
||||||
*/
|
|
||||||
DT_COMPLEX32 = 5,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: DT_COMPLEX64 = 6;
|
|
||||||
*/
|
|
||||||
DT_COMPLEX64 = 6,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: DT_COMPLEX128 = 7;
|
|
||||||
*/
|
|
||||||
DT_COMPLEX128 = 7,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: DT_UINT8 = 8;
|
|
||||||
*/
|
|
||||||
DT_UINT8 = 8,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: DT_INT8 = 9;
|
|
||||||
*/
|
|
||||||
DT_INT8 = 9,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: DT_INT16 = 10;
|
|
||||||
*/
|
|
||||||
DT_INT16 = 10,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: DT_INT32 = 11;
|
|
||||||
*/
|
|
||||||
DT_INT32 = 11,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: DT_INT64 = 12;
|
|
||||||
*/
|
|
||||||
DT_INT64 = 12,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: DT_BOOL = 13;
|
|
||||||
*/
|
|
||||||
DT_BOOL = 13,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: DT_QUINT8 = 14;
|
|
||||||
*/
|
|
||||||
DT_QUINT8 = 14,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: DT_QINT8 = 15;
|
|
||||||
*/
|
|
||||||
DT_QINT8 = 15,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: DT_QINT32 = 16;
|
|
||||||
*/
|
|
||||||
DT_QINT32 = 16,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: DT_QUINT4_2 = 17;
|
|
||||||
*/
|
|
||||||
DT_QUINT4_2 = 17
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum tensors.AttributeType
|
|
||||||
*/
|
|
||||||
export enum AttributeType {
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: AT_PARAMETER = 0;
|
|
||||||
*/
|
|
||||||
AT_PARAMETER = 0,
|
|
||||||
/**
|
|
||||||
* @generated from protobuf enum value: AT_BUFFER = 1;
|
|
||||||
*/
|
|
||||||
AT_BUFFER = 1
|
|
||||||
}
|
|
||||||
// @generated message type with reflection information, may provide speed optimized methods
|
|
||||||
class Tensor$Type extends MessageType<Tensor> {
|
|
||||||
constructor() {
|
|
||||||
super("tensors.Tensor", [
|
|
||||||
{ no: 1, name: "dtype", kind: "enum", T: () => ["tensors.Dtype", Dtype] },
|
|
||||||
{ no: 2, name: "shape", kind: "scalar", repeat: 1 /*RepeatType.PACKED*/, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ },
|
|
||||||
{ no: 3, name: "data", kind: "scalar", T: 12 /*ScalarType.BYTES*/ },
|
|
||||||
{ no: 4, name: "attr_type", kind: "enum", opt: true, T: () => ["tensors.AttributeType", AttributeType] }
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
create(value?: PartialMessage<Tensor>): Tensor {
|
|
||||||
const message = { dtype: 0, shape: [], data: new Uint8Array(0) };
|
|
||||||
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
|
||||||
if (value !== undefined)
|
|
||||||
reflectionMergePartial<Tensor>(this, message, value);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Tensor): Tensor {
|
|
||||||
let message = target ?? this.create(), end = reader.pos + length;
|
|
||||||
while (reader.pos < end) {
|
|
||||||
let [fieldNo, wireType] = reader.tag();
|
|
||||||
switch (fieldNo) {
|
|
||||||
case /* tensors.Dtype dtype */ 1:
|
|
||||||
message.dtype = reader.int32();
|
|
||||||
break;
|
|
||||||
case /* repeated int64 shape */ 2:
|
|
||||||
if (wireType === WireType.LengthDelimited)
|
|
||||||
for (let e = reader.int32() + reader.pos; reader.pos < e;)
|
|
||||||
message.shape.push(reader.int64().toBigInt());
|
|
||||||
else
|
|
||||||
message.shape.push(reader.int64().toBigInt());
|
|
||||||
break;
|
|
||||||
case /* bytes data */ 3:
|
|
||||||
message.data = reader.bytes();
|
|
||||||
break;
|
|
||||||
case /* optional tensors.AttributeType attr_type */ 4:
|
|
||||||
message.attrType = reader.int32();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
let u = options.readUnknownField;
|
|
||||||
if (u === "throw")
|
|
||||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
|
||||||
let d = reader.skip(wireType);
|
|
||||||
if (u !== false)
|
|
||||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
internalBinaryWrite(message: Tensor, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
|
||||||
/* tensors.Dtype dtype = 1; */
|
|
||||||
if (message.dtype !== 0)
|
|
||||||
writer.tag(1, WireType.Varint).int32(message.dtype);
|
|
||||||
/* repeated int64 shape = 2; */
|
|
||||||
if (message.shape.length) {
|
|
||||||
writer.tag(2, WireType.LengthDelimited).fork();
|
|
||||||
for (let i = 0; i < message.shape.length; i++)
|
|
||||||
writer.int64(message.shape[i]);
|
|
||||||
writer.join();
|
|
||||||
}
|
|
||||||
/* bytes data = 3; */
|
|
||||||
if (message.data.length)
|
|
||||||
writer.tag(3, WireType.LengthDelimited).bytes(message.data);
|
|
||||||
/* optional tensors.AttributeType attr_type = 4; */
|
|
||||||
if (message.attrType !== undefined)
|
|
||||||
writer.tag(4, WireType.Varint).int32(message.attrType);
|
|
||||||
let u = options.writeUnknownFields;
|
|
||||||
if (u !== false)
|
|
||||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
|
||||||
return writer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated MessageType for protobuf message tensors.Tensor
|
|
||||||
*/
|
|
||||||
export const Tensor = new Tensor$Type();
|
|
||||||
// @generated message type with reflection information, may provide speed optimized methods
|
|
||||||
class Attribute$Type extends MessageType<Attribute> {
|
|
||||||
constructor() {
|
|
||||||
super("tensors.Attribute", [
|
|
||||||
{ no: 1, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
|
||||||
{ no: 3, name: "module", kind: "message", oneof: "value", T: () => Module },
|
|
||||||
{ no: 4, name: "tensor", kind: "message", oneof: "value", T: () => Tensor },
|
|
||||||
{ no: 5, name: "string", kind: "scalar", oneof: "value", T: 9 /*ScalarType.STRING*/ },
|
|
||||||
{ no: 6, name: "int64", kind: "scalar", oneof: "value", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ },
|
|
||||||
{ no: 7, name: "float", kind: "scalar", oneof: "value", T: 2 /*ScalarType.FLOAT*/ },
|
|
||||||
{ no: 8, name: "bool", kind: "scalar", oneof: "value", T: 8 /*ScalarType.BOOL*/ }
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
create(value?: PartialMessage<Attribute>): Attribute {
|
|
||||||
const message = { name: "", value: { oneofKind: undefined } };
|
|
||||||
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
|
||||||
if (value !== undefined)
|
|
||||||
reflectionMergePartial<Attribute>(this, message, value);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Attribute): Attribute {
|
|
||||||
let message = target ?? this.create(), end = reader.pos + length;
|
|
||||||
while (reader.pos < end) {
|
|
||||||
let [fieldNo, wireType] = reader.tag();
|
|
||||||
switch (fieldNo) {
|
|
||||||
case /* string name */ 1:
|
|
||||||
message.name = reader.string();
|
|
||||||
break;
|
|
||||||
case /* tensors.Module module */ 3:
|
|
||||||
message.value = {
|
|
||||||
oneofKind: "module",
|
|
||||||
module: Module.internalBinaryRead(reader, reader.uint32(), options, (message.value as any).module)
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case /* tensors.Tensor tensor */ 4:
|
|
||||||
message.value = {
|
|
||||||
oneofKind: "tensor",
|
|
||||||
tensor: Tensor.internalBinaryRead(reader, reader.uint32(), options, (message.value as any).tensor)
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case /* string string */ 5:
|
|
||||||
message.value = {
|
|
||||||
oneofKind: "string",
|
|
||||||
string: reader.string()
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case /* int64 int64 */ 6:
|
|
||||||
message.value = {
|
|
||||||
oneofKind: "int64",
|
|
||||||
int64: reader.int64().toBigInt()
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case /* float float */ 7:
|
|
||||||
message.value = {
|
|
||||||
oneofKind: "float",
|
|
||||||
float: reader.float()
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case /* bool bool */ 8:
|
|
||||||
message.value = {
|
|
||||||
oneofKind: "bool",
|
|
||||||
bool: reader.bool()
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
let u = options.readUnknownField;
|
|
||||||
if (u === "throw")
|
|
||||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
|
||||||
let d = reader.skip(wireType);
|
|
||||||
if (u !== false)
|
|
||||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
internalBinaryWrite(message: Attribute, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
|
||||||
/* string name = 1; */
|
|
||||||
if (message.name !== "")
|
|
||||||
writer.tag(1, WireType.LengthDelimited).string(message.name);
|
|
||||||
/* tensors.Module module = 3; */
|
|
||||||
if (message.value.oneofKind === "module")
|
|
||||||
Module.internalBinaryWrite(message.value.module, writer.tag(3, WireType.LengthDelimited).fork(), options).join();
|
|
||||||
/* tensors.Tensor tensor = 4; */
|
|
||||||
if (message.value.oneofKind === "tensor")
|
|
||||||
Tensor.internalBinaryWrite(message.value.tensor, writer.tag(4, WireType.LengthDelimited).fork(), options).join();
|
|
||||||
/* string string = 5; */
|
|
||||||
if (message.value.oneofKind === "string")
|
|
||||||
writer.tag(5, WireType.LengthDelimited).string(message.value.string);
|
|
||||||
/* int64 int64 = 6; */
|
|
||||||
if (message.value.oneofKind === "int64")
|
|
||||||
writer.tag(6, WireType.Varint).int64(message.value.int64);
|
|
||||||
/* float float = 7; */
|
|
||||||
if (message.value.oneofKind === "float")
|
|
||||||
writer.tag(7, WireType.Bit32).float(message.value.float);
|
|
||||||
/* bool bool = 8; */
|
|
||||||
if (message.value.oneofKind === "bool")
|
|
||||||
writer.tag(8, WireType.Varint).bool(message.value.bool);
|
|
||||||
let u = options.writeUnknownFields;
|
|
||||||
if (u !== false)
|
|
||||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
|
||||||
return writer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated MessageType for protobuf message tensors.Attribute
|
|
||||||
*/
|
|
||||||
export const Attribute = new Attribute$Type();
|
|
||||||
// @generated message type with reflection information, may provide speed optimized methods
|
|
||||||
class Module$Type extends MessageType<Module> {
|
|
||||||
constructor() {
|
|
||||||
super("tensors.Module", [
|
|
||||||
{ no: 1, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
|
||||||
{ no: 2, name: "names", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ },
|
|
||||||
{ no: 3, name: "attributes", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => Attribute }
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
create(value?: PartialMessage<Module>): Module {
|
|
||||||
const message = { name: "", names: [], attributes: [] };
|
|
||||||
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
|
||||||
if (value !== undefined)
|
|
||||||
reflectionMergePartial<Module>(this, message, value);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Module): Module {
|
|
||||||
let message = target ?? this.create(), end = reader.pos + length;
|
|
||||||
while (reader.pos < end) {
|
|
||||||
let [fieldNo, wireType] = reader.tag();
|
|
||||||
switch (fieldNo) {
|
|
||||||
case /* string name */ 1:
|
|
||||||
message.name = reader.string();
|
|
||||||
break;
|
|
||||||
case /* repeated string names */ 2:
|
|
||||||
message.names.push(reader.string());
|
|
||||||
break;
|
|
||||||
case /* repeated tensors.Attribute attributes */ 3:
|
|
||||||
message.attributes.push(Attribute.internalBinaryRead(reader, reader.uint32(), options));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
let u = options.readUnknownField;
|
|
||||||
if (u === "throw")
|
|
||||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
|
||||||
let d = reader.skip(wireType);
|
|
||||||
if (u !== false)
|
|
||||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
internalBinaryWrite(message: Module, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
|
||||||
/* string name = 1; */
|
|
||||||
if (message.name !== "")
|
|
||||||
writer.tag(1, WireType.LengthDelimited).string(message.name);
|
|
||||||
/* repeated string names = 2; */
|
|
||||||
for (let i = 0; i < message.names.length; i++)
|
|
||||||
writer.tag(2, WireType.LengthDelimited).string(message.names[i]);
|
|
||||||
/* repeated tensors.Attribute attributes = 3; */
|
|
||||||
for (let i = 0; i < message.attributes.length; i++)
|
|
||||||
Attribute.internalBinaryWrite(message.attributes[i], writer.tag(3, WireType.LengthDelimited).fork(), options).join();
|
|
||||||
let u = options.writeUnknownFields;
|
|
||||||
if (u !== false)
|
|
||||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
|
||||||
return writer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated MessageType for protobuf message tensors.Module
|
|
||||||
*/
|
|
||||||
export const Module = new Module$Type();
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
export * as Dashboard from "./Generated/dashboard";
|
|
||||||
export * from "./Generated/dashboard.client";
|
|
||||||
export * as Engines from "./Generated/engines";
|
|
||||||
export * from "./Generated/engines.client";
|
|
||||||
export * as Generation from "./Generated/generation";
|
|
||||||
export * from "./Generated/generation.client";
|
|
||||||
export * as Project from "./Generated/project";
|
|
||||||
export * from "./Generated/project.client";
|
|
||||||
export * from "./Generated/google/protobuf/struct";
|
|
||||||
@@ -1,634 +0,0 @@
|
|||||||
import { GrpcWebFetchTransport } from "@protobuf-ts/grpcweb-transport";
|
|
||||||
import { RpcError } from "@protobuf-ts/runtime-rpc";
|
|
||||||
import * as StableStudio from "@stability/stablestudio-plugin";
|
|
||||||
|
|
||||||
import {
|
|
||||||
EnginesServiceClient,
|
|
||||||
Generation,
|
|
||||||
GenerationServiceClient,
|
|
||||||
Project,
|
|
||||||
ProjectServiceClient,
|
|
||||||
Struct,
|
|
||||||
} from "./Proto";
|
|
||||||
|
|
||||||
const getStableDiffusionDefaultCount = () => 4;
|
|
||||||
const getStableDiffusionDefaultInputFromPrompt = (prompt: string) => ({
|
|
||||||
prompts: [
|
|
||||||
{
|
|
||||||
text: prompt,
|
|
||||||
weight: 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
text: "",
|
|
||||||
weight: -0.75,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
model: "stable-diffusion-xl-beta-v2-2-2",
|
|
||||||
sampler: { id: "0", name: "DDIM" },
|
|
||||||
style: "enhance",
|
|
||||||
|
|
||||||
width: 512,
|
|
||||||
height: 512,
|
|
||||||
|
|
||||||
cfgScale: 7,
|
|
||||||
steps: 50,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const createPlugin = StableStudio.createPlugin<{
|
|
||||||
settings: {
|
|
||||||
apiKey: StableStudio.PluginSettingString;
|
|
||||||
};
|
|
||||||
}>(({ context, set }) => {
|
|
||||||
const functionsWhichNeedAPIKey = (
|
|
||||||
apiKey?: string
|
|
||||||
): Pick<
|
|
||||||
StableStudio.Plugin,
|
|
||||||
| "createStableDiffusionImages"
|
|
||||||
| "getStableDiffusionExistingImages"
|
|
||||||
| "getStableDiffusionModels"
|
|
||||||
| "deleteStableDiffusionImages"
|
|
||||||
| "getStatus"
|
|
||||||
| "getStableDiffusionSamplers"
|
|
||||||
> => {
|
|
||||||
if (!apiKey)
|
|
||||||
return {
|
|
||||||
createStableDiffusionImages: undefined,
|
|
||||||
getStableDiffusionExistingImages: undefined,
|
|
||||||
getStableDiffusionModels: undefined,
|
|
||||||
deleteStableDiffusionImages: undefined,
|
|
||||||
|
|
||||||
getStatus: () => ({
|
|
||||||
indicator: "error",
|
|
||||||
text: "Missing API key",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const transport = new GrpcWebFetchTransport({
|
|
||||||
baseUrl: "https://grpc.stability.ai",
|
|
||||||
meta: {
|
|
||||||
Authorization: `Bearer ${apiKey}`,
|
|
||||||
|
|
||||||
"stability-client-id": "StableStudio",
|
|
||||||
"stability-client-version": context.getGitHash(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const generation = new GenerationServiceClient(transport);
|
|
||||||
const engines = new EnginesServiceClient(transport);
|
|
||||||
const project = new ProjectServiceClient(transport);
|
|
||||||
|
|
||||||
return {
|
|
||||||
createStableDiffusionImages: async (options) => {
|
|
||||||
const count = options?.count ?? getStableDiffusionDefaultCount();
|
|
||||||
const defaultStableDiffusionInput =
|
|
||||||
getStableDiffusionDefaultInputFromPrompt(
|
|
||||||
context.getStableDiffusionRandomPrompt()
|
|
||||||
);
|
|
||||||
|
|
||||||
const input = {
|
|
||||||
...defaultStableDiffusionInput,
|
|
||||||
...options?.input,
|
|
||||||
};
|
|
||||||
|
|
||||||
const width = input.width ?? defaultStableDiffusionInput.width;
|
|
||||||
const height = input.height ?? defaultStableDiffusionInput.height;
|
|
||||||
|
|
||||||
const prompt =
|
|
||||||
input.prompts?.map(
|
|
||||||
({ text = context.getStableDiffusionRandomPrompt(), weight }) =>
|
|
||||||
Generation.Prompt.create({
|
|
||||||
prompt: { oneofKind: "text", text },
|
|
||||||
parameters: { weight },
|
|
||||||
})
|
|
||||||
) ?? [];
|
|
||||||
|
|
||||||
// add init and mask
|
|
||||||
if (input.maskImage?.blob) {
|
|
||||||
prompt.push(
|
|
||||||
Generation.Prompt.create({
|
|
||||||
parameters: { init: false, weight: 1 },
|
|
||||||
prompt: {
|
|
||||||
oneofKind: "artifact",
|
|
||||||
artifact: Generation.Artifact.create({
|
|
||||||
type: Generation.ArtifactType.ARTIFACT_MASK,
|
|
||||||
mime: "image/png",
|
|
||||||
data: {
|
|
||||||
oneofKind: "binary",
|
|
||||||
binary: new Uint8Array(
|
|
||||||
await input.maskImage.blob.arrayBuffer()
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.initialImage?.blob) {
|
|
||||||
prompt.push(
|
|
||||||
Generation.Prompt.create({
|
|
||||||
parameters: { init: true, weight: input.initialImage.weight },
|
|
||||||
prompt: {
|
|
||||||
oneofKind: "artifact",
|
|
||||||
artifact: Generation.Artifact.create({
|
|
||||||
type: Generation.ArtifactType.ARTIFACT_IMAGE,
|
|
||||||
mime: "image/png",
|
|
||||||
data: {
|
|
||||||
oneofKind: "binary",
|
|
||||||
binary: new Uint8Array(
|
|
||||||
await input.initialImage.blob.arrayBuffer()
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(input.sampler.id ? parseInt(input.sampler.id) : 0);
|
|
||||||
|
|
||||||
const imageParams = Generation.ImageParameters.create({
|
|
||||||
width: BigInt(width),
|
|
||||||
height: BigInt(height),
|
|
||||||
|
|
||||||
steps: BigInt(input.steps),
|
|
||||||
samples: BigInt(count),
|
|
||||||
seed: Array.from({ length: count }, () => input.seed ?? 0),
|
|
||||||
|
|
||||||
transform: Generation.TransformType.create({
|
|
||||||
type: {
|
|
||||||
oneofKind: "diffusion",
|
|
||||||
diffusion: input.sampler.id ? parseInt(input.sampler.id) : 0,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
parameters: [
|
|
||||||
Generation.StepParameter.create({
|
|
||||||
sampler: Generation.SamplerParameters.create({
|
|
||||||
cfgScale: input.cfgScale,
|
|
||||||
}),
|
|
||||||
|
|
||||||
scaledStep: 0,
|
|
||||||
schedule: Generation.ScheduleParameters.create({
|
|
||||||
start: input.initialImage
|
|
||||||
? 1 - (input.initialImage.weight ?? 0)
|
|
||||||
: 1,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const extras = (input.style || input.width !== input.height) && {
|
|
||||||
extras: Struct.fromJson({
|
|
||||||
$IPC: {
|
|
||||||
preset: input.style,
|
|
||||||
...(input.width !== input.height && { mode: "multistage" }),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const request = generation.chainGenerate({
|
|
||||||
requestId: "",
|
|
||||||
stage: [
|
|
||||||
{
|
|
||||||
id: "Main",
|
|
||||||
|
|
||||||
request: Generation.Request.create({
|
|
||||||
prompt,
|
|
||||||
|
|
||||||
engineId: input.model,
|
|
||||||
requestedType: Generation.ArtifactType.ARTIFACT_IMAGE,
|
|
||||||
params: { oneofKind: "image", image: imageParams },
|
|
||||||
|
|
||||||
...extras,
|
|
||||||
}),
|
|
||||||
|
|
||||||
onStatus: [
|
|
||||||
{
|
|
||||||
target: "Asset",
|
|
||||||
reason: [],
|
|
||||||
action: [
|
|
||||||
Generation.StageAction.PASS,
|
|
||||||
Generation.StageAction.RETURN,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: "Asset",
|
|
||||||
|
|
||||||
request: Generation.Request.create({
|
|
||||||
engineId: "asset-service",
|
|
||||||
params: {
|
|
||||||
oneofKind: "asset",
|
|
||||||
asset: {
|
|
||||||
projectId: "",
|
|
||||||
action: Generation.AssetAction.ASSET_PUT,
|
|
||||||
use: Generation.AssetUse.OUTPUT,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
onStatus: [
|
|
||||||
{ action: [Generation.StageAction.RETURN], reason: [] },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
let id: string | undefined;
|
|
||||||
const images: StableStudio.StableDiffusionImage[] = [];
|
|
||||||
|
|
||||||
for await (const response of request.responses) {
|
|
||||||
for (const artifact of response.artifacts) {
|
|
||||||
if (
|
|
||||||
artifact.type === Generation.ArtifactType.ARTIFACT_TEXT &&
|
|
||||||
artifact.finishReason === Generation.FinishReason.FILTER
|
|
||||||
)
|
|
||||||
throw new RpcError("Banned word detected!", "BANNED_TERM");
|
|
||||||
|
|
||||||
if (
|
|
||||||
artifact.type === Generation.ArtifactType.ARTIFACT_IMAGE &&
|
|
||||||
artifact.data.oneofKind === "binary"
|
|
||||||
) {
|
|
||||||
id = response.requestId;
|
|
||||||
images.push({
|
|
||||||
input: {
|
|
||||||
...input,
|
|
||||||
seed: artifact.seed,
|
|
||||||
},
|
|
||||||
id: artifact.uuid,
|
|
||||||
blob: new Blob([artifact.data.binary], { type: "image/png" }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return id ? { id, images } : undefined;
|
|
||||||
},
|
|
||||||
|
|
||||||
getStableDiffusionExistingImages: async (options) => {
|
|
||||||
const { limit, exclusiveStartImageID } = { limit: 25, ...options };
|
|
||||||
if (limit <= 0) return [];
|
|
||||||
|
|
||||||
const { response } = await project.queryAssets({
|
|
||||||
id: "",
|
|
||||||
limit: BigInt(limit),
|
|
||||||
startKey: exclusiveStartImageID,
|
|
||||||
use: [Project.ProjectAssetUse.OUTPUT],
|
|
||||||
sortDir: Project.ProjectSortDir.DESC,
|
|
||||||
tags: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
type ImageWithBlobPromise = Omit<
|
|
||||||
StableStudio.StableDiffusionImage,
|
|
||||||
"blob"
|
|
||||||
> & { blob: Promise<Blob> };
|
|
||||||
|
|
||||||
type ImagesWithBlobPromises = Record<
|
|
||||||
StableStudio.ID,
|
|
||||||
Omit<StableStudio.StableDiffusionImages, "images"> & {
|
|
||||||
images: ImageWithBlobPromise[];
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
|
|
||||||
const images = response.assets.reduce((previous, response) => {
|
|
||||||
if (
|
|
||||||
!response.request ||
|
|
||||||
response.request.params.oneofKind !== "image"
|
|
||||||
)
|
|
||||||
return previous;
|
|
||||||
|
|
||||||
const prompts = response.request.prompt.reduce((prompts, prompt) => {
|
|
||||||
if (
|
|
||||||
prompt.prompt.oneofKind !== "artifact" ||
|
|
||||||
prompt.prompt.artifact.data.oneofKind !== "text"
|
|
||||||
)
|
|
||||||
return prompts;
|
|
||||||
|
|
||||||
return [
|
|
||||||
...prompts,
|
|
||||||
{
|
|
||||||
text: prompt.prompt.artifact.data.text,
|
|
||||||
weight: prompt.parameters?.weight ?? 1,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, [] as StableStudio.StableDiffusionPrompt[]);
|
|
||||||
|
|
||||||
if (prompts.length === 0) return previous;
|
|
||||||
|
|
||||||
const id = response.request.requestId.split(":")[0];
|
|
||||||
if (!id) return previous;
|
|
||||||
|
|
||||||
const imageID = response.id;
|
|
||||||
const createdAt = new Date(Number(response.createdAt) * 1000);
|
|
||||||
|
|
||||||
const model = response.request.engineId;
|
|
||||||
const style = parseExtras(response.request.extras?.fields).$IPC
|
|
||||||
?.preset;
|
|
||||||
|
|
||||||
const width = response.request.params.image.width
|
|
||||||
? Number(response.request.params.image.width)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const height = response.request.params.image.height
|
|
||||||
? Number(response.request.params.image.height)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const cfgScale = response.request.params.image.parameters.reduce(
|
|
||||||
(_, parameters) =>
|
|
||||||
parameters.sampler?.cfgScale
|
|
||||||
? Number(parameters.sampler.cfgScale)
|
|
||||||
: undefined,
|
|
||||||
undefined as number | undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
const steps = response.request.params.image.steps
|
|
||||||
? Number(response.request.params.image.steps)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const seed = response.request.params.image.seed[0]
|
|
||||||
? Number(response.request.params.image.seed[0])
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const input: StableStudio.StableDiffusionInput = {
|
|
||||||
prompts,
|
|
||||||
|
|
||||||
model,
|
|
||||||
style,
|
|
||||||
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
|
|
||||||
cfgScale,
|
|
||||||
steps,
|
|
||||||
seed,
|
|
||||||
};
|
|
||||||
|
|
||||||
const src = response.uri
|
|
||||||
?.replace(
|
|
||||||
"https://object.lga1.coreweave.com/stability-staging-assets",
|
|
||||||
"https://staging-cdn.stability.ai/assets"
|
|
||||||
)
|
|
||||||
?.replace(
|
|
||||||
"https://object.lga1.coreweave.com/stability-assets",
|
|
||||||
"https://cdn.stability.ai/assets"
|
|
||||||
);
|
|
||||||
|
|
||||||
const image = {
|
|
||||||
id: imageID,
|
|
||||||
createdAt,
|
|
||||||
input,
|
|
||||||
blob: fetch(src).then((response) => response.blob()),
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
...previous,
|
|
||||||
[id]: {
|
|
||||||
...previous[id],
|
|
||||||
...(exclusiveStartImageID === imageID && {
|
|
||||||
exclusiveStartImageID: imageID,
|
|
||||||
}),
|
|
||||||
|
|
||||||
images: [...(previous[id]?.images ?? []), image],
|
|
||||||
id: id,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}, {} as ImagesWithBlobPromises);
|
|
||||||
|
|
||||||
return Promise.all(
|
|
||||||
Object.values(images).map(async ({ id, images }) => ({
|
|
||||||
id,
|
|
||||||
images: await Promise.all(
|
|
||||||
images?.map(async ({ blob, ...image }) => ({
|
|
||||||
...image,
|
|
||||||
blob: await blob,
|
|
||||||
}))
|
|
||||||
),
|
|
||||||
exclusiveStartImageID: images?.[images.length - 1]?.id,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
getStableDiffusionModels: async () => {
|
|
||||||
const request = await engines.listEngines({});
|
|
||||||
const allEngines = await request.response.engine;
|
|
||||||
return allEngines.filter((engine) => engine.type === 1 && engine.ready);
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteStableDiffusionImages: async (options) => {
|
|
||||||
const imageIDs = options?.imageIDs;
|
|
||||||
imageIDs &&
|
|
||||||
(await project.deleteAssets({ id: "", assetIds: imageIDs }));
|
|
||||||
},
|
|
||||||
|
|
||||||
getStatus: () => ({
|
|
||||||
indicator: "success",
|
|
||||||
text: "Ready",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
...functionsWhichNeedAPIKey(
|
|
||||||
localStorage.getItem("stability-apiKey") ?? undefined
|
|
||||||
),
|
|
||||||
|
|
||||||
getStableDiffusionSamplers: () => [
|
|
||||||
{ id: "0", name: "DDIM" },
|
|
||||||
{ id: "1", name: "DDPM" },
|
|
||||||
{ id: "2", name: "K Euler" },
|
|
||||||
{ id: "3", name: "K Euler Ancestral" },
|
|
||||||
{ id: "4", name: "K Heun" },
|
|
||||||
{ id: "5", name: "K DPM 2" },
|
|
||||||
{ id: "6", name: "K DPM 2 Ancestral" },
|
|
||||||
{ id: "7", name: "K LMS" },
|
|
||||||
{ id: "8", name: "K DPM++ 2S Ancestral" },
|
|
||||||
{ id: "9", name: "K DPM++ 2M" },
|
|
||||||
{ id: "10", name: "K DPM++ SDE" },
|
|
||||||
],
|
|
||||||
|
|
||||||
getStableDiffusionStyles: () => [
|
|
||||||
{
|
|
||||||
id: "enhance",
|
|
||||||
name: "Enhance",
|
|
||||||
image: "https://dreamstudio.ai/presets/enhance.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "anime",
|
|
||||||
name: "Anime",
|
|
||||||
image: "https://dreamstudio.ai/presets/anime.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "photographic",
|
|
||||||
name: "Photographic",
|
|
||||||
image: "https://dreamstudio.ai/presets/photographic.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "digital-art",
|
|
||||||
name: "Digital art",
|
|
||||||
image: "https://dreamstudio.ai/presets/digital-art.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "comic-book",
|
|
||||||
name: "Comic book",
|
|
||||||
image: "https://dreamstudio.ai/presets/comic-book.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "fantasy-art",
|
|
||||||
name: "Fantasy art",
|
|
||||||
image: "https://dreamstudio.ai/presets/fantasy-art.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "analog-film",
|
|
||||||
name: "Analog film",
|
|
||||||
image: "https://dreamstudio.ai/presets/analog-film.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "neon-punk",
|
|
||||||
name: "Neon punk",
|
|
||||||
image: "https://dreamstudio.ai/presets/neon-punk.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "isometric",
|
|
||||||
name: "Isometric",
|
|
||||||
image: "https://dreamstudio.ai/presets/isometric.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "low-poly",
|
|
||||||
name: "Low poly",
|
|
||||||
image: "https://dreamstudio.ai/presets/low-poly.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "origami",
|
|
||||||
name: "Origami",
|
|
||||||
image: "https://dreamstudio.ai/presets/origami.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "line-art",
|
|
||||||
name: "Line art",
|
|
||||||
image: "https://dreamstudio.ai/presets/line-art.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "modeling-compound",
|
|
||||||
name: "Craft clay",
|
|
||||||
image: "https://dreamstudio.ai/presets/modeling-compound.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "cinematic",
|
|
||||||
name: "Cinematic",
|
|
||||||
image: "https://dreamstudio.ai/presets/cinematic.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "3d-model",
|
|
||||||
name: "3D model",
|
|
||||||
image: "https://dreamstudio.ai/presets/3d-model.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "pixel-art",
|
|
||||||
name: "Pixel art",
|
|
||||||
image: "https://dreamstudio.ai/presets/pixel-art.png",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
getStableDiffusionDefaultCount,
|
|
||||||
getStableDiffusionDefaultInput: () =>
|
|
||||||
getStableDiffusionDefaultInputFromPrompt(
|
|
||||||
context.getStableDiffusionRandomPrompt()
|
|
||||||
),
|
|
||||||
|
|
||||||
settings: {
|
|
||||||
apiKey: {
|
|
||||||
type: "string",
|
|
||||||
|
|
||||||
title: "API key",
|
|
||||||
description:
|
|
||||||
"You can find your Stability API key at https://dreamstudio.ai/account",
|
|
||||||
|
|
||||||
placeholder: "sk-...",
|
|
||||||
required: true,
|
|
||||||
password: true,
|
|
||||||
|
|
||||||
value: localStorage.getItem("stability-apiKey") ?? "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
setSetting: (key, value) => {
|
|
||||||
set(({ settings }) => ({
|
|
||||||
settings: {
|
|
||||||
...settings,
|
|
||||||
[key]: { ...settings[key], value: value as string },
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (key === "apiKey" && typeof value === "string") {
|
|
||||||
localStorage.setItem("stability-apiKey", value);
|
|
||||||
set((plugin) => ({ ...plugin, ...functionsWhichNeedAPIKey(value) }));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
manifest: {
|
|
||||||
author: "Stability AI",
|
|
||||||
description: markdownDescription,
|
|
||||||
name: "Stability AI",
|
|
||||||
license: "MIT",
|
|
||||||
link: "https://stability.ai",
|
|
||||||
version: "0.0.0",
|
|
||||||
icon: "https://stability.ai/favicon.ico",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
function parseExtras(extras: any): any | undefined {
|
|
||||||
if (!extras) return undefined;
|
|
||||||
|
|
||||||
if (extras.kind?.oneofKind === "structValue") {
|
|
||||||
return parseExtras(extras.kind.structValue.fields);
|
|
||||||
} else if (extras.kind?.oneofKind === "listValue") {
|
|
||||||
return extras.listValue.values.map(parseExtras);
|
|
||||||
} else if (extras.kind?.oneofKind === "numberValue") {
|
|
||||||
return Number(extras.kind.numberValue);
|
|
||||||
} else if (extras.kind?.oneofKind === "stringValue") {
|
|
||||||
return extras.kind.stringValue;
|
|
||||||
} else if (extras.kind?.oneofKind === "boolValue") {
|
|
||||||
return extras.kind.boolValue;
|
|
||||||
} else if (extras.kind?.oneofKind === undefined) {
|
|
||||||
return {
|
|
||||||
...Object.keys(extras).reduce((previous, key) => {
|
|
||||||
return { ...previous, [key]: parseExtras(extras[key]) };
|
|
||||||
}, {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const markdownDescription = `
|
|
||||||
# Welcome to StableStudio!
|
|
||||||
|
|
||||||
## [📖 README](https://github.com/Stability-AI/StableStudio) · [🎮 Discord](https://discord.com/channels/1002292111942635562/1108055793674227782) · [🌈 DreamStudio](https://dreamstudio.ai) · [💬 Discussion](https://github.com/Stability-AI/StableStudio/discussions)
|
|
||||||
|
|
||||||
# Setup
|
|
||||||
|
|
||||||
To get started, you'll need to sign up for a [DreamStudio](https://dreamstudio.ai) account.
|
|
||||||
|
|
||||||
Once you're logged in, head to the [account page](https://dreamstudio.ai/account).
|
|
||||||
|
|
||||||
You should see a section called \`API keys\`...
|
|
||||||

|
|
||||||
|
|
||||||
If you don't already have a key, you can create one via the plus button...
|
|
||||||

|
|
||||||
|
|
||||||
You can copy your API key by clicking the copy button...
|
|
||||||

|
|
||||||
|
|
||||||
You'll be asked to accept the terms of service.
|
|
||||||
|
|
||||||
Now, paste the key into the field below...
|
|
||||||
|
|
||||||
The plugin status should change to \`Ready\` once everything is working.
|
|
||||||
` as const;
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../tsconfig.json",
|
|
||||||
"include": ["src/**/*"],
|
|
||||||
"exclude": ["node_modules"],
|
|
||||||
"compilerOptions": {
|
|
||||||
"emitDeclarationOnly": true,
|
|
||||||
"declaration": true,
|
|
||||||
"noUncheckedIndexedAccess": false,
|
|
||||||
|
|
||||||
"outDir": "./lib",
|
|
||||||
"baseUrl": "./",
|
|
||||||
"paths": {
|
|
||||||
"~/*": ["./src/*"]
|
|
||||||
},
|
|
||||||
|
|
||||||
"plugins": [
|
|
||||||
{ "transform": "typescript-transform-paths" },
|
|
||||||
{ "transform": "typescript-transform-paths", "afterDeclarations": true }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{ "extends": ["../../.eslintrc.json"] }
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
lib
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2023 Stability AI
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@stability/stablestudio-plugin-webgpu",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"main": "./lib/index.js",
|
|
||||||
"types": "./lib/index.d.ts",
|
|
||||||
"files": [
|
|
||||||
"lib"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"clean": "rimraf lib && rimraf node_modules",
|
|
||||||
"build:types": "ttsc --project tsconfig.json",
|
|
||||||
"build:javascript": "tsx scripts/Build.ts",
|
|
||||||
"build": "yarn build:types && yarn build:javascript",
|
|
||||||
"dev": "nodemon --watch src --ext ts,tsx,json --exec \"yarn build\""
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@stability/stablestudio-plugin": "workspace:^"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.33.1",
|
|
||||||
"@typescript-eslint/parser": "^5.33.1",
|
|
||||||
"eslint": "8.22.0",
|
|
||||||
"eslint-config-prettier": "^8.5.0",
|
|
||||||
"eslint-plugin-import": "^2.26.0",
|
|
||||||
"eslint-plugin-markdown": "^3.0.0",
|
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
|
||||||
"eslint-plugin-react": "^7.30.1",
|
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
|
||||||
"nodemon": "^2.0.20",
|
|
||||||
"prettier": "^2.7.1",
|
|
||||||
"rimraf": "^3.0.2",
|
|
||||||
"ts-node": "^10.9.1",
|
|
||||||
"tsx": "^3.12.1",
|
|
||||||
"ttypescript": "^1.5.13",
|
|
||||||
"typescript": "4.8.4",
|
|
||||||
"typescript-transform-paths": "^3.4.4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import * as ESBuild from "esbuild";
|
|
||||||
|
|
||||||
const main = async () => {
|
|
||||||
try {
|
|
||||||
await ESBuild.build({
|
|
||||||
entryPoints: ["src/index.ts"],
|
|
||||||
outdir: "lib",
|
|
||||||
bundle: true,
|
|
||||||
sourcemap: true,
|
|
||||||
minify: true,
|
|
||||||
splitting: true,
|
|
||||||
format: "esm",
|
|
||||||
target: ["esnext"],
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
main();
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import * as StableStudio from "@stability/stablestudio-plugin";
|
|
||||||
|
|
||||||
export const createPlugin = StableStudio.createPlugin(() => ({}));
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../tsconfig.json",
|
|
||||||
"include": ["src/**/*"],
|
|
||||||
"exclude": ["node_modules"],
|
|
||||||
"compilerOptions": {
|
|
||||||
"emitDeclarationOnly": true,
|
|
||||||
"declaration": true,
|
|
||||||
"noUncheckedIndexedAccess": false,
|
|
||||||
|
|
||||||
"outDir": "./lib",
|
|
||||||
"baseUrl": "./",
|
|
||||||
"paths": {
|
|
||||||
"~/*": ["./src/*"]
|
|
||||||
},
|
|
||||||
|
|
||||||
"plugins": [
|
|
||||||
{ "transform": "typescript-transform-paths" },
|
|
||||||
{ "transform": "typescript-transform-paths", "afterDeclarations": true }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{ "extends": ["../../.eslintrc.json"] }
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
lib
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2023 Stability AI
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
<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)**
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</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
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import * as ESBuild from "esbuild";
|
|
||||||
|
|
||||||
const main = async () => {
|
|
||||||
try {
|
|
||||||
await ESBuild.build({
|
|
||||||
entryPoints: ["src/index.ts"],
|
|
||||||
outdir: "lib",
|
|
||||||
bundle: true,
|
|
||||||
sourcemap: true,
|
|
||||||
minify: true,
|
|
||||||
splitting: true,
|
|
||||||
format: "esm",
|
|
||||||
target: ["esnext"],
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
main();
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
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 | null,
|
|
||||||
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,394 +0,0 @@
|
|||||||
import * as StableStudio from "@stability/stablestudio-plugin";
|
|
||||||
import { StableDiffusionImage } from "@stability/stablestudio-plugin";
|
|
||||||
|
|
||||||
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"
|
|
||||||
> => {
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let webuiHostUrl = localStorage.getItem("webui-host-url");
|
|
||||||
|
|
||||||
if (!webuiHostUrl || webuiHostUrl === "") {
|
|
||||||
webuiHostUrl = "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,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../tsconfig.json",
|
|
||||||
"include": ["src/**/*"],
|
|
||||||
"exclude": ["node_modules"],
|
|
||||||
"compilerOptions": {
|
|
||||||
"emitDeclarationOnly": true,
|
|
||||||
"declaration": true,
|
|
||||||
"noUncheckedIndexedAccess": false,
|
|
||||||
|
|
||||||
"outDir": "./lib",
|
|
||||||
"baseUrl": "./",
|
|
||||||
"paths": {
|
|
||||||
"~/*": ["./src/*"]
|
|
||||||
},
|
|
||||||
|
|
||||||
"plugins": [
|
|
||||||
{ "transform": "typescript-transform-paths" },
|
|
||||||
{ "transform": "typescript-transform-paths", "afterDeclarations": true }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -191,11 +191,6 @@ export type Plugin<P extends PluginTypeHelper = PluginTypeHelperDefault> = {
|
|||||||
StableDiffusionStyle[] | undefined
|
StableDiffusionStyle[] | undefined
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/** If you want to provide a list of resolutions in pixels to choose from, you can return them via this function and they will be presented as a slider in the UI */
|
|
||||||
getStableDiffusionAllowedResolutions?: (
|
|
||||||
model?: ID
|
|
||||||
) => MaybePromise<{ width: number; height: number }[] | undefined>;
|
|
||||||
|
|
||||||
/** Determines the default count passed to `createStableDiffusionImages` */
|
/** Determines the default count passed to `createStableDiffusionImages` */
|
||||||
getStableDiffusionDefaultCount?: () => number | undefined;
|
getStableDiffusionDefaultCount?: () => number | undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
"build:preview": "yarn build:types && vite build --mode preview",
|
"build:preview": "yarn build:types && vite build --mode preview",
|
||||||
"build:production": "yarn build:types && vite build --mode production",
|
"build:production": "yarn build:types && vite build --mode production",
|
||||||
"dev:types": "tsc --noEmit --watch",
|
"dev:types": "tsc --noEmit --watch",
|
||||||
"dev": "vite"
|
"dev": "cargo tauri dev",
|
||||||
|
"dev:vite": "vite"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/css": "^11.10.5",
|
"@emotion/css": "^11.10.5",
|
||||||
@@ -21,11 +22,11 @@
|
|||||||
"@react-hook/resize-observer": "^1.2.6",
|
"@react-hook/resize-observer": "^1.2.6",
|
||||||
"@reecelucas/react-use-hotkeys": "^2.0.0",
|
"@reecelucas/react-use-hotkeys": "^2.0.0",
|
||||||
"@stability/stablestudio-plugin": "workspace:^",
|
"@stability/stablestudio-plugin": "workspace:^",
|
||||||
"@stability/stablestudio-plugin-example": "workspace:^",
|
"@stability/stablestudio-plugin-comfy": "workspace:^",
|
||||||
"@stability/stablestudio-plugin-stability": "workspace:^",
|
|
||||||
"@tanstack/react-query": "^4.22.0",
|
"@tanstack/react-query": "^4.22.0",
|
||||||
"@tanstack/react-query-devtools": "^4.22.0",
|
"@tanstack/react-query-devtools": "^4.22.0",
|
||||||
"@tanstack/react-virtual": "beta",
|
"@tanstack/react-virtual": "beta",
|
||||||
|
"@tauri-apps/api": "^1.4.0",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.29.3",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
@@ -41,6 +42,7 @@
|
|||||||
"notistack": "^3.0.0-alpha.11",
|
"notistack": "^3.0.0-alpha.11",
|
||||||
"query-string": "^8.1.0",
|
"query-string": "^8.1.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-circular-progressbar": "^2.1.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-konva": "^18.2.3",
|
"react-konva": "^18.2.3",
|
||||||
"react-konva-utils": "^0.3.1",
|
"react-konva-utils": "^0.3.1",
|
||||||
@@ -54,6 +56,7 @@
|
|||||||
"remark-custom-heading-id": "^1.0.0",
|
"remark-custom-heading-id": "^1.0.0",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"tailwind-merge": "^1.8.1",
|
"tailwind-merge": "^1.8.1",
|
||||||
|
"tauri-plugin-upload": "https://github.com/tauri-apps/tauri-plugin-upload#v1",
|
||||||
"throttled-queue": "^2.1.4",
|
"throttled-queue": "^2.1.4",
|
||||||
"tiny-invariant": "^1.3.1",
|
"tiny-invariant": "^1.3.1",
|
||||||
"ts-custom-error": "^3.3.1",
|
"ts-custom-error": "^3.3.1",
|
||||||
|
|||||||
7
packages/stablestudio-ui/public/file-code.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="24" height="24" fill="#3D3D3D"/>
|
||||||
|
<path d="M5 21H17.25C17.7141 21 18.1592 20.8104 18.4874 20.4728C18.8156 20.1352 19 19.6774 19 19.2V7.95L14.1875 3H6.75C6.28587 3 5.84075 3.18964 5.51256 3.52721C5.18437 3.86477 5 4.32261 5 4.8V8.4" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M14 3V8H19" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M9 17L12 14.5L9 12" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M6 12L3 14.5L6 17" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 757 B |
3
packages/stablestudio-ui/src-tauri/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
||||||
4296
packages/stablestudio-ui/src-tauri/Cargo.lock
generated
Normal file
37
packages/stablestudio-ui/src-tauri/Cargo.toml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
[package]
|
||||||
|
name = "app"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "A Tauri App"
|
||||||
|
authors = ["you"]
|
||||||
|
license = ""
|
||||||
|
repository = ""
|
||||||
|
default-run = "app"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.60"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "1.4.0", features = [] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
tauri = { version = "1.4.1", features = [ "os-all", "fs-all", "path-all", "process-command-api", "devtools"] }
|
||||||
|
tauri-plugin-upload = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
|
zip = "0.6.6"
|
||||||
|
zip-extensions = "0.6.1"
|
||||||
|
reqwest = { version = "0.11.6", features = ["blocking"] }
|
||||||
|
tiny_http = "0.12.0"
|
||||||
|
portpicker = "0.1.1"
|
||||||
|
http = "0.2.9"
|
||||||
|
once_cell = "1.18.0"
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
fork = "0.1.22"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
|
||||||
|
# If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes.
|
||||||
|
# DO NOT REMOVE!!
|
||||||
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
3
packages/stablestudio-ui/src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
tauri_build::build()
|
||||||
|
}
|
||||||
BIN
packages/stablestudio-ui/src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
packages/stablestudio-ui/src-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
packages/stablestudio-ui/src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 540 B |
BIN
packages/stablestudio-ui/src-tauri/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
packages/stablestudio-ui/src-tauri/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
packages/stablestudio-ui/src-tauri/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
packages/stablestudio-ui/src-tauri/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
packages/stablestudio-ui/src-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
packages/stablestudio-ui/src-tauri/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
packages/stablestudio-ui/src-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
packages/stablestudio-ui/src-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
packages/stablestudio-ui/src-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
packages/stablestudio-ui/src-tauri/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
packages/stablestudio-ui/src-tauri/icons/icon.icns
Normal file
BIN
packages/stablestudio-ui/src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
packages/stablestudio-ui/src-tauri/icons/icon.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
240
packages/stablestudio-ui/src-tauri/src/main.rs
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
use settings::Settings;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
use tauri::api::process::CommandEvent;
|
||||||
|
use tauri::api::process::{
|
||||||
|
Command,
|
||||||
|
CommandEvent::{Error, Stderr, Stdout, Terminated},
|
||||||
|
};
|
||||||
|
use tauri::async_runtime::Receiver;
|
||||||
|
use tauri::utils::config::{AppUrl, WindowConfig};
|
||||||
|
use tauri::{RunEvent, Window, WindowBuilder, WindowUrl};
|
||||||
|
use tauri_plugin_upload;
|
||||||
|
|
||||||
|
mod server;
|
||||||
|
mod settings;
|
||||||
|
mod show_path;
|
||||||
|
mod workflows;
|
||||||
|
|
||||||
|
static WINDOW: OnceLock<Window> = OnceLock::new();
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let port = portpicker::pick_unused_port().expect("failed to find unused port");
|
||||||
|
|
||||||
|
let mut context = tauri::generate_context!();
|
||||||
|
let url = format!("http://localhost:{}", port).parse().unwrap();
|
||||||
|
let window_url = WindowUrl::External(url);
|
||||||
|
// rewrite the config so the IPC is enabled on this URL
|
||||||
|
if !cfg!(dev) {
|
||||||
|
context.config_mut().build.dist_dir = AppUrl::Url(window_url.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
tauri::Builder::default()
|
||||||
|
.plugin(server::Builder::new(port).build())
|
||||||
|
.setup(move |app| {
|
||||||
|
let window = WindowBuilder::from_config(
|
||||||
|
app,
|
||||||
|
WindowConfig {
|
||||||
|
url: {
|
||||||
|
if cfg!(dev) {
|
||||||
|
Default::default()
|
||||||
|
} else {
|
||||||
|
window_url
|
||||||
|
}
|
||||||
|
},
|
||||||
|
height: 950.0,
|
||||||
|
width: 1800.0,
|
||||||
|
resizable: true,
|
||||||
|
title: "StableStudio".to_string(),
|
||||||
|
fullscreen: false,
|
||||||
|
center: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let app_data_dir = app.path_resolver().app_data_dir().unwrap();
|
||||||
|
let _ = std::fs::create_dir_all(&app_data_dir);
|
||||||
|
|
||||||
|
let mut store_location = app_data_dir.clone();
|
||||||
|
store_location.push("settings.json");
|
||||||
|
|
||||||
|
let comfy_location = app_data_dir.clone();
|
||||||
|
|
||||||
|
settings::create_settings(
|
||||||
|
store_location,
|
||||||
|
Settings {
|
||||||
|
comfyui_location: Box::new(comfy_location.to_str().unwrap().to_string()),
|
||||||
|
comfyui_url: Box::new("http://localhost:5000".to_string()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
_ = WINDOW.set(window);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.plugin(tauri_plugin_upload::init())
|
||||||
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
extract_comfy,
|
||||||
|
launch_comfy,
|
||||||
|
show_path::show_in_folder,
|
||||||
|
settings::get_setting,
|
||||||
|
settings::set_setting,
|
||||||
|
workflows::fetch_workflows,
|
||||||
|
workflows::save_workflow,
|
||||||
|
workflows::delete_workflow,
|
||||||
|
])
|
||||||
|
.build(context)
|
||||||
|
.expect("error while building tauri application")
|
||||||
|
.run(move |_app_handle, event| match event {
|
||||||
|
RunEvent::ExitRequested { .. } => {
|
||||||
|
println!("Killing comfy...");
|
||||||
|
tauri::api::process::kill_children();
|
||||||
|
println!("Backend gracefully shutdown.");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// tauri command to extract a zip from an arbitrary file path
|
||||||
|
#[tauri::command]
|
||||||
|
fn extract_comfy(handle: tauri::AppHandle) -> Result<String, String> {
|
||||||
|
let mut path = handle.path_resolver().app_data_dir().unwrap();
|
||||||
|
path.push("comfyui.zip");
|
||||||
|
|
||||||
|
let target_dir = *settings::SETTINGS
|
||||||
|
.get()
|
||||||
|
.unwrap()
|
||||||
|
.data
|
||||||
|
.comfyui_location
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
println!("extracting zip from {:?} to {}", path, target_dir);
|
||||||
|
let file = File::open(path).unwrap();
|
||||||
|
let mut archive = zip::ZipArchive::new(file).unwrap();
|
||||||
|
|
||||||
|
// unarchive in a thread
|
||||||
|
let extract_parent = target_dir.clone();
|
||||||
|
let extract_thread = std::thread::spawn(move || {
|
||||||
|
archive.extract(extract_parent).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
// dont block the main thread
|
||||||
|
extract_thread.join().unwrap();
|
||||||
|
|
||||||
|
println!("extracted zip");
|
||||||
|
Ok("completed".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_event(event: &str, data: Option<String>) {
|
||||||
|
match WINDOW.get() {
|
||||||
|
Some(window) => {
|
||||||
|
window
|
||||||
|
.emit(event, data)
|
||||||
|
.unwrap_or_else(|_| println!("[!!] event failed"));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
println!("[!!] window not set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn watch_comfy(
|
||||||
|
mut rx: Receiver<CommandEvent>,
|
||||||
|
) -> (Receiver<CommandEvent>, Result<String, String>) {
|
||||||
|
while let Some(i) = rx.recv().await {
|
||||||
|
// check if output starts with "To see the GUI go to:"
|
||||||
|
match i {
|
||||||
|
Stdout(line) if line.len() > 1 => {
|
||||||
|
println!("[ComfyUI] stdout: {}", line);
|
||||||
|
emit_event("comfy-output", Some(format!("stdout:{line}")));
|
||||||
|
|
||||||
|
if line.starts_with("To see the GUI go to:") {
|
||||||
|
println!("Comfy launched successfully!");
|
||||||
|
return (rx, Ok("completed".to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Stderr(line) => {
|
||||||
|
println!("[ComfyUI] stderr: {}", line);
|
||||||
|
emit_event("comfy-output", Some(format!("stderr:{line}")));
|
||||||
|
}
|
||||||
|
Error(line) => {
|
||||||
|
println!("[ComfyUI] error: {}", line);
|
||||||
|
emit_event("comfy-output", Some(format!("error:{line}")));
|
||||||
|
}
|
||||||
|
Terminated(_) => {
|
||||||
|
println!("Comfy terminated!");
|
||||||
|
return (rx, Err("failed".to_string()));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (rx, Err("failed".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// tauri command to launch python process
|
||||||
|
#[tauri::command]
|
||||||
|
async fn launch_comfy() -> Result<String, String> {
|
||||||
|
let comfyui_location = *settings::SETTINGS
|
||||||
|
.get()
|
||||||
|
.unwrap()
|
||||||
|
.data
|
||||||
|
.comfyui_location
|
||||||
|
.clone();
|
||||||
|
let mut path = std::path::PathBuf::from(comfyui_location.clone());
|
||||||
|
path.push("ComfyUI");
|
||||||
|
let url = *settings::SETTINGS.get().unwrap().data.comfyui_url.clone();
|
||||||
|
|
||||||
|
// set working directory
|
||||||
|
std::env::set_current_dir(path.clone()).unwrap();
|
||||||
|
|
||||||
|
// test to make sure its not already running (just test port 5000)
|
||||||
|
println!("Checking for existing comfy process...");
|
||||||
|
let client = reqwest::Client::builder()
|
||||||
|
.connect_timeout(std::time::Duration::from_secs(1))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let resp = client.get(url.clone()).send().await;
|
||||||
|
if resp.is_ok() {
|
||||||
|
println!("Comfy already running, skipping launch.");
|
||||||
|
return Ok("completed".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("--> launching comfy...");
|
||||||
|
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let (mut rx, _child) = Command::new({
|
||||||
|
if cfg!(unix) {
|
||||||
|
"python_embeded/python"
|
||||||
|
} else if cfg!(windows) {
|
||||||
|
"python_embeded/python.exe"
|
||||||
|
} else if cfg!(macos) {
|
||||||
|
"python_embeded/python.app"
|
||||||
|
} else {
|
||||||
|
panic!("Unsupported platform")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.args([
|
||||||
|
"-s",
|
||||||
|
"ComfyUI/main.py",
|
||||||
|
"--port",
|
||||||
|
url.split(":").collect::<Vec<&str>>().last().unwrap(),
|
||||||
|
"--preview-method",
|
||||||
|
"auto",
|
||||||
|
])
|
||||||
|
.envs(HashMap::from([("PYTHONUNBUFFERED".into(), "1".into())]))
|
||||||
|
.spawn()
|
||||||
|
.expect("Failed to spawn ComfyUI process");
|
||||||
|
|
||||||
|
// print output from python process
|
||||||
|
let (rx, result) = watch_comfy(rx).await;
|
||||||
|
tauri::async_runtime::spawn(watch_comfy(rx));
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
216
packages/stablestudio-ui/src-tauri/src/server.rs
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
use std::{collections::HashMap, fs::{File, self}, sync::Arc};
|
||||||
|
|
||||||
|
use http::{Uri, HeaderValue, HeaderName};
|
||||||
|
use tauri::{
|
||||||
|
plugin::{Builder as PluginBuilder, TauriPlugin},
|
||||||
|
Runtime,
|
||||||
|
};
|
||||||
|
use tiny_http::{Header, Response as HttpResponse, Server};
|
||||||
|
|
||||||
|
use crate::settings;
|
||||||
|
|
||||||
|
pub struct Response {
|
||||||
|
headers: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Response {
|
||||||
|
pub fn add_header<H: Into<String>, V: Into<String>>(&mut self, header: H, value: V) {
|
||||||
|
self.headers.insert(header.into(), value.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Builder {
|
||||||
|
port: u16
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder {
|
||||||
|
pub fn new(port: u16) -> Self {
|
||||||
|
Self {
|
||||||
|
port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
|
||||||
|
let port = self.port;
|
||||||
|
|
||||||
|
PluginBuilder::new("localhost")
|
||||||
|
.setup(move |app| {
|
||||||
|
let server = Server::http(&format!("localhost:{port}")).expect("Unable to spawn server");
|
||||||
|
let server = Arc::new(server);
|
||||||
|
let mut guards = Vec::with_capacity(4);
|
||||||
|
|
||||||
|
for _ in 0..5 {
|
||||||
|
let server = server.clone();
|
||||||
|
let asset_resolver = app.asset_resolver();
|
||||||
|
let guard = std::thread::spawn(move || {
|
||||||
|
// aquire a copy of appdata_path
|
||||||
|
for mut req in server.incoming_requests() {
|
||||||
|
let path = req
|
||||||
|
.url()
|
||||||
|
.parse::<Uri>()
|
||||||
|
.map(|uri| uri.path().to_string())
|
||||||
|
.unwrap_or_else(|_| req.url().into());
|
||||||
|
|
||||||
|
let split_path = path.split("/").collect::<Vec<&str>>();
|
||||||
|
let sliced_path = &split_path[1..split_path.len()];
|
||||||
|
|
||||||
|
let (file_name, mimetype, external_url): (
|
||||||
|
Option<String>,
|
||||||
|
Option<String>,
|
||||||
|
Option<String>,
|
||||||
|
) = match sliced_path {
|
||||||
|
["lib" | "scripts", ..] => (
|
||||||
|
Some(sliced_path.clone().join("/")),
|
||||||
|
Some(if sliced_path.last().unwrap().ends_with(".js") {
|
||||||
|
"text/javascript".to_string()
|
||||||
|
} else {
|
||||||
|
"text/css".to_string()
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
["style.css"] => (
|
||||||
|
Some("style.css".to_owned()),
|
||||||
|
Some("text/css".to_string()),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
["comfyui"] => (
|
||||||
|
Some("index.html".to_owned()),
|
||||||
|
Some("text/html".to_string()),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
["api" | "prompt" | "object_info" | "view" | "upload" | "history" | "queue" | "interrupt" | "extensions", ..] => (
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Some(
|
||||||
|
settings::SETTINGS.get().unwrap().data.comfyui_url.to_string() + "/" + &sliced_path.join("/") + "?" + &req.url().split("?").collect::<Vec<&str>>()[1..].join("?"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
[] | [_, _, ..] | [&_] => (None, None, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(file_name) = file_name {
|
||||||
|
let path_name = format!(
|
||||||
|
"{}/ComfyUI/ComfyUI/web/{}",
|
||||||
|
settings::SETTINGS.get().unwrap().data.comfyui_location.to_string(),
|
||||||
|
file_name
|
||||||
|
);
|
||||||
|
|
||||||
|
let file = File::open(&path_name);
|
||||||
|
|
||||||
|
if file.is_err() {
|
||||||
|
println!("file not found: {}", path_name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace "/ws" with "/api/ws" in the file
|
||||||
|
let mut file_contents = fs::read_to_string(&path_name).unwrap();
|
||||||
|
file_contents = file_contents.replace(
|
||||||
|
"`ws${window.location.protocol === \"https:\" ? \"s\" : \"\"}://${this.api_host}${this.api_base}/ws${existingSession}`",
|
||||||
|
"`ws://localhost:5000/ws${existingSession}`"
|
||||||
|
);
|
||||||
|
|
||||||
|
// add some stuff to app.js
|
||||||
|
if path_name.ends_with("app.js") && !file_contents.ends_with("app.api = api;") {
|
||||||
|
file_contents = file_contents + "\napp.api = api;";
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = HttpResponse::from_data(file_contents.as_bytes()).with_status_code(200).with_header(
|
||||||
|
Header::from_bytes("Content-Type", mimetype.unwrap().as_str()).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
req.respond(response).expect("unable to setup response");
|
||||||
|
println!("served file: {}", path_name);
|
||||||
|
} else if let Some(external_url) = external_url {
|
||||||
|
println!("forwarding request to {}", external_url);
|
||||||
|
|
||||||
|
let mut content = String::new();
|
||||||
|
req.as_reader().read_to_string(&mut content).unwrap();
|
||||||
|
|
||||||
|
// forward the request (using same headers and method and body)
|
||||||
|
let mut res: reqwest::blocking::Request = reqwest::blocking::Client::new()
|
||||||
|
.request(
|
||||||
|
reqwest::Method::from_bytes(req.method().as_str().as_bytes())
|
||||||
|
.unwrap(),
|
||||||
|
external_url,
|
||||||
|
)
|
||||||
|
.body(content)
|
||||||
|
.build().unwrap();
|
||||||
|
|
||||||
|
for header in req.headers() {
|
||||||
|
res.headers_mut().insert(
|
||||||
|
HeaderName::from_bytes(header.field.as_str().as_bytes()).unwrap(),
|
||||||
|
HeaderValue::from_str(header.value.as_str()).unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = match reqwest::blocking::Client::new().execute(res) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
println!("error forwarding request: {:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let status = res.status();
|
||||||
|
let headers = res.headers().clone();
|
||||||
|
|
||||||
|
// copy the response into the body
|
||||||
|
let mut response = HttpResponse::from_data(res.bytes().unwrap().to_vec()).with_status_code(status.as_u16() as u16);
|
||||||
|
|
||||||
|
// set the response headers
|
||||||
|
for header in headers.into_iter() {
|
||||||
|
response.add_header(Header::from_bytes(header.0.unwrap().as_str(), header.1.to_str().unwrap()).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
req.respond(response).expect("unable to setup response");
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
if let Some(mut asset) = asset_resolver.get(path.to_string()) {
|
||||||
|
|
||||||
|
let mut response = Response {
|
||||||
|
headers: Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
response.add_header("Content-Type", asset.mime_type);
|
||||||
|
if let Some(csp) = asset.csp_header {
|
||||||
|
response
|
||||||
|
.headers
|
||||||
|
.insert("Content-Security-Policy".into(), csp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
if let Some(response_csp) =
|
||||||
|
response.headers.get("Content-Security-Policy")
|
||||||
|
{
|
||||||
|
let html = String::from_utf8_lossy(&asset.bytes);
|
||||||
|
let body =
|
||||||
|
html.replacen(tauri::utils::html::CSP_TOKEN, response_csp, 1);
|
||||||
|
asset.bytes = body.as_bytes().to_vec();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut resp = HttpResponse::from_data(asset.bytes);
|
||||||
|
for (header, value) in response.headers {
|
||||||
|
if let Ok(h) = Header::from_bytes(header.as_bytes(), value) {
|
||||||
|
resp.add_header(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req.respond(resp).expect("unable to setup response");
|
||||||
|
} else {
|
||||||
|
req.respond(
|
||||||
|
HttpResponse::from_string("404 - Not Found")
|
||||||
|
.with_status_code(404),
|
||||||
|
)
|
||||||
|
.expect("unable to setup response");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
guards.push(guard);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
100
packages/stablestudio-ui/src-tauri/src/settings.rs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
fs::File,
|
||||||
|
io::BufReader,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::OnceLock,
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Settings {
|
||||||
|
pub comfyui_location: Box<String>,
|
||||||
|
pub comfyui_url: Box<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Store {
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub data: Settings,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Store {
|
||||||
|
pub fn save(&self) -> Result<(), Box<dyn Error>> {
|
||||||
|
let file = File::create(&self.path)?;
|
||||||
|
serde_json::to_writer(file, &self.data)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Settings {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Settings {
|
||||||
|
comfyui_location: self.comfyui_location.clone(),
|
||||||
|
comfyui_url: self.comfyui_url.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static SETTINGS: OnceLock<Store> = OnceLock::new();
|
||||||
|
|
||||||
|
pub fn create_settings(path: PathBuf, default: Settings) -> Result<&'static Store, Box<dyn Error>> {
|
||||||
|
let file = match File::open(&path) {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(_) => {
|
||||||
|
let file = File::create(&path.as_path())?;
|
||||||
|
serde_json::to_writer(file, &default)?;
|
||||||
|
return create_settings(path, default);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
|
||||||
|
let u = match serde_json::from_reader::<_, Settings>(reader) {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(_) => {
|
||||||
|
let file = File::create(&path.as_path())?;
|
||||||
|
serde_json::to_writer(file, &default)?;
|
||||||
|
default
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let s = Store { path, data: u };
|
||||||
|
|
||||||
|
let _ = SETTINGS.set(s);
|
||||||
|
|
||||||
|
Ok(SETTINGS.get().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_setting(key: String) -> Result<String, String> {
|
||||||
|
let s = SETTINGS.get().unwrap();
|
||||||
|
match key.as_str() {
|
||||||
|
"comfyui_location" => Ok(s.data.comfyui_location.to_string()),
|
||||||
|
"comfyui_url" => Ok(s.data.comfyui_url.to_string()),
|
||||||
|
_ => Err("Invalid key".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn set_setting(key: String, value: String) -> Result<(), String> {
|
||||||
|
let mut s = SETTINGS.get().unwrap().clone();
|
||||||
|
match key.as_str() {
|
||||||
|
"comfyui_location" => {
|
||||||
|
let path = Path::new(&value);
|
||||||
|
if !path.exists() {
|
||||||
|
return Err("Invalid path".to_string());
|
||||||
|
}
|
||||||
|
s.data.comfyui_location = Box::new(value);
|
||||||
|
}
|
||||||
|
"comfyui_url" => {
|
||||||
|
s.data.comfyui_url = Box::new(value);
|
||||||
|
}
|
||||||
|
_ => return Err("Invalid key".to_string()),
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = s.save();
|
||||||
|
let _ = SETTINGS.set(s);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
52
packages/stablestudio-ui/src-tauri/src/show_path.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use fork::{daemon, Fork};
|
||||||
|
use std::process::Command;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use std::{fs::metadata, path::PathBuf};
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn show_in_folder(path: String) {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
Command::new("explorer")
|
||||||
|
.args(["/select,", &path]) // The comma after select is not a typo
|
||||||
|
.spawn()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
if path.contains(",") {
|
||||||
|
// see https://gitlab.freedesktop.org/dbus/dbus/-/issues/76
|
||||||
|
let new_path = match metadata(&path).unwrap().is_dir() {
|
||||||
|
true => path,
|
||||||
|
false => {
|
||||||
|
let mut path2 = PathBuf::from(path);
|
||||||
|
path2.pop();
|
||||||
|
path2.into_os_string().into_string().unwrap()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Command::new("xdg-open").arg(&new_path).spawn().unwrap();
|
||||||
|
} else {
|
||||||
|
if let Ok(Fork::Child) = daemon(false, false) {
|
||||||
|
Command::new("dbus-send")
|
||||||
|
.args([
|
||||||
|
"--session",
|
||||||
|
"--dest=org.freedesktop.FileManager1",
|
||||||
|
"--type=method_call",
|
||||||
|
"/org/freedesktop/FileManager1",
|
||||||
|
"org.freedesktop.FileManager1.ShowItems",
|
||||||
|
format!("array:string:\"file://{path}\"").as_str(),
|
||||||
|
"string:\"\"",
|
||||||
|
])
|
||||||
|
.spawn()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
Command::new("open").args(["-R", &path]).spawn().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
94
packages/stablestudio-ui/src-tauri/src/workflows.rs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
use std::{
|
||||||
|
fs,
|
||||||
|
io::{BufReader, BufWriter},
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Workflow {
|
||||||
|
pub name: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub icon: Option<String>,
|
||||||
|
pub serialized_workflow: String,
|
||||||
|
pub created_at: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn fetch_workflows(handle: tauri::AppHandle) -> Result<Vec<Workflow>, String> {
|
||||||
|
let mut workflows: Vec<Workflow> = Vec::new();
|
||||||
|
let mut path = handle.path_resolver().app_data_dir().unwrap();
|
||||||
|
path.push("workflows");
|
||||||
|
|
||||||
|
// create directory if it doesn't exist
|
||||||
|
std::fs::create_dir_all(&path).unwrap();
|
||||||
|
|
||||||
|
for entry in fs::read_dir(path).unwrap() {
|
||||||
|
let entry = entry.unwrap();
|
||||||
|
let path = entry.path();
|
||||||
|
let metadata = fs::metadata(&path).unwrap();
|
||||||
|
if metadata.is_file() {
|
||||||
|
let workflow_file = fs::File::open(path).unwrap();
|
||||||
|
let reader = BufReader::new(workflow_file);
|
||||||
|
if let Ok(workflow_data) = serde_json::from_reader(reader) {
|
||||||
|
workflows.push(workflow_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(workflows)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn save_workflow(
|
||||||
|
handle: tauri::AppHandle,
|
||||||
|
serialized_workflow: String,
|
||||||
|
name: String,
|
||||||
|
description: Option<String>,
|
||||||
|
icon: Option<String>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let mut workflow_path = handle.path_resolver().app_data_dir().unwrap();
|
||||||
|
workflow_path.push("workflows");
|
||||||
|
|
||||||
|
// create directory if it doesn't exist
|
||||||
|
std::fs::create_dir_all(&workflow_path).unwrap();
|
||||||
|
|
||||||
|
let file_name = name.replace(" ", "_").to_lowercase();
|
||||||
|
workflow_path.push(format!("{file_name}.json"));
|
||||||
|
|
||||||
|
let workflow_file = fs::File::create(workflow_path).unwrap();
|
||||||
|
let writer = BufWriter::new(workflow_file);
|
||||||
|
|
||||||
|
match serde_json::to_writer(
|
||||||
|
writer,
|
||||||
|
&Workflow {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
icon,
|
||||||
|
serialized_workflow,
|
||||||
|
created_at: std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs()
|
||||||
|
.to_string(),
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn delete_workflow(handle: tauri::AppHandle, name: String) -> Result<(), String> {
|
||||||
|
let mut workflow_path = handle.path_resolver().app_data_dir().unwrap();
|
||||||
|
workflow_path.push("workflows");
|
||||||
|
|
||||||
|
// create directory if it doesn't exist
|
||||||
|
std::fs::create_dir_all(&workflow_path).unwrap();
|
||||||
|
|
||||||
|
workflow_path.push(format!("{name}.json"));
|
||||||
|
|
||||||
|
match fs::remove_file(workflow_path) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
69
packages/stablestudio-ui/src-tauri/tauri.conf.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"beforeBuildCommand": "yarn build",
|
||||||
|
"beforeDevCommand": "yarn dev:vite",
|
||||||
|
"devPath": "http://localhost:3000",
|
||||||
|
"distDir": "../dist"
|
||||||
|
},
|
||||||
|
"package": {
|
||||||
|
"productName": "StableStudio",
|
||||||
|
"version": "0.1.0"
|
||||||
|
},
|
||||||
|
"tauri": {
|
||||||
|
"allowlist": {
|
||||||
|
"fs": {
|
||||||
|
"all": true,
|
||||||
|
"scope": ["$APPDATA/**"]
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"all": true
|
||||||
|
},
|
||||||
|
"os": {
|
||||||
|
"all": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"category": "GraphicsAndDesign",
|
||||||
|
"copyright": "",
|
||||||
|
"deb": {
|
||||||
|
"depends": []
|
||||||
|
},
|
||||||
|
"externalBin": [],
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
],
|
||||||
|
"identifier": "com.stabilityai.stablestudio",
|
||||||
|
"longDescription": "",
|
||||||
|
"macOS": {
|
||||||
|
"entitlements": null,
|
||||||
|
"exceptionDomain": "",
|
||||||
|
"frameworks": [],
|
||||||
|
"providerShortName": null,
|
||||||
|
"signingIdentity": null
|
||||||
|
},
|
||||||
|
"resources": [],
|
||||||
|
"shortDescription": "",
|
||||||
|
"targets": "all",
|
||||||
|
"windows": {
|
||||||
|
"certificateThumbprint": null,
|
||||||
|
"digestAlgorithm": "sha256",
|
||||||
|
"timestampUrl": "",
|
||||||
|
"wix": {
|
||||||
|
"language": "en-US"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"csp": null
|
||||||
|
},
|
||||||
|
"updater": {
|
||||||
|
"active": false
|
||||||
|
},
|
||||||
|
"windows": []
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,7 +47,7 @@ export function Section(props: Section.Props) {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{title && (
|
{title && (
|
||||||
<div className="flex items-center py-2 px-2">
|
<div className="flex items-center px-2 py-2">
|
||||||
<Theme.Button
|
<Theme.Button
|
||||||
{...buttonProps}
|
{...buttonProps}
|
||||||
className={classes(
|
className={classes(
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
import { listen } from "@tauri-apps/api/event";
|
||||||
|
import {
|
||||||
|
BaseDirectory,
|
||||||
|
exists,
|
||||||
|
FileEntry,
|
||||||
|
readDir,
|
||||||
|
removeFile,
|
||||||
|
} from "@tauri-apps/api/fs";
|
||||||
|
import { appDataDir } from "@tauri-apps/api/path";
|
||||||
|
import { invoke } from "@tauri-apps/api/tauri";
|
||||||
|
import { download } from "tauri-plugin-upload";
|
||||||
|
import { shallow } from "zustand/shallow";
|
||||||
|
import { Comfy } from "~/Comfy";
|
||||||
import { Router } from "~/Router";
|
import { Router } from "~/Router";
|
||||||
import { Shortcut } from "~/Shortcut";
|
import { Shortcut } from "~/Shortcut";
|
||||||
import { Theme } from "~/Theme";
|
import { Theme } from "~/Theme";
|
||||||
@@ -9,26 +22,48 @@ import { TopBar } from "./TopBar";
|
|||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const isMobileDevice = Theme.useIsMobileDevice();
|
const isMobileDevice = Theme.useIsMobileDevice();
|
||||||
|
const { isSetup, message, progress } = App.useSetupState();
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => (
|
() => (
|
||||||
<Providers>
|
<Providers>
|
||||||
<div className="absolute top-0 left-0 -z-50 h-screen w-screen dark:bg-zinc-800" />
|
<div className="absolute left-0 top-0 -z-50 h-screen w-screen dark:bg-zinc-800" />
|
||||||
<div className="absolute top-0 left-0 flex h-screen w-screen flex-col text-white sm:overflow-x-auto">
|
<div className="absolute left-0 top-0 flex h-screen w-screen flex-col text-white sm:overflow-x-auto">
|
||||||
|
{isSetup !== App.SetupState.ComfyRunning ? (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-grow flex-col items-center justify-center gap-16">
|
||||||
|
<h1 className="text-6xl font-bold">Welcome to StableStudio</h1>
|
||||||
|
<div className="flex w-full flex-col items-center gap-4">
|
||||||
|
<p className="font-mono opacity-75">{message}</p>
|
||||||
|
{typeof progress === "number" && (
|
||||||
|
<Theme.Progress
|
||||||
|
className="max-w-[25rem]"
|
||||||
|
value={progress * 100}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<Shortcut.Palette />
|
<Shortcut.Palette />
|
||||||
<TopBar />
|
<TopBar />
|
||||||
<Sidebars />
|
<Sidebars />
|
||||||
<div className="flex min-h-0 grow overflow-auto sm:min-w-[1000px]">
|
<div className="flex min-h-0 grow overflow-auto sm:min-w-[1000px]">
|
||||||
<Sidebar position="left" />
|
<Sidebar position="left" />
|
||||||
<div className="shrink grow overflow-y-auto">
|
<div className="relative shrink grow overflow-y-auto">
|
||||||
<Router />
|
<Router />
|
||||||
|
<Comfy />
|
||||||
</div>
|
</div>
|
||||||
<Sidebar position="right" />
|
<Sidebar position="right" />
|
||||||
</div>
|
</div>
|
||||||
{isMobileDevice && <BottomBar />}
|
{isMobileDevice && <BottomBar />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Providers>
|
</Providers>
|
||||||
),
|
),
|
||||||
[isMobileDevice]
|
[isMobileDevice, isSetup, message, progress]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,4 +74,155 @@ export declare namespace App {
|
|||||||
export namespace App {
|
export namespace App {
|
||||||
App.Sidebar = Sidebar;
|
App.Sidebar = Sidebar;
|
||||||
App.TopBar = TopBar;
|
App.TopBar = TopBar;
|
||||||
|
|
||||||
|
export enum SetupState {
|
||||||
|
NotStarted,
|
||||||
|
ComfyInstalled,
|
||||||
|
WeightsInstalled,
|
||||||
|
ComfyRunning,
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listAppDataDir(path: string) {
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
await exists(path, {
|
||||||
|
dir: BaseDirectory.AppData,
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
(await readDir(path, {
|
||||||
|
dir: BaseDirectory.AppData,
|
||||||
|
})) ?? []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSetupState = () => {
|
||||||
|
const [isSetup, setIsSetup] = useState<SetupState>(SetupState.NotStarted);
|
||||||
|
const [message, setMessage] = useState<string>("");
|
||||||
|
const [progress, setProgress] = useState<number | null>(null);
|
||||||
|
const [setUnlisteners, print] = Comfy.use(
|
||||||
|
(state) => [state.setUnlisteners, state.print],
|
||||||
|
shallow
|
||||||
|
);
|
||||||
|
const nonce = useRef<number>(0);
|
||||||
|
|
||||||
|
const check = useCallback(async () => {
|
||||||
|
if (isSetup !== SetupState.NotStarted || nonce.current !== 0) return;
|
||||||
|
nonce.current++;
|
||||||
|
|
||||||
|
const appDataPath = await appDataDir();
|
||||||
|
const comfyui_location = await invoke("get_setting", {
|
||||||
|
key: "comfyui_location",
|
||||||
|
})
|
||||||
|
.then((res) => res)
|
||||||
|
.catch(() => appDataPath);
|
||||||
|
|
||||||
|
let entries: FileEntry[] = await listAppDataDir(
|
||||||
|
`${comfyui_location}/ComfyUI/ComfyUI/models/checkpoints`
|
||||||
|
);
|
||||||
|
|
||||||
|
// filter for actual files (not directories or symlinks or "put_checkpoints_here" files)
|
||||||
|
entries = entries.filter((entry) => entry.name?.includes("."));
|
||||||
|
|
||||||
|
console.log(entries);
|
||||||
|
|
||||||
|
if (entries.length === 0) {
|
||||||
|
const comfyExists = await exists(
|
||||||
|
`${comfyui_location}/ComfyUI/ComfyUI/main.py`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!comfyExists) {
|
||||||
|
setIsSetup(SetupState.NotStarted);
|
||||||
|
setMessage("Downloading ComfyUI...");
|
||||||
|
|
||||||
|
// delete the old comfyui zip if it exists
|
||||||
|
if (!(await exists(`${comfyui_location}/comfyui.zip`))) {
|
||||||
|
let comulativeProgress = 0;
|
||||||
|
|
||||||
|
console.log("downloading comfyui");
|
||||||
|
|
||||||
|
await download(
|
||||||
|
"https://pub-5e5adf378ed14628a527d735b7743e4e.r2.dev/stability-downloads/ComfyUI/ComfyUI_windows_portable.zip",
|
||||||
|
`${comfyui_location}\\comfyui.zip`,
|
||||||
|
(p, total) => {
|
||||||
|
comulativeProgress += p;
|
||||||
|
setProgress(comulativeProgress / total);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setMessage("Extracting ComfyUI...");
|
||||||
|
setProgress(null);
|
||||||
|
try {
|
||||||
|
const result = await invoke("extract_comfy");
|
||||||
|
|
||||||
|
if (result !== "completed") {
|
||||||
|
throw new Error("Failed to extract comfyui.zip");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
setIsSetup(SetupState.NotStarted);
|
||||||
|
setMessage(`Error installing ComfyUI: ${error}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await removeFile(`${comfyui_location}/comfyui.zip`);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSetup(SetupState.ComfyInstalled);
|
||||||
|
setMessage("Downloading Stable Diffusion...");
|
||||||
|
|
||||||
|
let comulativeProgress = 0;
|
||||||
|
|
||||||
|
console.log("downloading weights");
|
||||||
|
|
||||||
|
await download(
|
||||||
|
"https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/v2-1_768-ema-pruned.safetensors",
|
||||||
|
`${comfyui_location}/ComfyUI/ComfyUI/models/checkpoints/v2-1_768-ema-pruned.safetensors`,
|
||||||
|
(p, total) => {
|
||||||
|
comulativeProgress += p;
|
||||||
|
setProgress(comulativeProgress / total);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
setMessage("done");
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSetup(SetupState.WeightsInstalled);
|
||||||
|
setMessage("Starting ComfyUI...");
|
||||||
|
setProgress(null);
|
||||||
|
|
||||||
|
// add listener
|
||||||
|
const unlisten = await listen("comfy-output", (event) => {
|
||||||
|
const [t, ...d] = `${event.payload}`.split(":");
|
||||||
|
console.log("[COMFYUI]", t, d);
|
||||||
|
print(t, d.join(":"));
|
||||||
|
});
|
||||||
|
setUnlisteners([unlisten]);
|
||||||
|
|
||||||
|
// start comfy
|
||||||
|
const result = await invoke("launch_comfy");
|
||||||
|
|
||||||
|
if (result !== "completed") {
|
||||||
|
setMessage(`Error launching ComfyUI: ${result}`);
|
||||||
|
throw new Error("Failed to launch ComfyUI");
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSetup(SetupState.ComfyRunning);
|
||||||
|
Comfy.registerListeners();
|
||||||
|
}, [isSetup, print, setUnlisteners]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
check();
|
||||||
|
}, [check]);
|
||||||
|
|
||||||
|
return { isSetup, message, progress };
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
324
packages/stablestudio-ui/src/Comfy/index.tsx
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { create } from "zustand";
|
||||||
|
import { Generation } from "~/Generation";
|
||||||
|
|
||||||
|
export type ComfyApp = {
|
||||||
|
setup: () => void;
|
||||||
|
registerNodes: () => void;
|
||||||
|
loadGraphData: (graph?: Graph) => void;
|
||||||
|
graphToPrompt: () => Promise<{
|
||||||
|
workflow: any;
|
||||||
|
prompt: any;
|
||||||
|
}>;
|
||||||
|
handleFile: (file: File) => void;
|
||||||
|
refreshComboInNodes: () => Promise<void>;
|
||||||
|
queuePrompt: (number: number, batchCount: number) => Promise<void>;
|
||||||
|
clean: () => void;
|
||||||
|
api: ComfyAPI;
|
||||||
|
graph: {
|
||||||
|
serialize: () => any;
|
||||||
|
_nodes: {
|
||||||
|
title: string;
|
||||||
|
type: string;
|
||||||
|
stableValues?: any;
|
||||||
|
widgets: {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
value: any;
|
||||||
|
}[];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ComfyAPI = {
|
||||||
|
addEventListener: (event: string, callback: (detail: any) => void) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ComfyOutput = {
|
||||||
|
images: {
|
||||||
|
filename: string;
|
||||||
|
subfolder: string;
|
||||||
|
type: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Graph = {
|
||||||
|
last_node_id: number;
|
||||||
|
last_link_id: number;
|
||||||
|
nodes: {
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
pos: [number, number];
|
||||||
|
size: {
|
||||||
|
0: number;
|
||||||
|
1: number;
|
||||||
|
};
|
||||||
|
flags: Record<string, unknown>;
|
||||||
|
order: number;
|
||||||
|
mode: number;
|
||||||
|
inputs: {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
link: number;
|
||||||
|
}[];
|
||||||
|
outputs: {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
links: number[];
|
||||||
|
slot_index: number;
|
||||||
|
}[];
|
||||||
|
properties: Record<string, unknown>;
|
||||||
|
widgets_values: any[];
|
||||||
|
}[];
|
||||||
|
links: [
|
||||||
|
number, // id
|
||||||
|
number, // input_node_id
|
||||||
|
number, // input_slot_index
|
||||||
|
number, // output_node_id
|
||||||
|
number, // output_slot_index
|
||||||
|
string // type
|
||||||
|
][];
|
||||||
|
groups: any;
|
||||||
|
config: any;
|
||||||
|
extra: any;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Comfy() {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
id="comfyui-window"
|
||||||
|
src="/comfyui"
|
||||||
|
className={classes(
|
||||||
|
"absolute h-full w-full",
|
||||||
|
!location.pathname.startsWith("/nodes") &&
|
||||||
|
"pointer-events-none h-0 w-0 opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_STDOUT_LENGTH = 500;
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
output: {
|
||||||
|
type: string;
|
||||||
|
data: string;
|
||||||
|
}[];
|
||||||
|
max_lines: number;
|
||||||
|
print: (type: string, data: string) => void;
|
||||||
|
|
||||||
|
running: boolean;
|
||||||
|
setRunning: (running: boolean) => void;
|
||||||
|
|
||||||
|
unlisteners: (() => void)[];
|
||||||
|
setUnlisteners: (unlisteners: (() => void)[]) => void;
|
||||||
|
|
||||||
|
runningPrompt: ID | null;
|
||||||
|
setRunningPrompt: (promptID: ID | null) => void;
|
||||||
|
|
||||||
|
lastOuput: ComfyOutput | null;
|
||||||
|
setLastOutput: (output: ComfyOutput | null) => void;
|
||||||
|
|
||||||
|
inlineExpanded: boolean;
|
||||||
|
setInlineExpanded: (expanded: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export namespace Comfy {
|
||||||
|
export const get = (): ComfyApp | null =>
|
||||||
|
((
|
||||||
|
(document.getElementById("comfyui-window") as HTMLIFrameElement)
|
||||||
|
?.contentWindow as Window & { app: ComfyApp }
|
||||||
|
)?.app as ComfyApp) ?? null;
|
||||||
|
|
||||||
|
export const use = create<State>((set) => ({
|
||||||
|
output: [],
|
||||||
|
max_lines: MAX_STDOUT_LENGTH,
|
||||||
|
print: (type, data) =>
|
||||||
|
set((state) => {
|
||||||
|
if (state.output.map((o) => o.data).includes(data)) return state;
|
||||||
|
|
||||||
|
const output = [...state.output, { type, data }];
|
||||||
|
if (output.length > MAX_STDOUT_LENGTH) output.shift();
|
||||||
|
return { output };
|
||||||
|
}),
|
||||||
|
|
||||||
|
running: false,
|
||||||
|
setRunning: (running) => set({ running }),
|
||||||
|
|
||||||
|
unlisteners: [],
|
||||||
|
setUnlisteners: (unlisteners) => set({ unlisteners }),
|
||||||
|
|
||||||
|
runningPrompt: null,
|
||||||
|
setRunningPrompt: (runningPrompt) => set({ runningPrompt }),
|
||||||
|
|
||||||
|
lastOuput: null,
|
||||||
|
setLastOutput: (lastOuput) => set({ lastOuput }),
|
||||||
|
|
||||||
|
inlineExpanded: false,
|
||||||
|
setInlineExpanded: (inlineExpanded) => set({ inlineExpanded }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const registerListeners = async () => {
|
||||||
|
let api = get()?.api;
|
||||||
|
|
||||||
|
while (!api) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
api = get()?.api;
|
||||||
|
}
|
||||||
|
|
||||||
|
api.addEventListener("progress", ({ detail }) => {
|
||||||
|
console.log("progress", detail);
|
||||||
|
const runningPrompt = use.getState().runningPrompt;
|
||||||
|
if (runningPrompt) {
|
||||||
|
Generation.Image.Output.set({
|
||||||
|
...Generation.Image.Output.get(runningPrompt),
|
||||||
|
progress: detail.value / detail.max,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
api.addEventListener("b_preview", ({ detail }) => {
|
||||||
|
console.log("b_preview", detail);
|
||||||
|
});
|
||||||
|
|
||||||
|
const executed = async ({ detail }: any) => {
|
||||||
|
const { output, prompt_id } = detail;
|
||||||
|
|
||||||
|
console.log("executed_in_comfy_domain", detail);
|
||||||
|
use.getState().setRunningPrompt(null);
|
||||||
|
|
||||||
|
const newInputs: Record<ID, Generation.Image.Input> = {};
|
||||||
|
const responses: Generation.Images = [];
|
||||||
|
|
||||||
|
const input = Generation.Image.Input.get(prompt_id);
|
||||||
|
|
||||||
|
const images = await Promise.all(
|
||||||
|
(output as ComfyOutput).images.map(async (image) => {
|
||||||
|
console.log("image", image);
|
||||||
|
const resp = await fetch(
|
||||||
|
`/view?filename=${image.filename}&subfolder=${
|
||||||
|
image.subfolder || ""
|
||||||
|
}&type=${image.type}`,
|
||||||
|
{
|
||||||
|
cache: "no-cache",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const blob = await resp.blob();
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
console.log("url", url);
|
||||||
|
|
||||||
|
const output = Generation.Image.Output.get(prompt_id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: ID.create(),
|
||||||
|
blob,
|
||||||
|
inputID: output?.inputID ?? "",
|
||||||
|
createdAt: new Date(),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const image of images) {
|
||||||
|
const inputID = ID.create();
|
||||||
|
const newInput = {
|
||||||
|
...Generation.Image.Input.initial(inputID),
|
||||||
|
...input,
|
||||||
|
seed: (input?.seed ?? 0) + images.indexOf(image),
|
||||||
|
id: inputID,
|
||||||
|
};
|
||||||
|
|
||||||
|
responses.push({
|
||||||
|
id: image.id,
|
||||||
|
inputID: newInput.id,
|
||||||
|
created: new Date(),
|
||||||
|
src: URL.createObjectURL(image.blob),
|
||||||
|
finishReason: 0,
|
||||||
|
});
|
||||||
|
newInputs[inputID] = newInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
Generation.Image.Inputs.set({
|
||||||
|
...Generation.Image.Inputs.get(),
|
||||||
|
...newInputs,
|
||||||
|
});
|
||||||
|
responses.forEach(Generation.Image.add);
|
||||||
|
Generation.Image.Output.received(prompt_id, responses);
|
||||||
|
use.getState().setLastOutput(detail);
|
||||||
|
};
|
||||||
|
|
||||||
|
api.addEventListener("executed", executed);
|
||||||
|
api.addEventListener("execution_cached", async ({ detail }) => {
|
||||||
|
const last: any = use.getState().lastOuput;
|
||||||
|
console.log("execution_cached", detail, last);
|
||||||
|
if (
|
||||||
|
use.getState().runningPrompt === detail.prompt_id &&
|
||||||
|
detail.nodes.includes(last?.node) &&
|
||||||
|
last.output
|
||||||
|
) {
|
||||||
|
console.log("last", last);
|
||||||
|
const d = { ...last, prompt_id: detail.prompt_id };
|
||||||
|
await executed({ detail: d });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
api.addEventListener("execution_error", ({ detail }) => {
|
||||||
|
console.log("execution_error", detail);
|
||||||
|
Generation.Image.Output.clear(detail.prompt_id);
|
||||||
|
use.getState().setRunningPrompt(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
api.addEventListener("execution_start", ({ detail }) => {
|
||||||
|
use.getState().setRunningPrompt(detail?.prompt_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("registered ComfyUI listeners");
|
||||||
|
use.getState().setRunning(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Output = ({
|
||||||
|
className,
|
||||||
|
small,
|
||||||
|
}: Styleable & {
|
||||||
|
small?: boolean;
|
||||||
|
}) => {
|
||||||
|
const { output, inlineExpanded, setInlineExpanded } = Comfy.use(
|
||||||
|
(state) => ({
|
||||||
|
output: state.output,
|
||||||
|
inlineExpanded: state.inlineExpanded,
|
||||||
|
setInlineExpanded: state.setInlineExpanded,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classes(
|
||||||
|
"flex max-h-[25rem] flex-col-reverse overflow-y-auto overflow-x-hidden whitespace-pre-wrap rounded bg-black/25 p-2 font-mono text-sm leading-4",
|
||||||
|
small && !inlineExpanded && "overflow-y-hidden",
|
||||||
|
small && "cursor-pointer",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
onClick={() => setInlineExpanded(!inlineExpanded)}
|
||||||
|
>
|
||||||
|
{(small && !inlineExpanded
|
||||||
|
? output.slice(-1)
|
||||||
|
: [...output].reverse()
|
||||||
|
).map((line, index) => (
|
||||||
|
<p
|
||||||
|
key={`${index}-${line}`}
|
||||||
|
className={classes(
|
||||||
|
"text-white",
|
||||||
|
line.type === "stdout" && "text-green-200",
|
||||||
|
line.type === "stderr" && "text-red-200"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{line.data}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
57
packages/stablestudio-ui/src/Comfy/plugin.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import * as StableStudio from "@stability/stablestudio-plugin";
|
||||||
|
|
||||||
|
export const createPlugin = StableStudio.createPlugin(() => {
|
||||||
|
return {
|
||||||
|
manifest: {
|
||||||
|
name: "ComfyUI",
|
||||||
|
},
|
||||||
|
|
||||||
|
getStableDiffusionModels: async () => {
|
||||||
|
const resp = await fetch("/object_info/CheckpointLoader", {
|
||||||
|
cache: "no-cache",
|
||||||
|
});
|
||||||
|
const jsonResp = await resp.json();
|
||||||
|
|
||||||
|
console.log(jsonResp);
|
||||||
|
|
||||||
|
return jsonResp?.CheckpointLoader?.input?.required?.ckpt_name?.[0]?.map(
|
||||||
|
(fileName: string) => ({
|
||||||
|
id: fileName,
|
||||||
|
name: fileName,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
getStableDiffusionSamplers: async () => {
|
||||||
|
const resp = await fetch("/object_info/KSamplerAdvanced", {
|
||||||
|
cache: "no-cache",
|
||||||
|
});
|
||||||
|
const jsonResp = await resp.json();
|
||||||
|
|
||||||
|
return jsonResp?.KSamplerAdvanced?.input?.required?.sampler_name?.[0]?.map(
|
||||||
|
(name: string) => ({
|
||||||
|
id: name,
|
||||||
|
name: name
|
||||||
|
.replace(/_/g, " ")
|
||||||
|
.replace("ddim", "DDIM")
|
||||||
|
.replace("lms", "LMS")
|
||||||
|
.replace("dpm", "DPM")
|
||||||
|
.replace("pp", "PP")
|
||||||
|
.replace("sde", "SDE")
|
||||||
|
.replace("2m", "2M")
|
||||||
|
.replace("2s", "2S")
|
||||||
|
.replace("gpu", "GPU")
|
||||||
|
.replace(/\w\S*/g, (w) => w.replace(/^\w/, (c) => c.toUpperCase())),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
getStatus: async () => {
|
||||||
|
const resp = await fetch("/comfyui", { cache: "no-cache" });
|
||||||
|
return {
|
||||||
|
indicator: resp.ok ? "success" : "error",
|
||||||
|
text: resp.ok ? "Running" : "Not Running",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { App } from "~/App";
|
import { App } from "~/App";
|
||||||
|
import { Comfy } from "~/Comfy";
|
||||||
import { Editor } from "~/Editor";
|
import { Editor } from "~/Editor";
|
||||||
import { Generation } from "~/Generation";
|
import { Generation } from "~/Generation";
|
||||||
import { Router } from "~/Router";
|
import { Router } from "~/Router";
|
||||||
@@ -39,6 +40,8 @@ export function Sidebar() {
|
|||||||
|
|
||||||
const bottom = selectedID && (
|
const bottom = selectedID && (
|
||||||
<App.Sidebar.Tab.Bottom>
|
<App.Sidebar.Tab.Bottom>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Comfy.Output small />
|
||||||
<Generation.Image.Create.Button
|
<Generation.Image.Create.Button
|
||||||
id={inputID}
|
id={inputID}
|
||||||
onIdleClick={() => createDream()}
|
onIdleClick={() => createDream()}
|
||||||
@@ -50,6 +53,7 @@ export function Sidebar() {
|
|||||||
}
|
}
|
||||||
loading={generating}
|
loading={generating}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</App.Sidebar.Tab.Bottom>
|
</App.Sidebar.Tab.Bottom>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import * as StableStudio from "@stability/stablestudio-plugin";
|
import { Comfy } from "~/Comfy";
|
||||||
import throttledQueue from "throttled-queue";
|
|
||||||
|
|
||||||
import { Generation } from "~/Generation";
|
import { Generation } from "~/Generation";
|
||||||
import { GlobalState } from "~/GlobalState";
|
import { GlobalState } from "~/GlobalState";
|
||||||
import { Plugin } from "~/Plugin";
|
|
||||||
|
|
||||||
import { Button } from "./Button";
|
import { Button } from "./Button";
|
||||||
|
|
||||||
@@ -23,35 +21,22 @@ export namespace Create {
|
|||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace Throttle {
|
|
||||||
const requestsPerInterval = 1;
|
|
||||||
const interval = 500;
|
|
||||||
const spaceEvenly = true;
|
|
||||||
|
|
||||||
const queue = throttledQueue(requestsPerInterval, interval, spaceEvenly);
|
|
||||||
export const wait = () => queue(() => Promise.resolve());
|
|
||||||
}
|
|
||||||
|
|
||||||
export const execute = async ({
|
export const execute = async ({
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
|
||||||
count = Generation.Image.Count.preset(),
|
count = Generation.Image.Count.preset(),
|
||||||
input,
|
input,
|
||||||
|
|
||||||
onStarted = doNothing,
|
onStarted = doNothing,
|
||||||
onException = doNothing,
|
onException = doNothing,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
|
||||||
onSuccess = doNothing,
|
onSuccess = doNothing,
|
||||||
onFinished = doNothing,
|
onFinished = doNothing,
|
||||||
}: Handlers & {
|
}: Handlers & {
|
||||||
count: number;
|
count: number;
|
||||||
input: Generation.Image.Input;
|
input: Generation.Image.Input;
|
||||||
}): Promise<Generation.Image.Exception | Generation.Images> => {
|
}): Promise<undefined | Generation.Image.Exception> => {
|
||||||
const { createStableDiffusionImages } = Plugin.get();
|
|
||||||
try {
|
try {
|
||||||
if (!createStableDiffusionImages) throw new Error("Plugin not found");
|
|
||||||
|
|
||||||
Latest.set(new Date());
|
Latest.set(new Date());
|
||||||
onStarted();
|
|
||||||
|
|
||||||
await Throttle.wait();
|
|
||||||
|
|
||||||
const initImg = await Generation.Image.Input.resizeInit(input);
|
const initImg = await Generation.Image.Input.resizeInit(input);
|
||||||
const pluginInput = await Generation.Image.Input.toInput(
|
const pluginInput = await Generation.Image.Input.toInput(
|
||||||
@@ -72,43 +57,58 @@ export namespace Create {
|
|||||||
pluginInput.width = Math.ceil((pluginInput.width ?? 512) / 64) * 64;
|
pluginInput.width = Math.ceil((pluginInput.width ?? 512) / 64) * 64;
|
||||||
}
|
}
|
||||||
|
|
||||||
const responses: Generation.Images = [];
|
const startingSeed =
|
||||||
const response = await createStableDiffusionImages({
|
input.seed === 0 ? Math.round(Math.random() * 100000) : input.seed;
|
||||||
input: pluginInput,
|
|
||||||
count,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response instanceof Error) throw response;
|
Comfy.get()
|
||||||
if (!response || !response?.images || response?.images?.length <= 0)
|
?.graph._nodes?.filter((node) => node.type === "StableStudioNode")
|
||||||
throw new Error();
|
.forEach((node) => {
|
||||||
|
if (!("stableValues" in node)) return;
|
||||||
|
|
||||||
const newInputs: Record<ID, Generation.Image.Input> = {};
|
node.stableValues = {
|
||||||
|
...node.stableValues,
|
||||||
for (const image of response.images) {
|
batch_size: count,
|
||||||
const inputID = ID.create();
|
height: input.height,
|
||||||
const newInput = {
|
width: input.width,
|
||||||
...Generation.Image.Input.initial(inputID),
|
negative_prompt:
|
||||||
...input,
|
input.prompts.find((p) => p.weight < 0)?.text ??
|
||||||
seed: image.input?.seed ?? input.seed,
|
node.stableValues.negative_prompt,
|
||||||
id: inputID,
|
positive_prompt:
|
||||||
|
input.prompts.find((p) => p.weight > 0)?.text ??
|
||||||
|
node.stableValues.positive_prompt,
|
||||||
|
seed: startingSeed,
|
||||||
|
steps: input.steps,
|
||||||
|
cfg: input.cfgScale ?? node.stableValues.cfg,
|
||||||
|
sampler_name:
|
||||||
|
(input.sampler?.id?.length || 0) > 2
|
||||||
|
? input.sampler?.id
|
||||||
|
: node.stableValues.sampler,
|
||||||
|
ckpt_name: input.model || node.stableValues.model,
|
||||||
};
|
};
|
||||||
|
|
||||||
const cropped = await cropImage(image, newInput);
|
|
||||||
if (!cropped) continue;
|
|
||||||
|
|
||||||
responses.push(cropped);
|
|
||||||
newInputs[inputID] = newInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
Generation.Image.Inputs.set({
|
|
||||||
...Generation.Image.Inputs.get(),
|
|
||||||
...newInputs,
|
|
||||||
});
|
});
|
||||||
|
Comfy.get()?.refreshComboInNodes();
|
||||||
|
console.log(Comfy.get());
|
||||||
|
const resp: any = await Comfy.get()?.queuePrompt(-1, 1);
|
||||||
|
console.log("queued", resp);
|
||||||
|
const { prompt_id } = resp;
|
||||||
|
|
||||||
onSuccess(responses);
|
if (prompt_id) {
|
||||||
onFinished(responses);
|
Generation.Image.Inputs.set((inputs) => ({
|
||||||
|
...inputs,
|
||||||
return responses;
|
[prompt_id]: {
|
||||||
|
...input,
|
||||||
|
seed: startingSeed,
|
||||||
|
id: prompt_id,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
const output = Generation.Image.Output.requested(
|
||||||
|
prompt_id,
|
||||||
|
{},
|
||||||
|
prompt_id
|
||||||
|
);
|
||||||
|
Generation.Image.Output.set(output);
|
||||||
|
onStarted(output);
|
||||||
|
}
|
||||||
} catch (caught: unknown) {
|
} catch (caught: unknown) {
|
||||||
const exception = Generation.Image.Exception.create(caught);
|
const exception = Generation.Image.Exception.create(caught);
|
||||||
|
|
||||||
@@ -142,30 +142,24 @@ export namespace Create {
|
|||||||
...modifiers,
|
...modifiers,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = Generation.Image.Output.requested(inputID, modifiers);
|
|
||||||
|
|
||||||
return execute({
|
return execute({
|
||||||
count: modifiers.count ?? Generation.Image.Count.get(),
|
count: modifiers.count ?? Generation.Image.Count.get(),
|
||||||
input,
|
input,
|
||||||
|
|
||||||
onStarted: () => {
|
onStarted: (output) => {
|
||||||
Generation.Image.Output.set(output);
|
|
||||||
onStarted(output);
|
onStarted(output);
|
||||||
},
|
},
|
||||||
|
|
||||||
onException: (exception) => {
|
onException: (exception) => {
|
||||||
showErrorSnackbar(exception);
|
showErrorSnackbar(exception);
|
||||||
onException(exception);
|
onException(exception);
|
||||||
Generation.Image.Output.clear(output.id);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onSuccess: (images) => {
|
onSuccess: (images) => {
|
||||||
images.forEach(Generation.Image.add);
|
|
||||||
onSuccess(images);
|
onSuccess(images);
|
||||||
},
|
},
|
||||||
|
|
||||||
onFinished: (result) => {
|
onFinished: (result) => {
|
||||||
Generation.Image.Output.received(output.id, result);
|
|
||||||
onFinished(result);
|
onFinished(result);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -174,10 +168,7 @@ export namespace Create {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useIsEnabled = () =>
|
export const useIsEnabled = () => Comfy.use(({ running }) => running);
|
||||||
Plugin.use(
|
|
||||||
({ createStableDiffusionImages }) => !!createStableDiffusionImages
|
|
||||||
);
|
|
||||||
|
|
||||||
export type Latest = Date;
|
export type Latest = Date;
|
||||||
export namespace Latest {
|
export namespace Latest {
|
||||||
@@ -202,52 +193,3 @@ export namespace Create {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move somewhere else
|
|
||||||
function cropImage(
|
|
||||||
image: StableStudio.StableDiffusionImage,
|
|
||||||
input: Generation.Image.Input
|
|
||||||
) {
|
|
||||||
return new Promise<Generation.Image | void>((resolve) => {
|
|
||||||
const id = image.id;
|
|
||||||
const blob = image.blob;
|
|
||||||
if (!blob || !id) return resolve();
|
|
||||||
|
|
||||||
// crop image to box size
|
|
||||||
const croppedCanvas = document.createElement("canvas");
|
|
||||||
croppedCanvas.width = input.width;
|
|
||||||
croppedCanvas.height = input.height;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
const croppedCtx = croppedCanvas.getContext("2d")!;
|
|
||||||
|
|
||||||
const img = new window.Image();
|
|
||||||
img.src = URL.createObjectURL(blob);
|
|
||||||
img.onload = () => {
|
|
||||||
croppedCtx.drawImage(
|
|
||||||
img,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
input.width,
|
|
||||||
input.height,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
input.width,
|
|
||||||
input.height
|
|
||||||
);
|
|
||||||
|
|
||||||
croppedCanvas.toBlob((blob) => {
|
|
||||||
if (blob) {
|
|
||||||
const objectURL = URL.createObjectURL(blob);
|
|
||||||
resolve({
|
|
||||||
id,
|
|
||||||
inputID: input.id,
|
|
||||||
created: new Date(),
|
|
||||||
src: objectURL,
|
|
||||||
finishReason: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { RpcError } from "@protobuf-ts/runtime-rpc";
|
|
||||||
|
|
||||||
import { Snackbar } from "./Snackbar";
|
import { Snackbar } from "./Snackbar";
|
||||||
|
|
||||||
export type Exception = {
|
export type Exception = {
|
||||||
/** The underlying error */
|
/** The underlying error */
|
||||||
readonly cause: Error | RpcError;
|
readonly cause: Error;
|
||||||
|
|
||||||
/** Human-readable description of the underlying error */
|
/** Human-readable description of the underlying error */
|
||||||
readonly description: string;
|
readonly description: string;
|
||||||
@@ -20,9 +18,6 @@ export declare namespace Exception {
|
|||||||
export namespace Exception {
|
export namespace Exception {
|
||||||
Exception.Snackbar = Snackbar;
|
Exception.Snackbar = Snackbar;
|
||||||
|
|
||||||
const defaultMessage =
|
|
||||||
"Something went wrong on our end, please try again later";
|
|
||||||
|
|
||||||
export function is(value: unknown): value is Exception {
|
export function is(value: unknown): value is Exception {
|
||||||
if (!value && typeof value !== "object") return false;
|
if (!value && typeof value !== "object") return false;
|
||||||
const exception = value as Exception;
|
const exception = value as Exception;
|
||||||
@@ -36,23 +31,6 @@ export namespace Exception {
|
|||||||
|
|
||||||
export function create(unknown: unknown = {}): Exception {
|
export function create(unknown: unknown = {}): Exception {
|
||||||
console.error(unknown);
|
console.error(unknown);
|
||||||
|
|
||||||
if (
|
|
||||||
unknown instanceof RpcError ||
|
|
||||||
(unknown instanceof Error && unknown.name === "RpcError")
|
|
||||||
) {
|
|
||||||
const rpcError = unknown as RpcError;
|
|
||||||
return {
|
|
||||||
cause: rpcError,
|
|
||||||
description: translateError(rpcError),
|
|
||||||
status: isBannedTermError(rpcError)
|
|
||||||
? "BANNED_TERM"
|
|
||||||
: isOutOfCreditsError(rpcError)
|
|
||||||
? "OUT_OF_CREDITS"
|
|
||||||
: rpcError.code,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const error =
|
const error =
|
||||||
unknown instanceof Error ? unknown : new Error(toJSON(unknown));
|
unknown instanceof Error ? unknown : new Error(toJSON(unknown));
|
||||||
|
|
||||||
@@ -61,154 +39,4 @@ export namespace Exception {
|
|||||||
description: error.message,
|
description: error.message,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function translateError(error: RpcError): string {
|
|
||||||
switch (true) {
|
|
||||||
case error.message.includes("Completion canceled by user."):
|
|
||||||
return "Canceled";
|
|
||||||
|
|
||||||
case error.message.includes(
|
|
||||||
"Unable to remove the only API key remaining."
|
|
||||||
):
|
|
||||||
return "Sorry, you can't delete your only API key";
|
|
||||||
|
|
||||||
case error.message.includes("Unable to get organization for request."):
|
|
||||||
return "We couldn't find the organization you're asking for";
|
|
||||||
|
|
||||||
case isOutOfCreditsError(error):
|
|
||||||
return "Not enough credits";
|
|
||||||
|
|
||||||
case isBannedTermError(error):
|
|
||||||
return "Something isn't quite right with your prompts";
|
|
||||||
|
|
||||||
case error.message.includes("Unable to get user for request."):
|
|
||||||
return "We couldn't find the user you're asking for";
|
|
||||||
|
|
||||||
case error.message.includes("Unable to locate request organization."):
|
|
||||||
return "We couldn't find the organization you're asking for ";
|
|
||||||
|
|
||||||
case error.message.includes("Error filtering prompts."):
|
|
||||||
return "Something isn't quite right with your prompts";
|
|
||||||
|
|
||||||
case error.message.includes("Invalid prompts detected"):
|
|
||||||
return "Something isn't quite right with your prompts";
|
|
||||||
|
|
||||||
case error.message.includes("no prompts provided"):
|
|
||||||
return "You have to provide at least one prompt";
|
|
||||||
|
|
||||||
case error.message.includes(
|
|
||||||
"Unable to create a new billing ticket for this request."
|
|
||||||
):
|
|
||||||
return "We couldn't create a charge for some reason";
|
|
||||||
|
|
||||||
case error.message.includes("cannot use empty Id for project file"):
|
|
||||||
return "You have to specify a project";
|
|
||||||
|
|
||||||
case error.message.includes("Incorrect time range provided."):
|
|
||||||
return "Something was wrong with the times you provided";
|
|
||||||
|
|
||||||
case error.message.includes("must provide image parameters"):
|
|
||||||
return "Something isn't right with the image you provided";
|
|
||||||
|
|
||||||
case error.message.includes("cannot create project with deleted status"):
|
|
||||||
return "That project was already deleted";
|
|
||||||
|
|
||||||
case error.message.includes("Incorrect amount value."):
|
|
||||||
return "You can't charge less than $0.00";
|
|
||||||
|
|
||||||
case error.message.includes('API key with ID "'):
|
|
||||||
return "We couldn't find the API key you're asking for";
|
|
||||||
|
|
||||||
case error.message.includes('Unable to find organization with ID "'):
|
|
||||||
return "We couldn't find the organization you're asking for";
|
|
||||||
|
|
||||||
case error.message.includes("No auto-charge intent."):
|
|
||||||
return "You aren't set up for auto-charging";
|
|
||||||
|
|
||||||
case error.message.includes(
|
|
||||||
"You do not have permission to access this resource."
|
|
||||||
):
|
|
||||||
return "You don't the right permissions";
|
|
||||||
|
|
||||||
case error.message.includes("EmailNotVerifiedMessage"):
|
|
||||||
return "You still need to verify your email address";
|
|
||||||
|
|
||||||
case error.message.includes(
|
|
||||||
"user is not a member of the requested organization"
|
|
||||||
):
|
|
||||||
return "You aren't a member of that organization";
|
|
||||||
|
|
||||||
case error.message.includes(
|
|
||||||
"You have insufficient privileges to access this resource."
|
|
||||||
):
|
|
||||||
return "You don't have the right permissions";
|
|
||||||
|
|
||||||
case error.message.includes("You have too many API keys."):
|
|
||||||
return "There are too many API keys";
|
|
||||||
|
|
||||||
case error.message.includes("Unable to set default organization."):
|
|
||||||
return "We couldn't set your default organization";
|
|
||||||
|
|
||||||
case error.message.includes("Unable to update client settings."):
|
|
||||||
return "We couldn't update your settings";
|
|
||||||
|
|
||||||
case error.message.includes(
|
|
||||||
"Unable to connect to the prompt filter requested"
|
|
||||||
):
|
|
||||||
return "We couldn't properly filter your prompt";
|
|
||||||
|
|
||||||
case error.message.includes(
|
|
||||||
"Unable to generate cost env for this request."
|
|
||||||
):
|
|
||||||
return "We couldn't generate a cost estimate for your request";
|
|
||||||
|
|
||||||
case error.message.includes("Unable to connect to the engine requested"):
|
|
||||||
return "Something's wrong with the model your're trying to use";
|
|
||||||
|
|
||||||
case error.message.includes(
|
|
||||||
"Unable to connect to the classifier requested"
|
|
||||||
):
|
|
||||||
return "We couldn't use the classifier you're asking for";
|
|
||||||
|
|
||||||
case error.message.includes("An unexpected server error occurred."):
|
|
||||||
return defaultMessage;
|
|
||||||
|
|
||||||
case error.message.includes("Unable to create a new API key."):
|
|
||||||
return "We couldn't create an API key for some reason";
|
|
||||||
|
|
||||||
case error.message.includes("Unable to delete the API key."):
|
|
||||||
return "We couldn't delete the API key for some reason";
|
|
||||||
|
|
||||||
case error.message.includes("Unable to create an auto-charge intent:"):
|
|
||||||
return "We couldn't set up auto-charge for some reason";
|
|
||||||
|
|
||||||
case error.message.includes("Unable to get auto-charge intent."):
|
|
||||||
return "We couldn't get your auto-charge settings";
|
|
||||||
|
|
||||||
case error.message.includes(
|
|
||||||
"Unable to create a checkout session for this charge."
|
|
||||||
):
|
|
||||||
return "We couldn't create a charge for some reason";
|
|
||||||
|
|
||||||
case error.message.includes("Unable to get charges."):
|
|
||||||
return "We couldn't get your charges";
|
|
||||||
|
|
||||||
case error.message.includes("Unable to delete account, contact support"):
|
|
||||||
return "We couldn't delete your account, please contact support";
|
|
||||||
|
|
||||||
case error.message.includes("image dimensions must be multiples of 64"):
|
|
||||||
return "We somehow sent an image with the wrong dimensions";
|
|
||||||
|
|
||||||
default:
|
|
||||||
return defaultMessage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isBannedTermError(error: RpcError): boolean {
|
|
||||||
return error.code === "BANNED_TERM";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isOutOfCreditsError(error: RpcError): boolean {
|
|
||||||
return error.message.includes("does not have enough balance");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ export function Images({ className }: Images.Props) {
|
|||||||
style={{ height: virtualizer.getTotalSize() }}
|
style={{ height: virtualizer.getTotalSize() }}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="absolute top-0 left-0 w-full"
|
className="absolute left-0 top-0 w-full"
|
||||||
style={{
|
style={{
|
||||||
transform: `translateY(${
|
transform: `translateY(${
|
||||||
(virtualItems[0]?.start ?? 0) - virtualizer.options.scrollMargin
|
(virtualItems[0]?.start ?? 0) - virtualizer.options.scrollMargin
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { StableDiffusionInput } from "@stability/stablestudio-plugin";
|
|||||||
|
|
||||||
import { Generation } from "~/Generation";
|
import { Generation } from "~/Generation";
|
||||||
import { GlobalState } from "~/GlobalState";
|
import { GlobalState } from "~/GlobalState";
|
||||||
import { Plugin } from "~/Plugin";
|
|
||||||
|
|
||||||
import { Image } from "./Image";
|
import { Image } from "./Image";
|
||||||
import { Inputs } from "./Inputs";
|
import { Inputs } from "./Inputs";
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useLocalStorage } from "react-use";
|
||||||
import { Generation } from "~/Generation";
|
import { Generation } from "~/Generation";
|
||||||
import { Theme } from "~/Theme";
|
import { Theme } from "~/Theme";
|
||||||
|
|
||||||
@@ -5,14 +6,27 @@ export function Dropdown({ id, className }: Styleable & { id: ID }) {
|
|||||||
const { setInput, input } = Generation.Image.Input.use(id);
|
const { setInput, input } = Generation.Image.Input.use(id);
|
||||||
const { data: models, isLoading } = Generation.Image.Models.use();
|
const { data: models, isLoading } = Generation.Image.Models.use();
|
||||||
|
|
||||||
|
const [value, setValue] = useLocalStorage<string | undefined>(
|
||||||
|
"default-model-id",
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
if (value) {
|
||||||
|
setInput((input) => {
|
||||||
|
input.model = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onClick = useCallback(
|
const onClick = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
setInput((input) => {
|
setInput((input) => {
|
||||||
console.log("model", value);
|
console.log("model", value);
|
||||||
input.model = value;
|
input.model = value;
|
||||||
|
setValue(value);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[setInput]
|
[setInput, setValue]
|
||||||
);
|
);
|
||||||
|
|
||||||
const options = useMemo(
|
const options = useMemo(
|
||||||
|
|||||||
@@ -53,8 +53,11 @@ export namespace StableDiffusionV1 {
|
|||||||
{ text: "", weight: -0.75 },
|
{ text: "", weight: -0.75 },
|
||||||
],
|
],
|
||||||
|
|
||||||
model: pluginDefaultInput?.model ?? "stable-diffusion-xl-beta-v2-2-2",
|
model: pluginDefaultInput?.model ?? "v2-1_768-ema-pruned.safetensors",
|
||||||
sampler: pluginDefaultInput?.sampler ?? { id: "0", name: "DDIM" },
|
sampler: pluginDefaultInput?.sampler ?? {
|
||||||
|
id: "dpmpp_sde",
|
||||||
|
name: "DPMPP SDE",
|
||||||
|
},
|
||||||
height: pluginDefaultInput?.width ?? 512,
|
height: pluginDefaultInput?.width ?? 512,
|
||||||
width: pluginDefaultInput?.height ?? 512,
|
width: pluginDefaultInput?.height ?? 512,
|
||||||
steps: pluginDefaultInput?.steps ?? 50,
|
steps: pluginDefaultInput?.steps ?? 50,
|
||||||
@@ -90,14 +93,7 @@ export namespace StableDiffusionV1 {
|
|||||||
export const baseResolution = (model: string) => {
|
export const baseResolution = (model: string) => {
|
||||||
if (model.includes("512")) return 512;
|
if (model.includes("512")) return 512;
|
||||||
if (model.includes("768")) return 768;
|
if (model.includes("768")) return 768;
|
||||||
|
if (model.includes("1024") || model.includes("sd_xl")) return 1024;
|
||||||
switch (model) {
|
|
||||||
case "stable-diffusion-v1-5":
|
|
||||||
return 512;
|
|
||||||
case "stable-diffusion-v1-4":
|
|
||||||
return 512;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 512;
|
return 512;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ export type State = {
|
|||||||
|
|
||||||
requested: (
|
requested: (
|
||||||
inputID: ID,
|
inputID: ID,
|
||||||
modifiers?: Generation.Image.Input.Modifiers
|
modifiers?: Generation.Image.Input.Modifiers,
|
||||||
|
nextID?: ID
|
||||||
) => Generation.Image.Output;
|
) => Generation.Image.Output;
|
||||||
|
|
||||||
received: (
|
received: (
|
||||||
@@ -37,8 +38,8 @@ export namespace State {
|
|||||||
|
|
||||||
nextID: ID.create(),
|
nextID: ID.create(),
|
||||||
|
|
||||||
requested: (inputID, modifiers) => {
|
requested: (inputID, modifiers, nextID) => {
|
||||||
const id = get().nextID;
|
const id = nextID ?? get().nextID;
|
||||||
const output = {
|
const output = {
|
||||||
id,
|
id,
|
||||||
inputID,
|
inputID,
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ export type Output = {
|
|||||||
|
|
||||||
requestedAt?: Date;
|
requestedAt?: Date;
|
||||||
completedAt?: Date;
|
completedAt?: Date;
|
||||||
|
progress?: number;
|
||||||
|
|
||||||
count: number;
|
count: number;
|
||||||
imageIDs: ID[];
|
imageIDs: ID[];
|
||||||
|
progressImageIDs?: ID[];
|
||||||
|
|
||||||
exception?: Generation.Image.Exception;
|
exception?: Generation.Image.Exception;
|
||||||
};
|
};
|
||||||
@@ -61,6 +63,8 @@ export function Output({ outputID, placeholder, divider }: Props) {
|
|||||||
key={keys("image", images.length, images.length - index)}
|
key={keys("image", images.length, images.length - index)}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
image={image}
|
image={image}
|
||||||
|
outputID={output?.id}
|
||||||
|
progress={output?.progress}
|
||||||
scale={1}
|
scale={1}
|
||||||
example={
|
example={
|
||||||
Generation.Image.Prompt.Examples.images[
|
Generation.Image.Prompt.Examples.images[
|
||||||
@@ -76,7 +80,14 @@ export function Output({ outputID, placeholder, divider }: Props) {
|
|||||||
{rendered}
|
{rendered}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}, [count, images, input?.id, placeholder, exampleStartIndex]);
|
}, [
|
||||||
|
count,
|
||||||
|
images,
|
||||||
|
input?.id,
|
||||||
|
placeholder,
|
||||||
|
output?.progress,
|
||||||
|
exampleStartIndex,
|
||||||
|
]);
|
||||||
|
|
||||||
const controls = useMemo(
|
const controls = useMemo(
|
||||||
() => (
|
() => (
|
||||||
|
|||||||