Added implementation of the Concepts Library tab made by @Qualzz.

Added implementation of the Concepts Library tab made by @Qualzz.
This commit is contained in:
ZeroCool 2022-09-20 21:08:40 -07:00 committed by GitHub
commit c66e1a36e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 794 additions and 114 deletions

View File

@ -108,4 +108,10 @@ h1 {
}
div.gallery:hover {
border: 1px solid #777;
}
.css-dg4u6x p {
font-size: 0.8rem;
text-align: center;
position: relative;
top: 6px;
}

11
frontend/index.html Normal file
View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Component Template</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

34
frontend/package.json Normal file
View File

@ -0,0 +1,34 @@
{
"name": "@codedealer/streamlit_template_vue",
"version": "0.1.0",
"description": "Starter template to build Streamlit component with vue 3",
"license": "MIT",
"author": "Thomas Mello",
"repository": {
"type": "git",
"url": "https://github.com/codedealer/streamlit-component-template-vue"
},
"scripts": {
"clean:dist": "rimraf dist",
"dev": "vite --mode dev",
"build": "yarn clean:dist && vite build --mode prod",
"preview": "vite preview",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
},
"dependencies": {
"vue": "^3.2.38",
"streamlit-component-lib": "^1.4.0"
},
"devDependencies": {
"@antfu/eslint-config": "^0.18.9",
"@types/node": "^17.0.21",
"@vitejs/plugin-vue": "^2.2.0",
"eslint": "^8.11.0",
"rimraf": "^3.0.2",
"typescript": "^4.5.4",
"vite": "^2.8.0",
"vue": "^3.2.38",
"vue-tsc": "^0.29.8"
}
}

243
frontend/src/Component.vue Normal file
View File

@ -0,0 +1,243 @@
<template>
<div class="bootstrap-wrapper pt-4">
<div class="concept-gallery row">
<div v-for="concept in args.concepts"
:key="concept.name"
class="col-12 col-sm-6 col-md-4 col-lg-4 col-xl-3">
<div class="concept-card p-4 container-fluid">
<div class="concept-card-content-wrapper">
<div class="card-header row no-gutters">
<div class="col">
<h1 class="concept-title pl-1"><span class="token-char pr-0">&lt;</span>{{ concept.name }}<span class="pl-0 token-char">&gt;</span></h1>
</div>
<!-- Favorite feature, not implemented yet -->
<!-- <div class="col-auto card-favorite" >
<img width="24"
height="24"
class="icon-star"
src="./icons/star.svg" />
</div> -->
</div>
<div class="concept-img-wrapper p-0 row no-gutters">
<div v-for="(img, img_index) in concept.images"
:key="'concept_img'+img_index"
:class="{
'p-1': true,
'col-6': concept.images.length % 2 == 0 || img_index < concept.images.length - 1,
'col-12': concept.images.length % 2 == 1 && img_index == concept.images.length - 1
}">
<div class="img-bg" :style="{'background-image': 'url(data:image/png;base64,' + img + ')'}"></div>
</div>
<div v-if="concept.images.length == 0"
class="col-12 p-4 no-preview">
<svg class="no-preview-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M 2 5 L 2 27 L 30 27 L 30 5 Z M 4 7 L 28 7 L 28 20.90625 L 22.71875 15.59375 L 22 14.875 L 17.46875 19.40625 L 11.71875 13.59375 L 11 12.875 L 4 19.875 Z M 24 9 C 22.894531 9 22 9.894531 22 11 C 22 12.105469 22.894531 13 24 13 C 25.105469 13 26 12.105469 26 11 C 26 9.894531 25.105469 9 24 9 Z M 11 15.71875 L 20.1875 25 L 4 25 L 4 22.71875 Z M 22 17.71875 L 28 23.71875 L 28 25 L 23.03125 25 L 18.875 20.8125 Z"/></svg>
<p style="opacity: 0.8">No preview available</p>
</div>
</div>
<div class="concept-card-footer row no-gutters pt-4">
<div class="col pl-1">
<div v-if="concept.type"
:class="{
'concept-type-tag': true,
'concept-type-style': concept.type.toLowerCase() === 'style',
'concept-type-object': concept.type.toLowerCase() === 'object'
}
">
{{ concept.type.toUpperCase() }}
</div>
</div>
<div class="col-auto">
<!-- Copy to clipboard button -->
<button class="button"
@click="copyToClipboard(concept.token)">
<!-- <svg class="icon-clipboard" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" ><path d="M 15 3 C 13.742188 3 12.847656 3.890625 12.40625 5 L 5 5 L 5 28 L 13 28 L 13 30 L 27 30 L 27 14 L 25 14 L 25 5 L 17.59375 5 C 17.152344 3.890625 16.257813 3 15 3 Z M 15 5 C 15.554688 5 16 5.445313 16 6 L 16 7 L 19 7 L 19 9 L 11 9 L 11 7 L 14 7 L 14 6 C 14 5.445313 14.445313 5 15 5 Z M 7 7 L 9 7 L 9 11 L 21 11 L 21 7 L 23 7 L 23 14 L 13 14 L 13 26 L 7 26 Z M 15 16 L 25 16 L 25 28 L 15 28 Z"/></svg> -->
Copy to clipboard
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue"
import { Streamlit, Theme } from "streamlit-component-lib"
import { useStreamlit } from "./streamlit"
interface IProps {
args: any;
disabled: boolean;
theme: Theme;
}
useStreamlit(); // lifecycle hooks for automatic Streamlit resize
const props = defineProps<IProps>();
const copyToClipboard = (text: string) => {
console.log("sending copy to clipboard event", text)
// Streamlit.setComponentValue({
// action: "copy_to_clipboard",
// value: text
// })
// copy to clipboard
navigator.clipboard.writeText(text)
}
// const enable_favorites = ref(false)
// const enable_copy_to_clipboard = ref(false)
</script>
<style>
/* svg.icon-clipboard {
fill: var(--text-color);
width: 18px;
height: 18px;
} */
svg.no-preview-icon {
fill: var(--text-color);
width: 72px;
height: 72px;
opacity: 0.2;
}
.no-preview {
align-self: center;
text-align: center;
color: var(--text-color);
}
.concept-card {
background-color: var(--secondary-background-color);
border-radius: 5px;
margin-bottom: 20px;
}
.concept-card-content-wrapper {
flex-direction: column !important;
display: flex !important;
height: 360px;
}
.concept-title {
margin-top: 0px;
margin-bottom: 24px;
font-size: 1em;
color: var(--text-color);
}
.concept-img-wrapper {
flex-grow: 1 !important;
}
.card-favorite {
text-align: end;
}
.concept-img {
max-height: 100%;
height: 100%;
}
.concept-img img {
border-radius: 8px;
object-fit: cover;
}
.img-bg {
background-size: cover;
background-position: center;
background-origin: content-box;
background-repeat: no-repeat;
height: 100%;
width: 100%;
border-radius: 8px;
}
.icon-star {
cursor: pointer;
position: relative;
top: -3px;
}
.token-char {
color: #939393;
font-weight: 700;
position: relative;
top: 1px;
}
.concept-card-footer {
align-items: center;
}
.concept-type-tag {
background-color: #898989;
border-radius: 16px;
padding: 5px 16px;
font-size: 0.7em;
color: #fff;
display: inline-block;
font-weight: bold;
}
.concept-type-style {
background-color: #0095ff;
}
.concept-type-object {
background-color: #ff9031;
}
.button {
height: 35px;
cursor: pointer;
display: inline-flex;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
font-weight: 400;
padding: 0.25rem 0.75rem;
border-radius: 0.25rem;
margin: 0px;
line-height: 1.6;
color: inherit;
width: auto;
user-select: none;
background-color: var(--background-color);
border: 1px solid rgba(128, 128, 128, 0.8);
}
.button:hover {
color: var(--primary-color);
border-color: var(--primary-color);
}
/* .button:focus {
box-shadow: rgb(var(--primary-color) / 50%) 0px 0px 0px 0.2rem;
outline: none;
}
.button:focus:not(:active) {
border-color: var(--primary-color);
color: var(--primary-color);
} */
</style>

52
frontend/src/app.vue Normal file

File diff suppressed because one or more lines are too long

8
frontend/src/env.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="#fff"><path d="M 16 2 C 14.742188 2 13.847656 2.890625 13.40625 4 L 5 4 L 5 29 L 27 29 L 27 4 L 18.59375 4 C 18.152344 2.890625 17.257813 2 16 2 Z M 16 4 C 16.554688 4 17 4.445313 17 5 L 17 6 L 20 6 L 20 8 L 12 8 L 12 6 L 15 6 L 15 5 C 15 4.445313 15.445313 4 16 4 Z M 7 6 L 10 6 L 10 10 L 22 10 L 22 6 L 25 6 L 25 27 L 7 27 Z M 21.28125 13.28125 L 15 19.5625 L 11.71875 16.28125 L 10.28125 17.71875 L 14.28125 21.71875 L 15 22.40625 L 15.71875 21.71875 L 22.71875 14.71875 Z"/></svg>

After

Width:  |  Height:  |  Size: 550 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="#fff"><path d="M 15 3 C 13.742188 3 12.847656 3.890625 12.40625 5 L 5 5 L 5 28 L 13 28 L 13 30 L 27 30 L 27 14 L 25 14 L 25 5 L 17.59375 5 C 17.152344 3.890625 16.257813 3 15 3 Z M 15 5 C 15.554688 5 16 5.445313 16 6 L 16 7 L 19 7 L 19 9 L 11 9 L 11 7 L 14 7 L 14 6 C 14 5.445313 14.445313 5 15 5 Z M 7 7 L 9 7 L 9 11 L 21 11 L 21 7 L 23 7 L 23 14 L 13 14 L 13 26 L 7 26 Z M 15 16 L 25 16 L 25 28 L 15 28 Z"/></svg>

After

Width:  |  Height:  |  Size: 481 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="#3F6078"><path d="M 30.335938 12.546875 L 20.164063 11.472656 L 16 2.132813 L 11.835938 11.472656 L 1.664063 12.546875 L 9.261719 19.394531 L 7.140625 29.398438 L 16 24.289063 L 24.859375 29.398438 L 22.738281 19.394531 Z"/></svg>

After

Width:  |  Height:  |  Size: 296 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="#3F6078"><path d="M 30.335938 12.546875 L 20.164063 11.472656 L 16 2.132813 L 11.835938 11.472656 L 1.664063 12.546875 L 9.261719 19.394531 L 7.140625 29.398438 L 16 24.289063 L 24.859375 29.398438 L 22.738281 19.394531 Z"/></svg>

After

Width:  |  Height:  |  Size: 296 B

4
frontend/src/main.ts Normal file
View File

@ -0,0 +1,4 @@
import { createApp } from 'vue'
import App from './app.vue'
createApp(App).mount('#app')

View File

@ -0,0 +1,27 @@
/**
* Vue.js specific composables
*/
import { onMounted, onUpdated } from "vue"
import { Streamlit } from "streamlit-component-lib"
export function useStreamlit() {
/**
* Optional Streamlit Vue-based setup.
*
* You are not required call this function on your Streamlit
* component. If you decide not to call it, you should implement the
* `onMounted` and `onUpdated` functions in your own component,
* so that your plugin properly resizes.
*/
onMounted((): void => {
// After we're rendered for the first time, tell Streamlit that our height
// has changed.
Streamlit.setFrameHeight()
})
onUpdated((): void => {
// After we're updated, tell Streamlit that our height may have changed.
Streamlit.setFrameHeight()
})
}

View File

@ -0,0 +1,83 @@
<template>
<div>
<!-- Error boundary. If our wrapped component threw an error, display it. -->
<div v-if="componentError !== ''">
<h1 class="err__title">Component Error</h1>
<div class="err__msg">Message: {{ componentError }}</div>
</div>
<!--
Else render the component slot and pass Streamlit event data in `args` props to it.
Don't render until we've gotten our first RENDER_EVENT from Streamlit.
All components get disabled while the app is being re-run, and become re-enabled when the re-run has finished.
-->
<slot
v-else-if="renderData != null"
:args="renderData.args"
:theme="renderData.theme"
:disabled="renderData.disabled"
></slot>
</div>
</template>
<script lang="ts">
import {
ref,
defineComponent,
onMounted,
onUpdated,
onUnmounted,
onErrorCaptured,
} from "vue"
import { Streamlit, RenderData } from "streamlit-component-lib"
export default defineComponent({
name: "WithStreamlitConnection",
setup() {
const renderData = ref<RenderData>((undefined as unknown) as RenderData)
const componentError = ref("")
const onRenderEvent = (event: Event): void => {
const renderEvent = event as CustomEvent<RenderData>
renderData.value = renderEvent.detail
componentError.value = ""
}
// Set up event listeners, and signal to Streamlit that we're ready.
// We won't render the component until we receive the first RENDER_EVENT.
onMounted(() => {
Streamlit.events.addEventListener(Streamlit.RENDER_EVENT, onRenderEvent)
Streamlit.setComponentReady()
})
onUpdated(() => {
// If our slot threw an error, we display it in render(). In this
// case, the slot won't be mounted and therefore won't call
// `setFrameHeight` on its own. We do it here so that the rendered
// error will be visible.
if (componentError.value != "") {
Streamlit.setFrameHeight()
}
})
onUnmounted(() => {
Streamlit.events.removeEventListener(
Streamlit.RENDER_EVENT,
onRenderEvent
)
})
onErrorCaptured(err => {
componentError.value = String(err)
})
return {
renderData,
componentError,
}
},
})
</script>
<style scoped>
.err__title,
.err__msg {
margin: 0;
}
</style>

View File

@ -0,0 +1,17 @@
/**
* @license
* Copyright 2018-2020 Streamlit Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { useStreamlit } from "./StreamlitVue"

39
frontend/tsconfig.json Normal file
View File

@ -0,0 +1,39 @@
{
"compilerOptions": {
"baseUrl": ".",
"outDir": "dist",
"declaration": false,
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"isolatedModules": true,
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"skipLibCheck": true,
"lib": [
"esnext",
"dom"
],
"paths": {
"@/*": ["src/*"]
},
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
],
"exclude": [
"node_modules",
"dist",
"public"
],
"references": [
{ "path": "./tsconfig.node.json" }
]
}

View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"composite": false,
"module": "esnext",
"moduleResolution": "node"
},
"include": ["vite.config.ts"]
}

25
frontend/vite.config.ts Normal file
View File

@ -0,0 +1,25 @@
import { resolve } from 'path'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig(({ mode }) => {
return {
plugins: [vue()],
base: "",
server: {
port: 3001,
},
/**
* DESC:
* defining aliases
*/
resolve: {
alias: [
{
find: '@',
replacement: resolve(__dirname, './src'),
},
],
},
}
})

80
scripts/merge.py Normal file
View File

@ -0,0 +1,80 @@
import torch
import sys
import tkinter as tk
window = tk.Tk()
window.title(string="Model Merger")
tk.Label(text = "Model Merger",font=("Arial",25)).pack()
tk.Label(text = "GUI by antrobot1234").pack()
frame1 = tk.Frame()
frame2 = tk.Frame()
frame3 = tk.Frame()
frameSlider = tk.Frame()
frameButton = tk.Frame()
tk.Label(frame1,text = "File 1:").pack(side="left")
file1text = tk.Entry(frame1,width=40)
file1text.pack(side="left")
tk.Label(frame2,text = "File 2:").pack(side="left")
file2text = tk.Entry(frame2,width=40)
file2text.pack(side="left")
tk.Label(frame3,text = "File Out:").pack(side="left")
fileOtext = tk.Entry(frame3,width=38)
fileOtext.pack(side="left")
tk.Label(frameSlider,text = "Weight of file 1").pack(side="left")
scale = tk.Scale(frameSlider,from_=0, to=100,orient="horizontal",tickinterval=10,length=450)
scale.pack(side="left")
goButton = tk.Button(frameButton,text="RUN",height=2,width=20,bg="green")
def merge(file1,file2,out,a):
alpha = (a)/100
if not(file1.endswith(".ckpt")):
file1 += ".ckpt"
if not(file2.endswith(".ckpt")):
file2 += ".ckpt"
if not(out.endswith(".ckpt")):
out += ".ckpt"
#Load Models
model_0 = torch.load(file1)
model_1 = torch.load(file2)
theta_0 = model_0['state_dict']
theta_1 = model_1['state_dict']
for key in theta_0.keys():
if 'model' in key and key in theta_1:
theta_0[key] = (alpha) * theta_0[key] + (1-alpha) * theta_1[key]
goButton.config(bg="red",text="RUNNING...\n(STAGE 2)")
window.update()
for key in theta_1.keys():
if 'model' in key and key not in theta_0:
theta_0[key] = theta_1[key]
torch.save(model_0, out)
def handleClick(event):
goButton.config(bg="red",text="RUNNING...\n(STAGE 1)")
window.update()
merge(file1text.get(),file2text.get(),fileOtext.get(),scale.get())
goButton.config(bg="green",text="RUN")
goButton.pack()
goButton.bind("<Button-1>",handleClick)
frame1.pack()
frame2.pack()
frame3.pack()
frameSlider.pack()
frameButton.pack()
window.mainloop()

View File

@ -3,19 +3,18 @@ from webui_streamlit import st
from sd_utils import *
# streamlit imports
import streamlit.components.v1 as components
#other imports
# Temp imports
#from sd_concept_browser import *
# Temp imports
# end of imports
#---------------------------------------------------------------------------------------------------------------
import os
from PIL import Image
try:
# this silences the annoying "Some weights of the model checkpoint were not used when initializing..." message at start.
from transformers import logging
@ -24,126 +23,166 @@ try:
except:
pass
# parent_dir = os.path.dirname(os.path.abspath(__file__))
# build_dir = os.path.join(parent_dir, "frontend/dist")
_component_func = components.declare_component("sd-concepts-browser", "./frontend/dist")
class plugin_info():
plugname = "concept_library"
description = "Concept Library"
displayPriority = 4
def getLatestGeneratedImagesFromPath():
#get the latest images from the generated images folder
#get the path to the generated images folder
generatedImagesPath = os.path.join(os.getcwd(), st.session_state['defaults'].general.sd_concepts_library_folder)
#get all the files from the folders and subfolders
files = []
ext = ('jpeg', 'jpg', "png")
#get the latest 10 images from the output folder without walking the subfolders
for r, d, f in os.walk(generatedImagesPath):
for file in f:
if file.endswith(ext):
files.append(os.path.join(r, file))
#sort the files by date
files.sort(reverse=True, key=os.path.getmtime)
latest = files
latest.reverse()
def sdConceptsBrowser(concepts, key=None):
component_value = _component_func(concepts=concepts, key=key, default="")
return component_value
# reverse the list so the latest images are first and truncate to
# a reasonable number of images, 10 pages worth
return [Image.open(f) for f in latest]
@st.cache(persist=True, allow_output_mutation=True, show_spinner=False, suppress_st_warning=True)
def getConceptsFromPath(page, conceptPerPage, searchText= ""):
#print("getConceptsFromPath", "page:", page, "conceptPerPage:", conceptPerPage, "searchText:", searchText)
# get the path where the concepts are stored
path = os.path.join(os.getcwd(), st.session_state['defaults'].general.sd_concepts_library_folder)
acceptedExtensions = ('jpeg', 'jpg', "png")
concepts = []
# List all folders (concepts) in the path
folders = [f for f in os.listdir(path) if os.path.isdir(os.path.join(path, f))]
filteredFolders = folders
# Filter the folders by the search text
if searchText != "":
filteredFolders = [f for f in folders if searchText.lower() in f.lower()]
conceptIndex = 1
for folder in filteredFolders:
# handle pagination
if conceptIndex > (page * conceptPerPage):
continue
if conceptIndex <= ((page - 1) * conceptPerPage):
conceptIndex += 1
continue
concept = {
"name": folder,
"token": "<" + folder + ">",
"images": [],
"type": ""
}
# type of concept is inside type_of_concept.txt
typePath = os.path.join(path, folder, "type_of_concept.txt")
binFile = os.path.join(path, folder, "learned_embeds.bin")
# Continue if the concept is not valid or the download has failed (no type_of_concept.txt or no binFile)
if not os.path.exists(typePath) or not os.path.exists(binFile):
continue
with open(typePath, "r") as f:
concept["type"] = f.read()
# List all files in the concept/concept_images folder
files = [f for f in os.listdir(os.path.join(path, folder, "concept_images")) if os.path.isfile(os.path.join(path, folder, "concept_images", f))]
# Retrieve only the 4 first images
for file in files[:4]:
if file.endswith(acceptedExtensions):
# Add a copy of the image to avoid file locking
originalImage = Image.open(os.path.join(path, folder, "concept_images", file))
# Maintain the aspect ratio (max 200x200)
resizedImage = originalImage.copy()
resizedImage.thumbnail((200, 200), Image.ANTIALIAS)
#concept["images"].append(resizedImage)
concept["images"].append(imageToBase64(resizedImage))
# Close original image
originalImage.close()
concepts.append(concept)
conceptIndex += 1
# print all concepts name
#print("Results:", [c["name"] for c in concepts])
return concepts
@st.cache(persist=True, allow_output_mutation=True, show_spinner=False, suppress_st_warning=True)
def imageToBase64(image):
import io
import base64
buffered = io.BytesIO()
image.save(buffered, format="PNG")
img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
return img_str
@st.cache(persist=True, allow_output_mutation=True, show_spinner=False, suppress_st_warning=True)
def getTotalNumberOfConcepts(searchText= ""):
# get the path where the concepts are stored
path = os.path.join(os.getcwd(), st.session_state['defaults'].general.sd_concepts_library_folder)
concepts = []
# List all folders (concepts) in the path
folders = [f for f in os.listdir(path) if os.path.isdir(os.path.join(path, f))]
filteredFolders = folders
# Filter the folders by the search text
if searchText != "":
filteredFolders = [f for f in folders if searchText.lower() in f.lower()]
return len(filteredFolders)
def layout():
st.markdown(f"<h1 style='text-align: center; color: white;'>Navigate 300+ Textual-Inversion community trained concepts</h1>", unsafe_allow_html=True)
latestImages = getLatestGeneratedImagesFromPath()
st.session_state['latestImages'] = latestImages
#with history_tab:
##---------------------------------------------------------
## image slideshow test
## Number of entries per screen
#slideshow_N = 9
#slideshow_page_number = 0
#slideshow_last_page = len(latestImages) // slideshow_N
## Add a next button and a previous button
#slideshow_prev, slideshow_image_col , slideshow_next = st.columns([1, 10, 1])
#with slideshow_image_col:
#slideshow_image = st.empty()
#slideshow_image.image(st.session_state['latestImages'][0])
#current_image = 0
#if slideshow_next.button("Next", key=1):
##print (current_image+1)
#current_image = current_image+1
#slideshow_image.image(st.session_state['latestImages'][current_image+1])
#if slideshow_prev.button("Previous", key=0):
##print ([current_image-1])
#current_image = current_image-1
#slideshow_image.image(st.session_state['latestImages'][current_image - 1])
#---------------------------------------------------------
# image gallery
# Number of entries per screen
gallery_N = 9
if not "galleryPage" in st.session_state:
st.session_state["galleryPage"] = 0
gallery_last_page = len(latestImages) // gallery_N
# Add a next button and a previous button
gallery_prev, gallery_refresh, gallery_pagination , gallery_next = st.columns([2, 2, 8, 1])
# the pagination doesnt work for now so its better to disable the buttons.
if gallery_refresh.button("Refresh", key=4):
st.session_state["galleryPage"] = 0
if gallery_next.button("Next", key=3):
if st.session_state["galleryPage"] + 1 > gallery_last_page:
st.session_state["galleryPage"] = 0
else:
st.session_state["galleryPage"] += 1
if gallery_prev.button("Previous", key=2):
if st.session_state["galleryPage"] - 1 < 0:
st.session_state["galleryPage"] = gallery_last_page
else:
st.session_state["galleryPage"] -= 1
#print(st.session_state["galleryPage"])
# Get start and end indices of the next page of the dataframe
gallery_start_idx = st.session_state["galleryPage"] * gallery_N
gallery_end_idx = (1 + st.session_state["galleryPage"]) * gallery_N
#---------------------------------------------------------
# Pagination
page = 1
conceptPerPage = 12
totalNumberOfConcepts = getTotalNumberOfConcepts()
if not "cl_page" in st.session_state:
st.session_state["cl_page"] = page
if not "cl_conceptPerPage" in st.session_state:
st.session_state["cl_conceptPerPage"] = conceptPerPage
#Search for a concept (totalNumberOfConcepts available)
searchInput = st.text_input("","", placeholder= f'Search for a concept ({totalNumberOfConcepts} available)')
if searchInput != "":
st.session_state["cl_page"] = 1
totalNumberOfConcepts = getTotalNumberOfConcepts(searchInput)
placeholder = st.empty()
#populate the 3 images per column
with placeholder.container():
col1, col2, col3 = st.columns(3)
col1_cont = st.container()
col2_cont = st.container()
col3_cont = st.container()
# Init session state
if not "concepts" in st.session_state:
st.session_state['concepts'] = []
#print (len(st.session_state['latestImages']))
images = list(reversed(st.session_state['latestImages']))[gallery_start_idx:(gallery_start_idx+gallery_N)]
# Refresh concepts
st.session_state['concepts'] = getConceptsFromPath(st.session_state["cl_page"], st.session_state["cl_conceptPerPage"], searchInput)
conceptsLenght = len(st.session_state['concepts'])
with col1_cont:
with col1:
[st.image(images[index], caption="") for index in [0, 3, 6] if index < len(images)]
with col2_cont:
with col2:
[st.image(images[index]) for index in [1, 4, 7] if index < len(images)]
with col3_cont:
with col3:
[st.image(images[index]) for index in [2, 5, 8] if index < len(images)]
if (conceptsLenght == 0):
if (searchInput == ""):
st.write("You don't have any concepts in your library ")
# Propose the user to go to "https://github.com/sd-webui/sd-concepts-library"
st.markdown("To add concepts to your library, download some from the [sd-concepts-library](https://github.com/sd-webui/sd-concepts-library) \
repository and save the content of `sd-concepts-library` into ```./models/custom/sd-concepts-library``` or just create your own concept.", unsafe_allow_html=False)
else:
st.write("No concepts found in the library matching your search: " + searchInput)
# print("Number of concept matching the query:", conceptsLenght)
sdConceptsBrowser(st.session_state['concepts'], key="clipboard")
#
# Pagination
last_page = math.ceil(getTotalNumberOfConcepts(searchInput) / st.session_state["cl_conceptPerPage"])
_prev, _per_page ,_next = st.columns([1, 10, 1])
if ("concepts" in st.session_state and len(st.session_state['concepts']) > 0):
# The condition doesnt work, it should be fixed
with _prev:
if st.button("Previous", disabled = st.session_state["cl_page"] == 1):
st.session_state["cl_page"] -= 1
st.session_state['concepts'] = getConceptsFromPath(st.session_state["cl_page"], st.session_state["cl_conceptPerPage"], searchInput)
with _per_page:
st.caption("Page " + str(st.session_state["cl_page"]) + " / " + str(last_page))
with _next:
if st.button("Next", disabled = st.session_state["cl_page"] == last_page):
st.session_state["cl_page"] += 1
st.session_state['concepts'] = getConceptsFromPath(st.session_state["cl_page"], st.session_state["cl_conceptPerPage"], searchInput)
return False