├── .gitignore ├── FoooXus-Fooocus-Extender.code-workspace ├── configjson.md ├── configpreset.md ├── executable.md ├── foooxus.bat ├── foooxus.py ├── readme.md ├── requirements.txt ├── src ├── api.py ├── console.py ├── device.py ├── models.py ├── sql.py └── utils.py ├── static ├── css │ ├── bootstrap-icons.min.css │ ├── bootstrap.min.css │ ├── extens.css │ ├── fonts │ │ ├── bootstrap-icons.woff │ │ └── bootstrap-icons.woff2 │ ├── jBox.TooltipDark.min.css │ └── jBox.all.min.css ├── fontawesome │ ├── css │ │ ├── all.min.css │ │ └── webfonts │ │ │ ├── fa-brands-400.ttf │ │ │ ├── fa-brands-400.woff2 │ │ │ ├── fa-regular-400.ttf │ │ │ ├── fa-regular-400.woff2 │ │ │ ├── fa-solid-900.ttf │ │ │ ├── fa-solid-900.woff2 │ │ │ ├── fa-v4compatibility.ttf │ │ │ └── fa-v4compatibility.woff2 │ ├── js │ │ └── all.min.js │ └── webfonts │ │ ├── fa-brands-400.ttf │ │ ├── fa-brands-400.woff2 │ │ ├── fa-regular-400.ttf │ │ ├── fa-regular-400.woff2 │ │ ├── fa-solid-900.ttf │ │ ├── fa-solid-900.woff2 │ │ ├── fa-v4compatibility.ttf │ │ └── fa-v4compatibility.woff2 ├── js │ ├── bootstrap.bundle.min.js │ ├── ejs.min.js │ ├── extens.js │ ├── help.js │ ├── jBox.all.min.js │ ├── jquery.min.js │ ├── showdown.min.js │ └── utils.js ├── picto │ ├── favicon.ico │ ├── fooocus.png │ └── foooxus.png └── views │ └── template.ejs └── templates ├── config.tpl ├── index.html └── structure.html /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore 2 | config.json 3 | foooxus.db 4 | exe.bat 5 | foooxus.exe 6 | foooxusExe.py 7 | FoooXus-Fooocus-Extender.code-workspace 8 | 9 | # Python 10 | *.py[cod] 11 | *.pyc 12 | *.pyo 13 | *.pyd 14 | __pycache__ 15 | *.so 16 | *.egg 17 | *.egg-info 18 | dist 19 | build 20 | *.spec 21 | 22 | # Directories 23 | .vscodepython foooxus.py 24 | .idea 25 | __pycache__ 26 | *.egg-info 27 | dist 28 | build 29 | outputs 30 | output 31 | venv 32 | work 33 | 34 | # OS generated files 35 | .DS_Store 36 | .DS_Store? 37 | ._* 38 | .Spotlight-V100 39 | .Trashes 40 | ehthumbs.db 41 | Thumbs.db 42 | 43 | -------------------------------------------------------------------------------- /FoooXus-Fooocus-Extender.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ] 7 | } -------------------------------------------------------------------------------- /configjson.md: -------------------------------------------------------------------------------- 1 | ## How to fill in config.json 2 | `config.json` file is auto generated at first launch with a template. 3 | You must configure it with two important values: 4 | 5 | 1- **fooocus-directory** contains the full path of your Fooocus installation 6 | Note that you MUST double the `\\` in each folder 7 | 8 | 2- **fooocus-address** contains the http address of Fooocus web UI. 9 | By default, the address is "127.0.0.1:7865" and should work 10 | 11 | **You must restart FoooXus app to take into account these file updates** 12 | 13 | The next part of `config.json` file is dedicated to illustration presets. 14 | See [this document to manage your illustration presets](configpreset.md). -------------------------------------------------------------------------------- /configpreset.md: -------------------------------------------------------------------------------- 1 | ## How to manage illustration presets in config.json 2 | 3 | After the first lines of `config.json` file, dedicated to [general configuration](configjson.md), you will find the 3 parts of illustration presets. 4 | First one is the preset arrays of **style-illustrations**. 5 | Each element defines a preset with : 6 | ``` 7 | { 8 | "name": "s1", 9 | "description": "Illustration #1 for all styles, A cat", 10 | "metadata": { 11 | "Base Model": "juggernautXL_v8Rundiffusion.safetensors", 12 | "Prompt": "A cat", 13 | "Seed": "314159" 14 | } 15 | }, 16 | ``` 17 | A preset contains : 18 | - a name 19 | - a description 20 | - a metadata object that describes how the picture must be generated 21 | 22 | Metadata part may contain that parameters : 23 | 24 | ``` 25 | { 26 | "name": "s1", 27 | "description": "Illustration #1 for all styles, A cat", 28 | "metadata": { 29 | "Base Model": "juggernautXL_v8Rundiffusion.safetensors", 30 | "Prompt": "A cat", 31 | "Seed": "314159", 32 | "ADM Guidance": "(1.5, 0.8, 0.3)", 33 | "Sampler": "dpmpp_2m_sde_gpu", 34 | "Scheduler": "karras", 35 | "Performance": "Quality", 36 | "Sharpness": 5, 37 | "Guidance Scale": 2, 38 | "Refiner Model": "juggernautXL_v8Rundiffusion.safetensors", 39 | "Refiner Switch": 0.5, 40 | "Lora 1": "sd_xl_offset_example", 41 | "Weight Lora 1": 1 42 | } 43 | }, 44 | ``` 45 | 46 | Add, edit or remove presets as you like. 47 | 48 | **You must restart FoooXus app to take into account these file updates** 49 | 50 | Models and Loras presets below in the file are similar 51 | -------------------------------------------------------------------------------- /executable.md: -------------------------------------------------------------------------------- 1 | ## How to install the standalone FoooXus executable 2 | 3 | You can download the last release of foooxus.zip. 4 | Because the file contains all the necessary modules, it is very heavy (about 3GB) 5 | 6 | 1 Move it on the folder you like (Desktop, Documents, whatever...) 7 | 8 | 2 Unzip it 9 | 10 | 3 Double-click on `start-foooxus.bat` 11 | 12 | 4 Some folders are created 13 | 14 | 5 A new `config.json` is created 15 | 16 | Fill in the **fooocus-directory** and **fooocus-address** values 17 | 18 | Read this document about [general configuration](configjson.md) for more informations 19 | 20 | 6 re-start `start-foooxus.bat` and open your browser on http://127.0.0.1:7878 to view FoooXus UI 21 | 22 | 23 | Note that the .exe usage is only created to users that are not fluent with terminal commands. 24 | 25 | The `foooxus.exe` can take more than 20 seconds to start. Be patient. 26 | -------------------------------------------------------------------------------- /foooxus.bat: -------------------------------------------------------------------------------- 1 | :: Activate venv, python virtual environment 2 | call venv\Scripts\activate 3 | 4 | :: Launch FoooXus app 5 | python foooxus.py 6 | 7 | pause -------------------------------------------------------------------------------- /foooxus.py: -------------------------------------------------------------------------------- 1 | # Made by toutjavascript.com 2 | # https://github.com/toutjavascript/FoooXus-Fooocus-Extender 3 | 4 | 5 | 6 | import json 7 | import multiprocessing 8 | import os 9 | import html 10 | import sys 11 | import shutil 12 | from gradio_client import Client 13 | from src import api 14 | from src import device 15 | from src import utils 16 | from src import sql 17 | from src import console 18 | from src.models import DeviceInfo, Metadata, Image 19 | from flask import Flask, render_template, request, redirect, url_for, send_from_directory 20 | import logging 21 | 22 | # Constants to identify version and execution modalities 23 | FOOOXUS_RELEASE="1.0.0" 24 | FOOOCUS_MIN_RELEASE="2.5.5" 25 | 26 | FOOOXUS_PYINSTALLER=False 27 | 28 | 29 | 30 | 31 | # Check versions of module versus requirements.txt 32 | def checkVersions(requirements): 33 | versions=utils.checkVersions(requirements) 34 | pythonVersion=utils.getPythonVersion() 35 | OS=utils.getOS() 36 | 37 | require=True 38 | 39 | if FOOOXUS_PYINSTALLER: 40 | console.printBB("You are running the [b]standalone foooxus.exe[/b] on [b]"+OS+"[/b]") 41 | else: 42 | console.printBB("You are running [b]Python V"+pythonVersion+"[/b] on [b]"+OS+"[/b]") 43 | if utils.in_venv(): 44 | console.printBB(" [ok] (venv) is activated[/ok]") 45 | else: 46 | console.printBB(" [error] Carefull, (venv) is not activated. You may experience module version issues[/error]") 47 | console.printBB("FoooXus ckecks installed module versions and compares them to requirements.txt") 48 | 49 | console.printBB(" [b]Modules Requirement Installed version[/b]") 50 | 51 | for module in requirements: 52 | if versions[module]==requirements[module]: 53 | version="[ok]✔ "+versions[module]+"[/ok]" 54 | else: 55 | version="[error]X "+versions[module]+"[/error]" 56 | require=False 57 | 58 | console.printBB(" [b]{:<19}".format(module)+"[/b]{:<18}".format(requirements[module])+version) 59 | 60 | if require: 61 | console.printBB("[ok]All requirements are met. FoooXus should start :)[/ok]") 62 | else: 63 | console.printBB(" [error]One requirement is not met. FoooXus could fail[/error]") 64 | print("") 65 | 66 | versions["OS"]=OS 67 | versions["python"]=pythonVersion 68 | versions["PyInstaller"]=FOOOXUS_PYINSTALLER 69 | 70 | return versions 71 | 72 | 73 | # Load configuration from config.json FoooXus 74 | def loadConfig(): 75 | 76 | requirements=utils.getRequirements("requirements.txt") 77 | 78 | versions=checkVersions(requirements) 79 | 80 | with open('config.json') as config_file: 81 | conf = json.load(config_file) 82 | if "illustrationResizeSizes" not in conf: 83 | conf["illustrationResizeSizes"]=[512, 512] 84 | if "illustrationResizeCompress" not in conf: 85 | conf["illustrationResizeCompress"]=80 86 | 87 | conf["illustrationsFolder"]="outputs/illustrations" 88 | conf["outputsFolder"]="outputs" 89 | conf["versions"]=versions 90 | conf["requirements"]=requirements 91 | conf["FOOOXUS_RELEASE"]=FOOOXUS_RELEASE 92 | conf["FOOOCUS_MIN_RELEASE"]=FOOOCUS_MIN_RELEASE 93 | 94 | # check if outputs folder exists 95 | utils.checkFolder(conf["outputsFolder"]) 96 | utils.checkFolder(conf["outputsFolder"]+"/tmp") 97 | utils.checkFolder(conf["illustrationsFolder"]+"/models") 98 | utils.checkFolder(conf["illustrationsFolder"]+"/styles") 99 | utils.checkFolder(conf["illustrationsFolder"]+"/loras") 100 | 101 | 102 | # clear tmp folder 103 | console.printBB("Cleaning tmp folder") 104 | 105 | utils.clearTmpFolder("outputs/tmp") 106 | 107 | 108 | conf["init"]=True # flag to tell no api call is made yet 109 | 110 | 111 | 112 | # check if database exists 113 | # sql.connect() 114 | # TODO later 115 | 116 | conf["fooocusConfig"]=loadFooocusConfig(conf["fooocus-directory"]) 117 | 118 | if conf["fooocusConfig"]: 119 | conf["messageConfig"]="" 120 | if "loras-directory" in conf: 121 | console.printBB("Loras folder: " + conf["loras-directory"]) 122 | else: 123 | conf["loras-directory"]=conf["fooocusConfig"]["path_loras"] 124 | else: 125 | conf["messageConfig"]="'fooocus-directory' parameter in config.json is not correct.
No Fooocus install found in this folder:
"+html.escape(conf["fooocus-directory"]) 126 | 127 | return conf 128 | 129 | # Load the config.txt Fooocus file 130 | def loadFooocusConfig(dir): 131 | try: 132 | with open(os.path.join(dir, "config.txt")) as config_file: 133 | fooocusConfig = json.load(config_file) 134 | except: 135 | fooocusConfig = False 136 | 137 | return fooocusConfig 138 | 139 | 140 | 141 | 142 | def checkInit(): 143 | # Check if config.json not exists: run initialization 144 | if os.path.exists("./config.json")==False: 145 | console.printBB("[reverse][h1]************************* FIRST INIT of FoooXuS APP V"+FOOOXUS_RELEASE+" *************************[/h1][/reverse][reset]") 146 | console.printBB(" [ok]Thanx for using. Please report issues and ideas on[/ok] ") 147 | console.printBB(" [u]https://github.com/toutjavascript/FoooXus-Fooocus-Extender[/u] ") 148 | 149 | shutil.copy2("./templates/config.tpl", "config.json") 150 | 151 | console.printBB("") 152 | console.printBB("A new config.json has been created. Fill in these values :") 153 | console.printBB('"[b]fooocus-directory[/b]": "[b]C:\\Users\\TJS\\Desktop\\IA\\StabilityMatrix\\Data\\Packages\\Fooocus[/b]"') 154 | console.printBB('"[b]fooocus-address[/b]": "[b]127.0.0.1:7865[/b]"') 155 | console.printBB("") 156 | if FOOOXUS_PYINSTALLER: 157 | console.printBB("Once checked, restart [shell]foooxus.exe[/shell] ") 158 | else: 159 | console.printBB("Once checked, restart [shell]python foooxus.py[/shell] "+("or [shell]foooxus.bat[/shell] here" if utils.is_windows() else "")) 160 | console.printBB("") 161 | 162 | 163 | sys.exit("") 164 | 165 | 166 | # STARTING FOOOXUS APP 167 | if __name__ == '__main__': 168 | try: 169 | multiprocessing.freeze_support() 170 | checkInit() 171 | 172 | console.printBB("[reverse][h1]********************** STARTING FoooXuS APP V"+FOOOXUS_RELEASE+" **********************[/h1][/reverse][reset]") 173 | console.printBB(" [ok]Thanx for using. Please report issues and ideas on[/ok] ") 174 | console.printBB(" [u]https://github.com/toutjavascript/FoooXus-Fooocus-Extender[/u] ") 175 | 176 | conf=loadConfig() 177 | 178 | app = Flask(__name__) 179 | 180 | appPath=utils.getAppPath() 181 | 182 | # Prevent http flask web server logging in terminal 183 | log = logging.getLogger('werkzeug') 184 | log.disabled = True 185 | 186 | 187 | console.printBB("[ok]Now, open FoooXus web UI on [u]http://"+conf["host"]+":"+str(conf["port"])+"[/u][/ok]") 188 | console.printBB("") 189 | 190 | myApi = api.FooocusApi(conf['fooocus-address']+"", "outputs/tmp", FOOOCUS_MIN_RELEASE) 191 | 192 | 193 | @app.route('/') 194 | def home(): 195 | return render_template('index.html', release=FOOOXUS_RELEASE) 196 | 197 | 198 | 199 | @app.route('/ajax/pingFoooxus', methods=['POST']) 200 | def pingFoooxus(): 201 | return {"ajax":True, "ping": True, "config": conf} 202 | 203 | @app.route('/ajax/getDeviceInfo', methods=['POST']) 204 | def getDeviceInfo(): 205 | deviceInfo=device.getDeviceInfo() 206 | return deviceInfo 207 | 208 | 209 | @app.route('/ajax/pingFooocus', methods=['POST']) 210 | def pingFooocus(): 211 | ping=myApi.pingFooocus(conf["init"]) 212 | conf["init"]=False 213 | return ping 214 | 215 | @app.route('/ajax/getModels', methods=['POST']) 216 | def getModels(): 217 | models=myApi.getModels() 218 | return json.dumps(models) 219 | 220 | @app.route('/ajax/getStyles', methods=['POST']) 221 | def getStyles(): 222 | models=myApi.getStyles() 223 | return json.dumps(models) 224 | 225 | @app.route('/ajax/getLoras', methods=['POST']) 226 | def getLoras(): 227 | loras=myApi.getLoras(conf["loras-directory"]) 228 | return json.dumps(loras) 229 | 230 | @app.route('/ajax/getConfig', methods=['POST']) 231 | def getConfig(): 232 | conf["ajax"]=True 233 | return conf 234 | 235 | @app.route('/ajax/sendCancel', methods=['POST']) 236 | def cancel(): 237 | cancel=myApi.sendCancel() 238 | return json.dumps(cancel) 239 | 240 | @app.route('/ajax/getIllustrations', methods=['POST']) 241 | def getIllustrations(): 242 | result={"illustrations":{}} 243 | result["illustrations"]["models"]=utils.getFiles(conf["illustrationsFolder"]+"/models", "jpg") 244 | result["illustrations"]["styles"]=utils.getFiles(conf["illustrationsFolder"]+"/styles", "jpg") 245 | result["illustrations"]["loras"]=utils.getFiles(conf["illustrationsFolder"]+"/loras", "jpg") 246 | return json.dumps(result) 247 | 248 | @app.route('/ajax/generateImage', methods=['POST']) 249 | def generateImage(): 250 | uid = request.form.get('uid', '') 251 | metadata = json.loads(request.form.get('metadata', '')) 252 | result=myApi.sendCreateImage(metadata, uid) 253 | return result 254 | 255 | 256 | 257 | @app.route('/'+conf["illustrationsFolder"]+'/models/', methods=['GET']) 258 | def serveImageModel(filename): 259 | return send_from_directory(os.path.join(appPath, conf["illustrationsFolder"]+"/models/"), filename) 260 | 261 | @app.route('/'+conf["illustrationsFolder"]+'/styles/', methods=['GET']) 262 | def serveImageStyle(filename): 263 | # print(os.path.join(os.path.join(appPath, conf["illustrationsFolder"]+"/styles/"), filename)) 264 | return send_from_directory(os.path.join(appPath, conf["illustrationsFolder"]+"/styles/"), filename) 265 | 266 | @app.route('/'+conf["illustrationsFolder"]+'/loras/', methods=['GET']) 267 | def serveImageLoral(filename): 268 | return send_from_directory(os.path.join(appPath, conf["illustrationsFolder"]+"/loras/"), filename) 269 | 270 | @app.route('/'+conf["outputsFolder"]+'/tmp/', methods=['GET']) 271 | def serveImage(filename): 272 | # print(os.path.join(os.path.join(appPath, conf["outputsFolder"]+"/tmp/"), filename)) 273 | return send_from_directory(os.path.join(appPath, conf["outputsFolder"]+"/tmp/"), filename) 274 | 275 | @app.route('/favicon.ico', methods=['GET']) 276 | def serveFavicon(): 277 | return send_from_directory(os.path.join(appPath, 'static/picto'), 'favicon.ico',mimetype='image/vnd.microsoft.icon') 278 | 279 | 280 | 281 | app.run(host=conf['host'], port=conf['port']) 282 | except SystemExit as e: 283 | print("") 284 | #print("FoooXus emits exception ", e) 285 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # FoooXus: Fooocus Extender 2 | 3 | ## What is it for? 4 | FoooXus uses **Fooocus Gradio API** to extend Fooocus functionnalities : 5 | - Manage your Styles, Models and LoRAs with preset prompts 6 | - Simply add or remove presets in config.json file 7 | - View all your images presets 8 | - Launch image creation works to a queue that is processed in background 9 | - Create generation batches with variations on some parameters (not ready yet) 10 | 11 | ![How it looks](https://github.com/toutjavascript/FoooXus-Fooocus-Extender/assets/30899600/9629f7d0-a710-4e2d-a698-4290d45f71a7) 12 | 13 | ## Prerequisites 14 | You need a running Fooocus instance to use FoooXus. 15 | You must install FoooXus on the same device than Fooocus. 16 | 17 | ## Use the standalone foooxus.exe 18 | NOT AVAILABLE ON FOOOCUS CURRENT RELEASE 19 | [See this document to use foooxus.exe executable](executable.md) 20 | 21 | ## How to install without running the .exe 22 | Natively, FoooXus is a python app server that launches a web UI and connect to Fooocus via API 23 | Install FoooXus following these standard steps. 24 | 25 | ``` 26 | # Get the sources 27 | git clone https://github.com/toutjavascript/FoooXus-Fooocus-Extender.git 28 | cd FoooXus-Fooocus-Extender 29 | 30 | # Activate the virtual python environment 31 | python -m venv venv 32 | venv\Scripts\activate 33 | 34 | # Install requirements 35 | pip install -r requirements.txt 36 | 37 | # First run of the FoooXus app to initialize 38 | python foooxus.py 39 | ``` 40 | 41 | Terminal will show this kind of message 42 | ![Init of app](https://github.com/toutjavascript/FoooXus-Fooocus-Extender/assets/30899600/1c16d3e7-b0af-48cf-920c-2a04c893ef01) 43 | 44 | Fill in the fields in config.json file 45 | 46 | FoooXus is now ready to start with a new call to 47 | ``` 48 | python foooxus.py 49 | ``` 50 | 51 | 52 | ## How to use every day 53 | On Windows, you can double-click foooxus.bat 54 | 55 | Or if you prefer 56 | ``` 57 | # Check you are in your (venv) virtual environment 58 | # if not, activate it 59 | venv\Scripts\activate 60 | 61 | # Run the FoooXus app 62 | python foooxus.py 63 | ``` 64 | 65 | Terminal shows now versions and much more informations to understand what happens 66 | ![FoooXus starts](https://github.com/toutjavascript/FoooXus-Fooocus-Extender/assets/30899600/2eda20a1-3f10-46a9-b5bc-531674226d28) 67 | 68 | Open your browser on http://127.0.0.1:7878 to view FoooXus UI 69 | 70 | ## How to update FoooXus 71 | To update FoooXus from this github repository, open a terminal in your FoooXus-Fooocus-Extender 72 | ``` 73 | git pull 74 | ``` 75 | 76 | FoooXus can now force Performance Settings when those models are used. New button to regenerate Models illustrations are displayed 77 | ![Capture d’écran 2024-03-19 à 15 22 02](https://github.com/toutjavascript/FoooXus-Fooocus-Extender/assets/30899600/65ac30fb-eeb1-4785-bcd2-c19aa1c58524) 78 | 79 | 80 | ## Updates on Fooocus 81 | - Fooocus could be updated at every moment. It could broke the API. 82 | - FoooXus version is tested with V2.4.1 Fooocus Release. 83 | 84 | ## History Log 85 | V1.0.0 Update to V2.5.5 Fooocus Release 86 | 87 | V0.9.7 Update to V2.4.1 Fooocus Release 88 | 89 | V0.9.6 New optional loras-directory parameter on config.json 90 | To fix issue https://github.com/toutjavascript/FoooXus-Fooocus-Extender/issues/7 where Loras are not found, 91 | you can add a new line in config.json. For example: 92 | "loras-directory": "D:\\datas\\StabilityMatrix\\Data\\Models\\Lora", 93 | 94 | V0.9.5 Force optimized Performance Settings when Turbo or Lightning models are used 95 | 96 | V0.9.1 Adapt to Gradio API to Fooocus 2.3.0 97 | Auto clean /outputs/tmp/ folder at launch 98 | 99 | V0.9.0 Adapt to Gradio API to Fooocus 2.2.1 100 | 101 | V0.8.3 Fix issue on macOS https://github.com/toutjavascript/FoooXus-Fooocus-Extender/issues/5 102 | 103 | V0.8.2 Add a button to clear queue 104 | 105 | V0.8.1 Improve the error messages 106 | 107 | V0.8 New standalone fooocus.exe 108 | - new config.json options 109 | - download the fooocus.exe executable file to avoid tedious python, git, pip installations 110 | 111 | V0.6 Updates on install process 112 | - If you have issues, please tell me, with a copy of a terminal 113 | 114 | V0.5 Very First release 115 | - Please, tell me if it is broken somewhere 116 | - Please, share your ideas by opening new discussions 117 | 118 | ## Some functionnalities 119 | Get notified when new image is generated 120 | ![Notifications](https://github.com/toutjavascript/FoooXus-Fooocus-Extender/assets/30899600/96146dca-fd97-4729-b7e4-0fd2699580c6) 121 | 122 | View history 123 | ![Queue and history](https://github.com/toutjavascript/FoooXus-Fooocus-Extender/assets/30899600/e82f3b8b-db2c-41b4-9f21-29fb315960e5) 124 | 125 | Python terminal shows all generations 126 | ![Terminal shows generation log](https://github.com/toutjavascript/FoooXus-Fooocus-Extender/assets/30899600/3d9190f8-e730-4893-8e9f-dda637419778) 127 | 128 | ## Tested and validated on Windows 10, 11 and macOS 129 | FoooXus should work on Linux too 130 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==3.0.2 2 | GPUtil==1.4.0 3 | gradio==3.41.2 4 | gradio_client==0.5.0 5 | Pillow==10.2.0 6 | psutil==5.9.5 7 | py_cpuinfo==9.0.0 8 | -------------------------------------------------------------------------------- /src/api.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import traceback 4 | import timeit 5 | from datetime import datetime 6 | import sys 7 | from src import utils 8 | from src import console 9 | from gradio_client import Client 10 | 11 | 12 | class ConfigApi: 13 | def __init__(self): 14 | self.ping=29 15 | self.cancel=0 16 | self.init=51 17 | self.generate=67 18 | self.models=46 19 | self.styles=64 20 | self.checkDelta=False 21 | self.delta=0 22 | self.deltaPing=0 23 | self.deltaCancel=0 24 | self.deltaInit=0 25 | self.deltaGenerate=0 26 | self.deltaModels=0 27 | self.deltaStyles=0 28 | 29 | 30 | class FooocusApi: 31 | def __init__(self, base_url, outputFolder, FOOOCUS_MIN_RELEASE): 32 | self.FOOOCUS_MIN_RELEASE=FOOOCUS_MIN_RELEASE 33 | self.config=ConfigApi() 34 | self.client=None 35 | self.outputFolder=outputFolder 36 | if base_url[0:7] != "http://"[0:7]: 37 | base_url="http://"+base_url 38 | self.base_url = base_url.strip() 39 | self.emptyImage="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" 40 | 41 | def getClient(self): 42 | if (self.client==None): 43 | # print("Open API client to Fooocus instance at "+self.base_url+" ...") 44 | try: 45 | self.client = Client(self.base_url, serialize=False) 46 | console.printBB("[ok]Great! FoooXus is now connected to Fooocus[/ok]") 47 | return self.client 48 | except Exception as e: 49 | self.client=None 50 | console.printBB("[error]Connection to Fooocus failed...[/error]") 51 | exceptionName=type(e).__name__ 52 | if exceptionName=="ConnectionError": 53 | console.printBB("[error] ConnectionError, check that:[/error]") 54 | console.printBB("[error] - Fooocus must be running, on the same device[/error]") 55 | console.printBB("[error] - Fooocus address config.json is set to "+self.base_url+" [/error]") 56 | else: 57 | console.printBB("[error] "+exceptionName+"[/error]") 58 | 59 | print(f"{e}") 60 | print("") 61 | return None 62 | else: 63 | return self.client 64 | 65 | 66 | # Ping Fooocus instance 67 | def pingFooocus(self, firstCall=False): 68 | try: 69 | client=self.getClient() 70 | if(client is None): 71 | # Not connected, so no call to predict 72 | return {"ajax":True, "error":True} 73 | 74 | result = client.predict( fn_index=self.config.ping+self.config.deltaPing ) 75 | if firstCall: 76 | console.printBB("[ok]FoooXus is connected to Fooocus. Let's play in the web UI ![/ok]") 77 | return {"ajax":True, "error":False, "ping":True, "fooocusUrl": self.base_url} 78 | except Exception as e: 79 | if self.config.deltaPing<=10: 80 | console.printBB("[warning]pingFooocus() failed. Trying to find the right gradio API fn_index ("+str(self.config.ping)+"+"+str(self.config.deltaPing)+")[/warning]") 81 | self.config.deltaPing+=1 82 | #self.config.deltaInit+=1 83 | #self.config.deltaGenerate+=1 84 | #self.config.deltaCancel+=1 85 | #self.config.deltaStyles+=1 86 | self.config.deltaModels+=1 87 | return self.pingFooocus(firstCall) 88 | else: 89 | console.printExceptionError(e) 90 | console.printBB("[error]FoooXus is not connected to Fooocus gradio API :([/error]") 91 | console.printBB("[error] - Fooocus must be running, on the same device[/error]") 92 | console.printBB("[error] - Fooocus must be at least "+self.FOOOCUS_MIN_RELEASE+"[/error]") 93 | console.printBB("[error] - If FoooXus app has already worked, Fooocus may have changed version.[/error]") 94 | console.printBB("[error] it may break API calls[/error]") 95 | console.printBB("") 96 | return {"ajax":True, "error":True} 97 | 98 | # Cancel generation 99 | def sendCancel(self): 100 | try: 101 | console.printBB("[b] /!\ You have canceled the queue[/b]") 102 | console.printBB("[b] [/b]") 103 | result = self.getClient().predict( fn_index=self.config.cancel ) 104 | return {"ajax":True, "error":False} 105 | except Exception as e: 106 | console.printExceptionError(e) 107 | return {"ajax":True, "error":True} 108 | 109 | 110 | # Get list of all models installed on the Fooocus folder 111 | def getModels(self): 112 | try: 113 | result = self.getClient().predict( fn_index=self.config.models+self.config.deltaModels ) 114 | models=[] 115 | if (result[0]): 116 | if (result[0]["choices"]): 117 | for model in result[0]["choices"]: 118 | models.append(model) 119 | return {"ajax":True, "error":False, "models":models} 120 | except Exception as e: 121 | if self.config.deltaModels<10: 122 | console.printBB("[warning]getModels() failed. Trying to find the right gradio API fn_index (i+"+str(self.config.deltaModels)+")[/warning]") 123 | self.config.deltaModels+=1 124 | self.config.deltaInit+=1 125 | self.config.deltaGenerate+=1 126 | return self.getModels() 127 | else: 128 | print("[error] getModels exception[/error]") 129 | print(f"Error: {e}") 130 | return {"ajax":True, "error":True} 131 | 132 | 133 | # Get all styles available for Fooocus 134 | def getStyles(self): 135 | try: 136 | result = self.getClient().predict( [], fn_index=self.config.styles+self.config.deltaStyles ) 137 | styles=[] 138 | if ("choices" in result): 139 | for style in result["choices"]: 140 | if style[0]!="Random Style": 141 | styles.append(style[0]) 142 | return {"ajax":True, "error":False, "styles":styles} 143 | except: 144 | if self.config.deltaStyles<10: 145 | console.printBB("[warning]getStyles() failed. Trying to find the right gradio API fn_index (i+"+str(self.config.deltaStyles)+")[/warning]") 146 | self.config.deltaStyles+=1 147 | self.config.deltaModels+=1 148 | self.config.deltaInit+=1 149 | self.config.deltaGenerate+=1 150 | return self.getStyles() 151 | else: 152 | return {"ajax":True, "error":True} 153 | 154 | # Get all Loras from the loras-directory 155 | def getLoras(self, dir): 156 | try: 157 | loras=utils.getFiles(dir, ".safetensors") 158 | return {"ajax":True, "error":False, "loras":loras} 159 | except: 160 | return {"ajax":True, "error":True} 161 | 162 | 163 | # InitFooocus does not exist since Fooocus V2.2.1. All parameters in CreateImage 164 | def initFooocus(self, metadata={}): 165 | 166 | return False 167 | 168 | try: 169 | adm=metadata.get("ADM Guidance").replace("(","").replace(")","") 170 | admSplit=adm.split(", ") 171 | result = self.getClient().predict( 172 | True, # bool in 'Disable Preview' Checkbox component 173 | float(admSplit[0]), # int | float (numeric value between 0.1 and 3.0) in 'Positive ADM Guidance Scaler' Slider component 174 | float(admSplit[1]), # int | float (numeric value between 0.1 and 3.0) in 'Negative ADM Guidance Scaler' Slider component 175 | float(admSplit[2]), # int | float (numeric value between 0.0 and 1.0) in 'ADM Guidance End At Step' Slider component 176 | 7, # int | float (numeric value between 1.0 and 30.0) in 'CFG Mimicking from TSNR' Slider component 177 | metadata["Sampler"], # str (Option from: ['euler', 'euler_ancestral', 'heun', 'heunpp2', 'dpm_2', 'dpm_2_ancestral', 'lms', 'dpm_fast', 'dpm_adaptive', 'dpmpp_2s_ancestral', 'dpmpp_sde', 'dpmpp_sde_gpu', 'dpmpp_2m', 'dpmpp_2m_sde', 'dpmpp_2m_sde_gpu', 'dpmpp_3m_sde', 'dpmpp_3m_sde_gpu', 'ddpm', 'lcm', 'ddim', 'uni_pc', 'uni_pc_bh2']) in 'Sampler' Dropdown component 178 | metadata["Scheduler"], # str (Option from: ['normal', 'karras', 'exponential', 'sgm_uniform', 'simple', 'ddim_uniform', 'lcm', 'turbo']) in 'Scheduler' Dropdown component 179 | False, # bool in 'Generate Image Grid for Each Batch' Checkbox component 180 | -1, # int | float (numeric value between -1 and 200) in 'Forced Overwrite of Sampling Step' Slider component 181 | -1, # int | float (numeric value between -1 and 200) in 'Forced Overwrite of Refiner Switch Step' Slider component 182 | -1, # int | float (numeric value between -1 and 2048) in 'Forced Overwrite of Generating Width' Slider component 183 | -1, # int | float (numeric value between -1 and 2048) in 'Forced Overwrite of Generating Height' Slider component 184 | -1, # int | float (numeric value between -1 and 1.0) in 'Forced Overwrite of Denoising Strength of "Vary"' Slider component 185 | -1, # int | float (numeric value between -1 and 1.0) in 'Forced Overwrite of Denoising Strength of "Upscale"' Slider component 186 | False, # bool in 'Mixing Image Prompt and Vary/Upscale' Checkbox component 187 | False, # bool in 'Mixing Image Prompt and Inpaint' Checkbox component 188 | False, # bool in 'Debug Preprocessors' Checkbox component 189 | False, # bool in 'Skip Preprocessors' Checkbox component 190 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Softness of ControlNet' Slider component 191 | 1, # int | float (numeric value between 1 and 255) in 'Canny Low Threshold' Slider component 192 | 1, # int | float (numeric value between 1 and 255) in 'Canny High Threshold' Slider component 193 | "joint", # str (Option from: ['joint', 'separate', 'vae']) in 'Refiner swap method' Dropdown component 194 | False, # bool in 'Enabled' Checkbox component 195 | 0, # int | float (numeric value between 0 and 2) in 'B1' Slider component 196 | 0, # int | float (numeric value between 0 and 2) in 'B2' Slider component 197 | 0, # int | float (numeric value between 0 and 4) in 'S1' Slider component 198 | 0, # int | float (numeric value between 0 and 4) in 'S2' Slider component 199 | False, # bool in 'Debug Inpaint Preprocessing' Checkbox component 200 | False, # bool in 'Disable initial latent in inpaint' Checkbox component 201 | "None", # str (Option from: ['None', 'v1', 'v2.5', 'v2.6']) in 'Inpaint Engine' Dropdown component 202 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Denoising Strength' Slider component 203 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Respective Field' Slider component 204 | False, # bool in 'Enable Mask Upload' Checkbox component 205 | False, # bool in 'Invert Mask' Checkbox component 206 | -64, # int | float (numeric value between -64 and 64) in 'Mask Erode or Dilate' Slider component 207 | fn_index=self.config.init+self.config.deltaInit 208 | ) 209 | return {"ajax":True, "error":False, "init":True} 210 | except Exception as e: 211 | if self.config.deltaInit<10: 212 | console.printBB("[warning]initFooocus() failed. Trying to find the right gradio API fn_index (i+"+str(self.config.deltaInit)+")[/warning]") 213 | self.config.deltaInit+=1 214 | self.config.deltaGenerate+=1 215 | return self.initFooocus(metadata) 216 | else: 217 | print("getModels exception") 218 | print(f"Error: {e}") 219 | return {"ajax":True, "error":True} 220 | 221 | 222 | 223 | def sendCreateImage(self, metadata, uid): 224 | try: 225 | adm=metadata.get("ADM Guidance").replace("(","").replace(")","") 226 | admSplit=adm.split(", ") 227 | start_time = timeit.default_timer() 228 | # self.initFooocus(metadata) # not present in Fooocus V2.2.1 229 | 230 | # Check Turbo or Lighting models 231 | 232 | 233 | 234 | now = datetime.now() 235 | console.printBB("[hour]"+now.strftime("%H:%M:%S")+"[/hour] [b]#"+uid+"[/b] New generation asked and sent to Fooocus") 236 | console.printBB(" [fade]Prompt:[/fade] "+metadata["Prompt"]) 237 | console.printBB(" [fade]Model:[/fade] "+metadata["Base Model"]) 238 | performance=metadata["Performance"] 239 | if "lightning" in metadata["Base Model"].lower(): 240 | console.printBB(" [fade]Lightning model:[/fade] "+"Force to Lightning Performance") 241 | performance="Lightning" 242 | 243 | if "turbo" in metadata["Base Model"].lower(): 244 | console.printBB(" [fade]Turbo model:[/fade] "+"Force to Extreme Speed Performance") 245 | performance="Extreme Speed" 246 | console.printBB(" [fade]Styles:[/fade] "+"," .join(str(s) for s in metadata["Styles"])) 247 | 248 | 249 | result = self.getClient().predict( 250 | False, # bool in 'Generate Image Grid for Each Batch' Checkbox component 251 | metadata["Prompt"], # str in 'parameter_10' Textbox component 252 | metadata["Negative Prompt"],# str in 'Negative Prompt' Textbox component 253 | metadata["Styles"], # List[str] in 'Selected Styles' Checkboxgroup component 254 | performance, # str in 'Performance' Radio component 255 | '1024×1024', # str in 'Aspect Ratios' Radio component 256 | 1, # int | float (numeric value between 1 and 64) in 'Image Number' Slider component 257 | "png", # str in 'Output Format' Radio component 258 | metadata["Seed"], # str in 'Seed' Textbox component 259 | True, # bool in 'Read wildcards in order' Checkbox component 260 | metadata["Sharpness"], # int | float (numeric value between 0.0 and 30.0) in 'Image Sharpness' Slider component 261 | metadata["Guidance Scale"], # int | float (numeric value between 1.0 and 30.0) in 'Guidance Scale' Slider component 262 | metadata["Base Model"], # str in 'Base Model (SDXL only)' Dropdown component 263 | metadata["Refiner Model"], # str in 'Refiner (SDXL or SD 1.5)' Dropdown component 264 | metadata["Refiner Switch"], # int | float (numeric value between 0.1 and 1.0) in 'Refiner Switch At' Slider component 265 | True, # bool in 'Enable' Checkbox component 266 | metadata["Lora 1"], # str in 'LoRA 1' Dropdown component 267 | metadata["Weight Lora 1"], # int | float (numeric value between -2 and 2) in 'Weight' Slider component 268 | True, # bool in 'Enable' Checkbox component 269 | metadata["Lora 2"], # str in 'LoRA 2' Dropdown component 270 | metadata["Weight Lora 2"], # int | float (numeric value between -2 and 2) in 'Weight' Slider component 271 | True, # bool in 'Enable' Checkbox component 272 | metadata["Lora 3"], # str in 'LoRA 3' Dropdown component 273 | metadata["Weight Lora 3"], # int | float (numeric value between -2 and 2) in 'Weight' Slider component 274 | True, # bool in 'Enable' Checkbox component 275 | metadata["Lora 4"], # str in 'LoRA 4' Dropdown component 276 | metadata["Weight Lora 4"], # int | float (numeric value between -2 and 2) in 'Weight' Slider component 277 | True, # bool in 'Enable' Checkbox component 278 | metadata["Lora 5"], # str in 'LoRA 5' Dropdown component 279 | metadata["Weight Lora 5"], # int | float (numeric value between -2 and 2) in 'Weight' Slider component 280 | True, # bool in 'Input Image' Checkbox component 281 | "Howdy!", # str in 'parameter_91' Textbox component 282 | "Disabled", # str in 'Upscale or Variation:' Radio component 283 | self.emptyImage, # str (filepath or URL to image) in 'Drag above image to here' Image component 284 | ["Left"], # List[str] in 'Outpaint Direction' Checkboxgroup component 285 | self.emptyImage, # str (filepath or URL to image) in 'Drag inpaint or outpaint image to here' Image component 286 | "Howdy!", # str in 'Inpaint Additional Prompt' Textbox component 287 | self.emptyImage, # str (filepath or URL to image) in 'Mask Upload' Image component 288 | True, # bool in 'Disable Preview' Checkbox component 289 | True, # bool in 'Disable Intermediate Results' Checkbox component 290 | True, # bool in 'Disable seed increment' Checkbox component 291 | False, # bool in 'Black Out NSFW' Checkbox component 292 | float(admSplit[0]), # int | float (numeric value between 0.1 and 3.0) in 'Positive ADM Guidance Scaler' Slider component 293 | float(admSplit[1]), # int | float (numeric value between 0.1 and 3.0) in 'Negative ADM Guidance Scaler' Slider component 294 | float(admSplit[2]), # int | float (numeric value between 0.0 and 1.0) in 'ADM Guidance End At Step' Slider component 295 | 7, # int | float (numeric value between 1.0 and 30.0) in 'CFG Mimicking from TSNR' Slider component 296 | 1, # int | float (numeric value between 1 and 12) 'CLIP Skip' Slider component 297 | metadata["Sampler"], # str (Option from: ['euler', 'euler_ancestral', 'heun', 'heunpp2', 'dpm_2', 'dpm_2_ancestral', 'lms', 'dpm_fast', 'dpm_adaptive', 'dpmpp_2s_ancestral', 'dpmpp_sde', 'dpmpp_sde_gpu', 'dpmpp_2m', 'dpmpp_2m_sde', 'dpmpp_2m_sde_gpu', 'dpmpp_3m_sde', 'dpmpp_3m_sde_gpu', 'ddpm', 'lcm', 'ddim', 'uni_pc', 'uni_pc_bh2']) in 'Sampler' Dropdown component 298 | metadata["Scheduler"], # str (Option from: ['normal', 'karras', 'exponential', 'sgm_uniform', 'simple', 'ddim_uniform', 'lcm', 'turbo']) in 'Scheduler' Dropdown component 299 | "Default (model)", # str (Option from: ['Default (model)']) 'VAE' Dropdown component 300 | -1, # int | float (numeric value between -1 and 200) in 'Forced Overwrite of Sampling Step' Slider component 301 | -1, # int | float (numeric value between -1 and 200) in 'Forced Overwrite of Refiner Switch Step' Slider component 302 | -1, # int | float (numeric value between -1 and 2048) in 'Forced Overwrite of Generating Width' Slider component 303 | -1, # int | float (numeric value between -1 and 2048) in 'Forced Overwrite of Generating Height' Slider component 304 | -1, # int | float (numeric value between -1 and 1.0) in 'Forced Overwrite of Denoising Strength of "Vary"' Slider component 305 | -1, # int | float (numeric value between -1 and 1.0) in 'Forced Overwrite of Denoising Strength of "Upscale"' Slider component 306 | False, # bool in 'Mixing Image Prompt and Vary/Upscale' Checkbox component 307 | False, # bool in 'Mixing Image Prompt and Inpaint' Checkbox component 308 | False, # bool in 'Debug Preprocessors' Checkbox component 309 | False, # bool in 'Skip Preprocessors' Checkbox component 310 | 1, # int | float (numeric value between 1 and 255) in 'Canny Low Threshold' Slider component 311 | 1, # int | float (numeric value between 1 and 255) in 'Canny High Threshold' Slider component 312 | "joint", # str (Option from: ['joint', 'separate', 'vae']) in 'Refiner swap method' Dropdown component 313 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Softness of ControlNet' Slider component 314 | False, # bool in 'Enabled' Checkbox component 315 | 0, # int | float (numeric value between 0 and 2) in 'B1' Slider component 316 | 0, # int | float (numeric value between 0 and 2) in 'B2' Slider component 317 | 0, # int | float (numeric value between 0 and 4) in 'S1' Slider component 318 | 0, # int | float (numeric value between 0 and 4) in 'S2' Slider component 319 | False, # bool in 'Debug Inpaint Preprocessing' Checkbox component 320 | False, # bool in 'Disable initial latent in inpaint' Checkbox component 321 | "None", # str (Option from: ['None', 'v1', 'v2.5', 'v2.6']) in 'Inpaint Engine' Dropdown component 322 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Denoising Strength' Slider component 323 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Respective Field' Slider component 324 | False, # bool in 'Enable Mask Upload' Checkbox component 325 | False, # bool in 'Invert Mask' Checkbox component 326 | -64, # int | float (numeric value between -64 and 64) in 'Mask Erode or Dilate' Slider component 327 | False, # bool in 'Save only final enhanced image' Checkbox component 328 | False, # bool in 'Save Metadata to Images' Checkbox component 329 | "fooocus", # str in 'Metadata Scheme' Radio component 330 | self.emptyImage, # str (filepath or URL to image) in 'Image' Image component 331 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Stop At' Slider component 332 | 0, # int | float (numeric value between 0.0 and 2.0) in 'Weight' Slider component 333 | "ImagePrompt", # str in 'Type' Radio component 334 | self.emptyImage, # str (filepath or URL to image) in 'Image' Image component 335 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Stop At' Slider component 336 | 0, # int | float (numeric value between 0.0 and 2.0) in 'Weight' Slider component 337 | "ImagePrompt", # str in 'Type' Radio component 338 | self.emptyImage, # str (filepath or URL to image) in 'Image' Image component 339 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Stop At' Slider component 340 | 0, # int | float (numeric value between 0.0 and 2.0) in 'Weight' Slider component 341 | "ImagePrompt", # str in 'Type' Radio component 342 | self.emptyImage, # str (filepath or URL to image) in 'Image' Image component 343 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Stop At' Slider component 344 | 0, # int | float (numeric value between 0.0 and 2.0) in 'Weight' Slider component 345 | "ImagePrompt", # str in 'Type' Radio component 346 | False, # bool in 'Debug GroundingDINO' Checkbox component 347 | -64, # int | float (numeric value between -64 and 64) in 'GroundingDINO Box Erode or Dilate' Slider component 348 | False, # bool in 'Debug Enhance Masks' Checkbox component 349 | self.emptyImage, # str (filepath or URL to image) 350 | False, # bool in 'Enhance' Checkbox component 351 | "Disabled", # str in 'Upscale or Variation:' Radio component 352 | "Before First Enhancement", # str in 'Order of Processing' Radio component 353 | "Original Prompts", # str in 'Prompt' Radio component 354 | False, # bool in 'Enable' Checkbox component 355 | "Howdy!", # str in 'Detection prompt' Textbox component 356 | "Howdy!", # str in 'Enhancement positive prompt' Textbox component 357 | "Howdy!", # str in 'Enhancement negative prompt' Textbox component 358 | "u2net", # str (Option from: ['u2net', 'u2netp', 'u2net_human_seg', 'u2net_cloth_seg', 'silueta', 'isnet-general-use', 'isnet-anime', 'sam']) 359 | "full", # str (Option from: ['full', 'upper', 'lower']) in 'Cloth category' Dropdown component 360 | "vit_b", # str (Option from: ['vit_b', 'vit_l', 'vit_h']) in 'SAM model' Dropdown component 361 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Text Threshold' Slider component 362 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Box Threshold' Slider component 363 | 0, # int | float (numeric value between 0 and 10) in 'Maximum number of detections' Slider component 364 | False, # bool in 'Disable initial latent in inpaint' Checkbox component 365 | "None", # str (Option from: ['None', 'v1', 'v2.5', 'v2.6']) in 'Inpaint Engine' Dropdown component 366 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Denoising Strength' Slider component 367 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Respective Field' Slider component 368 | -64, # int | float (numeric value between -64 and 64) in 'Mask Erode or Dilate' Slider component 369 | False, # bool in 'Invert Mask' Checkbox component 370 | False, # bool in 'Enable' Checkbox component 371 | "Howdy!", # str in 'Detection prompt' Textbox component 372 | "Howdy!", # str in 'Enhancement positive prompt' Textbox component 373 | "Howdy!", # str in 'Enhancement negative prompt' Textbox component 374 | "u2net", # str (Option from: ['u2net', 'u2netp', 'u2net_human_seg', 'u2net_cloth_seg', 'silueta', 'isnet-general-use', 'isnet-anime', 'sam']) in 'Mask generation model' Dropdown component 375 | "full", # str (Option from: ['full', 'upper', 'lower']) in 'Cloth category' Dropdown component 376 | "vit_b", # str (Option from: ['vit_b', 'vit_l', 'vit_h']) in 'SAM model' Dropdown component 377 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Text Threshold' Slider component 378 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Box Threshold' Slider component 379 | 0, # int | float (numeric value between 0 and 10) in 'Maximum number of detections' Slider component 380 | False, # bool in 'Disable initial latent in inpaint' Checkbox component 381 | "None", # str (Option from: ['None', 'v1', 'v2.5', 'v2.6']) in 'Inpaint Engine' Dropdown component 382 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Denoising Strength' Slider component 383 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Respective Field' Slider component 384 | -64, # int | float (numeric value between -64 and 64) in 'Mask Erode or Dilate' Slider component 385 | False, # bool in 'Invert Mask' Checkbox component 386 | False, # bool in 'Enable' Checkbox component 387 | "Howdy!", # str in 'Detection prompt' Textbox component 388 | "Howdy!", # str in 'Enhancement positive prompt' Textbox component 389 | "Howdy!", # str in 'Enhancement negative prompt' Textbox component 390 | "u2net", # str (Option from: ['u2net', 'u2netp', 'u2net_human_seg', 'u2net_cloth_seg', 'silueta', 'isnet-general-use', 'isnet-anime', 'sam']) 391 | "full", # str (Option from: ['full', 'upper', 'lower']) in 'Cloth category' Dropdown component 392 | "vit_b", # str (Option from: ['vit_b', 'vit_l', 'vit_h']) in 'SAM model' Dropdown component 393 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Text Threshold' Slider component 394 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Box Threshold' Slider component 395 | 0, # int | float (numeric value between 0 and 10) in 'Maximum number of detections' Slider component 396 | False, # bool in 'Disable initial latent in inpaint' Checkbox component 397 | "None", # str (Option from: ['None', 'v1', 'v2.5', 'v2.6']) in 'Inpaint Engine' Dropdown component 398 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Denoising Strength' Slider component 399 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Respective Field' Slider component 400 | -64, # int | float (numeric value between -64 and 64) in 'Mask Erode or Dilate' Slider component 401 | False, # bool in 'Invert Mask' Checkbox component 402 | fn_index=self.config.generate+self.config.deltaGenerate 403 | ) 404 | 405 | result = self.getClient().predict(fn_index=self.config.generate+1) 406 | 407 | end_time = timeit.default_timer() 408 | 409 | 410 | 411 | # Generation failure 412 | if len(result[3]['value'])==0: 413 | console.printBB(" [b]#"+uid+"[/b] Generation image [error]failed[/error]") 414 | return { 415 | "ajax":True, 416 | "error":True, 417 | "image":False, 418 | "uid": uid, 419 | "metadata": metadata, 420 | "start_time": start_time, 421 | "end_time": end_time, 422 | "elapsed_time": round((end_time - start_time)*1000), 423 | "temp": "", 424 | "name": "", 425 | "file_size": "", 426 | "result": result 427 | } 428 | 429 | # Generation success 430 | if "name" in result[3]['value'][0]: 431 | now = datetime.now() 432 | console.printBB("[hour]"+now.strftime("%H:%M:%S")+"[/hour] [b]#"+uid+"[/b] [ok]Image is generated[/ok]") 433 | 434 | picture=result[3]['value'][0]['name'] 435 | name=self.outputFolder+"/"+uid+result[3]['value'][0]['name'][-4:] 436 | shutil.copyfile(picture, name) 437 | 438 | if "action" in metadata: 439 | if "compress" in metadata["action"]: 440 | if "resize" in metadata["action"]: 441 | if "copy" in metadata["action"]: 442 | w, h=metadata["action"]["resize"] 443 | # dirname = os.path.dirname(sys.argv[0]) 444 | # filename = utils.pathJoin(dirname, metadata["action"]["copy"]) 445 | # print("Remove file ? "+filename) 446 | # if os.path.exists(filename): 447 | # print("REMOVE FILE: "+filename) 448 | # os.remove(filename) 449 | utils.resizeAndCompressImage(picture, w, h, metadata["action"]["compress"], metadata["action"]["copy"]) 450 | 451 | image={ "ajax": True, 452 | "error": False, 453 | "image": True, 454 | "uid": uid, 455 | "metadata": metadata, 456 | "start_time": start_time, 457 | "end_time": end_time, 458 | "elapsed_time": round((end_time - start_time)*1000), 459 | "temp": result[3]['value'][0]['name'], 460 | "name": "/"+name, 461 | "file_size": os.path.getsize(picture), 462 | "result": result 463 | } 464 | 465 | 466 | 467 | return image 468 | 469 | except Exception as e: 470 | if self.config.deltaGenerate<0: 471 | console.printBB("[warning]sendCreateImage() failed. Trying to find the right gradio API fn_index (i+"+str(self.config.deltaGenerate)+")[/warning]") 472 | self.config.deltaGenerate+=1 473 | return self.sendCreateImage(metadata, uid) 474 | else: 475 | print("sendCreateImage exception") 476 | print(f"Error: {e}") 477 | print(" Check the Fooocus console terminal to view more indications") 478 | traceback.print_exc() # Print the full traceback 479 | return {"ajax":True, "error":True} 480 | -------------------------------------------------------------------------------- /src/console.py: -------------------------------------------------------------------------------- 1 | # Python Console Module that allows to print BB code in terminal 2 | # Pimp your terminal with colors 3 | # @toutjavascript https://github.com/toutjavascript 4 | # V1 : 2024 5 | 6 | 7 | 8 | import re 9 | import inspect 10 | 11 | # parse BB code and print it in console 12 | def printBB(text): 13 | text=re.sub(r"(\[h1\])([^\[\]]+)(\[/h1\])", "\033[32;1m\\2\033[0m",text,re.IGNORECASE) 14 | text=re.sub(r"(\[ok\])(.+)(\[/ok\])", "\033[32;1m\\2\033[0m",text,re.IGNORECASE) 15 | text=re.sub(r"(\[error\])([^\[\]]+)(\[/error\])", "\033[31;1m\\2\033[0m",text,re.IGNORECASE) 16 | text=re.sub(r"(\[b\])([^\[\]]+)(\[/b\])","\033[1m\\2\033[0m",text,re.IGNORECASE) 17 | text=re.sub(r"(\[u\])([^\[\]]+)(\[/u\])","\033[4m\\2\033[24m",text,re.IGNORECASE) 18 | text=re.sub(r"(\[d\])([^\[\]]+)(\[/d\])","\033[2m\\2\033[22m",text,re.IGNORECASE) 19 | text=re.sub(r"(\[fade\])([^\[\]]+)(\[/fade\])","\033[2m\\2\033[22m",text,re.IGNORECASE) 20 | text=re.sub(r"(\[warning\])([^\[\]]+)(\[/warning\])","\033[33m\\2\033[22m",text,re.IGNORECASE) 21 | text=re.sub(r"(\[reset\])","\033[0m\033[49m",text,re.IGNORECASE) 22 | text=re.sub(r"(\[reverse\])(.+)(\[/reverse\])","\033[7m\\2\033[0m",text,re.IGNORECASE) 23 | text=re.sub(r"(\[header\])([^\[\]]+)(\[/header\])","\\033[1m \\2\033[0m",text,re.IGNORECASE) 24 | text=re.sub(r"(\[hour\])([^\[\]]+)(\[/hour\])","\\033[48;5;255m\\2\033[0m",text,re.IGNORECASE) 25 | text=re.sub(r"(\[shell\])([^\[\]]+)(\[/shell\])","\\033[44;1;97m\\2\033[0m",text,re.IGNORECASE) 26 | print(text) 27 | 28 | 29 | def printExceptionError(error): 30 | caller_name = inspect.stack()[1].function 31 | file=inspect.stack()[1].filename 32 | file=file[file.rfind("\\")+1:] 33 | printBB("[error]Exception Error on "+file+"/"+caller_name+"():[/error]"); 34 | printBB(" [error]"+repr(error)+"[/error]") 35 | 36 | # From this great tuto https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences 37 | def test(): 38 | for i in range(30, 37 + 1): 39 | print("\033[%dm%d\t\t\033[%dm%d" % (i, i, i + 60, i + 60)) 40 | 41 | print("\033[39m\\033[49m - Reset color") 42 | print("\\033[2K - Clear Line") 43 | print("\\033[;H or \\033[;f - Put the cursor at line L and column C.") 44 | print("\\033[A - Move the cursor up N lines") 45 | print("\\033[B - Move the cursor down N lines") 46 | print("\\033[C - Move the cursor forward N columns") 47 | print("\\033[D - Move the cursor backward N columns\n") 48 | print("\\033[2J - Clear the screen, move to (0,0)") 49 | print("\\033[K - Erase to end of line") 50 | print("\\033[s - Save cursor position") 51 | print("\\033[u - Restore cursor position\n") 52 | print("\\033[4m - Underline on") 53 | print("\\033[24m - Underline off\n") 54 | print("\\033[1m - Bold on") 55 | print("\\033[21m - Bold off") -------------------------------------------------------------------------------- /src/device.py: -------------------------------------------------------------------------------- 1 | import psutil 2 | import cpuinfo 3 | import GPUtil 4 | import timeit 5 | import platform 6 | from src import console 7 | from src import utils 8 | 9 | 10 | # Get hardware informations about the device (CPU, RAM, GPU) 11 | def getDeviceInfo(): 12 | start_time0 = timeit.default_timer() 13 | 14 | start_time = timeit.default_timer() 15 | try: 16 | gpus = GPUtil.getGPUs() 17 | gpu_info = [{'id': gpu.id, 18 | 'uuid': gpu.uuid, 19 | 'name': gpu.name, 20 | 'serial': gpu.serial, 21 | 'temperature': gpu.temperature, 22 | 'load': gpu.load, 23 | 'memoryTotal': gpu.memoryTotal, 24 | 'memoryUtil': gpu.memoryUtil, 25 | 'memoryUsed': gpu.memoryUsed, 26 | 'memoryFree': gpu.memoryFree, 27 | 'display_mode': gpu.display_mode, 28 | 'display_active': gpu.display_active 29 | } 30 | for gpu in gpus] 31 | except: 32 | gpu_info = [{ 33 | 'id': "", 34 | 'uuid': "", 35 | 'name': "", 36 | 'serial': "", 37 | 'temperature': "", 38 | 'load': "", 39 | 'memoryTotal': 0, 40 | 'memoryUtil': 0, 41 | 'memoryUsed': 0, 42 | 'memoryFree': 0, 43 | 'display_mode': "", 44 | 'display_active': ""}] 45 | console.printBB(" [d] GPUtil is ignored in this python version [/d]") 46 | 47 | end_time = timeit.default_timer() 48 | #print(f"Execution time GPUtil.getGPUs(): {end_time - start_time} seconds") 49 | 50 | try: 51 | cpu_util = psutil.cpu_freq() 52 | end_time = timeit.default_timer() 53 | #print(f"Execution time psutil.cpu_freq(): {end_time - start_time0} seconds") 54 | 55 | start_time = timeit.default_timer() 56 | cpu_name = cpuinfo.get_cpu_info() 57 | end_time = timeit.default_timer() 58 | #print(f"Execution time cpuinfo.get_cpu_info(): {end_time - start_time} seconds") 59 | 60 | start_time = timeit.default_timer() 61 | ram_info = psutil.virtual_memory() 62 | end_time = timeit.default_timer() 63 | #print(f"Execution time psutil.virtual_memory(): {end_time - start_time} seconds") 64 | 65 | 66 | start_time = timeit.default_timer() 67 | hdd_info = psutil.disk_usage('/') 68 | end_time = timeit.default_timer() 69 | #print(f"Execution time psutil.disk_usage(): {end_time - start_time} seconds") 70 | 71 | #new object to return 72 | device = { 73 | "detected": True, 74 | "raw": { 75 | "cpu_util": cpu_util, 76 | "cpu_name": cpu_name, 77 | "ram_info": ram_info, 78 | "gpus": gpu_info, 79 | "hdd_info": hdd_info 80 | }, 81 | "cpu_brand": cpu_name.get("vendor_id_raw"), 82 | "cpu_name": cpu_name.get("brand_raw"), 83 | "cpu_freq": cpu_name.get("hz_advertised")[0], 84 | "cpu_threads": cpu_name.get("count"), 85 | "cpu_max_freq": cpu_name.get("hz_advertised"), 86 | "l3_cache_size": cpu_name.get("l3_cache_size"), 87 | "cpu_arch": cpu_name.get("arch"), 88 | "ram_installed": ram_info[0], 89 | "hdd_total": hdd_info.total, 90 | "hdd_used": hdd_info.used, 91 | "hdd_free": hdd_info.free, 92 | "gpus": gpu_info, 93 | "getDeviceInfoTime": end_time - start_time0, 94 | "os_name": platform.system(), 95 | "os_version": platform.release(), 96 | "os_details": platform.platform(), 97 | "description": "" 98 | } 99 | device["description"] = f"""CPU: {device.get("cpu_name")} - {utils.formatFrequencies(device.get("cpu_freq"))} {device.get("cpu_threads")} threads - {utils.formatBytes(device.get("l3_cache_size"))} L3 cache 100 | RAM: {utils.formatBytes(round(device.get("ram_installed"), 0))} 101 | HDD: Total: {utils.formatBytes(device.get("hdd_total"))} Free: {utils.formatBytes(device.get("hdd_free"))}\n""" 102 | 103 | if len(device.get("gpus")) == 0: 104 | device["description"] = f"No GPU found" 105 | else: 106 | gpu=device.get("gpus")[0] 107 | device["description"] += f"""GPU: {gpu.get("name")} ({gpu.get("memoryTotal")/1024} GB)""" 108 | 109 | device["description"] += f"""\nOS: {device.get("os_name")} {device.get("os_version")}""" 110 | except: 111 | console.printBB(" [d] getDeviceInfo() is ignored with your device [/d]") 112 | device = { 113 | "detected": False, 114 | "raw": { 115 | "cpu_util": "", 116 | "cpu_name": "", 117 | "ram_info": "", 118 | "gpus": gpu_info, 119 | "hdd_info": "" 120 | }, 121 | "cpu_brand": "", 122 | "cpu_name": "", 123 | "cpu_freq": "", 124 | "cpu_threads": "", 125 | "cpu_max_freq": "", 126 | "l3_cache_size": "", 127 | "cpu_arch": "", 128 | "ram_installed": "", 129 | "hdd_total": "", 130 | "hdd_used": "", 131 | "hdd_free": "", 132 | "gpus": gpu_info, 133 | "getDeviceInfoTime": 0, 134 | "os_name": platform.system(), 135 | "os_version": platform.release(), 136 | "os_details": platform.platform(), 137 | "description": "Hardware not detected" 138 | } 139 | 140 | return device 141 | 142 | 143 | -------------------------------------------------------------------------------- /src/models.py: -------------------------------------------------------------------------------- 1 | class DeviceInfo: 2 | def __init__(self, cpu_name, cpu_freq, ram_info, gpu_info, hdd_info): 3 | self.cpu_name = cpu_name 4 | self.cpu_freq = cpu_freq 5 | self.ram_info = ram_info 6 | self.gpu_info = gpu_info 7 | self.hdd_info = hdd_info 8 | 9 | def __str__(self): 10 | return f"CPU: {self.cpu_name}\nCPU Frequency: {self.cpu_freq}\nRAM: {self.ram_info}\nGPU: {self.gpu_info}\nHDD: {self.hdd_info}" 11 | 12 | def __repr__(self): 13 | return f"CPU: {self.cpu_name}\nCPU Frequency: {self.cpu_freq}\nRAM: {self.ram_info}\nGPU: {self.gpu_info}\nHDD: {self.hdd_info}" 14 | 15 | def getDevice(self): 16 | cpu_name = "Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz" 17 | cpu_freq = "2.8 GHz" 18 | ram_info = "16 GB" 19 | gpu_info = "NVIDIA GeForce GTX 1050 Ti" 20 | hdd_info = "1 TB" 21 | return DeviceInfo(cpu_name, cpu_freq, ram_info, gpu_info, hdd_info) 22 | 23 | class Metadata: 24 | def __init__(self, prompt, negativePrompt, selectedStyles, performance, aspectRatios, imageNumber, seed, imageSharpness, guidanceScale, baseModel, refiner, refinerSwitchAt, lora1, weight1, lora2, weight2, lora3, weight3, lora4, weight4, lora5, weight5): 25 | self.prompt = prompt 26 | self.negativePrompt = negativePrompt 27 | self.selectedStyles = selectedStyles 28 | self.performance = performance 29 | self.aspectRatios = aspectRatios 30 | self.imageNumber = imageNumber 31 | self.seed = seed 32 | self.imageSharpness = imageSharpness 33 | self.guidanceScale = guidanceScale 34 | self.baseModel = baseModel 35 | self.refiner = refiner 36 | self.refinerSwitchAt = refinerSwitchAt 37 | self.lora1 = lora1 38 | self.weight1 = weight1 39 | self.lora2 = lora2 40 | self.weight2 = weight2 41 | self.lora3 = lora3 42 | self.weight3 = weight3 43 | self.lora4 = lora4 44 | self.weight4 = weight4 45 | self.lora5 = lora5 46 | self.weight5 = weight5 47 | 48 | 49 | class Image: 50 | def __init__(self, metadata, uid, path, start_time, end_time, elapsed_time): 51 | self.metadata = metadata 52 | self.uid = uid 53 | self.path = path 54 | self.start_time = start_time 55 | self.end_time = end_time 56 | self.elapsed_time = elapsed_time 57 | 58 | -------------------------------------------------------------------------------- /src/sql.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import json 3 | 4 | 5 | def table_exists(cur, table_name): 6 | cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,)) 7 | return cur.fetchone() is not None 8 | 9 | def connect(): 10 | conn = sqlite3.connect("foooxus.db") 11 | cur = conn.cursor() 12 | 13 | if table_exists(cur, "work"): 14 | return True 15 | 16 | # Create tables if they don't exist 17 | cur.execute("CREATE TABLE IF NOT EXISTS work (uid TEXT PRIMARY KEY, idBatch KEY INT, image TEXT, metadata TEXT, dtGenerated TEXT, elapsedTime FLOAT)") 18 | cur.execute("CREATE TABLE IF NOT EXISTS batch (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, metadata TEXT, variations TEXT, dtCreated TEXT)") 19 | 20 | # Add some series 21 | metadata = { 22 | "Prompt":"Portrait of a web programmer man in an office", 23 | "Negative Prompt":"", 24 | "Styles":"[]", 25 | "Performance":"Quality", 26 | "Resolution":"(1024, 1024)", 27 | "Guidance Scale":"4", 28 | "Sharpness":"2", 29 | "ADM Guidance":"(1.5, 0.8, 0.3)", 30 | "Base Model":"juggernautXL_v8Rundiffusion.safetensors", 31 | "Refiner Model":"None", 32 | "Refiner Switch":"0.5", 33 | "Sampler":"dpmpp_2m_sde_gpu", 34 | "Scheduler":"karras", 35 | "Seed":"314159" 36 | } 37 | cur.execute("INSERT INTO batch VALUES (1, ?, ?, ?, datetime('now'))", ("Portrait", json.dumps(metadata), "" )) 38 | 39 | metadata = { 40 | "Prompt":"Landscape with forest, hills, moutains and river", 41 | "Negative Prompt":"", 42 | "Styles":"[]", 43 | "Performance":"Quality", 44 | "Resolution":"(1024, 1024)", 45 | "Guidance Scale":"4", 46 | "Sharpness":"2", 47 | "ADM Guidance":"(1.5, 0.8, 0.3)", 48 | "Base Model":"juggernautXL_v8Rundiffusion.safetensors", 49 | "Refiner Model":"None", 50 | "Refiner Switch":"0.5", 51 | "Sampler":"dpmpp_2m_sde_gpu", 52 | "Scheduler":"karras", 53 | "Seed":"314159" 54 | } 55 | cur.execute("INSERT INTO batch VALUES (2, ?, ?, ?, datetime('now'))", ("Landscape", json.dumps(metadata), "" )) 56 | 57 | conn.commit() 58 | conn.close() 59 | 60 | -------------------------------------------------------------------------------- /src/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | from PIL import Image 4 | import importlib.metadata 5 | import sys 6 | import platform 7 | 8 | # Return true if the python script is running in a venv environment 9 | def in_venv(): 10 | return sys.prefix != sys.base_prefix 11 | 12 | # Return true if the python script is running on a windows platform (with .bat support) 13 | def is_windows(): 14 | return platform.system().lower().find("windows")>=0 15 | 16 | def formatBytes(B, round_to=2): 17 | B = float(B) 18 | KB = float(1024) 19 | MB = float(KB ** 2) # 1,048,576 20 | GB = float(KB ** 3) # 1,073,741,824 21 | TB = float(KB ** 4) # 1,099,511,627,776 22 | 23 | if B < KB: 24 | return '{0} {1}'.format(B,'Bytes' if 0 == B > 1 else 'Byte') 25 | elif KB <= B < MB: 26 | return "{0:.2f} KB".format(B/KB) 27 | elif MB <= B < GB: 28 | return "{0:.2f} MB".format(B/MB) 29 | elif GB <= B < TB: 30 | return "{0:.2f} GB".format(B/GB) 31 | elif TB <= B: 32 | return "{0:.2f} TB".format(B/TB) 33 | 34 | 35 | def formatFrequencies(H, round_to=1): 36 | H = float(H) 37 | KH = float(1000) 38 | MH = float(KH ** 2) # 1,048,576 39 | GH = float(KH ** 3) # 1,073,741,824 40 | TH = float(KH ** 4) # 1,099,511,627,776 41 | 42 | if H < KH: 43 | return '{0} {1}'.format(H,'Hz' if 0 == H > 1 else 'Hz') 44 | elif KH <= H < MH: 45 | return "{0:.2f} KHz".format(H/KH) 46 | elif MH <= H < GH: 47 | return "{0:.2f} MHz".format(H/MH) 48 | elif GH <= H < TH: 49 | return "{0:.2f} GHz".format(H/GH) 50 | 51 | # create a folder if it doesn't exist 52 | def checkFolder(directory): 53 | if not os.path.exists(directory): 54 | # print("Creating directory: " + directory) 55 | os.makedirs(directory) 56 | 57 | # resize and compress an image 58 | def resizeAndCompressImage(imageSource, width, height, quality, imageDestination): 59 | try: 60 | img = Image.open(imageSource) 61 | img.thumbnail((width, height), Image.LANCZOS) 62 | except: 63 | print("Error resizeAndCompressImage img.thumbnail") 64 | try: 65 | img.save(imageDestination, optimize=True, quality=quality) 66 | except: 67 | print("Error resizeAndCompressImage img.save") 68 | return True 69 | 70 | def getAppPath(): 71 | realpath=os.path.realpath(__file__) 72 | appPath=os.path.dirname(realpath)[0:-4] 73 | #print("os.path.realpath(__file__):"+os.path.realpath(__file__)) # => C:\IA\FoooXus-Fooocus-Extender\src\utils.py 74 | #print('full path =', appPath) # => C:\IA\FoooXus-Fooocus-Extender 75 | 76 | return appPath 77 | 78 | def getFiles(dir, extension): 79 | if extension[0]==".": 80 | extension=extension[1:] 81 | files = glob.glob(os.path.join(dir,"*." + extension)) 82 | for i in range(len(files)): 83 | files[i] = files[i].replace(dir+'\\', "") 84 | return files 85 | 86 | 87 | def getPythonVersion(): 88 | if sys.version.find(" ")>1: 89 | return sys.version[0:sys.version.find(" ")] 90 | return sys.version 91 | 92 | def getOS(): 93 | os=platform.system()+" "+platform.release() 94 | release=platform.release() 95 | version=platform.version() 96 | build=version 97 | if os=="Windows 10": 98 | build=version[version.rfind(".")+1:] 99 | if (build>="22000"): 100 | release="11" 101 | if platform.system()=="Darwin": 102 | os="macOS" 103 | build=release 104 | release="" 105 | 106 | return platform.system()+" "+release+" Build "+build 107 | 108 | 109 | def checkVersions(modules): 110 | versions={} 111 | for module in modules: 112 | try: 113 | versions[module]=importlib.metadata.version(module) 114 | except: 115 | versions[module]="ERROR" 116 | return versions 117 | 118 | 119 | def getRequirements(file="requirements.txt", display=False): 120 | requirements={} 121 | 122 | with open(file) as f: 123 | lines = f.readlines() 124 | for line in lines: 125 | line=line.strip() 126 | if line.find("==")>1: 127 | name=line[0: line.find("==")].strip() 128 | val=line[line.find("==")+2:].strip() 129 | else: 130 | name=line 131 | val="" 132 | requirements[name]=val 133 | 134 | if display: 135 | print("Content of "+file) 136 | for module in requirements: 137 | print(" "+"{:<18}".format(module)+requirements[module]) 138 | 139 | return requirements 140 | 141 | def pathJoin(dir, file): 142 | return os.path.join(dir, file.replace("/", "\\")) 143 | 144 | 145 | def clearTmpFolder(folder_path="outputs/tmp"): 146 | for filename in os.listdir(folder_path): 147 | file_path = os.path.join(folder_path, filename) 148 | if os.path.isfile(file_path): 149 | os.remove(file_path) -------------------------------------------------------------------------------- /static/css/extens.css: -------------------------------------------------------------------------------- 1 | 2 | /* Extend the default bootstrap navbar */ 3 | 4 | code { 5 | color:#7748a0 !important; 6 | font-weight: bold; 7 | } 8 | 9 | .form-text { 10 | margin:0px; 11 | margin-bottom:2px; 12 | } 13 | 14 | body, label, div, input, select, textarea, button, span, a, button { 15 | font-size:15px; 16 | } 17 | 18 | .start-20{left:20%!important} 19 | .start-25{left:25%!important} 20 | .start-33{left:33%!important} 21 | .start-40{left:40%!important} 22 | .start-60{left:60%!important} 23 | .start-66{left:66%!important} 24 | .start-75{left:20%!important} 25 | .start-80{left:80%!important} 26 | 27 | .btn-primary { 28 | background-color: #813FBB !important; 29 | border-color: #813FBB !important; 30 | } 31 | .btn-primary:hover { 32 | background-color: #813FBB !important; 33 | border-color: #813FBB !important; 34 | } 35 | 36 | .progress-bar-foooxus { 37 | background-color: #813FBB !important; 38 | } 39 | .form-label { 40 | font-weight: bold; 41 | margin-bottom:0px; 42 | margin-top:8px; 43 | } 44 | 45 | .row-cols-7 > * { 46 | flex: 0 0 14.28% !important; 47 | max-width: 14.28% !important; 48 | } 49 | .row-cols-8 > * { 50 | flex: 0 0 12.5% !important; 51 | max-width: 12.5% !important; 52 | } 53 | .row-cols-9 > * { 54 | flex: 0 0 11.1% !important; 55 | max-width: 11.1% !important; 56 | } 57 | .row-cols-10 > * { 58 | flex: 0 0 10% !important; 59 | max-width: 10% !important; 60 | } 61 | .row-cols-11 > * { 62 | flex: 0 0 9.09% !important; 63 | max-width: 9.09% !important; 64 | } 65 | .row-cols-12 > * { 66 | flex: 0 0 8.333% !important; 67 | max-width: 8.333% !important; 68 | } 69 | .row-cols-13 > * { 70 | flex: 0 0 7.69% !important; 71 | max-width: 7.69% !important; 72 | } 73 | .row-cols-14 > * { 74 | flex: 0 0 7.14% !important; 75 | max-width: 7.14% !important; 76 | } 77 | 78 | 79 | /* Extend jbox */ 80 | .jBox-title { 81 | padding:0px !important; 82 | } 83 | 84 | /* Foooxus CSS */ 85 | 86 | .preset-param { 87 | font-size: 12px; 88 | } 89 | .preset-name { 90 | font-size: 13px; 91 | font-weight: bold; 92 | } 93 | 94 | img#FoooLogo { 95 | border-radius:50%; 96 | } 97 | 98 | 99 | div#step { 100 | font-weight: bold; 101 | color:#fff; 102 | 103 | } 104 | 105 | div#containerStep { 106 | border:1px solid #813FBB; 107 | background-color: #813FBB; 108 | border-radius: 10px; 109 | color:#fff; 110 | } 111 | 112 | 113 | span.IPconnected { 114 | font-weight: bold; 115 | background-color: #6f0; 116 | color:#000; 117 | border-radius: 10px; 118 | padding:2px 6px 119 | } 120 | span.IPwaiting { 121 | font-weight: bold; 122 | background-color:#ccc; 123 | color:#000; 124 | border-radius: 10px; 125 | padding:2px 6px 126 | } 127 | span.IPdisconnected { 128 | font-weight: bold; 129 | background-color: #f00; 130 | color:#fff; 131 | border-radius: 10px; 132 | padding:2px 6px 133 | } 134 | 135 | div#hardware { 136 | margin-top:2px; 137 | font-size:12px !important; 138 | font-weight: bold; 139 | } 140 | 141 | button.foooxus-step { 142 | font-weight: bold; 143 | } 144 | 145 | img.newImageNotice { 146 | width: 320px; 147 | height: auto; 148 | } 149 | 150 | img.illustration { 151 | max-width:256px; 152 | height: auto; 153 | } 154 | 155 | .text-help { 156 | font-size: 13px; 157 | color: #666; 158 | 159 | } 160 | 161 | img.responsive { 162 | width: 100%; 163 | height: auto; 164 | max-width: 100%; 165 | } 166 | 167 | div.imgViewerGrid { 168 | font-size: 11px; 169 | } 170 | 171 | div.imgViewer { 172 | font-size: 11px; 173 | min-width: 128px; 174 | max-width: 256px; 175 | } 176 | div.imgViewer .fileName { 177 | overflow-x: hidden; 178 | } 179 | .p-2px { 180 | padding: 2px !important; 181 | } 182 | fs-14px { 183 | font-size: 14px !important;; 184 | } 185 | fs-12px { 186 | font-size: 12px !important; 187 | } 188 | fs-13px { 189 | font-size: 13px !important; 190 | } 191 | .text-prompt { 192 | font-size: 13px; 193 | font-weight: bold 194 | } 195 | 196 | /* Queue app */ 197 | td.work-delay { 198 | min-width:90px; 199 | } 200 | 201 | /* Menu nav */ 202 | .menu-nav-link { 203 | cursor: pointer !important; 204 | border:2px solid transparent ; 205 | font-weight: bold; 206 | margin-left:8px; 207 | color:#813FBB !important; 208 | } 209 | .menu-nav-link:hover { 210 | border:2px solid #813FBB; 211 | } 212 | .menu-nav-link.active { 213 | background: #813FBB !important; 214 | border:2px solid #813FBB; 215 | color: #fff !important; 216 | } 217 | 218 | /* Legend on an image */ 219 | .image-container { 220 | position: relative; 221 | border: 1px solid #333; 222 | background-color: #333; 223 | margin:0px; 224 | padding:0px; 225 | } 226 | 227 | .image-title { 228 | display: block; 229 | margin:0px; 230 | overflow-y: hidden; 231 | width:100%; 232 | color:#fff; 233 | text-shadow:1px 1px 1px #000; 234 | font-size:13px; 235 | font-weight: bold; 236 | background-color: #333; 237 | padding: 1px; 238 | } 239 | 240 | /* About */ 241 | p#aboutApp { 242 | font-size:12px; 243 | font-weight: bold; 244 | color:#666; 245 | margin-top:12px; 246 | } -------------------------------------------------------------------------------- /static/css/fonts/bootstrap-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutjavascript/FoooXus-Fooocus-Extender/9ce42d98a720891335ae99a2c009cfda85e57194/static/css/fonts/bootstrap-icons.woff -------------------------------------------------------------------------------- /static/css/fonts/bootstrap-icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutjavascript/FoooXus-Fooocus-Extender/9ce42d98a720891335ae99a2c009cfda85e57194/static/css/fonts/bootstrap-icons.woff2 -------------------------------------------------------------------------------- /static/css/jBox.TooltipDark.min.css: -------------------------------------------------------------------------------- 1 | .jBox-TooltipDark .jBox-container{border-radius:4px;background:#000;color:#fff;box-shadow:0 0 6px rgba(0,0,0,.4)}.jBox-TooltipDark .jBox-pointer:after{background:#000}.jBox-TooltipDark .jBox-closeButton{background:#000}.jBox-TooltipDark.jBox-closeButton-box:before{box-shadow:0 0 6px rgba(0,0,0,.4)}.jBox-TooltipDark.jBox-closeButton-box .jBox-closeButton path{fill:#ddd}.jBox-TooltipDark.jBox-closeButton-box .jBox-closeButton:hover path{fill:#fff}.jBox-TooltipDark.jBox-closeButton-box .jBox-closeButton:active path{fill:#bbb} -------------------------------------------------------------------------------- /static/css/jBox.all.min.css: -------------------------------------------------------------------------------- 1 | .jBox-wrapper{text-align:left;box-sizing:border-box}.jBox-container,.jBox-content,.jBox-title{position:relative;word-break:break-word;box-sizing:border-box}.jBox-container{background:#fff}.jBox-content{padding:8px 12px;overflow-x:hidden;overflow-y:auto;transition:opacity .2s}.jBox-footer{box-sizing:border-box}.jBox-Mouse .jBox-container,.jBox-Tooltip .jBox-container{border-radius:4px;box-shadow:0 0 3px rgba(0,0,0,.25)}.jBox-Mouse .jBox-title,.jBox-Tooltip .jBox-title{padding:8px 10px 0;font-weight:700}.jBox-Mouse.jBox-hasTitle .jBox-content,.jBox-Tooltip.jBox-hasTitle .jBox-content{padding-top:5px}.jBox-Mouse{pointer-events:none}.jBox-pointer{position:absolute;overflow:hidden;box-sizing:border-box}.jBox-pointer:after{content:'';width:20px;height:20px;position:absolute;background:#fff;transform:rotate(45deg);box-sizing:border-box}.jBox-pointer-top{top:0}.jBox-pointer-top:after{left:5px;top:6px;box-shadow:-1px -1px 2px rgba(0,0,0,.15)}.jBox-pointer-right{right:0}.jBox-pointer-right:after{top:5px;right:6px;box-shadow:1px -1px 2px rgba(0,0,0,.15)}.jBox-pointer-left{left:0}.jBox-pointer-left:after{top:5px;left:6px;box-shadow:-1px 1px 2px rgba(0,0,0,.15)}.jBox-pointer-bottom{bottom:0}.jBox-pointer-bottom:after{left:5px;bottom:6px;box-shadow:1px 1px 2px rgba(0,0,0,.15)}.jBox-pointer-bottom,.jBox-pointer-top{width:30px;height:12px}.jBox-pointer-left,.jBox-pointer-right{width:12px;height:30px}.jBox-Modal .jBox-container{border-radius:4px}.jBox-Modal .jBox-container,.jBox-Modal.jBox-closeButton-box:before{box-shadow:0 3px 15px rgba(0,0,0,.4),0 0 5px rgba(0,0,0,.4)}.jBox-Modal .jBox-content{padding:15px 20px}.jBox-Modal .jBox-title{border-radius:4px 4px 0 0;padding:15px 20px;background:#fafafa;border-bottom:1px solid #eee}.jBox-Modal.jBox-closeButton-title .jBox-title{padding-right:65px}.jBox-Modal .jBox-footer{border-radius:0 0 4px 4px}.jBox-closeButton{z-index:1;cursor:pointer;position:absolute;box-sizing:border-box}.jBox-closeButton svg{position:absolute;top:50%;right:50%}.jBox-closeButton path{fill:#aaa;transition:fill .2s}.jBox-closeButton:hover path{fill:#888}.jBox-overlay .jBox-closeButton{top:0;right:0;width:40px;height:40px}.jBox-overlay .jBox-closeButton svg{width:20px;height:20px;margin-top:-10px;margin-right:-10px}.jBox-overlay .jBox-closeButton path{fill:#ddd}.jBox-overlay .jBox-closeButton:hover path{fill:#fff}.jBox-closeButton-title .jBox-closeButton{top:0;right:0;bottom:0;width:50px}.jBox-closeButton-title svg{width:12px;height:12px;margin-top:-6px;margin-right:-6px}.jBox-closeButton-box{box-sizing:border-box}.jBox-closeButton-box .jBox-closeButton{top:-8px;right:-10px;width:24px;height:24px;background:#fff;border-radius:50%}.jBox-closeButton-box .jBox-closeButton svg{width:10px;height:10px;margin-top:-5px;margin-right:-5px}.jBox-closeButton-box:before{content:'';position:absolute;top:-8px;right:-10px;width:24px;height:24px;border-radius:50%;box-shadow:0 0 5px rgba(0,0,0,.3)}.jBox-closeButton-box.jBox-pointerPosition-top:before{top:5px}.jBox-closeButton-box.jBox-pointerPosition-right:before{right:2px}.jBox-Modal.jBox-hasTitle.jBox-closeButton-box .jBox-closeButton{background:#fafafa}.jBox-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.82)}.jBox-footer{background:#fafafa;border-top:1px solid #eee;padding:8px 10px;border-radius:0 0 3px 3px}body[class*=" jBox-blockScroll-"],body[class^=jBox-blockScroll-]{overflow:hidden}.jBox-draggable{cursor:move}@keyframes jBoxLoading{to{transform:rotate(360deg)}}.jBox-loading .jBox-content{opacity:.2}.jBox-loading-spinner .jBox-content{min-height:38px!important;min-width:38px!important;opacity:0}.jBox-spinner{box-sizing:border-box;position:absolute;top:50%;left:50%;width:24px;height:24px;margin-top:-12px;margin-left:-12px}.jBox-spinner:before{display:block;box-sizing:border-box;content:'';width:24px;height:24px;border-radius:50%;border:2px solid rgba(0,0,0,.2);border-top-color:rgba(0,0,0,.8);animation:jBoxLoading .6s linear infinite}.jBox-countdown{border-radius:4px 4px 0 0;z-index:0;background:#000;opacity:.2;position:absolute;top:0;left:0;right:0;height:3px;overflow:hidden}.jBox-countdown-inner{top:0;right:0;width:100%;height:3px;position:absolute;background:#fff}[class*=" jBox-animated-"],[class^=jBox-animated-]{animation-fill-mode:both}@keyframes jBox-tada{0%{transform:scale(1)}10%,20%{transform:scale(.8) rotate(-4deg)}30%,50%,70%,90%{transform:scale(1.2) rotate(4deg)}40%,60%,80%{transform:scale(1.2) rotate(-4deg)}100%{transform:scale(1) rotate(0)}}.jBox-animated-tada{animation:jBox-tada 1s}@keyframes jBox-tadaSmall{0%{transform:scale(1)}10%,20%{transform:scale(.9) rotate(-2deg)}30%,50%,70%,90%{transform:scale(1.1) rotate(2deg)}40%,60%,80%{transform:scale(1.1) rotate(-2deg)}100%{transform:scale(1) rotate(0)}}.jBox-animated-tadaSmall{animation:jBox-tadaSmall 1s}@keyframes jBox-flash{0%,100%,50%{opacity:1}25%,75%{opacity:0}}.jBox-animated-flash{animation:jBox-flash .5s}@keyframes jBox-shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-6px)}40%,80%{transform:translateX(6px)}}.jBox-animated-shake{animation:jBox-shake .4s}@keyframes jBox-pulseUp{0%{transform:scale(1)}50%{transform:scale(1.15)}100%{transform:scale(1)}}.jBox-animated-pulseUp{animation:jBox-pulseUp .25s}@keyframes jBox-pulseDown{0%{transform:scale(1)}50%{transform:scale(.85)}100%{transform:scale(1)}}.jBox-animated-pulseDown{animation:jBox-pulseDown .25s}@keyframes jBox-popIn{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}.jBox-animated-popIn{animation:jBox-popIn .25s}@keyframes jBox-popOut{0%{transform:scale(1)}50%{transform:scale(1.1)}100%{transform:scale(0)}}.jBox-animated-popOut{animation:jBox-popOut .25s}@keyframes jBox-fadeIn{0%{opacity:0}100%{opacity:1}}.jBox-animated-fadeIn{animation:jBox-fadeIn .2s}@keyframes jBox-fadeOut{0%{opacity:1}100%{opacity:0}}.jBox-animated-fadeOut{animation:jBox-fadeOut .2s}@keyframes jBox-slideUp{0%{transform:translateY(0)}100%{transform:translateY(-300px);opacity:0}}.jBox-animated-slideUp{animation:jBox-slideUp .4s}@keyframes jBox-slideRight{0%{transform:translateX(0)}100%{transform:translateX(300px);opacity:0}}.jBox-animated-slideRight{animation:jBox-slideRight .4s}@keyframes jBox-slideDown{0%{transform:translateY(0)}100%{transform:translateY(300px);opacity:0}}.jBox-animated-slideDown{animation:jBox-slideDown .4s}@keyframes jBox-slideLeft{0%{transform:translateX(0)}100%{transform:translateX(-300px);opacity:0}}.jBox-animated-slideLeft{animation:jBox-slideLeft .4s}.jBox-Confirm .jBox-content{text-align:center;padding:46px 35px}@media (max-width:500px){.jBox-Confirm .jBox-content{padding:32px 20px}}.jBox-Confirm-footer{height:46px}.jBox-Confirm-button{display:block;float:left;cursor:pointer;text-align:center;width:50%;line-height:46px;height:46px;overflow:hidden;padding:0 10px;transition:color .2s,background-color .2s;box-sizing:border-box}.jBox-Confirm-button-cancel{border-bottom-left-radius:4px;background:#ddd;color:#666}.jBox-Confirm-button-cancel:active,.jBox-Confirm-button-cancel:hover{background:#ccc}.jBox-Confirm-button-cancel:active{box-shadow:inset 0 1px 3px rgba(0,0,0,.2)}.jBox-Confirm-button-submit{border-bottom-right-radius:4px;background:#7d0;color:#fff}.jBox-Confirm-button-submit:active,.jBox-Confirm-button-submit:hover{background:#6c0}.jBox-Confirm-button-submit:active{box-shadow:inset 0 1px 3px rgba(0,0,0,.2)}.jBox-Image .jBox-container{background-color:transparent}.jBox-Image .jBox-content{padding:0;width:100%;height:100%}.jBox-image-container{background:center center no-repeat;position:absolute;width:100%;height:100%;opacity:0}.jBox-image-label-wrapper{position:absolute;top:100%;left:0;right:0;height:40px;z-index:100;display:flex}.jBox-image-label-container{position:relative;flex:1}.jBox-image-label{box-sizing:border-box;position:absolute;left:0;bottom:0;width:100%;text-align:center;color:#fff;padding:8px 12px;font-size:15px;line-height:24px;transition:opacity .36s;opacity:0;z-index:0;pointer-events:none}.jBox-image-label.expanded{background:#000}.jBox-image-label:not(.expanded){text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.jBox-image-label.active{opacity:1;pointer-events:all}@media (max-width:600px){.jBox-image-label{font-size:13px}}.jBox-image-pointer-next,.jBox-image-pointer-prev{flex-shrink:0;width:40px;height:40px;cursor:pointer;opacity:.8;transition:opacity .2s;background:no-repeat center center url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ijc0LjcgMjI0IDE4LjcgMzIiPg0KPHBhdGggZmlsbD0iI2ZmZmZmZiIgZD0iTTkzLDIyNy40TDgwLjQsMjQwTDkzLDI1Mi42YzAuNCwwLjQsMC40LDEuMSwwLDEuNWwtMS42LDEuNmMtMC40LDAuNC0xLDAuNS0xLjUsMEw3NSwyNDAuN2MtMC40LTAuNC0wLjUtMSwwLTEuNWwxNC45LTE0LjljMC40LTAuNCwxLTAuNCwxLjUsMGwxLjYsMS42QzkzLjUsMjI2LjQsOTMuNCwyMjcsOTMsMjI3LjR6Ii8+DQo8L3N2Zz4=);background-size:11px auto;user-select:none;z-index:1}.jBox-image-pointer-next:hover,.jBox-image-pointer-prev:hover{opacity:1}.jBox-image-pointer-next{transform:scaleX(-1)}.jBox-image-counter-container{flex-shrink:0;white-space:nowrap;height:40px;line-height:40px;font-size:13px;color:#fff;text-align:right;display:none}.jBox-image-has-counter .jBox-image-counter-container{display:block}.jBox-overlay.jBox-overlay-Image{background:#000}.jBox-image-not-found{background:#000}.jBox-image-not-found:before{content:'';box-sizing:border-box;display:block;width:80px;height:80px;margin-top:-40px;margin-left:-40px;position:absolute;top:50%;left:50%;border:5px solid #222;border-radius:50%}.jBox-image-not-found:after{content:'';display:block;box-sizing:content-box;z-index:auto;width:6px;height:74px;margin-top:-37px;margin-left:-3px;position:absolute;top:50%;left:50%;background:#222;transform:rotateZ(45deg);transform-origin:50% 50% 0}.jBox-image-download-button-wrapper{position:absolute;top:-40px;right:35px;height:40px;display:flex;cursor:pointer;opacity:.8;transition:opacity .2s}.jBox-image-download-button-wrapper:hover{opacity:1}.jBox-image-download-button-icon{width:40px;height:40px;background:center center no-repeat url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NDAgNjQwIj48cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNNDE2IDI1NnYtMTkyaC0xOTJ2MTkyaC0xNjBsMjU2IDI1NiAyNTYtMjU2aC0xNjB6TTAgNTc2aDY0MHY2NGgtNjQwdi02NHoiPjwvcGF0aD48L3N2Zz4=);background-size:60%}.jBox-image-download-button-text{white-space:nowrap;line-height:40px;padding:0 10px 0 0;color:#fff;font-size:14px}@keyframes jBoxImageLoading{to{transform:rotate(360deg)}}.jBox-image-loading:before{content:'';position:absolute;top:50%;left:50%;width:32px;height:32px;margin-top:-16px;margin-left:-16px;border:4px solid #333;border-bottom-color:#666;animation:jBoxImageLoading 1.2s linear infinite;border-radius:50%}.jBox-Notice{transition:margin .2s}.jBox-Notice .jBox-container{border-radius:4px;box-shadow:inset 1px 1px 0 0 rgba(255,255,255,.25),inset -1px -1px 0 0 rgba(0,0,0,.1)}.jBox-Notice .jBox-content{border-radius:4px;padding:12px 20px}@media (max-width:768px){.jBox-Notice .jBox-content{padding:10px 15px}}@media (max-width:500px){.jBox-Notice .jBox-content{padding:8px 10px}}.jBox-Notice.jBox-hasTitle .jBox-content{padding-top:5px}@media (max-width:500px){.jBox-Notice.jBox-hasTitle .jBox-content{padding-top:0}}.jBox-Notice.jBox-hasTitle .jBox-title{padding:12px 20px 0;font-weight:700}@media (max-width:768px){.jBox-Notice.jBox-hasTitle .jBox-title{padding:10px 15px 0}}@media (max-width:500px){.jBox-Notice.jBox-hasTitle .jBox-title{padding:8px 10px 0}}.jBox-Notice.jBox-closeButton-title .jBox-title{padding-right:55px}.jBox-Notice.jBox-closeButton-title.jBox-hasTitle .jBox-closeButton{width:40px}.jBox-Notice.jBox-Notice-black .jBox-container{color:#fff;background:#000}.jBox-Notice.jBox-Notice-black.jBox-closeButton-title.jBox-hasTitle .jBox-closeButton path,.jBox-Notice.jBox-Notice-black.jBox-closeButton-title.jBox-hasTitle .jBox-closeButton:hover path{fill:#fff}.jBox-Notice.jBox-Notice-gray .jBox-container{color:#222;background:#f6f6f6}.jBox-Notice.jBox-Notice-gray.jBox-closeButton-title.jBox-hasTitle .jBox-closeButton path,.jBox-Notice.jBox-Notice-gray.jBox-closeButton-title.jBox-hasTitle .jBox-closeButton:hover path{fill:#222}.jBox-Notice.jBox-Notice-red .jBox-container{color:#fff;background:#d00}.jBox-Notice.jBox-Notice-red.jBox-closeButton-title.jBox-hasTitle .jBox-closeButton path,.jBox-Notice.jBox-Notice-red.jBox-closeButton-title.jBox-hasTitle .jBox-closeButton:hover path{fill:#fff}.jBox-Notice.jBox-Notice-green .jBox-container{color:#fff;background:#5d0}.jBox-Notice.jBox-Notice-green.jBox-closeButton-title.jBox-hasTitle .jBox-closeButton path,.jBox-Notice.jBox-Notice-green.jBox-closeButton-title.jBox-hasTitle .jBox-closeButton:hover path{fill:#fff}.jBox-Notice.jBox-Notice-blue .jBox-container{color:#fff;background:#49d}.jBox-Notice.jBox-Notice-blue.jBox-closeButton-title.jBox-hasTitle .jBox-closeButton path,.jBox-Notice.jBox-Notice-blue.jBox-closeButton-title.jBox-hasTitle .jBox-closeButton:hover path{fill:#fff}.jBox-Notice.jBox-Notice-yellow .jBox-container{color:#000;background:#fd0}.jBox-Notice.jBox-Notice-yellow.jBox-closeButton-title.jBox-hasTitle .jBox-closeButton path,.jBox-Notice.jBox-Notice-yellow.jBox-closeButton-title.jBox-hasTitle .jBox-closeButton:hover path{fill:#fff}.jBox-NoticeFancy .jBox-content,.jBox-NoticeFancy .jBox-title{padding-left:25px}.jBox-NoticeFancy.jBox-Notice-color .jBox-container{color:#fff;background:#000}.jBox-NoticeFancy.jBox-Notice-color .jBox-container:after{content:'';position:absolute;top:0;left:0;bottom:0;width:8px;border-radius:4px 0 0 4px;background-image:linear-gradient(45deg,rgba(255,255,255,.4) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.4) 50%,rgba(255,255,255,.4) 75%,transparent 75%,transparent);background-size:14px 14px}.jBox-NoticeFancy.jBox-Notice-black .jBox-container:after,.jBox-NoticeFancy.jBox-Notice-gray .jBox-container:after{background-color:#888}.jBox-NoticeFancy.jBox-Notice-red .jBox-container:after{background-color:#e00}.jBox-NoticeFancy.jBox-Notice-green .jBox-container:after{background-color:#6c0}.jBox-NoticeFancy.jBox-Notice-blue .jBox-container:after{background-color:#49d}.jBox-NoticeFancy.jBox-Notice-yellow .jBox-container:after{background-color:#fb0}.jBox-NoticeFancy .jBox-countdown{left:8px;border-radius:0 4px 0 0}.jBox-TooltipBorder .jBox-container,.jBox-TooltipBorder .jBox-pointer:after{border:2px solid #49d}.jBox-TooltipBorder .jBox-pointer:after{width:22px;height:22px}.jBox-TooltipBorder .jBox-pointer-bottom,.jBox-TooltipBorder .jBox-pointer-top{width:34px;height:13px}.jBox-TooltipBorder .jBox-pointer-bottom:after,.jBox-TooltipBorder .jBox-pointer-top:after{left:6px}.jBox-TooltipBorder .jBox-pointer-left,.jBox-TooltipBorder .jBox-pointer-right{width:13px;height:34px}.jBox-TooltipBorder .jBox-pointer-left:after,.jBox-TooltipBorder .jBox-pointer-right:after{top:6px}.jBox-TooltipBorder.jBox-closeButton-box:before{width:28px;height:28px;background:#49d}.jBox-TooltipBorderThick .jBox-container{box-shadow:none;border-radius:8px;border:4px solid #ccc}.jBox-TooltipBorderThick .jBox-pointer:after{box-shadow:none;border:4px solid #ccc;width:24px;height:24px}.jBox-TooltipBorderThick .jBox-pointer-bottom,.jBox-TooltipBorderThick .jBox-pointer-top{width:38px;height:13px}.jBox-TooltipBorderThick .jBox-pointer-left,.jBox-TooltipBorderThick .jBox-pointer-right{width:13px;height:38px}.jBox-TooltipBorderThick.jBox-closeButton-box:before{width:32px;height:32px;background:#ccc}.jBox-TooltipDark .jBox-container{border-radius:4px;background:#000;color:#fff;box-shadow:0 0 6px rgba(0,0,0,.4)}.jBox-TooltipDark .jBox-pointer:after{background:#000}.jBox-TooltipDark .jBox-closeButton{background:#000}.jBox-TooltipDark.jBox-closeButton-box:before{box-shadow:0 0 6px rgba(0,0,0,.4)}.jBox-TooltipDark.jBox-closeButton-box .jBox-closeButton path{fill:#ddd}.jBox-TooltipDark.jBox-closeButton-box .jBox-closeButton:hover path{fill:#fff}.jBox-TooltipDark.jBox-closeButton-box .jBox-closeButton:active path{fill:#bbb}.jBox-TooltipError{pointer-events:none}.jBox-TooltipError .jBox-container{border-radius:2px;background:#d00;color:#fff;font-weight:700;font-size:13px}.jBox-TooltipError .jBox-content{padding:0 10px;line-height:28px}.jBox-TooltipError .jBox-pointer:after{background:#d00;width:20px;height:20px}.jBox-TooltipError .jBox-pointer-bottom,.jBox-TooltipError .jBox-pointer-top{width:22px;height:8px}.jBox-TooltipError .jBox-pointer-left,.jBox-TooltipError .jBox-pointer-right{width:8px;height:22px}.jBox-TooltipError .jBox-pointer-top:after{left:1px;top:6px}.jBox-TooltipError .jBox-pointer-right:after{top:1px;right:6px}.jBox-TooltipError .jBox-pointer-bottom:after{left:1px;bottom:6px}.jBox-TooltipError .jBox-pointer-left:after{top:1px;left:6px}.jBox-TooltipSmall{pointer-events:none}.jBox-TooltipSmall .jBox-container{border-radius:2px}.jBox-TooltipSmall .jBox-content{padding:0 10px;line-height:28px}.jBox-TooltipSmall .jBox-pointer:after{width:20px;height:20px}.jBox-TooltipSmall .jBox-pointer-bottom,.jBox-TooltipSmall .jBox-pointer-top{width:22px;height:8px}.jBox-TooltipSmall .jBox-pointer-left,.jBox-TooltipSmall .jBox-pointer-right{width:8px;height:22px}.jBox-TooltipSmall .jBox-pointer-top:after{left:1px;top:6px}.jBox-TooltipSmall .jBox-pointer-right:after{top:1px;right:6px}.jBox-TooltipSmall .jBox-pointer-bottom:after{left:1px;bottom:6px}.jBox-TooltipSmall .jBox-pointer-left:after{top:1px;left:6px}.jBox-TooltipSmallGray{pointer-events:none}.jBox-TooltipSmallGray .jBox-container{font-size:13px;line-height:24px;border-radius:12px;background-image:linear-gradient(to bottom,#fafafa,#f2f2f2)}.jBox-TooltipSmallGray .jBox-content{padding:0 10px}.jBox-TooltipSmallGray .jBox-pointer:after{width:20px;height:20px}.jBox-TooltipSmallGray .jBox-pointer-bottom,.jBox-TooltipSmallGray .jBox-pointer-top{width:22px;height:8px}.jBox-TooltipSmallGray .jBox-pointer-left,.jBox-TooltipSmallGray .jBox-pointer-right{width:8px;height:22px}.jBox-TooltipSmallGray .jBox-pointer-top:after{background:#fafafa;left:1px;top:6px}.jBox-TooltipSmallGray .jBox-pointer-right:after{top:1px;right:6px}.jBox-TooltipSmallGray .jBox-pointer-bottom:after{background:#f2f2f2;left:1px;bottom:6px}.jBox-TooltipSmallGray .jBox-pointer-left:after{top:1px;left:6px} -------------------------------------------------------------------------------- /static/fontawesome/css/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutjavascript/FoooXus-Fooocus-Extender/9ce42d98a720891335ae99a2c009cfda85e57194/static/fontawesome/css/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /static/fontawesome/css/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutjavascript/FoooXus-Fooocus-Extender/9ce42d98a720891335ae99a2c009cfda85e57194/static/fontawesome/css/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /static/fontawesome/css/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutjavascript/FoooXus-Fooocus-Extender/9ce42d98a720891335ae99a2c009cfda85e57194/static/fontawesome/css/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /static/fontawesome/css/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutjavascript/FoooXus-Fooocus-Extender/9ce42d98a720891335ae99a2c009cfda85e57194/static/fontawesome/css/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /static/fontawesome/css/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutjavascript/FoooXus-Fooocus-Extender/9ce42d98a720891335ae99a2c009cfda85e57194/static/fontawesome/css/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /static/fontawesome/css/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutjavascript/FoooXus-Fooocus-Extender/9ce42d98a720891335ae99a2c009cfda85e57194/static/fontawesome/css/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /static/fontawesome/css/webfonts/fa-v4compatibility.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutjavascript/FoooXus-Fooocus-Extender/9ce42d98a720891335ae99a2c009cfda85e57194/static/fontawesome/css/webfonts/fa-v4compatibility.ttf -------------------------------------------------------------------------------- /static/fontawesome/css/webfonts/fa-v4compatibility.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutjavascript/FoooXus-Fooocus-Extender/9ce42d98a720891335ae99a2c009cfda85e57194/static/fontawesome/css/webfonts/fa-v4compatibility.woff2 -------------------------------------------------------------------------------- /static/fontawesome/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutjavascript/FoooXus-Fooocus-Extender/9ce42d98a720891335ae99a2c009cfda85e57194/static/fontawesome/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /static/fontawesome/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutjavascript/FoooXus-Fooocus-Extender/9ce42d98a720891335ae99a2c009cfda85e57194/static/fontawesome/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /static/fontawesome/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutjavascript/FoooXus-Fooocus-Extender/9ce42d98a720891335ae99a2c009cfda85e57194/static/fontawesome/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /static/fontawesome/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutjavascript/FoooXus-Fooocus-Extender/9ce42d98a720891335ae99a2c009cfda85e57194/static/fontawesome/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /static/fontawesome/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutjavascript/FoooXus-Fooocus-Extender/9ce42d98a720891335ae99a2c009cfda85e57194/static/fontawesome/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /static/fontawesome/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutjavascript/FoooXus-Fooocus-Extender/9ce42d98a720891335ae99a2c009cfda85e57194/static/fontawesome/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /static/fontawesome/webfonts/fa-v4compatibility.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutjavascript/FoooXus-Fooocus-Extender/9ce42d98a720891335ae99a2c009cfda85e57194/static/fontawesome/webfonts/fa-v4compatibility.ttf -------------------------------------------------------------------------------- /static/fontawesome/webfonts/fa-v4compatibility.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutjavascript/FoooXus-Fooocus-Extender/9ce42d98a720891335ae99a2c009cfda85e57194/static/fontawesome/webfonts/fa-v4compatibility.woff2 -------------------------------------------------------------------------------- /static/js/ejs.min.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.ejs=f()}})(function(){var define,module,exports;return function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i1;if(options.cache){if(!filename){throw new Error("cache option requires a filename")}func=exports.cache.get(filename);if(func){return func}if(!hasTemplate){template=fileLoader(filename).toString().replace(_BOM,"")}}else if(!hasTemplate){if(!filename){throw new Error("Internal EJS error: no file name or template "+"provided")}template=fileLoader(filename).toString().replace(_BOM,"")}func=exports.compile(template,options);if(options.cache){exports.cache.set(filename,func)}return func}function tryHandleCache(options,data,cb){var result;if(!cb){if(typeof exports.promiseImpl=="function"){return new exports.promiseImpl(function(resolve,reject){try{result=handleCache(options)(data);resolve(result)}catch(err){reject(err)}})}else{throw new Error("Please provide a callback function")}}else{try{result=handleCache(options)(data)}catch(err){return cb(err)}cb(null,result)}}function fileLoader(filePath){return exports.fileLoader(filePath)}function includeFile(path,options){var opts=utils.shallowCopy(utils.createNullProtoObjWherePossible(),options);opts.filename=getIncludePath(path,opts);if(typeof options.includer==="function"){var includerResult=options.includer(path,opts.filename);if(includerResult){if(includerResult.filename){opts.filename=includerResult.filename}if(includerResult.template){return handleCache(opts,includerResult.template)}}}return handleCache(opts)}function rethrow(err,str,flnm,lineno,esc){var lines=str.split("\n");var start=Math.max(lineno-3,0);var end=Math.min(lines.length,lineno+3);var filename=esc(flnm);var context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" >> ":" ")+curr+"| "+line}).join("\n");err.path=filename;err.message=(filename||"ejs")+":"+lineno+"\n"+context+"\n\n"+err.message;throw err}function stripSemi(str){return str.replace(/;(\s*$)/,"$1")}exports.compile=function compile(template,opts){var templ;if(opts&&opts.scope){if(!scopeOptionWarned){console.warn("`scope` option is deprecated and will be removed in EJS 3");scopeOptionWarned=true}if(!opts.context){opts.context=opts.scope}delete opts.scope}templ=new Template(template,opts);return templ.compile()};exports.render=function(template,d,o){var data=d||utils.createNullProtoObjWherePossible();var opts=o||utils.createNullProtoObjWherePossible();if(arguments.length==2){utils.shallowCopyFromList(opts,data,_OPTS_PASSABLE_WITH_DATA)}return handleCache(opts,template)(data)};exports.renderFile=function(){var args=Array.prototype.slice.call(arguments);var filename=args.shift();var cb;var opts={filename:filename};var data;var viewOpts;if(typeof arguments[arguments.length-1]=="function"){cb=args.pop()}if(args.length){data=args.shift();if(args.length){utils.shallowCopy(opts,args.pop())}else{if(data.settings){if(data.settings.views){opts.views=data.settings.views}if(data.settings["view cache"]){opts.cache=true}viewOpts=data.settings["view options"];if(viewOpts){utils.shallowCopy(opts,viewOpts)}}utils.shallowCopyFromList(opts,data,_OPTS_PASSABLE_WITH_DATA_EXPRESS)}opts.filename=filename}else{data=utils.createNullProtoObjWherePossible()}return tryHandleCache(opts,data,cb)};exports.Template=Template;exports.clearCache=function(){exports.cache.reset()};function Template(text,opts){opts=opts||utils.createNullProtoObjWherePossible();var options=utils.createNullProtoObjWherePossible();this.templateText=text;this.mode=null;this.truncate=false;this.currentLine=1;this.source="";options.client=opts.client||false;options.escapeFunction=opts.escape||opts.escapeFunction||utils.escapeXML;options.compileDebug=opts.compileDebug!==false;options.debug=!!opts.debug;options.filename=opts.filename;options.openDelimiter=opts.openDelimiter||exports.openDelimiter||_DEFAULT_OPEN_DELIMITER;options.closeDelimiter=opts.closeDelimiter||exports.closeDelimiter||_DEFAULT_CLOSE_DELIMITER;options.delimiter=opts.delimiter||exports.delimiter||_DEFAULT_DELIMITER;options.strict=opts.strict||false;options.context=opts.context;options.cache=opts.cache||false;options.rmWhitespace=opts.rmWhitespace;options.root=opts.root;options.includer=opts.includer;options.outputFunctionName=opts.outputFunctionName;options.localsName=opts.localsName||exports.localsName||_DEFAULT_LOCALS_NAME;options.views=opts.views;options.async=opts.async;options.destructuredLocals=opts.destructuredLocals;options.legacyInclude=typeof opts.legacyInclude!="undefined"?!!opts.legacyInclude:true;if(options.strict){options._with=false}else{options._with=typeof opts._with!="undefined"?opts._with:true}this.opts=options;this.regex=this.createRegex()}Template.modes={EVAL:"eval",ESCAPED:"escaped",RAW:"raw",COMMENT:"comment",LITERAL:"literal"};Template.prototype={createRegex:function(){var str=_REGEX_STRING;var delim=utils.escapeRegExpChars(this.opts.delimiter);var open=utils.escapeRegExpChars(this.opts.openDelimiter);var close=utils.escapeRegExpChars(this.opts.closeDelimiter);str=str.replace(/%/g,delim).replace(//g,close);return new RegExp(str)},compile:function(){var src;var fn;var opts=this.opts;var prepended="";var appended="";var escapeFn=opts.escapeFunction;var ctor;var sanitizedFilename=opts.filename?JSON.stringify(opts.filename):"undefined";if(!this.source){this.generateSource();prepended+=' var __output = "";\n'+" function __append(s) { if (s !== undefined && s !== null) __output += s }\n";if(opts.outputFunctionName){if(!_JS_IDENTIFIER.test(opts.outputFunctionName)){throw new Error("outputFunctionName is not a valid JS identifier.")}prepended+=" var "+opts.outputFunctionName+" = __append;"+"\n"}if(opts.localsName&&!_JS_IDENTIFIER.test(opts.localsName)){throw new Error("localsName is not a valid JS identifier.")}if(opts.destructuredLocals&&opts.destructuredLocals.length){var destructuring=" var __locals = ("+opts.localsName+" || {}),\n";for(var i=0;i0){destructuring+=",\n "}destructuring+=name+" = __locals."+name}prepended+=destructuring+";\n"}if(opts._with!==false){prepended+=" with ("+opts.localsName+" || {}) {"+"\n";appended+=" }"+"\n"}appended+=" return __output;"+"\n";this.source=prepended+this.source+appended}if(opts.compileDebug){src="var __line = 1"+"\n"+" , __lines = "+JSON.stringify(this.templateText)+"\n"+" , __filename = "+sanitizedFilename+";"+"\n"+"try {"+"\n"+this.source+"} catch (e) {"+"\n"+" rethrow(e, __lines, __filename, __line, escapeFn);"+"\n"+"}"+"\n"}else{src=this.source}if(opts.client){src="escapeFn = escapeFn || "+escapeFn.toString()+";"+"\n"+src;if(opts.compileDebug){src="rethrow = rethrow || "+rethrow.toString()+";"+"\n"+src}}if(opts.strict){src='"use strict";\n'+src}if(opts.debug){console.log(src)}if(opts.compileDebug&&opts.filename){src=src+"\n"+"//# sourceURL="+sanitizedFilename+"\n"}try{if(opts.async){try{ctor=new Function("return (async function(){}).constructor;")()}catch(e){if(e instanceof SyntaxError){throw new Error("This environment does not support async/await")}else{throw e}}}else{ctor=Function}fn=new ctor(opts.localsName+", escapeFn, include, rethrow",src)}catch(e){if(e instanceof SyntaxError){if(opts.filename){e.message+=" in "+opts.filename}e.message+=" while compiling ejs\n\n";e.message+="If the above error is not helpful, you may want to try EJS-Lint:\n";e.message+="https://github.com/RyanZim/EJS-Lint";if(!opts.async){e.message+="\n";e.message+="Or, if you meant to create an async function, pass `async: true` as an option."}}throw e}var returnedFn=opts.client?fn:function anonymous(data){var include=function(path,includeData){var d=utils.shallowCopy(utils.createNullProtoObjWherePossible(),data);if(includeData){d=utils.shallowCopy(d,includeData)}return includeFile(path,opts)(d)};return fn.apply(opts.context,[data||utils.createNullProtoObjWherePossible(),escapeFn,include,rethrow])};if(opts.filename&&typeof Object.defineProperty==="function"){var filename=opts.filename;var basename=path.basename(filename,path.extname(filename));try{Object.defineProperty(returnedFn,"name",{value:basename,writable:false,enumerable:false,configurable:true})}catch(e){}}return returnedFn},generateSource:function(){var opts=this.opts;if(opts.rmWhitespace){this.templateText=this.templateText.replace(/[\r\n]+/g,"\n").replace(/^\s+|\s+$/gm,"")}this.templateText=this.templateText.replace(/[ \t]*<%_/gm,"<%_").replace(/_%>[ \t]*/gm,"_%>");var self=this;var matches=this.parseTemplateText();var d=this.opts.delimiter;var o=this.opts.openDelimiter;var c=this.opts.closeDelimiter;if(matches&&matches.length){matches.forEach(function(line,index){var closing;if(line.indexOf(o+d)===0&&line.indexOf(o+d+d)!==0){closing=matches[index+2];if(!(closing==d+c||closing=="-"+d+c||closing=="_"+d+c)){throw new Error('Could not find matching close tag for "'+line+'".')}}self.scanLine(line)})}},parseTemplateText:function(){var str=this.templateText;var pat=this.regex;var result=pat.exec(str);var arr=[];var firstPos;while(result){firstPos=result.index;if(firstPos!==0){arr.push(str.substring(0,firstPos));str=str.slice(firstPos)}arr.push(result[0]);str=str.slice(result[0].length);result=pat.exec(str)}if(str){arr.push(str)}return arr},_addOutput:function(line){if(this.truncate){line=line.replace(/^(?:\r\n|\r|\n)/,"");this.truncate=false}if(!line){return line}line=line.replace(/\\/g,"\\\\");line=line.replace(/\n/g,"\\n");line=line.replace(/\r/g,"\\r");line=line.replace(/"/g,'\\"');this.source+=' ; __append("'+line+'")'+"\n"},scanLine:function(line){var self=this;var d=this.opts.delimiter;var o=this.opts.openDelimiter;var c=this.opts.closeDelimiter;var newLineCount=0;newLineCount=line.split("\n").length-1;switch(line){case o+d:case o+d+"_":this.mode=Template.modes.EVAL;break;case o+d+"=":this.mode=Template.modes.ESCAPED;break;case o+d+"-":this.mode=Template.modes.RAW;break;case o+d+"#":this.mode=Template.modes.COMMENT;break;case o+d+d:this.mode=Template.modes.LITERAL;this.source+=' ; __append("'+line.replace(o+d+d,o+d)+'")'+"\n";break;case d+d+c:this.mode=Template.modes.LITERAL;this.source+=' ; __append("'+line.replace(d+d+c,d+c)+'")'+"\n";break;case d+c:case"-"+d+c:case"_"+d+c:if(this.mode==Template.modes.LITERAL){this._addOutput(line)}this.mode=null;this.truncate=line.indexOf("-")===0||line.indexOf("_")===0;break;default:if(this.mode){switch(this.mode){case Template.modes.EVAL:case Template.modes.ESCAPED:case Template.modes.RAW:if(line.lastIndexOf("//")>line.lastIndexOf("\n")){line+="\n"}}switch(this.mode){case Template.modes.EVAL:this.source+=" ; "+line+"\n";break;case Template.modes.ESCAPED:this.source+=" ; __append(escapeFn("+stripSemi(line)+"))"+"\n";break;case Template.modes.RAW:this.source+=" ; __append("+stripSemi(line)+")"+"\n";break;case Template.modes.COMMENT:break;case Template.modes.LITERAL:this._addOutput(line);break}}else{this._addOutput(line)}}if(self.opts.compileDebug&&newLineCount){this.currentLine+=newLineCount;this.source+=" ; __line = "+this.currentLine+"\n"}}};exports.escapeXML=utils.escapeXML;exports.__express=exports.renderFile;exports.VERSION=_VERSION_STRING;exports.name=_NAME;if(typeof window!="undefined"){window.ejs=exports}},{"../package.json":6,"./utils":2,fs:3,path:4}],2:[function(require,module,exports){"use strict";var regExpChars=/[|\\{}()[\]^$+*?.]/g;var hasOwnProperty=Object.prototype.hasOwnProperty;var hasOwn=function(obj,key){return hasOwnProperty.apply(obj,[key])};exports.escapeRegExpChars=function(string){if(!string){return""}return String(string).replace(regExpChars,"\\$&")};var _ENCODE_HTML_RULES={"&":"&","<":"<",">":">",'"':""","'":"'"};var _MATCH_HTML=/[&<>'"]/g;function encode_char(c){return _ENCODE_HTML_RULES[c]||c}var escapeFuncStr="var _ENCODE_HTML_RULES = {\n"+' "&": "&"\n'+' , "<": "<"\n'+' , ">": ">"\n'+' , \'"\': """\n'+' , "\'": "'"\n'+" }\n"+" , _MATCH_HTML = /[&<>'\"]/g;\n"+"function encode_char(c) {\n"+" return _ENCODE_HTML_RULES[c] || c;\n"+"};\n";exports.escapeXML=function(markup){return markup==undefined?"":String(markup).replace(_MATCH_HTML,encode_char)};function escapeXMLToString(){return Function.prototype.toString.call(this)+";\n"+escapeFuncStr}try{if(typeof Object.defineProperty==="function"){Object.defineProperty(exports.escapeXML,"toString",{value:escapeXMLToString})}else{exports.escapeXML.toString=escapeXMLToString}}catch(err){console.warn("Unable to set escapeXML.toString (is the Function prototype frozen?)")}exports.shallowCopy=function(to,from){from=from||{};if(to!==null&&to!==undefined){for(var p in from){if(!hasOwn(from,p)){continue}if(p==="__proto__"||p==="constructor"){continue}to[p]=from[p]}}return to};exports.shallowCopyFromList=function(to,from,list){list=list||[];from=from||{};if(to!==null&&to!==undefined){for(var i=0;i=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up--;up){parts.unshift("..")}}return parts}exports.resolve=function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:process.cwd();if(typeof path!=="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){continue}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=path.charAt(0)==="/"}resolvedPath=normalizeArray(filter(resolvedPath.split("/"),function(p){return!!p}),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."};exports.normalize=function(path){var isAbsolute=exports.isAbsolute(path),trailingSlash=substr(path,-1)==="/";path=normalizeArray(filter(path.split("/"),function(p){return!!p}),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path};exports.isAbsolute=function(path){return path.charAt(0)==="/"};exports.join=function(){var paths=Array.prototype.slice.call(arguments,0);return exports.normalize(filter(paths,function(p,index){if(typeof p!=="string"){throw new TypeError("Arguments to path.join must be strings")}return p}).join("/"))};exports.relative=function(from,to){from=exports.resolve(from).substr(1);to=exports.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i=1;--i){code=path.charCodeAt(i);if(code===47){if(!matchedSlash){end=i;break}}else{matchedSlash=false}}if(end===-1)return hasRoot?"/":".";if(hasRoot&&end===1){return"/"}return path.slice(0,end)};function basename(path){if(typeof path!=="string")path=path+"";var start=0;var end=-1;var matchedSlash=true;var i;for(i=path.length-1;i>=0;--i){if(path.charCodeAt(i)===47){if(!matchedSlash){start=i+1;break}}else if(end===-1){matchedSlash=false;end=i+1}}if(end===-1)return"";return path.slice(start,end)}exports.basename=function(path,ext){var f=basename(path);if(ext&&f.substr(-1*ext.length)===ext){f=f.substr(0,f.length-ext.length)}return f};exports.extname=function(path){if(typeof path!=="string")path=path+"";var startDot=-1;var startPart=0;var end=-1;var matchedSlash=true;var preDotState=0;for(var i=path.length-1;i>=0;--i){var code=path.charCodeAt(i);if(code===47){if(!matchedSlash){startPart=i+1;break}continue}if(end===-1){matchedSlash=false;end=i+1}if(code===46){if(startDot===-1)startDot=i;else if(preDotState!==1)preDotState=1}else if(startDot!==-1){preDotState=-1}}if(startDot===-1||end===-1||preDotState===0||preDotState===1&&startDot===end-1&&startDot===startPart+1){return""}return path.slice(startDot,end)};function filter(xs,f){if(xs.filter)return xs.filter(f);var res=[];for(var i=0;i1){for(var i=1;i (http://fleegix.org)",license:"Apache-2.0",bin:{ejs:"./bin/cli.js"},main:"./lib/ejs.js",jsdelivr:"ejs.min.js",unpkg:"ejs.min.js",repository:{type:"git",url:"git://github.com/mde/ejs.git"},bugs:"https://github.com/mde/ejs/issues",homepage:"https://github.com/mde/ejs",dependencies:{jake:"^10.8.5"},devDependencies:{browserify:"^16.5.1",eslint:"^6.8.0","git-directory-deploy":"^1.5.1",jsdoc:"^4.0.2","lru-cache":"^4.0.1",mocha:"^10.2.0","uglify-js":"^3.3.16"},engines:{node:">=0.10.0"},scripts:{test:"mocha -u tdd"}}},{}]},{},[1])(1)}); 2 | -------------------------------------------------------------------------------- /static/js/help.js: -------------------------------------------------------------------------------- 1 | function viewHelpConfigPreset() { 2 | var converter = new showdown.Converter(); 3 | var text = ` 4 | ## How to manage illustration presets in config.json 5 | 6 | After the first lines of "config.json" file, dedicated to general configuration, you will find the 3 parts of illustration presets. 7 | 8 | First one is the preset arrays of **style-illustrations**. 9 | 10 | Each element defines a preset with : 11 | 12 | { 13 | "name": "s1", 14 | "description": "Illustration #1 for all styles, A cat", 15 | "metadata": { 16 | "Base Model": "juggernautXL_v8Rundiffusion.safetensors",foooxusQueue 17 | "Prompt": "A cat", 18 | "Seed": "314159" 19 | } 20 | }, 21 | 22 | A preset contains : 23 | - a name 24 | - a description 25 | - a metadata object that describes how the picture must be generated 26 | 27 | 28 | Metadata part may contain that parameters : 29 | 30 | { 31 | "name": "s1", 32 | "description": "Illustration #1 for all styles, A cat", 33 | "metadata": { 34 | "Base Model": "juggernautXL_v8Rundiffusion.safetensors", 35 | "Prompt": "A cat", 36 | "Seed": "314159", 37 | "ADM Guidance": "(1.5, 0.8, 0.3)", 38 | "Sampler": "dpmpp_2m_sde_gpu", 39 | "Scheduler": "karras", 40 | "Performance": "Quality", 41 | "Sharpness": 5, 42 | "Guidance Scale": 2, 43 | "Refiner Model": "juggernautXL_v8Rundiffusion.safetensors", 44 | "Refiner Switch": 0.5, 45 | "Lora 1": "sd_xl_offset_example", 46 | "Weight Lora 1": 1 47 | } 48 | }, 49 | 50 | Add, edit or remove presets as you like. 51 | 52 | **You must restart FoooXus app to take into account these file updates** 53 | 54 | Models and Loras presets below in the file are similar 55 | 56 | 57 | ` 58 | 59 | 60 | openHelpModal(converter.makeHtml(text)) 61 | } 62 | 63 | 64 | 65 | function viewHelpConfigFile() { 66 | var converter = new showdown.Converter(); 67 | var text = ` 68 | ## How to fill in config.json 69 | "config.json" file is auto generated at first launch with a template. 70 | You must configure it with two important values: 71 | 72 | 1- **fooocus-directory** contains the full path of your Fooocus installation 73 | Note that you **MUST** double the "\\\\" in each folder 74 | 75 | 2- **fooocus-address** contains the http address of Fooocus web UI. 76 | By default, the address is "127.0.0.1:7865" and should work 77 | 78 | **You must restart FoooXus app to take into account these file updates** 79 | 80 | The next part of "config.json" file is dedicated to illustration presets. 81 | ` 82 | 83 | 84 | openHelpModal(converter.makeHtml(text)) 85 | 86 | } 87 | 88 | 89 | function openHelpModal(html) { 90 | var myModal = new jBox('Modal', { 91 | content: html 92 | }); 93 | myModal.open(); 94 | 95 | } -------------------------------------------------------------------------------- /static/js/jBox.all.min.js: -------------------------------------------------------------------------------- 1 | function jBoxWrapper(j){function h(t,i){return this.options={id:null,width:"auto",height:"auto",minWidth:null,minHeight:null,maxWidth:null,maxHeight:null,responsiveWidth:!0,responsiveHeight:!0,responsiveMinWidth:100,responsiveMinHeight:100,attach:null,trigger:"click",preventDefault:!1,content:null,getContent:null,title:null,getTitle:null,footer:null,isolateScroll:!0,ajax:{url:null,data:"",reload:!1,getURL:"data-url",getData:"data-ajax",setContent:!0,loadingClass:!0,spinner:!0,spinnerDelay:300,spinnerReposition:!0},cancelAjaxOnClose:!0,target:null,position:{x:"center",y:"center"},outside:null,offset:0,attributes:{x:"left",y:"top"},fixed:!1,adjustPosition:!0,adjustTracker:!1,adjustDistance:5,reposition:!0,repositionOnOpen:!0,repositionOnContent:!0,holdPosition:!0,pointer:!1,pointTo:"target",fade:180,animation:null,theme:"Default",addClass:null,overlay:!1,overlayClass:null,zIndex:1e4,delayOpen:0,delayClose:0,closeOnEsc:!1,closeOnClick:!1,closeOnMouseleave:!1,closeButton:!1,appendTo:j("body"),createOnInit:!1,blockScroll:!1,blockScrollAdjust:!0,draggable:!1,dragOver:!0,autoClose:!1,delayOnHover:!1,showCountdown:!1,preloadAudio:!0,audio:null,volume:100,onInit:null,onAttach:null,onPosition:null,onCreated:null,onOpen:null,onOpenComplete:null,onClose:null,onCloseComplete:null,onDragStart:null,onDragEnd:null},this._pluginOptions={Tooltip:{getContent:"title",trigger:"mouseenter",position:{x:"center",y:"top"},outside:"y",pointer:!0},Mouse:{responsiveWidth:!1,responsiveHeight:!1,adjustPosition:"flip",target:"mouse",trigger:"mouseenter",position:{x:"right",y:"bottom"},outside:"xy",offset:5},Modal:{target:j(window),fixed:!0,blockScroll:!0,closeOnEsc:!0,closeOnClick:"overlay",closeButton:!0,overlay:!0,animation:"zoomIn"}},this.options=j.extend(!0,this.options,this._pluginOptions[t]||h._pluginOptions[t],i),"string"==j.type(t)&&(this.type=t),this.isTouchDevice=function(){var t=" -webkit- -moz- -o- -ms- ".split(" ");if("ontouchstart"in window||window.DocumentTouch&&document instanceof DocumentTouch)return!0;var i,t=["(",t.join("touch-enabled),("),"heartz",")"].join("");return i=t,window.matchMedia(i).matches}(),this.isTouchDevice&&"mouseenter"===this.options.trigger&&!1===this.options.closeOnClick&&(this.options.closeOnClick="body"),this._fireEvent=function(t,i){this.options["_"+t]&&this.options["_"+t].bind(this)(i),this.options[t]&&this.options[t].bind(this)(i)},null===this.options.id&&(this.options.id="jBox"+h._getUniqueID()),this.id=this.options.id,("center"==this.options.position.x&&"x"==this.options.outside||"center"==this.options.position.y&&"y"==this.options.outside)&&(this.options.outside=null),"target"!=this.options.pointTo||this.options.outside&&"xy"!=this.options.outside||(this.options.pointer=!1),"object"!=j.type(this.options.offset)?this.options.offset={x:this.options.offset,y:this.options.offset}:this.options.offset=j.extend({x:0,y:0},this.options.offset),"object"!=j.type(this.options.adjustDistance)?this.options.adjustDistance={top:this.options.adjustDistance,right:this.options.adjustDistance,bottom:this.options.adjustDistance,left:this.options.adjustDistance}:this.options.adjustDistance=j.extend({top:5,left:5,right:5,bottom:5},this.options.adjustDistance),this.outside=!(!this.options.outside||"xy"==this.options.outside)&&this.options.position[this.options.outside],this.align=this.outside||("center"!=this.options.position.y&&"number"!=j.type(this.options.position.y)?this.options.position.x:"center"!=this.options.position.x&&"number"!=j.type(this.options.position.x)?this.options.position.y:this.options.attributes.x),h.zIndexMax=Math.max(h.zIndexMax||0,"auto"===this.options.zIndex?1e4:this.options.zIndex),"auto"===this.options.zIndex&&(this.adjustZIndexOnOpen=!0,this.options.zIndex=h.zIndexMax+=2,this.trueModal=this.options.overlay),this._getOpp=function(t){return{left:"right",right:"left",top:"bottom",bottom:"top",x:"y",y:"x"}[t]},this._getXY=function(t){return{left:"x",right:"x",top:"y",bottom:"y",center:"x"}[t]},this._getTL=function(t){return{left:"left",right:"left",top:"top",bottom:"top",center:"left",x:"left",y:"top"}[t]},this._getInt=function(t,i){return"auto"==t?"auto":t&&"string"==j.type(t)&&"%"==t.slice(-1)?j(window)["height"==i?"innerHeight":"innerWidth"]()*parseInt(t.replace("%",""))/100:t},this._createSVG=function(t,i){var o=document.createElementNS("http://www.w3.org/2000/svg",t);return j.each(i,function(t,i){o.setAttribute(i[0],i[1]||"")}),o},this._isolateScroll=function(e){e&&e.length&&e.on("DOMMouseScroll.jBoxIsolateScroll mousewheel.jBoxIsolateScroll",function(t){var i=t.wheelDelta||t.originalEvent&&t.originalEvent.wheelDelta||-t.detail,o=0<=this.scrollTop+e.outerHeight()-this.scrollHeight,s=this.scrollTop<=0;(i<0&&o||0",{id:this.id,class:"jBox-wrapper"+(this.type?" jBox-"+this.type:"")+(this.options.theme?" jBox-"+this.options.theme:"")+(this.options.addClass?" "+this.options.addClass:"")}).css({position:this.options.fixed?"fixed":"absolute",display:"none",opacity:0,zIndex:this.options.zIndex}).data("jBox",this),this.options.closeOnMouseleave&&this.wrapper.on("mouseleave",function(t){!this.source||t.relatedTarget!=this.source[0]&&-1===j.inArray(this.source[0],j(t.relatedTarget).parents("*"))&&this.close()}.bind(this)),"box"==this.options.closeOnClick&&this.wrapper.on("click tap",function(){this.close({ignoreDelay:!0})}.bind(this)),this.container=j('
').appendTo(this.wrapper),this.content=j('
').appendTo(this.container),this.options.footer&&(this.footer=j('