diff --git a/official-templates/better-ai-launcher/.dockerignore b/official-templates/better-ai-launcher/.dockerignore new file mode 100644 index 0000000..566bea3 --- /dev/null +++ b/official-templates/better-ai-launcher/.dockerignore @@ -0,0 +1,31 @@ +**/.DS_Store +**/.dockerenv +**/filebrowser.db +**/docker-bake.hcl +**/__pycache__ +**/.venv +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/bin +**/charts +**/docker-compose* +**/compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md diff --git a/official-templates/better-ai-launcher/.gitignore b/official-templates/better-ai-launcher/.gitignore new file mode 100644 index 0000000..eef3538 --- /dev/null +++ b/official-templates/better-ai-launcher/.gitignore @@ -0,0 +1,5 @@ +**/.DS_Store +**/.dockerenv +**/__pycache__ +**/.venv +**/.env diff --git a/official-templates/better-ai-launcher/.vscode/launch.json b/official-templates/better-ai-launcher/.vscode/launch.json new file mode 100644 index 0000000..101b4d6 --- /dev/null +++ b/official-templates/better-ai-launcher/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + "configurations": [ + { + "name": "Docker: Python - Flask", + "type": "docker", + "request": "launch", + "preLaunchTask": "docker-run: debug", + "python": { + "pathMappings": [ + { + "localRoot": "${workspaceFolder}", + "remoteRoot": "/app" + } + ], + "projectType": "flask" + } + }, + { // this whole section was added manually to add "justMyCode": false + "name": "Python: Debug Tests", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "purpose": ["debug-test"], + "console": "integratedTerminal", + "justMyCode": false + } + ] +} \ No newline at end of file diff --git a/official-templates/better-ai-launcher/.vscode/tasks.json b/official-templates/better-ai-launcher/.vscode/tasks.json new file mode 100644 index 0000000..f07dd81 --- /dev/null +++ b/official-templates/better-ai-launcher/.vscode/tasks.json @@ -0,0 +1,72 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "docker-build", + "label": "docker-build", + "platform": "python", + "dockerBuild": { + "tag": "madiator2011/better-launcher:dev", + "dockerfile": "${workspaceFolder}/Dockerfile", + "context": "${workspaceFolder}", + "pull": true + } + }, + { + "type": "docker-run", + "label": "docker-run: debug", + "dependsOn": [ + "docker-build" + ], + "dockerRun": { + "containerName": "madiator2011-better-launcher", // no "/" allowed here for container name + "image": "madiator2011/better-launcher:dev", + "envFiles": ["${workspaceFolder}/.env"], // pass additional env-vars (hf_token, civitai token, ssh public-key) from ".env" file to container + "env": { // this ENV vars go into the docker container to support local debugging + "LOCAL_DEBUG": "True", // change app to localhost Urls and local Websockets (unsecured) + "FLASK_APP": "app/app.py", + "FLASK_ENV": "development", // changed from "production" + "GEVENT_SUPPORT": "True" // gevent monkey-patching is being used, enable gevent support in the debugger + // "FLASK_DEBUG": "0" // "1" allows debugging in Chrome, but then VSCode debugger not works + }, + "volumes": [ + { + "containerPath": "/app", + "localPath": "${workspaceFolder}" // the "/app" folder (and sub-folders) will be mapped locally for debugging and hot-reload + }, + { + "containerPath": "/workspace", + // TODO: create the below folder before you run! + "localPath": "${userHome}/Projects/Docker/Madiator/workspace" + } + ], + "ports": [ + { + "containerPort": 7222, // main Flask app port "App-Manager" + "hostPort": 7222 + }, + { + "containerPort": 8181, // File-Browser + "hostPort": 8181 + }, + { + "containerPort": 7777, // VSCode-Server + "hostPort": 7777 + } + ] + }, + "python": { + "args": [ + "run", + // "--no-debugger", // disabled to support VSCode debugger + // "--no-reload", // disabled to support hot-reload + "--host", + "0.0.0.0", + "--port", + "7222" + ], + "module": "flask" + } + } + ] +} \ No newline at end of file diff --git a/official-templates/better-ai-launcher/Dockerfile b/official-templates/better-ai-launcher/Dockerfile index 4fedb34..ae3cdab 100644 --- a/official-templates/better-ai-launcher/Dockerfile +++ b/official-templates/better-ai-launcher/Dockerfile @@ -1,13 +1,20 @@ # Use the specified base image -FROM madiator2011/better-base:cuda12.1 as base + + # lutzapps - use uppercase "AS" +FROM madiator2011/better-base:cuda12.1 AS base + +# lutzapps - prepare for local developement and debugging +# needed to change the ORDER of "apt-get commands" and move the "update-alternatives" for python3 +# AFTER the "apt-get remove -y python3.10" cmd, OTHERWISE the symlink to python3 +# is broken in the image and the VSCode debugger could not exec "python3" as CMD overwrite # Install Python 3.11, set it as default, and remove Python 3.10 RUN apt-get update && \ apt-get install -y python3.11 python3.11-venv python3.11-dev python3.11-distutils aria2 git \ pv git rsync zstd libtcmalloc-minimal4 bc nginx ffmpeg && \ + apt-get remove -y python3.10 python3.10-minimal libpython3.10-minimal libpython3.10-stdlib && \ update-alternatives --install /usr/bin/python python /usr/bin/python3.11 1 && \ update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 1 && \ - apt-get remove -y python3.10 python3.10-minimal libpython3.10-minimal libpython3.10-stdlib && \ apt-get autoremove -y && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* @@ -23,46 +30,64 @@ WORKDIR /app # Copy the requirements file COPY requirements.txt . -# Install the Python dependencies +# Install the Python dependencies (as "managed pip") RUN python3.11 -mpip install --no-cache-dir -r requirements.txt # Copy the application code -COPY . . +# lutzapps - only copy the "app" folder and not the root (".") to avoid cluttering the docker container with src-files +COPY app . # Install File Browser RUN curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash -# Set environment variables for production +# Set environment variables for developent/production +# overwrite this ENV in "tasks.json" docker run "env" section or overwrite in your ".env" file to "development" ENV FLASK_ENV=production + +# gevent monkey-patching is being used, enable gevent support in the debugger with GEVENT_SUPPORT=True +# add this ENV in "tasks.json" docker run "env" section or populate in your ".env" file +# ENV GEVENT_SUPPORT=True + +# lutzapps - keep Python from generating .pyc files in the container +ENV PYTHONDONTWRITEBYTECODE=1 + +# Turns off buffering for easier container logging ENV PYTHONUNBUFFERED=1 + ENV APP_PATH=/app/app.py # Expose the port Nginx will listen on EXPOSE 7222 +# lutzapps - moved the 4 static assets (3x PNG, 1x MP3) into the /app/static" folder for cleaner view +# lutzapps - added a "app/tests" folder with script and testdata and readme file +# lutzapps - grouped NGINX files in a sub-folder for cleaner view + # Copy the README.md -COPY README.md /usr/share/nginx/html/README.md +COPY nginx/README.md /usr/share/nginx/html/README.md # NGINX configuration -COPY nginx.conf /etc/nginx/nginx.conf -COPY readme.html /usr/share/nginx/html/readme.html - -# Create a directory for static files -RUN mkdir -p /app/static - -# Copy the Poddy animation files to the static directory -COPY poddy.png /app/static/poddy.png -COPY mushroom.png /app/static/mushroom.png -COPY snake.png /app/static/snake.png -COPY poddy-song.mp3 /app/static/poddy-song.mp3 +COPY nginx/nginx.conf /etc/nginx/nginx.conf +COPY nginx/readme.html /usr/share/nginx/html/readme.html # Copy all necessary scripts COPY --from=scripts start.sh / -COPY pre_start.sh /pre_start.sh -RUN chmod +x /pre_start.sh /start.sh +# --from=scripts is defined as a "shared" location in "docker-bake.hcl" in the "contexts" dict: +# scripts = "../../container-template" +# the local "start.sh" is (intentionally) empty +# to build all from *one* location, copy "start.sh" here into the project workspace folder first +# cp ../../container-template/scripts/start.sh start.sh +#COPY start.sh / + +COPY pre_start.sh / +# lutzapps - add execution flags to added "/app/tests/populate_testdata.sh" +RUN chmod +x /pre_start.sh /start.sh /app/tests/populate_testdata.sh + # Copy the download_venv.sh script and make it executable COPY download_venv.sh /app/download_venv.sh RUN chmod +x /app/download_venv.sh # CMD +# During debugging, this entry point will be overridden as "python3". +# For more information, please refer to https://aka.ms/vscode-docker-python-debug CMD ["/start.sh"] \ No newline at end of file diff --git a/official-templates/better-ai-launcher/README-Development.txt b/official-templates/better-ai-launcher/README-Development.txt new file mode 100644 index 0000000..523717f --- /dev/null +++ b/official-templates/better-ai-launcher/README-Development.txt @@ -0,0 +1,62 @@ +For local development of this image, you can run the image with 3 options: +- Docker Compose (production or Development) +- VS Code (F5 Debugging) + + +PREPARE ENV variables: + +Both docker-compose YML files and the VS-Code "tasks.json" configuration file +pass a ".env" file into the container, to set User-specific ENV variables. +Edit the supplied "env.txt" template file with your values, and then rename it to ".env". +The ".env" file is in the ".gitignore" file to avoid unwanted secret-sharing with GitHub! + + +To build and run the image with DOCKER COMPOSE: + +To run in "production": +Use the command "docker compose up": + That runs the container without debugger, but enables localhost and a workspace bind. + It uses the default "docker-compose.yml", which should be ADJUSTED to your workspace bind location. + This YML file binds all application ports (7222, 8181, 7777) all browsable from localhost, + and your SSH public key will be configured and be usable! +It uses 1 docker bind mount: +- "/workspace" can be bound to any location on your local machine (ADJUST the location in the YML file) +This option uses the default entry CMD "start.sh" as defined in the "Dockerfile". + +To run in "development": +Use the command "docker compose -f docker-compose.debug.yml" + That runs the container with a python debugger (debugpy) attached, enables localhost browsing, + and binds the workspace from a local folder of your choice. +It uses 2 docker bind mounts: +- "/app" to mirror your app into the container (supports hot-reload) and debugging against your source files +- "/workspace" can be bound to any location on your local machine (ADJUST the location in the YML file) + +This second debug YML configuration is configured with the same settings that are used for +VS-Code debugging with the VS-Code Docker Extension with Run -> Debug (F5). +BUT YOU NEED TO ATTACH THE DEBUGGER YOURSELF +Use the debugpy.wait_for_client() function to block program execution until the client is attached. +See more at https://github.com/microsoft/debugpy + + +It is much easier to build and run the image with VS-CODE (Run -> Debug F5): + +The VS-Code Docker Extension uses 2 files in the hidden folder ".vscode": +- launch.json +- tasks.json + +The "tasks.json" file is the file with most configuration settings +which basically mirrors to the "docker-compose.debug.yml" + +ALL these 5 mention files (env.txt, docker-compose.yml, docker-compose.debug.yml, +launch.json, tasks.json, en.txt) are heavily commented. + + +NOTES +Both debugging options with "docker-compose.debug.yml", or VS-Code Docker Extenstions "tasks.json" +replace the CMD entry point of the Dockerfile to "python3", instead of "start.sh" !!! + +That means that the apps on port 8181 (File-Browser) and 7777 (VSCode-Server) are +NOT available during debugging, neither will your SSH public key be configured and is not usable, +as these configuration things happen in "start.sh", but they are not needed during development. + +Only the NGINX server and the Flask module will be started during debugging. \ No newline at end of file diff --git a/official-templates/better-ai-launcher/app.py b/official-templates/better-ai-launcher/app/app.py similarity index 81% rename from official-templates/better-ai-launcher/app.py rename to official-templates/better-ai-launcher/app/app.py index 49c9679..0cc72de 100644 --- a/official-templates/better-ai-launcher/app.py +++ b/official-templates/better-ai-launcher/app/app.py @@ -17,8 +17,44 @@ from utils.filebrowser_utils import configure_filebrowser, start_filebrowser, st from utils.app_utils import ( run_app, update_process_status, check_app_directories, get_app_status, force_kill_process_by_name, update_webui_user_sh, save_install_status, - get_install_status, download_and_unpack_venv, fix_custom_nodes, is_process_running, install_app, update_model_symlinks + get_install_status, download_and_unpack_venv, fix_custom_nodes, is_process_running, install_app #, update_model_symlinks ) +# lutzapps - CHANGE #1 +LOCAL_DEBUG = os.environ.get('LOCAL_DEBUG', 'False') # support local browsing for development/debugging + +# use the new "utils.shared_models" module for app model sharing +from utils.shared_models import ( + update_model_symlinks, # main WORKER function (file/folder symlinks, Fix/remove broken symlinks, pull back local app models into shared) + SHARED_MODELS_DIR, SHARED_MODEL_FOLDERS, SHARED_MODEL_FOLDERS_FILE, ensure_shared_models_folders, + APP_INSTALL_DIRS, APP_INSTALL_DIRS_FILE, init_app_install_dirs, # APP_INSTALL_DIRS dict/file/function + MAP_APPS, sync_with_app_configs_install_dirs, # internal MAP_APPS dict and sync function + SHARED_MODEL_APP_MAP, SHARED_MODEL_APP_MAP_FILE, init_shared_model_app_map, # SHARED_MODEL_APP_MAP dict/file/function + write_dict_to_jsonfile, read_dict_from_jsonfile, PrettyDICT # JSON helper functions +) +# the "update_model_symlinks()" function replaces the app.py function with the same same +# and redirects to same function name "update_model_symlinks()" in the new "utils.shared_models" module +# +# this function does ALL the link management, including deleting "stale" symlinks, +# so the "recreate_symlinks()" function will be also re-routed to the +# "utils.shared_models.update_model_symlinks()" function (see CHANGE #3a and CHANGE #3b) + +# the "ensure_shared_models_folders()" function will be called from app.py::create_shared_folders(), +# and replaces this function (see CHANGE #3) + +# the "init_app_install_dirs() function initializes the +# global module 'APP_INSTALL_DIRS' dict: { 'app_name': 'app_installdir' } +# which does a default mapping from app code or (if exists) from external JSON 'APP_INSTALL_DIRS_FILE' file +# NOTE: this APP_INSTALL_DIRS dict is temporary synced with the 'app_configs' dict (see next) + +# the "sync_with_app_configs_install_dirs() function syncs the 'APP_INSTALL_DIRS' dict's 'app_installdir' entries +# from the 'app_configs' dict's 'app_path' entries and uses the MAP_APPS dict for this task +# NOTE: this syncing is a temporary solution, and needs to be better integrated later + +# the "init_shared_model_app_map()" function initializes the +# global module 'SHARED_MODEL_APP_MAP' dict: 'model_type' -> 'app_name:app_model_dir' (relative path) +# which does a default mapping from app code or (if exists) from external JSON 'SHARED_MODEL_APP_MAP_FILE' file + + from utils.websocket_utils import send_websocket_message, active_websockets from utils.app_configs import get_app_configs, add_app_config, remove_app_config, app_configs from utils.model_utils import download_model, check_civitai_url, check_huggingface_url, SHARED_MODELS_DIR, format_size @@ -91,6 +127,11 @@ def index(): pod_id=RUNPOD_POD_ID, RUNPOD_PUBLIC_IP=os.environ.get('RUNPOD_PUBLIC_IP'), RUNPOD_TCP_PORT_22=os.environ.get('RUNPOD_TCP_PORT_22'), + + # lutzapps - CHANGE #2 - allow localhost Url for unsecure "http" and "ws" WebSockets protocol, + # according to LOCAL_DEBUG ENV var (used 3x in "index.html" changes) + enable_unsecure_localhost=os.environ.get('LOCAL_DEBUG'), + settings=settings, current_auth_method=current_auth_method, ssh_password=ssh_password, @@ -297,7 +338,20 @@ def remove_existing_app_config(app_name): return jsonify({'status': 'success', 'message': f'App {app_name} removed successfully'}) return jsonify({'status': 'error', 'message': f'App {app_name} not found'}) +# unused function +def obsolate_update_model_symlinks(): + # lutzapps - CHANGE #3 - use the new "shared_models" module for app model sharing + # remove this whole now unused function + return "replaced by utils.shared_models.update_model_symlinks()" + +# modified function def setup_shared_models(): + # lutzapps - CHANGE #4 - use the new "shared_models" module for app model sharing + jsonResult = update_model_symlinks() + + return SHARED_MODELS_DIR # shared_models_dir is now owned and managed by the "shared_models" utils module + # remove below unused code + shared_models_dir = '/workspace/shared_models' model_types = ['Stable-diffusion', 'VAE', 'Lora', 'ESRGAN'] @@ -326,7 +380,12 @@ def setup_shared_models(): return shared_models_dir -def update_model_symlinks(): +# unused function +def obsolate_update_model_symlinks(): + # lutzapps - CHANGE #5 - use the new "shared_models" module for app model sharing + # remove this whole now unused function + return "replaced by utils.shared_models.update_model_symlinks()" + shared_models_dir = '/workspace/shared_models' apps = { 'stable-diffusion-webui': '/workspace/stable-diffusion-webui/models', @@ -367,7 +426,7 @@ def update_model_symlinks(): print("Model symlinks updated.") def update_symlinks_periodically(): - while True: + while True: update_model_symlinks() time.sleep(300) # Check every 5 minutes @@ -375,7 +434,12 @@ def start_symlink_update_thread(): thread = threading.Thread(target=update_symlinks_periodically, daemon=True) thread.start() -def recreate_symlinks(): +# unused function +def obsolate_recreate_symlinks(): + # lutzapps - CHANGE #6 - use the new "shared_models" module for app model sharing + # remove this whole now unused function + return "replaced by utils.shared_models.update_model_symlinks()" + shared_models_dir = '/workspace/shared_models' apps = { 'stable-diffusion-webui': '/workspace/stable-diffusion-webui/models', @@ -421,16 +485,29 @@ def recreate_symlinks(): return "Symlinks recreated successfully." +# modified function @app.route('/recreate_symlinks', methods=['POST']) def recreate_symlinks_route(): + # lutzapps - CHANGE #7 - use the new "shared_models" module for app model sharing + jsonResult = update_model_symlinks() + + return jsonResult + # remove below unused code + try: message = recreate_symlinks() return jsonify({'status': 'success', 'message': message}) except Exception as e: return jsonify({'status': 'error', 'message': str(e)}) +# modified function @app.route('/create_shared_folders', methods=['POST']) def create_shared_folders(): + # lutzapps - CHANGE #8 - use the new "shared_models" module for app model sharing + jsonResult = ensure_shared_models_folders() + return jsonResult + # remove below unused code + try: shared_models_dir = '/workspace/shared_models' model_types = ['Stable-diffusion', 'Lora', 'embeddings', 'VAE', 'hypernetworks', 'aesthetic_embeddings', 'controlnet', 'ESRGAN'] @@ -487,6 +564,35 @@ def get_civitai_token_route(): token = load_civitai_token() return jsonify({'token': token}) +# lutzapps - CHANGE #9 - return model_types to populate the Download manager Select Option +# new function to support the "Model Downloader" with the 'SHARED_MODEL_FOLDERS' dictionary +@app.route('/get_model_types', methods=['GET']) +def get_model_types_route(): + model_types_dict = {} + + # check if the SHARED_MODELS_DIR exists at the "/workspace" location! + # that only happens AFTER the the user clicked the "Create Shared Folders" button + # on the "Settings" Tab of the app's WebUI! + # to reload existing SHARED_MODEL_FOLDERS into the select options dropdown list, + # we send a WebSockets message to "index.html" + + if not os.path.exists(SHARED_MODELS_DIR): + # return an empty model_types_dict, so the "Download Manager" does NOT get + # the already in-memory SHARED_MODEL_FOLDERS code-generated default dict + # BEFORE the workspace folders in SHARED_MODELS_DIR exists + return model_types_dict + + i = 0 + for model_type, model_type_description in SHARED_MODEL_FOLDERS.items(): + model_types_dict[i] = { + 'modelfolder': model_type, + 'desc': model_type_description + } + + i = i + 1 + + return model_types_dict + @app.route('/download_model', methods=['POST']) def download_model_route(): url = request.json.get('url') diff --git a/official-templates/better-ai-launcher/gunicorn.conf.py b/official-templates/better-ai-launcher/app/gunicorn.conf.py similarity index 100% rename from official-templates/better-ai-launcher/gunicorn.conf.py rename to official-templates/better-ai-launcher/app/gunicorn.conf.py diff --git a/official-templates/better-ai-launcher/mushroom.png b/official-templates/better-ai-launcher/app/static/mushroom.png similarity index 100% rename from official-templates/better-ai-launcher/mushroom.png rename to official-templates/better-ai-launcher/app/static/mushroom.png diff --git a/official-templates/better-ai-launcher/poddy-song.mp3 b/official-templates/better-ai-launcher/app/static/poddy-song.mp3 similarity index 100% rename from official-templates/better-ai-launcher/poddy-song.mp3 rename to official-templates/better-ai-launcher/app/static/poddy-song.mp3 diff --git a/official-templates/better-ai-launcher/poddy.png b/official-templates/better-ai-launcher/app/static/poddy.png similarity index 100% rename from official-templates/better-ai-launcher/poddy.png rename to official-templates/better-ai-launcher/app/static/poddy.png diff --git a/official-templates/better-ai-launcher/snake.png b/official-templates/better-ai-launcher/app/static/snake.png similarity index 100% rename from official-templates/better-ai-launcher/snake.png rename to official-templates/better-ai-launcher/app/static/snake.png diff --git a/official-templates/better-ai-launcher/templates/index.html b/official-templates/better-ai-launcher/app/templates/index.html similarity index 95% rename from official-templates/better-ai-launcher/templates/index.html rename to official-templates/better-ai-launcher/app/templates/index.html index cacb3af..9b319c7 100644 --- a/official-templates/better-ai-launcher/templates/index.html +++ b/official-templates/better-ai-launcher/app/templates/index.html @@ -1211,11 +1211,11 @@
- + + + + @@ -1302,7 +1302,23 @@ let currentLogAppName = null; const podId = '{{ pod_id }}'; const WS_PORT = 7222; // This is the Nginx port - const WS_URL = `wss://${podId}-${WS_PORT}.proxy.runpod.net/ws`; + + + // *** lutzapps - Change #2 - support to run locally at http://localhost:${WS_PORT} (3 locations in "index.html") + const enable_unsecure_localhost = '{{ enable_unsecure_localhost }}'; + + // default is to use the "production" WeckSockets CloudFlare URL + // NOTE: ` (back-ticks) are used here for template literals + var WS_URL = `wss://${podId}-${WS_PORT}.proxy.runpod.net/ws`; // need to be declared as var + + if (`${enable_unsecure_localhost}` === 'True') { // value of LOCAL_DEBUG ENV var + // make sure to use "ws" Protocol (insecure) instead of "wss" (WebSockets Secure) for localhost, + // otherwise you will get the 'loadingOverlay' stay and stays on screen with ERROR: + // "WebSocket disconnected. Attempting to reconnect..." blocking the webpage http://localhost:7222 + WS_URL = `ws://localhost:${WS_PORT}/ws`; // localhost WS (unsecured) + //alert(`Running locally with WS_URL=${WS_URL}`); + } + let socket; let reconnectAttempts = 0; @@ -1446,7 +1462,14 @@ } function openApp(appKey, port) { - const url = `https://${podId}-${port}.proxy.runpod.net/`; + // *** lutzapps - Change #3 - support to run locally + // NOTE: ` (back-ticks) are used here for template literals + var url = `https://${podId}-${port}.proxy.runpod.net/`; // need to be declared as var + if (`${enable_unsecure_localhost}` === 'True') { + url = `http://localhost:${port}/`; // remove runpod.net proxy + //alert(`openApp URL=${url}`); + } + window.open(url, '_blank'); } @@ -1510,6 +1533,41 @@ updateStatus(); setInterval(updateStatus, 5000); setInterval(updateLogs, 1000); + + initModelTypes(); // lutzapps - Change #4a - initialize the ModelTypes for the "Model Downloader" + } + + // lutzapps - Change #4b - populate modeltype select options from shared_models + async function initModelTypes() { + const modelTypeSelect = document.getElementById('modelType'); + + // get the data from the Server + const response = await fetch('/get_model_types'); + const result = await response.json(); + + //alert(JSON.stringify(result)); // show the JSON-String + var model_types = result; // get the JSON-Object + var count = Object.keys(model_types).length; // #18 when using the default SHARED_MODEL_FOLDERS dict + + // the "/get_model_types" app.get_model_types_route() function checks + // if the SHARED_MODELS_DIR shared files already exists at the "/workspace" location. + // that only happens AFTER the the user clicked the "Create Shared Folders" button + // on the "Settings" Tab of the app's WebUI. + // it will return an empty model_types_dict, so the "Download Manager" does NOT get + // the already in-memory SHARED_MODEL_FOLDERS code-generated default dict + // BEFORE the workspace folders in SHARED_MODELS_DIR exists! + // + // when SHARED_MODELS_DIR exists (or updates), this function will be called via a Socket Message + // to "refresh" its content automatically + + for (i = 0 ; i < count; i += 1) { + modelTypeOption = document.createElement('option'); + + modelTypeOption.setAttribute('value', model_types[String(i)]['modelfolder']); + modelTypeOption.appendChild(document.createTextNode(model_types[String(i)]['desc'])); + + modelTypeSelect.appendChild(modelTypeOption); + } } async function downloadModel() { @@ -1914,11 +1972,19 @@ } function openFileBrowser() { - const podId = '{{ pod_id }}'; - const url = `https://${podId}-7222.proxy.runpod.net/fileapp/`; - window.open(url, '_blank'); + const podId = '{{ pod_id }}'; + + // *** lutzapps - Change #5 - support to run locally + // NOTE: ` (back-ticks) are used here for template literals + var url = `https://${podId}-7222.proxy.runpod.net/fileapp/`; // need to be declared as var + if (`${enable_unsecure_localhost}` === 'True') { + url = `http://localhost:7222/fileapp/`; // remove runpod.net proxy + //alert(`FileBrowser URL=${url}`); } + window.open(url, '_blank'); + } + async function controlFileBrowser(action) { try { const response = await fetch(`/${action}_filebrowser`); @@ -2015,6 +2081,9 @@ updateModelDownloadProgress(data.data); } else if (data.type === 'status_update') { updateAppStatus(data.data); + // lutzapps - Change #6 - int the "Model Downloader's" ModelTypes select dropdown list + } else if (data.type === 'init_model_downloader_model_types') { + initModelTypes(data.data); } // Handle other message types as needed } catch (error) { diff --git a/official-templates/better-ai-launcher/app/tests/README-SHARED_MODELS.txt b/official-templates/better-ai-launcher/app/tests/README-SHARED_MODELS.txt new file mode 100644 index 0000000..b7a75b5 --- /dev/null +++ b/official-templates/better-ai-launcher/app/tests/README-SHARED_MODELS.txt @@ -0,0 +1,469 @@ +TESTDATA AND EXPLANATION OF MAPPING EVERYTHING YOU WANT + +In the folder "/app/tests" you find the following files: + +/app/tests/ + + - "README-SHARED_MODELS.txt" (this file) + + - "populate_testdata.sh" (bash script to un-tar and expand all testdata into the "/workspace" folder) + + - "testdata_shared_models_link.tar.gz" (Testcase #1, read below) + - "testdata_stable-diffusion-webui_pull.tar.gz" (Testcase #2, read below) + - "testdata_installed_apps_pull.tar.gz" (Testcase #3, read below) + + +CREATE TESTDATA (once done already): + +cd /workspace + +# For Testcase #1 - create testdata in "shared_models" folder with dummy models for most model_types: +$ tar -czf testdata_shared_models_link.tar.gz shared_models + +# For Testcase #2 - create testdata with SD-Models for A1111 to be pulled back into "shared_models" and linked back: +$ tar -czf testdata_stable-diffusion-webui_pull.tar.gz stable-diffusion-webui + +# For Testcase #3 -create testdata with all possible "Apps" installed into your "/workspace" +$ tar -czf /app/tests/testdata_installed_apps_pull.tar.gz Apps + + +USE TESTDATA: + +# BEFORE(!) you run this script, read the readme ;-) + +/app/tests/populate_testdata.sh:: + +# use these 3 test cases and extract/merge them accordingly into your workspace, bur READ before you mess you up too much!! +tar -xzf /app/tests/testdata_shared_models_link.tar.gz /workspace +tar -xzf /app/tests/testdata_stable-diffusion-webui_pull.tar.gz /workspace +tar -xzf /app/tests/testdata_installed_apps_pull.tar.gz /workspace + + +Testcase #1: + +When you expand "./testdata_shared_models_link.tar.gz" into the "/workspace" folder, you get: + +$ tree shared_models + +shared_models +├── LLM +│   └── Meta-Llama-3.1-8B +│   ├── llm-Llama-modelfile1.txt +│   ├── llm-Llama-modelfile2.txt +│   └── llm-Llama-modelfile3.txt +├── ckpt +│   ├── ckpt-model1.txt +│   └── ckpt-model2.txt +├── clip +│   └── clip-model1.txt +├── controlnet +│   └── controlnet-model1.txt +├── embeddings +│   ├── embedding-model1.txt +│   └── embedding-model2.txt +├── hypernetworks +│   └── hypernetworks-model1.txt +├── insightface +│   └── insightface-model1.txt +├── ipadapters +│   ├── ipadapter-model1.txt +│   └── xlabs +│   └── xlabs-ipadapter-model1.txt +├── loras +│   ├── flux +│   │   └── flux-lora-model1.txt +│   ├── lora-SD-model1.txt +│   ├── lora-SD-model2.txt +│   ├── lora-SD-model3.txt +│   ├── lora-SD-model4.txt +│   ├── lora-SD-model5.txt +│   ├── lora-model1.txt +│   ├── lora-model2.txt +│   └── xlabs +│   └── xlabs-lora-model1.txt +├── reactor +│   ├── faces +│   │   └── reactor-faces-model1.txt +│   └── reactor-model1.txt +├── unet +│   ├── unet-model1.txt +│   └── unet-model2.txt +├── upscale_models +│   └── esrgan-model1.txt +├── vae +│   └── vae-model1.txt +└── vae-approx + └── vae-apporox-model1.txt + +20 directories, 29 files + + +All these "*.txt" files "simulate" model files of a specific category (model type). +When you have this test data and you click the "Recreate Symlinks" button on the "Settings" Tab, all these models will be shared with all "installed" apps, like: + +A1111: /workspace/stable-diffusion-webui +Forge: /workspace/stable-diffusion-webui-forge +ComfyUI: /workspace/ComfyUI +Kohya_ss: /workspace/Kohya_ss +CUSTOM1: /workspace/joy-caption-batch + +To "simulate" the installed app, you just need to create one or all of these folders manually, as empty folders. Maybe try it one-by-one, like you would do "in-real-life". + +After there is at least ONE app installed, you can test the model sharing with the above mentioned Button "Recreate Symlinks". +All of these 29 models should be shared into all "installed" apps. + +When you "add" a second app, also this new app will get all these models shared into its model folders, which can be differently named. + +Some model types (e.g. UNET) have a separate model folder from Checkpoints in ComfyUI, but in A1111/Forge, these 2 model types will be merged ("flattened") in one "Stable-Diffusion" model folder. See later in the third MAP shown here. + +The same goes in the other direction. +LoRA models which are "organized" in subfolders like "flux" or "slabs" in shared models folder, or in ComfyUI will again be flattened for app like A1111/Forge into the only ONE "Lora" folder. +Pay attention to the details, as the Folder is called "Lora" (no "s" and capital "L" for A111/Forge), but is called "Loras" (with ending "s" and all lower-case). + +You can even map/share LLM models, which mostly come installed as a folder with many files needed for one LLM. For these models, FOLDER symlinks will be created instead or regular file symlinks which are sufficient for most model files of many model types. + +Some model types, like "Embeddings" are managed differently for A1111/Forge (outside its regular model folder), which is not the case for ComfyUI. All this can be tested. + +You can also test to delete a "shared model" in the "shared_models directory, and all its "symlinked" copies should be also automatically removed. + + +Testcase #2: + +In the second testdata TAR archive, you have some SD-models which simulate the installation of the model files once for ONE app, in this test case only for A1111. +The "./testdata_stable-diffusion-webui_pull.tar.gz" is easier to handle than the one for Testcase #3, as it installs directly into the "original" App install location. + +$ tree stable-diffusion-webui + +stable-diffusion-webui +├── _add +│   ├── lora-SD-model2.txt +│   ├── lora-SD-model3.txt +│   ├── lora-SD-model4.txt +│   └── lora-SD-model5.txt +└── models + └── Lora + └── lora-SD-model1.txt + +4 directories, 5 files + + +Testcase #3: + +In this test case you also have other apps installed already, but the principle is the same, just a little bit more careful folder management. + +The "./testdata_installed_apps_pull.tar.gz.tar.gz" extracts into an "Apps" folder. +All folders in this extracted "Apps" folder should be copied into the "/workspace" folder, to simulate an installed A1111, Forge, ComfyUI and Kohya_ss. Make sure that at the end you NOT see the extracted "Apps" folder anymore, as you only used its SUB-FOLDERS to copy/move them into "/workspace" and replace/merge existing folder. + +$ tree Apps + +Apps +├── ComfyUI +├── Kohya_ss +├── _add +│   ├── lora-SD-model2.txt +│   ├── lora-SD-model3.txt +│   ├── lora-SD-model4.txt +│   └── lora-SD-model5.txt +├── joy-caption-batch +├── stable-diffusion-webui +│   └── models +│   └── Lora +│   └── lora-SD-model1.txt +└── stable-diffusion-webui-forge + +9 directories, 5 files + + +This test cases #2 and #3 start with one SD LoRA model "lora-SD-model1.txt" installed only for "stable-diffusion-webui" (A1111) in its "Lora" model folder. + +Wenn you have this test data installed, and you click the "Recreate Symlinks" button of the "better-ai-laucncher" template, then this "locally" (one-app-only) installed LoRA model will be found and "pulled" back into the "shared_models/loras" folder, and "re-shared" back to the pulled location in A1111. + +But it will then also be shared to all other "installed" apps, like ComfyUI, Forge. But not into Kohya, as there is no "mapping rule" defined for Kohya for LoRA models. + +The only "mapping rule" for Kohya which is defined, is to get all "ckpt" (Checkpoint) model files and all UNET model files shared from the corresponding "shared_models" subfolders into its /models folder (see later in the 3rd MAP below). + +In the testdata "Apps" folder you also find an "_add" folder, with 4 more SD-Models to play around with the App Sharing/Syncing framework. Put the in any local app model folder and watch what happens to them and where they the can be seen/used from other apps. You either wait a fewMinutes to let this happen automatically (every 5 Minutes), or you press the "Recreate Symlinks" button at any time to kick this off. + +You can also test to see what happens, when you DELETE a model file from the shared_models sub-folders, and that all its symlinks shared to all apps will also automatically be removed, so no broken links will be left behind. + +When you delete a symlink in an app model folder, only the local app "looses" the model (it is just only a link to the original shared model), so no worries here. Such locally removed symlinks however will be re-created again automatically. + + +BUT THAT IS ONLY THE BEGINNING. +All this logic described here is controlled via 3 (three) "MAP" dictionary JSON files, which can be found also in the "/workspace/shared_models" folder, after you click the "Create Shared Folders" button on the "Settings" Tab. They will be auto-generated, if missing, or otherwise "used-as-is" : + +1.) The "SHARED_MODEL_FOLDERS" map found as "SHARED_MODEL_FOLDERS_FILE" +"/workspace/shared_models/_shared_model_folders.json": +{ + # "model_type" (=subdir_name of SHARED_MODELS_DIR): "model_type_description" + "ckpt": "Model Checkpoint (Full model including a CLIP and VAE model)", + "clip": "CLIP Model (used together with UNET models)", + "controlnet": "ControlNet model (Canny, Depth, Hed, OpenPose, Union-Pro, etc.)", + "embeddings": "Embedding (aka Textual Inversion) Model", + "hypernetworks": "HyperNetwork Model", + "insightface": "InsightFace Model", + "ipadapters": "ControlNet IP-Adapter Model", + "ipadapters/xlabs": "IP-Adapter from XLabs-AI", + "LLM": "LLM (aka Large-Language Model) is folder mapped (1 folder per model), append '/*' in the map", + "loras": "LoRA (aka Low-Ranking Adaption) Model", + "loras/xlabs": "LoRA Model from XLabs-AI", + "loras/flux": "LoRA Model trained on Flux.1 Dev or Flux.1 Schnell", + "reactor": "Reactor Model", + "reactor/faces": "Reactor Face Model", + "unet": "UNET Model Checkpoint (need separate CLIP and VAE Models)", + "upscale_models": "Upscaling Model (based on ESRGAN)", + "vae": "VAE En-/Decoder Model", + "vae-approx": "Approximate VAE Model" +} + +This is the "SHARED_MODEL_FOLDERS" map for the "shared_models" sub-folders, which are used by the App Model Sharing, and this is also the dictionary which is used by the "Model Downloader" Dropdown Listbox, which can be found on the "Models" Tab of the WebUI. + +The idea is to make it easy for people to download their models from "Huggingface" or "CivitAI" directly into the right model type subfolder of the "/workspace/shared_models" main folder. +This "SHARED_MODEL_FOLDERS" map also is used when you click the "Create Shared Folders" button. + +NOTE: pay special attention to the examples with the "loras" folder. There is one regular model type for "loras", and 2 "grouping" model types, "loras/flux" and "loras/xlabs". +We come back to "grouping" mapping rules later, when we discuss the 3rd map. + +Feel free to add/remove/edit/rename items here, alls should be re-created automatically. Just nothing from renamed folders will be deleted. + +NOTE: if you change something in this map file, you need to "read" it into the App via the "Create Shared Folders" button on the "Settings" Tab of the WebUI. +If new folders are found in the map, they will be created, but nothing you have already created before will be deleted or renamed automatically, so be careful not generating two folders for the same model type, or move the models manually into the renamed folder. +IMPORTANT: Also be aware that when you add or change/rename folder names here, you need to also add or change/rename these folder names in the third "SHARED_MODEL_APP_MAP" explained below!!! + + +2.) The "APP_INSTALL_DIRS" map found as "APP_INSTALL_DIRS_FILE" +"/workspace/shared_models/_app_install_dirs.json": +{ + # "app_name": "app_install_dir" + "A1111": "/workspace/stable-diffusion-webui", + "Forge": "/workspace/stable-diffusion-webui-forge", + "ComfyUI": "/workspace/ComfyUI", + "Kohya_ss": "/workspace/Kohya_ss", + "CUSTOM1": "/workspace/joy-caption-batch" +} + +This is the "APP_INSTALL_DIRS" map for the app install dirs within the "/workspace", and as you see, it also supports "CUSTOM" apps to be installed and participating at the model sharing. + +This dictionary is "synced" with the main apps "app_configs" dictionary, so the installation folders are the same, and this should NOT be changed. What you can change in this MAP is to add "CUSTOM" apps, like "CUSTOM1" here e.g. to re-use the Llama LLM model which is centrally installed in "shared_models" under the LLM folder to be "shared" between ComfyUI and "Joy Caption Batch" tool, which is nice to generate your "Caption" files for your LoRA Training files with "Kohya_ss" for example. + + +3.) The "SHARED_MODEL_APP_MAP" map found as "SHARED_MODEL_APP_MAP_FILE" +"/workspace/shared_models/_shared_model_app_map.json": +{ + "ckpt": { # "model_type" (=subdir_name of SHARED_MODELS_DIR) + # "app_name": "app_model_folderpath" (for this "model_type", path is RELATIVE to "app_install_dir" of APP_INSTALL_DIRS map) + "ComfyUI": "/models/checkpoints", + "A1111": "/models/Stable-diffusion", + "Forge": "/models/Stable-diffusion", + "Kohya_ss": "/models" # flatten all "ckpt" / "unet" models here + }, + + "clip": { + "ComfyUI": "/models/clip", + "A1111": "/models/text_encoder", + "Forge": "/models/text_encoder" + }, + + "controlnet": { + "ComfyUI": "/models/controlnet", + "A1111": "/models/ControlNet", + "Forge": "/models/ControlNet" + #"A1111": "/extensions/sd-webui-controlnet/models", # SD1.5 ControlNets + #"Forge": "/extensions/sd-webui-controlnet/models" # SD1.5 ControlNets + }, + + # EMBEDDINGS map outside of models folder for FORGE / A1111 + "embeddings": { + "ComfyUI": "/models/embeddings", + "A1111": "/embeddings", + "Forge": "/embeddings" + }, + + "hypernetworks": { + "ComfyUI": "/models/hypernetworks", + "A1111": "/models/hypernetworks", + "Forge": "/models/hypernetworks" + }, + + "insightface": { + "ComfyUI": "/models/insightface", + "A1111": "/models/insightface", # unverified location + "Forge": "/models/insightface" # unverified location + }, + + "ipadapters": { + "ComfyUI": "/models/ipadapter/", + "A1111": "/extensions/sd-webui-controlnet/models", # unverified location + "Forge": "/extensions/sd-webui-controlnet/models" # unverified location + }, + + "ipadapters/xlabs": { # sub-folders for XLabs-AI IP-Adapters + "ComfyUI": "/models/xlabs/ipadapters", + "A1111": "/extensions/sd-webui-controlnet/models", # flatten all "xlabs" ipadapters here + "Forge": "/extensions/sd-webui-controlnet/models" # flatten all "xlabs" ipadapters here + }, + + # some LoRAs get stored here in sub-folders, e.g. "/xlabs/*" + "loras": { + "ComfyUI": "/models/loras", + "A1111": "/models/Lora", + "Forge": "/models/Lora" + }, + + # Support "XLabs-AI" LoRA models + "loras/xlabs": { # special syntax for "grouping" + "ComfyUI": "/models/loras/xlabs", + "A1111": "/models/Lora", # flatten all "xlabs" LoRAs here + "Forge": "/models/Lora" # flatten all "xlabs" LoRAs here + }, + + # Support "Grouping" all FLUX LoRA models into a LoRA "flux" sub-folder for ComfyUI, + # which again need to be flattened for other apps + "loras/flux": { + "ComfyUI": "/models/loras/flux", + "A1111": "/models/Lora", # flatten all "flux" LoRAs here + "Forge": "/models/Lora" # flatten all "flux" LoRAs here + }, + + "reactor": { + "ComfyUI": "/models/reactor", # unverified location + "A1111": "/models/reactor", + "Forge": "/models/reactor", + }, + + "reactor/faces": { + "ComfyUI": "/models/reactor/faces", # unverified location + "A1111": "/models/reactor", + "Forge": "/models/reactor", + }, + + # UNET models map into the CKPT folders of all other apps, except for ComfyUI + "unet": { + "ComfyUI": "/models/unet", + "A1111": "/models/Stable-diffusion", # flatten all "ckpts" / "unet" models here + "Forge": "/models/Stable-diffusion", # flatten all "ckpts" / "unet" models here + "Kohya_ss": "/models" # flatten all "ckpt" / "unet" models here + }, + + "upscale_models": { + "ComfyUI": "/models/upscale_models", + "A1111": "/models/ESRGAN", + "Forge": "/models/ESRGAN" + }, + + "vae": { + "ComfyUI": "/models/vae", + "A1111": "/models/VAE", + "Forge": "/models/VAE" + }, + + "vae-approx": { + "ComfyUI": "/models/vae_approx", + "A1111": "/models/VAE-approx", + "Forge": "/models/VAE-approx" + }, + + # E.g. Custom Apps support for Joytag-Caption-Batch Tool (which uses the "Meta-Llama-3.1-8B" LLM) + # to share the model with e.g. ComfyUI. This LLM model come as full folders with more than one file! + # Pay attention to the special syntax for folder mappings (add a "/*" suffix to denote a folder mapping) + "LLM/Meta-Llama-3.1-8B/*": { # special syntax for "folder" symlink (the "/*" is mandatory) + "ComfyUI": "/models/LLM/Meta-Llama-3.1-8B/*", # special syntax for "folder" symlink, the "/*" is optional + "CUSTOM1": "/model/*" # special syntax for "folder" symlink, the "/*" is optional + } +} + +This third and last "SHARED_MODEL_APP_MAP" "connects" the "SHARED_MODEL_FOLDERS" map with the "APP_INSTALL_DIRS" map, to have a very flexible "Mapping" between all apps. + +You can custom define all folder/directory layouts and namings of model types, which you can think of. + +Add new mappings to your likings. +And if you don't need some of these mappings, then change them, or delete them. +This is sample data to give you a head start what you can do. + +NOTE: as already introduced before, with the 3 "loras" model type folders, here we now see the 2 grouping model types "/loras/flux" and "/loras/xlabs" now applied in a "grouping" map rule. +"Grouping" of specific model. + +E.g. LoRAs into separate sub-folders for LoRAs (e.g. "flux", "xlabs") for easier "filtering" in ComfyUI. Look this MAP and the testdata for LoRA samples of that feature. +It shows it for 2 LoRA sub-folders as just mentioned. These 2 "grouping" map rules also show how to "flatten" these sub-folders into only one "Lora" model folder for A1111/Forge, all of them go flat into their "Lora" folder, as these are apps, that do NOT support sub-folders per model type, and need therfore to have these "flux" and "xlabs" LoRAs "flattended" to see and consume them. + +Try to add your own "grouping" map rule, or delete "grouping" map rules you not need. + +Otherwise this "SHARED_MODEL_APP_MAP" should be self-explanatory, except for the last part with the FOLDER sharing syntax, as shown here and already mentioned LLM "Meta-Llama-3.1-8B", which will be used here, to show how an LLM model as "Meta-Llama-3.1-8B" can be shared between the app "ComfyUI" and a "CUSTOM1" defined app "joy-caption-batch". + +FOLDER SHARING, e.g. LLM folder-based models: + +_app_install.dirs.json: +{ + ... + "Kohya_ss": "/workspace/Kohya_ss", + "CUSTOM1": "/workspace/joy-caption-batch" +} + +_shared_model_folders.json: +{ + ... + "LLM": "LLM (aka Large-Language Model) is folder mapped (1 folder per model), append '/*' in the map", +} + +To define a "folder" map rule, the "rule" must be an EXISTING shared foldername trailing with "/*", + +e.g. +_shared_model_app_map.json: +{ + ... + "LLM/Meta-Llama-3.1-8B/*": { + "ComfyUI": "/models/LLM/Meta-Llama-3.1-8B/*", + "CUSTOM1": "/model/*" + } +} + +This difference in the syntax with a trailing "/*" shows the shared_models Framework, that you NOT want the model files IN the folder to be shared one-by-one (which would not work anyway for a LLM model), but that you want to share the whole FOLDER, so EVERYTHING within the folder is also automatically shared. + +Just append a "/*" to the MAP rule of the physical folder name "LLM/Meta-Llama-3.1-8B", and it will understand, that you want a FOLDER symlink from the folder path with the "/*" removed. +All pathes are relative to "/workspace/shared_models". + +This will "trigger" a "folder" symlink to all defined target app folder mapped folders. +The target foldernames (defined with or without the trailing "/*") must be a NON-EXISTING app foldername, which will be the target folder symlink from the shared folder source. + +Here are the detailes of this folder symlink for "ComfyUI": + +$ cd ComfyUI/models/LLM +$ ls -la +drwxr-xr-x 3 root root 96 Oct 25 11:45 . +drwxr-xr-x 18 root root 576 Oct 25 11:45 .. +lrwxr-xr-x 1 root root 46 Oct 25 11:45 Meta-Llama-3.1-8B -> /workspace/shared_models/LLM/Meta-Llama-3.1-8B + +NOTE: pay special attention to the second folder map rule for the "CUSTOM1" app "joy-cation-batch". The target mapped folder "model" has a different name from the source folder "LLM/Meta-Llama-3.1-8B/", and the 3 sample LLM model files all go directly into the linked "/model" folder. + +Here are the detailes of this folder symlink for "CUSTOM1" (joy-caption-batch): + +$ cd joy-caption-batch +$ ls -la +drwxr-xr-x 3 root root 96 Oct 25 11:53 . +drwxr-xr-x 10 root root 320 Oct 25 11:45 .. +lrwxr-xr-x 1 root root 46 Oct 25 11:53 model -> /workspace/shared_models/LLM/Meta-Llama-3.1-8B + +$ cd model +$ ls -la +drwxr-xr-x 6 root root 192 Oct 23 10:22 . +drwxr-xr-x 5 root root 160 Oct 23 10:27 .. +-rwx------ 1 root root 51 Oct 18 11:29 llm-Llama-modelfile1.txt +-rwx------ 1 root root 51 Oct 18 11:29 llm-Llama-modelfile2.txt +-rwx------ 1 root root 51 Oct 18 11:29 llm-Llama-modelfile3.txt + + +Folder Sharing rules is an advanced technique, and they can only be manually added, editing the "SHARED_MODEL_APP_MAP" file for such rules, as shown above. + +While all new downloaded "single" model files of a model type will be automatically shared into all app model folders, "folder models" (as typically found for LLM models), need to be added to this "SHARED_MODELS_MAP" JSON file manually to be shared, as it is shown in this example. + +You could also use this folder mappings for "custom" LoRA sub-categories in ComfyUI "per folder", instead of sub-folders with separate file symlinks, but I not want to go deeper here. +You can try it, nothing bad will happen. + +If you delete one of these three MAP JSON files, they will be re-generated with its shown "default" content. +But when you edit/change these 3 MAP JSON files, they will be used with your changes/addings every time you use this "/workspace" volume. + + +That's it for the FULL version of "shared_models" sharing between apps, and how you can easily test this before using the "real data" ;-) + +Regards, +lutzapps \ No newline at end of file diff --git a/official-templates/better-ai-launcher/app/tests/populate_testdata.sh b/official-templates/better-ai-launcher/app/tests/populate_testdata.sh new file mode 100755 index 0000000..f93ef0d --- /dev/null +++ b/official-templates/better-ai-launcher/app/tests/populate_testdata.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# please use the "./readme-testdata.txt" before you extract these TARs!!! + +# Testcase #1 +tar -xzf /app/tests/testdata_shared_models_link.tar.gz /workspace + +# Testcase #2 +tar -xzf /app/tests/testdata_stable-diffusion-webui_pull.tar.gz /workspace + +# Testcase #3 +tar -xzf /app/tests/testdata_installed_apps_pull.tar.gz /workspace \ No newline at end of file diff --git a/official-templates/better-ai-launcher/app/tests/testdata_installed_apps_pull.tar.gz b/official-templates/better-ai-launcher/app/tests/testdata_installed_apps_pull.tar.gz new file mode 100644 index 0000000..8e4e633 Binary files /dev/null and b/official-templates/better-ai-launcher/app/tests/testdata_installed_apps_pull.tar.gz differ diff --git a/official-templates/better-ai-launcher/app/tests/testdata_shared_models_link.tar.gz b/official-templates/better-ai-launcher/app/tests/testdata_shared_models_link.tar.gz new file mode 100644 index 0000000..79645b8 Binary files /dev/null and b/official-templates/better-ai-launcher/app/tests/testdata_shared_models_link.tar.gz differ diff --git a/official-templates/better-ai-launcher/app/tests/testdata_stable-diffusion-webui_pull.tar.gz b/official-templates/better-ai-launcher/app/tests/testdata_stable-diffusion-webui_pull.tar.gz new file mode 100644 index 0000000..eaffd4b Binary files /dev/null and b/official-templates/better-ai-launcher/app/tests/testdata_stable-diffusion-webui_pull.tar.gz differ diff --git a/official-templates/better-ai-launcher/utils/app_configs.py b/official-templates/better-ai-launcher/app/utils/app_configs.py similarity index 100% rename from official-templates/better-ai-launcher/utils/app_configs.py rename to official-templates/better-ai-launcher/app/utils/app_configs.py diff --git a/official-templates/better-ai-launcher/utils/app_utils.py b/official-templates/better-ai-launcher/app/utils/app_utils.py similarity index 98% rename from official-templates/better-ai-launcher/utils/app_utils.py rename to official-templates/better-ai-launcher/app/utils/app_utils.py index e54290b..c259781 100644 --- a/official-templates/better-ai-launcher/utils/app_utils.py +++ b/official-templates/better-ai-launcher/app/utils/app_utils.py @@ -300,7 +300,12 @@ def install_app(app_name, app_configs, send_websocket_message): else: return False, f"Unknown app: {app_name}" -def update_model_symlinks(): +# unused function +def onsolate_update_model_symlinks(): + # lutzapps - CHANGE #7 - use the new "shared_models" module for app model sharing + # remove this whole now unused function + return "replaced by utils.shared_models.update_model_symlinks()" + shared_models_dir = '/workspace/shared_models' apps = { 'stable-diffusion-webui': '/workspace/stable-diffusion-webui/models', diff --git a/official-templates/better-ai-launcher/utils/filebrowser_utils.py b/official-templates/better-ai-launcher/app/utils/filebrowser_utils.py similarity index 100% rename from official-templates/better-ai-launcher/utils/filebrowser_utils.py rename to official-templates/better-ai-launcher/app/utils/filebrowser_utils.py diff --git a/official-templates/better-ai-launcher/utils/model_utils.py b/official-templates/better-ai-launcher/app/utils/model_utils.py similarity index 100% rename from official-templates/better-ai-launcher/utils/model_utils.py rename to official-templates/better-ai-launcher/app/utils/model_utils.py diff --git a/official-templates/better-ai-launcher/app/utils/shared_models.py b/official-templates/better-ai-launcher/app/utils/shared_models.py new file mode 100644 index 0000000..8ded206 --- /dev/null +++ b/official-templates/better-ai-launcher/app/utils/shared_models.py @@ -0,0 +1,915 @@ +import os +import shutil +import datetime +import threading +import time +import json + +from flask import jsonify +from utils.websocket_utils import send_websocket_message, active_websockets +from utils.app_configs import (get_app_configs) + +### shared_models-v0.7 by ViennaFlying, Oct 25th 2024 ### +### dev-my-v0.3 + +# to run (and optionally DEBUG) this docker image "better-ai-launcher" in a local container on your own machine +# you need to define the ENV var "LOCAL_DEBUG" in the "VSCode Docker Extension" +# file ".vscode/tasks.json" in the ENV settings of the "dockerRun" section (or any other way), +# and pass into the docker container: +# tasks.json: +# ... +# "dockerRun": { +# "containerName": "madiator2011-better-launcher", // no "/" allowed here for container name +# "image": "madiator2011/better-launcher:dev", +# "envFiles": ["${workspaceFolder}/.env"], // pass additional env-vars (hf_token, civitai token, ssh public-key) from ".env" file to container +# "env": { // this ENV vars go into the docker container to support local debugging +# "LOCAL_DEBUG": "True", // change app to localhost Urls and local Websockets (unsecured) +# "FLASK_APP": "app/app.py", +# "FLASK_ENV": "development", // changed from "production" +# "GEVENT_SUPPORT": "True" // gevent monkey-patching is being used, enable gevent support in the debugger +# // "FLASK_DEBUG": "0" // "1" allows debugging in Chrome, but then VSCode debugger not works +# }, +# "volumes": [ +# { +# "containerPath": "/app", +# "localPath": "${workspaceFolder}" // the "/app" folder (and sub-folders) will be mapped locally for debugging and hot-reload +# }, +# { +# "containerPath": "/workspace", +# // TODO: create this folder before you run! +# "localPath": "${userHome}/Projects/Docker/Madiator/workspace" +# } +# ], +# "ports": [ +# { +# "containerPort": 7222, // main Flask app port "AppManager" +# "hostPort": 7222 +# }, +# ... +# +# +# NOTE: to use the "LOCAL_DEBUG" ENV var just for local consumption of this image, you can run it like +# +# docker run -it -d --name madiator-better-launcher -p 22:22 -p 7777:7777 -p 7222:7222 -p 3000:3000 -p 7862:7862 -p 7863:7863 +# -e LOCAL_DEBUG="True" -e RUNPOD_PUBLIC_IP="127.0.0.1" -e RUNPOD_TCP_PORT_22="22" +# -e PUBLIC_KEY="ssh-ed25519 XXXXXXX...XXXXX user@machine-DNS.local" +# --mount type=bind,source=/Users/test/Projects/Docker/madiator/workspace,target=/workspace +# madiator2011/better-launcher:dev +# +# To run the full 'app.py' / 'index.html' webserver locally, is was needed to "patch" the +# '/app/app.py' main application file and the +# '/app/templates/index.html'file according to the "LOCAL_DEBUG" ENV var, +# to switch the CF "proxy.runpod.net" Url for DEBUG: +# +### '/app/app.py' CHANGES ###: +# # lutzapps - CHANGE #1 +# LOCAL_DEBUG = os.environ.get('LOCAL_DEBUG', 'False') # support local browsing for development/debugging +# ... +# filebrowser_status = get_filebrowser_status() +# return render_template('index.html', +# apps=app_configs, +# app_status=app_status, +# pod_id=RUNPOD_POD_ID, +# RUNPOD_PUBLIC_IP=os.environ.get('RUNPOD_PUBLIC_IP'), +# RUNPOD_TCP_PORT_22=os.environ.get('RUNPOD_TCP_PORT_22'), +# # lutzapps - CHANGE #2 - allow localhost Url for unsecure "http" and "ws" WebSockets protocol, +# according to LOCAL_DEBUG ENV var (used 3x in "index.html" changes) +# enable_unsecure_localhost=os.environ.get('LOCAL_DEBUG'), +# ... +# other (non-related) app.py changes omitted here +# +### '/app/template/index.html' CHANGES ###: +#