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. SOFTWARE.
[1]: http://eligrey.com [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", "pycloudflared": "0.2.0",
"ruamel.yaml": "0.17.21", "ruamel.yaml": "0.17.21",
"sqlalchemy": "2.0.19", "sqlalchemy": "2.0.19",
"python-multipart": "0.0.6",
# "xformers": "0.0.16", # "xformers": "0.0.16",
} }
modules_to_log = ["torch", "torchvision", "sdkit", "stable-diffusion-sdkit"] 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 typing import List
from fastapi import Depends, FastAPI, HTTPException from fastapi import Depends, FastAPI, HTTPException, Response, File
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from easydiffusion import bucket_crud, bucket_models, bucket_schemas from easydiffusion.easydb import crud, models, schemas
from easydiffusion.bucket_database import SessionLocal, engine 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(): def init():
from easydiffusion.server import server_api from easydiffusion.server import server_api
bucket_models.BucketBase.metadata.create_all(bind=engine) models.BucketBase.metadata.create_all(bind=engine)
# Dependency # Dependency
@ -21,37 +41,63 @@ def init():
finally: finally:
db.close() 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) if filename==None:
def create_bucket(bucket: bucket_schemas.BucketCreate, db: Session = Depends(get_db)): bucket = crud.get_bucket_by_path(db, path=path)
db_bucket = bucket_crud.get_bucket_by_path(db, path=bucket.path) if bucket == None:
if db_bucket: raise HTTPException(status_code=404, detail="Bucket not found")
raise HTTPException(status_code=400, detail="Bucket already exists") bucketfiles = db.query(models.BucketFile).with_entities(models.BucketFile.filename).filter(models.BucketFile.bucket_id == bucket.id).all()
return bucket_crud.create_bucket(db=db, bucket=bucket) bucketfiles = [ x.filename for x in bucketfiles ]
return bucketfiles
@server_api.get("/buckets/", response_model=List[bucket_schemas.Bucket]) else:
def read_bucket(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): bucket_id = crud.get_bucket_by_path(db, path).id
buckets = bucket_crud.get_buckets(db, skip=skip, limit=limit) bucketfile = db.query(models.BucketFile).filter(models.BucketFile.bucket_id == bucket_id, models.BucketFile.filename == filename).first()
return buckets
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) @server_api.post("/buckets/{bucket_id}/items/", response_model=schemas.BucketFile)
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)
def create_bucketfile_in_bucket( 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 get_filename_from_url(url):
def read_bucketfiles(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): path = urlparse(url).path
bucketfiles = bucket_crud.get_bucketfiles(db, skip=skip, limit=limit) name = path[path.rfind('/')+1:]
return bucketfiles 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 import Boolean, Column, ForeignKey, Integer, String, BLOB
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from easydiffusion.bucket_database import BucketBase from easydiffusion.easydb.database import BucketBase
class Bucket(BucketBase): class Bucket(BucketBase):
@ -16,10 +16,10 @@ class Bucket(BucketBase):
class BucketFile(BucketBase): class BucketFile(BucketBase):
__tablename__ = "bucketfile" __tablename__ = "bucketfile"
id = Column(Integer, primary_key=True, index=True) filename = Column(String, index=True, primary_key=True)
filename = Column(String, index=True) bucket_id = Column(Integer, ForeignKey("bucket.id"), primary_key=True)
data = Column(BLOB, index=False) data = Column(BLOB, index=False)
bucket_id = Column(Integer, ForeignKey("bucket.id"))
bucket = relationship("Bucket", back_populates="bucketfiles") bucket = relationship("Bucket", back_populates="bucketfiles")

View File

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

View File

@ -18,12 +18,14 @@
<link rel="stylesheet" href="/media/css/image-modal.css"> <link rel="stylesheet" href="/media/css/image-modal.css">
<link rel="stylesheet" href="/media/css/plugins.css"> <link rel="stylesheet" href="/media/css/plugins.css">
<link rel="stylesheet" href="/media/css/animations.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"> <link rel="manifest" href="/media/manifest.webmanifest">
<script src="/media/js/jquery-3.6.1.min.js"></script> <script src="/media/js/jquery-3.6.1.min.js"></script>
<script src="/media/js/jquery-confirm.min.js"></script> <script src="/media/js/jquery-confirm.min.js"></script>
<script src="/media/js/jszip.min.js"></script> <script src="/media/js/jszip.min.js"></script>
<script src="/media/js/FileSaver.min.js"></script> <script src="/media/js/FileSaver.min.js"></script>
<script src="/media/js/marked.min.js"></script> <script src="/media/js/marked.min.js"></script>
<script src="/media/js/croppr.js"></script>
</head> </head>
<body> <body>
<div id="container"> <div id="container">
@ -630,6 +632,35 @@
</div> </div>
</dialog> </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 id="image-editor" class="popup image-editor-popup">
<div> <div>
<i class="close-button fa-solid fa-xmark"></i> <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; 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 { .diffusers-disabled-on-startup .diffusers-restart-needed {
font-size: 0; 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 saveAllJSONToggle = document.querySelector("#json_toggle")
let saveAllFoldersOption = document.querySelector("#download-add-folders") let saveAllFoldersOption = document.querySelector("#download-add-folders")
let splashScreenPopup = document.querySelector("#splash-screen") 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") let maskSetting = document.querySelector("#enable_mask")
@ -529,6 +531,7 @@ function showImages(reqBody, res, outputContainer, livePreview) {
{ text: "Upscale", on_click: onUpscaleClick }, { text: "Upscale", on_click: onUpscaleClick },
{ text: "Fix Faces", on_click: onFixFacesClick }, { text: "Fix Faces", on_click: onFixFacesClick },
], ],
{ text: "Use as Thumbnail", on_click: onUseAsThumbnailClick },
] ]
// include the plugins // include the plugins
@ -669,6 +672,25 @@ function onMakeSimilarClick(req, img) {
createTask(newTaskRequest) 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) { function enqueueImageVariationTask(req, img, reqDiff) {
const imageSeed = img.getAttribute("data-seed") const imageSeed = img.getAttribute("data-seed")
@ -2356,19 +2378,33 @@ document.getElementById("toggle-tensorrt-install").addEventListener("click", fun
/* Embeddings */ /* Embeddings */
let icl = []
function updateEmbeddingsList(filter = "") { function updateEmbeddingsList(filter = "") {
function html(model, prefix = "", filter = "") { function html(model, iconlist = [], prefix = "", filter = "") {
filter = filter.toLowerCase() filter = filter.toLowerCase()
let toplevel = "" let toplevel = ""
let folders = "" 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) => { model?.forEach((m) => {
if (typeof m == "string") { if (typeof m == "string") {
if (m.toLowerCase().search(filter) != -1) { let token=m.toLowerCase()
toplevel += `<button data-embedding="${m}">${m}</button> ` 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 { } else {
let subdir = html(m[1], prefix + m[0] + "/", filter) let subdir = html(m[1], iconlist, prefix + m[0] + "/", filter)
if (subdir != "") { if (subdir != "") {
folders += folders +=
`<div class="embedding-category"><h4 class="collapsible">${prefix}${m[0]}</h4><div class="collapsible-content">` + `<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) { 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") const insertIntoNegative = e.shiftKey || positiveEmbeddingText.classList.contains("displayNone")
if (embeddingsModeField.value == "insert") { if (embeddingsModeField.value == "insert") {
@ -2416,14 +2452,18 @@ function updateEmbeddingsList(filter = "") {
} }
// END of remove block // END of remove block
embeddingsList.innerHTML = warning + html(modelsOptions.embeddings, "", filter) fetch("/bucket/embeddings/")
embeddingsList.querySelectorAll("button").forEach((b) => { .then(response => response.status==200 ? response.json(): [])
b.addEventListener("click", onButtonClick) .then(iconlist => {
}) embeddingsList.innerHTML = warning + html(modelsOptions.embeddings, iconlist, "", filter)
createCollapsibles(embeddingsList) embeddingsList.querySelectorAll("button").forEach((b) => {
if (filter != "") { b.addEventListener("click", onButtonClick)
embeddingsExpandAll() })
} createCollapsibles(embeddingsList)
if (filter != "") {
embeddingsExpandAll()
}
})
} }
function showEmbeddingDialog() { function showEmbeddingDialog() {