From d4be39a22c09acd247892a30c8dd5ce2946632b8 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Thu, 22 Aug 2024 20:08:02 -0700 Subject: [PATCH] feat(core): implement plugins - add plugins feature using importlib - edit .gitignore - change enabled state of AUTOGGUF_SERVER to "enabled" from "true" for consistency --- .gitignore | 5 +++ plugins/example.py | 13 +++++++ src/AutoGGUF.py | 80 ++++++++++++++++++++++++++++++++++++++++++++ src/Localizations.py | 12 +++++++ src/main.py | 20 ++++++++++- 5 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 plugins/example.py diff --git a/.gitignore b/.gitignore index ce6dd8d..bfd4365 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,11 @@ src/* docs/* !docs/*.py +# Allow plugins folder and its .py files +!plugins/ +plugins/* +!plugins/*.py + # Allow assets folder, but only .svg, .png, .rc, .css, .iss and .ico files !assets/ assets/* diff --git a/plugins/example.py b/plugins/example.py new file mode 100644 index 0000000..10c679b --- /dev/null +++ b/plugins/example.py @@ -0,0 +1,13 @@ +class ExamplePlugin: + def init(self, autogguf_instance): + # This gets called after the plugin is loaded + print("Plugin initialized") + + def __data__(self): + return { + "name": "ExamplePlugin", + "description": "This is an example plugin.", + "compatible_versions": ["*"], + "author": "leafspark", + "version": "v1.0.0", + } diff --git a/src/AutoGGUF.py b/src/AutoGGUF.py index e77a286..8382e0c 100644 --- a/src/AutoGGUF.py +++ b/src/AutoGGUF.py @@ -1,6 +1,7 @@ import json import re import shutil +import importlib from functools import partial from datetime import datetime @@ -777,8 +778,87 @@ def __init__(self, args): # Load models self.load_models() + + # Load plugins + self.plugins = self.load_plugins() + self.apply_plugins() + self.logger.info(AUTOGGUF_INITIALIZATION_COMPLETE) + def load_plugins(self): + plugins = {} + plugin_dir = "plugins" + + if not os.path.exists(plugin_dir): + self.logger.info(PLUGINS_DIR_NOT_EXIST.format(plugin_dir)) + return plugins + + if not os.path.isdir(plugin_dir): + self.logger.warning(PLUGINS_DIR_NOT_DIRECTORY.format(plugin_dir)) + return plugins + + for file in os.listdir(plugin_dir): + if file.endswith(".py") and not file.endswith(".disabled.py"): + name = file[:-3] + path = os.path.join(plugin_dir, file) + + try: + spec = importlib.util.spec_from_file_location(name, path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + for item_name in dir(module): + item = getattr(module, item_name) + if isinstance(item, type) and hasattr(item, "__data__"): + plugin_instance = item() + plugin_data = plugin_instance.__data__() + + compatible_versions = plugin_data.get( + "compatible_versions", [] + ) + if ( + "*" in compatible_versions + or AUTOGGUF_VERSION in compatible_versions + ): + plugins[name] = { + "instance": plugin_instance, + "data": plugin_data, + } + self.logger.info( + PLUGIN_LOADED.format( + plugin_data["name"], plugin_data["version"] + ) + ) + else: + self.logger.warning( + PLUGIN_INCOMPATIBLE.format( + plugin_data["name"], + plugin_data["version"], + AUTOGGUF_VERSION, + ", ".join(compatible_versions), + ) + ) + break + except Exception as e: + self.logger.error(PLUGIN_LOAD_FAILED.format(name, str(e))) + + return plugins + + def apply_plugins(self): + if not self.plugins: + self.logger.info(NO_PLUGINS_LOADED) + return + + for plugin_name, plugin_info in self.plugins.items(): + plugin_instance = plugin_info["instance"] + for attr_name in dir(plugin_instance): + if not attr_name.startswith("__") and attr_name != "init": + attr_value = getattr(plugin_instance, attr_name) + setattr(self, attr_name, attr_value) + + if hasattr(plugin_instance, "init") and callable(plugin_instance.init): + plugin_instance.init(self) + def check_for_updates(self): try: response = requests.get( diff --git a/src/Localizations.py b/src/Localizations.py index 75afffe..5df4063 100644 --- a/src/Localizations.py +++ b/src/Localizations.py @@ -43,6 +43,18 @@ def __init__(self): "Found {} concatenated file parts. Please concat the files first." ) + # Plugins + self.PLUGINS_DIR_NOT_EXIST = ( + "Plugins directory '{}' does not exist. No plugins will be loaded." + ) + self.PLUGINS_DIR_NOT_DIRECTORY = ( + "'{}' exists but is not a directory. No plugins will be loaded." + ) + self.PLUGIN_LOADED = "Loaded plugin: {} {}" + self.PLUGIN_INCOMPATIBLE = "Plugin {} {} is not compatible with AutoGGUF version {}. Supported versions: {}" + self.PLUGIN_LOAD_FAILED = "Failed to load plugin {}: {}" + self.NO_PLUGINS_LOADED = "No plugins loaded." + # GPU Monitoring self.GPU_USAGE = "GPU Usage:" self.GPU_USAGE_FORMAT = "GPU: {:.1f}% | VRAM: {:.1f}% ({} MB / {} MB)" diff --git a/src/main.py b/src/main.py index e7eee9b..ae4d8d5 100644 --- a/src/main.py +++ b/src/main.py @@ -39,8 +39,26 @@ def get_backends(): ) return jsonify({"backends": backends}) + @server.route("/v1/plugins", methods=["GET"]) + def get_plugins(): + if window: + return jsonify( + { + "plugins": [ + { + "name": plugin_data["data"]["name"], + "version": plugin_data["data"]["version"], + "description": plugin_data["data"]["description"], + "author": plugin_data["data"]["author"], + } + for plugin_data in window.plugins.values() + ] + } + ) + return jsonify({"plugins": []}) + def run_flask(): - if os.environ.get("AUTOGGUF_SERVER", "").lower() == "true": + if os.environ.get("AUTOGGUF_SERVER", "").lower() == "enabled": server.run( host="0.0.0.0", port=int(os.environ.get("AUTOGGUF_SERVER_PORT", 5000)),