Merge branch 'bucketlite' of github.com:JeLuF/stable-diffusion-ui into bucketlite

This commit is contained in:
JeLuF
2023-08-01 00:00:41 +02:00
14 changed files with 245 additions and 83 deletions

View File

@ -712,3 +712,31 @@ FileSaver.js is licensed under the MIT license:
SOFTWARE.
[1]: http://eligrey.com
croppr.js
=========
https://github.com/jamesssooi/Croppr.js
croppr.js is licensed under the MIT license:
MIT License
Copyright (c) 2017 James Ooi
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.

View File

@ -26,6 +26,7 @@ modules_to_check = {
"pycloudflared": "0.2.0",
"ruamel.yaml": "0.17.21",
"sqlalchemy": "2.0.19",
"python-multipart": "0.0.6",
# "xformers": "0.0.16",
}
modules_to_log = ["torch", "torchvision", "sdkit", "stable-diffusion-sdkit"]

View File

@ -1,36 +0,0 @@
from sqlalchemy.orm import Session
from easydiffusion import bucket_models, bucket_schemas
def get_bucket(db: Session, bucket_id: int):
return db.query(bucket_models.Bucket).filter(bucket_models.Bucket.id == bucket_id).first()
def get_bucket_by_path(db: Session, path: str):
return db.query(bucket_models.Bucket).filter(bucket_models.Bucket.path == path).first()
def get_buckets(db: Session, skip: int = 0, limit: int = 100):
return db.query(bucket_models.Bucket).offset(skip).limit(limit).all()
def create_bucket(db: Session, bucket: bucket_schemas.BucketCreate):
db_bucket = bucket_models.Bucket(path=bucket.path)
db.add(db_bucket)
db.commit()
db.refresh(db_bucket)
return db_bucket
def get_bucketfiles(db: Session, skip: int = 0, limit: int = 100):
return db.query(bucket_models.BucketFile).offset(skip).limit(limit).all()
def create_bucketfile(db: Session, bucketfile: bucket_schemas.BucketFileCreate, bucket_id: int):
db_bucketfile = bucket_models.BucketFile(**bucketfile.dict(), bucket_id=bucket_id)
db.add(db_bucketfile)
db.commit()
db.refresh(db_bucketfile)
return db_bucketfile

View File

@ -1,16 +1,36 @@
from typing import List
from fastapi import Depends, FastAPI, HTTPException
from fastapi import Depends, FastAPI, HTTPException, Response, File
from sqlalchemy.orm import Session
from easydiffusion import bucket_crud, bucket_models, bucket_schemas
from easydiffusion.bucket_database import SessionLocal, engine
from easydiffusion.easydb import crud, models, schemas
from easydiffusion.easydb.database import SessionLocal, engine
from requests.compat import urlparse
import base64, json
MIME_TYPES = {
"jpg": "image/jpeg",
"jpeg": "image/jpeg",
"gif": "image/gif",
"png": "image/png",
"webp": "image/webp",
"js": "text/javascript",
"htm": "text/html",
"html": "text/html",
"css": "text/css",
"json": "application/json",
"mjs": "application/json",
"yaml": "application/yaml",
"svg": "image/svg+xml",
"txt": "text/plain",
}
def init():
from easydiffusion.server import server_api
bucket_models.BucketBase.metadata.create_all(bind=engine)
models.BucketBase.metadata.create_all(bind=engine)
# Dependency
@ -21,37 +41,63 @@ def init():
finally:
db.close()
@server_api.get("/bucket/{obj_path:path}")
def bucket_get_object(obj_path: str, db: Session = Depends(get_db)):
filename = get_filename_from_url(obj_path)
path = get_path_from_url(obj_path)
@server_api.post("/buckets/", response_model=bucket_schemas.Bucket)
def create_bucket(bucket: bucket_schemas.BucketCreate, db: Session = Depends(get_db)):
db_bucket = bucket_crud.get_bucket_by_path(db, path=bucket.path)
if db_bucket:
raise HTTPException(status_code=400, detail="Bucket already exists")
return bucket_crud.create_bucket(db=db, bucket=bucket)
if filename==None:
bucket = crud.get_bucket_by_path(db, path=path)
if bucket == None:
raise HTTPException(status_code=404, detail="Bucket not found")
bucketfiles = db.query(models.BucketFile).with_entities(models.BucketFile.filename).filter(models.BucketFile.bucket_id == bucket.id).all()
bucketfiles = [ x.filename for x in bucketfiles ]
return bucketfiles
@server_api.get("/buckets/", response_model=List[bucket_schemas.Bucket])
def read_bucket(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
buckets = bucket_crud.get_buckets(db, skip=skip, limit=limit)
return buckets
else:
bucket_id = crud.get_bucket_by_path(db, path).id
bucketfile = db.query(models.BucketFile).filter(models.BucketFile.bucket_id == bucket_id, models.BucketFile.filename == filename).first()
suffix = get_suffix_from_filename(filename)
return Response(content=bucketfile.data, media_type=MIME_TYPES.get(suffix, "application/octet-stream"))
@server_api.post("/bucket/{obj_path:path}")
def bucket_post_object(obj_path: str, file: bytes = File(), db: Session = Depends(get_db)):
filename = get_filename_from_url(obj_path)
path = get_path_from_url(obj_path)
bucket = crud.get_bucket_by_path(db, path)
if bucket == None:
bucket_id = crud.create_bucket(db=db, bucket=schemas.BucketCreate(path=path))
else:
bucket_id = bucket.id
bucketfile = schemas.BucketFileCreate(filename=filename, data=file)
result = crud.create_bucketfile(db=db, bucketfile=bucketfile, bucket_id=bucket_id)
result.data = base64.encodestring(result.data)
return result
@server_api.get("/buckets/{bucket_id}", response_model=bucket_schemas.Bucket)
def read_bucket(bucket_id: int, db: Session = Depends(get_db)):
db_bucket = bucket_crud.get_bucket(db, bucket_id=bucket_id)
if db_bucket is None:
raise HTTPException(status_code=404, detail="Bucket not found")
return db_bucket
@server_api.post("/buckets/{bucket_id}/items/", response_model=bucket_schemas.BucketFile)
@server_api.post("/buckets/{bucket_id}/items/", response_model=schemas.BucketFile)
def create_bucketfile_in_bucket(
bucket_id: int, bucketfile: bucket_schemas.BucketFileCreate, db: Session = Depends(get_db)
bucket_id: int, bucketfile: schemas.BucketFileCreate, db: Session = Depends(get_db)
):
return bucket_crud.create_bucketfile(db=db, bucketfile=bucketfile, bucket_id=bucket_id)
bucketfile.data = base64.decodestring(bucketfile.data)
result = crud.create_bucketfile(db=db, bucketfile=bucketfile, bucket_id=bucket_id)
result.data = base64.encodestring(result.data)
return result
@server_api.get("/bucketfiles/", response_model=List[bucket_schemas.BucketFile])
def read_bucketfiles(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
bucketfiles = bucket_crud.get_bucketfiles(db, skip=skip, limit=limit)
return bucketfiles
def get_filename_from_url(url):
path = urlparse(url).path
name = path[path.rfind('/')+1:]
return name or None
def get_path_from_url(url):
path = urlparse(url).path
path = path[0:path.rfind('/')]
return path or None
def get_suffix_from_filename(filename):
return filename[filename.rfind('.')+1:]

View File

@ -0,0 +1,25 @@
from sqlalchemy.orm import Session
from easydiffusion.easydb import models, schemas
def get_bucket_by_path(db: Session, path: str):
return db.query(models.Bucket).filter(models.Bucket.path == path).first()
def create_bucket(db: Session, bucket: schemas.BucketCreate):
db_bucket = models.Bucket(path=bucket.path)
db.add(db_bucket)
db.commit()
db.refresh(db_bucket)
return db_bucket
def create_bucketfile(db: Session, bucketfile: schemas.BucketFileCreate, bucket_id: int):
db_bucketfile = models.BucketFile(**bucketfile.dict(), bucket_id=bucket_id)
db.merge(db_bucketfile)
db.commit()
from pprint import pprint
db_bucketfile = db.query(models.BucketFile).filter(models.BucketFile.bucket_id==bucket_id, models.BucketFile.filename==bucketfile.filename).first()
return db_bucketfile

View File

@ -1,7 +1,7 @@
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, BLOB
from sqlalchemy.orm import relationship
from easydiffusion.bucket_database import BucketBase
from easydiffusion.easydb.database import BucketBase
class Bucket(BucketBase):
@ -16,10 +16,10 @@ class Bucket(BucketBase):
class BucketFile(BucketBase):
__tablename__ = "bucketfile"
id = Column(Integer, primary_key=True, index=True)
filename = Column(String, index=True)
filename = Column(String, index=True, primary_key=True)
bucket_id = Column(Integer, ForeignKey("bucket.id"), primary_key=True)
data = Column(BLOB, index=False)
bucket_id = Column(Integer, ForeignKey("bucket.id"))
bucket = relationship("Bucket", back_populates="bucketfiles")

View File

@ -13,7 +13,6 @@ class BucketFileCreate(BucketFileBase):
class BucketFile(BucketFileBase):
id: int
bucket_id: int
class Config:

View File

@ -18,12 +18,14 @@
<link rel="stylesheet" href="/media/css/image-modal.css">
<link rel="stylesheet" href="/media/css/plugins.css">
<link rel="stylesheet" href="/media/css/animations.css">
<link rel="stylesheet" href="/media/css/croppr.css" rel="stylesheet"/>
<link rel="manifest" href="/media/manifest.webmanifest">
<script src="/media/js/jquery-3.6.1.min.js"></script>
<script src="/media/js/jquery-confirm.min.js"></script>
<script src="/media/js/jszip.min.js"></script>
<script src="/media/js/FileSaver.min.js"></script>
<script src="/media/js/marked.min.js"></script>
<script src="/media/js/croppr.js"></script>
</head>
<body>
<div id="container">
@ -630,6 +632,35 @@
</div>
</dialog>
<dialog id="use-as-thumb-dialog">
<div id="use-as-thumb-dialog-header" class="dialog-header">
<div id="use-as-thumb-dialog-header-left" class="dialog-header-left">
<h4>Use as thumbnail</h4>
<span>Use a pictures as thumbnail for embeddings, LORAs, etc.</span>
</div>
<div id="use-as-thumb-dialog-header-right">
<i id="use-as-thumb-dialog-close-button" class="fa-solid fa-xmark fa-lg"></i>
</div>
</div>
<div>
<div class="use-as-thumb-grid">
<div class="use-as-thumb-preview">
<img id="use-as-thumb-image" src="/media/images/noimg.png" width="512" height="512">
</div>
<div class="use-as-thumb-select">
<h4>Use the thumbnail for &hellip;</h4>
<select size="8" multiple>
<option>Embedding1</option>
</select>
</div>
<div class="use-as-thumb-buttons">
<button class="tertiaryButton">OK</button>
<button class="tertiaryButton">Cancel</button>
</div>
</div>
</div>
</dialog>
<div id="image-editor" class="popup image-editor-popup">
<div>
<i class="close-button fa-solid fa-xmark"></i>

1
ui/media/css/croppr.css Normal file
View File

@ -0,0 +1 @@
.croppr-container *{user-select:none;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;box-sizing:border-box;-webkit-box-sizing:border-box;-moz-box-sizing:border-box}.croppr-container img{vertical-align:middle;max-width:100%}.croppr{position:relative;display:inline-block}.croppr-handle,.croppr-imageClipped,.croppr-overlay,.croppr-region{position:absolute;top:0}.croppr-overlay{background:rgba(0,0,0,.5);right:0;bottom:0;left:0;z-index:1;cursor:crosshair}.croppr-region{border:1px dashed rgba(0,0,0,.5);z-index:3;cursor:move}.croppr-imageClipped{right:0;bottom:0;left:0;z-index:2;pointer-events:none}.croppr-handle{border:1px solid #000;background-color:#fff;width:10px;height:10px;z-index:4}

View File

@ -1741,6 +1741,32 @@ body.wait-pause {
float: right;
}
.use-as-thumb-grid { display: grid;
grid-template-columns: 1fr auto;
grid-template-rows: 1fr auto;
gap: 8px 8px;
grid-auto-flow: row;
grid-template-areas:
"uat-preview uat-select"
"uat-buttons uat-buttons";
}
.use-as-thumb-preview {
justify-self: center;
align-self: center;
grid-area: uat-preview;
}
.use-as-thumb-select {
grid-area: uat-select;
}
.use-as-thumb-buttons {
justify-self: center;
grid-area: uat-buttons;
}
.diffusers-disabled-on-startup .diffusers-restart-needed {
font-size: 0;
}

BIN
ui/media/images/noimg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

1
ui/media/js/croppr.js Executable file

File diff suppressed because one or more lines are too long

View File

@ -164,6 +164,8 @@ let saveAllTreeToggle = document.querySelector("#tree_toggle")
let saveAllJSONToggle = document.querySelector("#json_toggle")
let saveAllFoldersOption = document.querySelector("#download-add-folders")
let splashScreenPopup = document.querySelector("#splash-screen")
let useAsThumbDialog = document.querySelector("#use-as-thumb-dialog")
let useAsThumbImage = document.querySelector("#use-as-thumb-image")
let maskSetting = document.querySelector("#enable_mask")
@ -529,6 +531,7 @@ function showImages(reqBody, res, outputContainer, livePreview) {
{ text: "Upscale", on_click: onUpscaleClick },
{ text: "Fix Faces", on_click: onFixFacesClick },
],
{ text: "Use as Thumbnail", on_click: onUseAsThumbnailClick },
]
// include the plugins
@ -669,6 +672,25 @@ function onMakeSimilarClick(req, img) {
createTask(newTaskRequest)
}
function onUseAsThumbnailClick(req, img) {
console.log(req)
console.log(img)
useAsThumbDialog.showModal()
useAsThumbImage.src = img.src
var croppr = new Croppr('#croppr', { aspectRatio: 1, minSize: [384,384,'px'], startSize: [100, 100, '%'], returnMode:"real" })
// fetch(img.src)
// .then(response => response.blob())
// .then(async function(blob) {
// const formData = new FormData()
// formData.append("file", blob)
// const response = await fetch(`bucket/embeddings/${embedding}.jpg`, { method: 'POST', body: formData });
// console.log(response)
// })
}
function enqueueImageVariationTask(req, img, reqDiff) {
const imageSeed = img.getAttribute("data-seed")
@ -2356,19 +2378,33 @@ document.getElementById("toggle-tensorrt-install").addEventListener("click", fun
/* Embeddings */
let icl = []
function updateEmbeddingsList(filter = "") {
function html(model, prefix = "", filter = "") {
function html(model, iconlist = [], prefix = "", filter = "") {
filter = filter.toLowerCase()
let toplevel = ""
let folders = ""
console.log(iconlist)
let embIcon = Object.assign({}, ...iconlist.map( x=> ({[x.toLowerCase().split('.').slice(0,-1).join('.')]:x})))
console.log(embIcon)
model?.forEach((m) => {
if (typeof m == "string") {
if (m.toLowerCase().search(filter) != -1) {
toplevel += `<button data-embedding="${m}">${m}</button> `
let token=m.toLowerCase()
if (token.search(filter) != -1) {
let img = '/media/images/noimg.png'
if (token in embIcon) {
img = `/bucket/embeddings/${embIcon[token]}`
}
if (iconlist.length==0) {
img=""
} else {
img=`<img src="${img}" height="128" width="128"><br>`
}
toplevel += `<button data-embedding="${m}">${img}${m}</button> `
}
} else {
let subdir = html(m[1], prefix + m[0] + "/", filter)
let subdir = html(m[1], iconlist, prefix + m[0] + "/", filter)
if (subdir != "") {
folders +=
`<div class="embedding-category"><h4 class="collapsible">${prefix}${m[0]}</h4><div class="collapsible-content">` +
@ -2381,7 +2417,7 @@ function updateEmbeddingsList(filter = "") {
}
function onButtonClick(e) {
let text = e.target.dataset["embedding"]
let text = e.target.closest("button").dataset["embedding"]
const insertIntoNegative = e.shiftKey || positiveEmbeddingText.classList.contains("displayNone")
if (embeddingsModeField.value == "insert") {
@ -2416,14 +2452,18 @@ function updateEmbeddingsList(filter = "") {
}
// END of remove block
embeddingsList.innerHTML = warning + html(modelsOptions.embeddings, "", filter)
embeddingsList.querySelectorAll("button").forEach((b) => {
b.addEventListener("click", onButtonClick)
})
createCollapsibles(embeddingsList)
if (filter != "") {
embeddingsExpandAll()
}
fetch("/bucket/embeddings/")
.then(response => response.status==200 ? response.json(): [])
.then(iconlist => {
embeddingsList.innerHTML = warning + html(modelsOptions.embeddings, iconlist, "", filter)
embeddingsList.querySelectorAll("button").forEach((b) => {
b.addEventListener("click", onButtonClick)
})
createCollapsibles(embeddingsList)
if (filter != "") {
embeddingsExpandAll()
}
})
}
function showEmbeddingDialog() {