Architecture Overview
Technical architecture and design of GGUF Loader
This document provides a technical overview of GGUF Loaderβs architecture and design decisions.
ποΈ High-Level Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β GGUF Loader Application β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
β β UI Layer β β Addon Layer β β Model Layer β β
β β (PySide6) β β (Plugins) β β (llama-cpp-python) β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Core Services β
β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββββββ β
β β Config β β Events β β Logger β β Addon Managerβ β
β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π§© Component Overview
UI Layer (PySide6)
The user interface is built with PySide6 (Qt for Python):
- Main Window: Application shell and layout
- Chat Interface: Message display and input
- Addon Sidebar: Addon management panel
- Settings Dialog: Configuration UI
Model Layer (llama-cpp-python)
Handles GGUF model operations:
- Model Loader: Load and initialize GGUF models
- Chat Generator: Generate text responses
- Tokenizer: Text tokenization and detokenization
- Context Manager: Manage conversation context
Addon Layer
Extensible plugin system:
- Addon Manager: Discover, load, and manage addons
- Addon API: Interface for addon development
- Event System: Communication between addons and core
Core Services
Shared functionality:
- Configuration: Settings management
- Event Bus: Application-wide events
- Logger: Logging and debugging
- Utils: Common utilities
π Data Flow
Chat Message Flow
User Input β UI Layer β Chat Handler β Model Layer β Response
β β
Validation Formatting
β β
Event Emit ββββββββββββββββββββββββββββββββββββ Display
Addon Integration Flow
Addon Discovery β Load __init__.py β Call register() β Return Widget
β β
Scan addons/ Add to Sidebar
β β
Validate Structure ββββββββββββββββββββββββββββ Connect Signals
π§± Key Classes
GGUFLoaderApp
Main application class:
class GGUFLoaderApp(QMainWindow):
# Signals
model_loaded = Signal(object)
model_unloaded = Signal()
# Properties
model: Optional[Llama]
ai_chat: AIChat
addon_manager: AddonManager
# Methods
def load_model(self, path: str) -> bool
def unload_model(self) -> None
def get_model_backend(self) -> Optional[Llama]
AddonManager
Manages addon lifecycle:
class AddonManager:
def discover_addons(self) -> List[str]
def load_addon(self, name: str) -> Optional[QWidget]
def unload_addon(self, name: str) -> bool
def get_addon(self, name: str) -> Optional[object]
ModelLoader
Handles model operations:
class ModelLoader:
def load(self, path: str, **kwargs) -> Llama
def unload(self) -> None
def is_loaded(self) -> bool
def get_info(self) -> Dict[str, Any]
π Extension Points
Addon Registration
def register(parent=None) -> Optional[QWidget]:
"""
Entry point for addons.
Args:
parent: Main application instance
Returns:
Widget for sidebar, or None for background addons
"""
Event Hooks
Available events for addons:
| Event | Description |
|---|---|
model_loaded |
Model successfully loaded |
model_unloaded |
Model unloaded |
chat_message |
New chat message |
addon_loaded |
Addon loaded |
settings_changed |
Settings updated |
ποΈ State Management
Application State
class AppState:
model: Optional[Llama]
model_path: Optional[str]
chat_history: List[Message]
settings: Dict[str, Any]
loaded_addons: Dict[str, object]
Persistence
- Settings: JSON file in config directory
- Chat History: SQLite database (optional)
- Model Preferences: Per-model JSON files
π Security Architecture
Addon Sandboxing
- Addons run in same process (trust model)
- File system access limited to addon directory
- Network access logged
- Sensitive APIs require permission
Data Protection
- No telemetry or data collection
- All processing local
- Clipboard access temporary and restored
- Logs contain no sensitive data
π Performance Considerations
Memory Management
- Models loaded on-demand
- Lazy UI component initialization
- Proper cleanup on unload
- Memory-mapped model files
Threading Model
- UI runs on main thread
- Model inference on worker thread
- Addon timers on main thread
- Background tasks use QThreadPool
π§ͺ Testing Architecture
Test Structure
tests/
βββ unit/
β βββ test_model_loader.py
β βββ test_addon_manager.py
β βββ test_config.py
βββ integration/
β βββ test_chat_flow.py
β βββ test_addon_loading.py
βββ e2e/
βββ test_full_workflow.py
Mocking Strategy
- Mock model for fast tests
- Mock clipboard for addon tests
- Mock file system for config tests
π Related Documentation
- Package Structure - Detailed file layout
- Addon API - Complete API docs
- Contributing - Development workflow
Questions about the architecture? Join our community discussions or contact support@ggufloader.com.
Related Documentation
- This document explains the structure of the gguf loader 2.0.0 pypi package and how the smart floating assistant addon is included. ## π¦ package overview **package name**: `ggufloader` **version**: `2.0.0` **command**: `ggufloader` when users install with `pip install ggufloader`, they get: - complete gguf loader application - smart floating assistant addon (pre-installed) - comprehensive documentation - all necessary dependencies ## ποΈ directory structure ``` ggufloader/ βββ pyproject.toml # package configuration βββ readme_pypi.md # pypi package description βββ build_pypi.py # build script for pypi βββ requirements.txt # dependencies βββ βββ # main application files βββ main.py # basic gguf loader (no addons) βββ gguf_loader_main.py # gguf loader with addon support βββ addon_manager.py # addon management system βββ config.py # configuration βββ utils.py # utilities βββ icon.ico # application icon βββ βββ # ui components βββ ui/ β βββ ai_chat_window.py # main chat interface β βββ apply_style.py # ui styling βββ βββ # core models βββ models/ β βββ model_loader.py # gguf model loading β βββ chat_generator.py # text generation βββ βββ # ui mixins βββ mixins/ β βββ ui_setup_mixin.py # ui setup β βββ model_handler_mixin.py # model handling β βββ chat_handler_mixin.py # chat functionality β βββ event_handler_mixin.py # event handling β βββ utils_mixin.py # utility functions βββ βββ # widgets βββ widgets/ β βββ chat_bubble.py # chat bubble component βββ βββ # pre-installed addons βββ addons/ β βββ smart_floater/ # smart floating assistant β βββ __init__.py # addon entry point β βββ simple_main.py # main addon logic β βββ main.py # full-featured version β βββ floater_ui.py # ui components β βββ comment_engine.py # text processing β βββ injector.py # text injection β βββ error_handler.py # error handling β βββ privacy_security.py # privacy features β βββ performance_optimizer.py # performance βββ βββ # documentation βββ docs/ βββ readme.md # documentation index βββ installation.md # installation guide βββ quick-start.md # quick start guide βββ user-guide.md # complete user manual βββ addon-development.md # addon development guide βββ addon-api.md # api reference βββ smart-floater-example.md # example addon βββ configuration.md # configuration guide βββ troubleshooting.md # troubleshooting βββ contributing.md # contributing guide βββ package-structure.md # this file ``` ## π installation and usage ### installation ```bash pip install ggufloader ``` ### launch application ```bash ggufloader ``` this command launches `gguf_loader_main.py` which includes: - full gguf loader functionality - smart floating assistant addon (automatically loaded) - addon management system - all ui components ## π§ how addons are included ### addon discovery when gguf loader starts, the `addonmanager` automatically: 1. **scans** the `addons/` directory 2. **finds** folders with `__init__.py` files 3. **loads** addons by calling their `register()` function 4. **displays** addon buttons in the sidebar ### smart floater integration the smart floating assistant is included as a pre-installed addon: ```python # addons/smart_floater/__init__.py from .simple_main import register __all__ = ["register"] # when loaded, it provides: # - global text selection detection # - floating button interface # - ai text processing (summarize/comment) # - seamless clipboard integration ``` ### addon lifecycle 1. **package installation**: addon files are installed with the package 2. **application start**: `addonmanager` discovers and loads addons 3. **user interaction**: users can access addons via the sidebar 4. **background operation**: smart floater runs continuously in background ## π package configuration ### pyproject.toml key sections ```toml [project] name = "ggufloader" version = "2.0.0" dependencies = [ "llama-cpp-python>=0.2.72", "pyside6>=6.6.1", "pyautogui>=0.9.54", "pyperclip>=1.8.2", "pywin32>=306; sys_platform == 'win32'", ] [project.scripts] ggufloader = "gguf_loader.gguf_loader_main:main" [tool.setuptools] packages = [ "gguf_loader", "gguf_loader.addons", "gguf_loader.addons.smart_floater" ] include-package-data = true ``` ### package data inclusion all necessary files are included: - python source code - documentation (`.md` files) - icons and images - configuration files - addon files ## π― user experience ### first-time users 1. **install**: `pip install ggufloader` 2. **launch**: `ggufloader` 3. **load model**: click "select gguf model" 4. **use smart floater**: select text anywhere β click β¨ button ### addon discovery - smart floater appears in addon sidebar automatically - users can click to open control panel - no additional installation required - works immediately after model loading ### documentation access users can access documentation: - online: github repository - locally: installed with package in `docs/` folder - in-app: help links and tooltips ## π version updates ### updating the package when releasing new versions: 1. **update version** in `pyproject.toml` 2. **update changelog** and documentation 3. **test addon compatibility** 4. **build and upload** to pypi ### addon updates smart floater updates are included in package updates: - bug fixes and improvements - new features and capabilities - performance optimizations - security enhancements ## π οΈ development workflow ### for package maintainers 1. **develop** new features and addons 2. **test** thoroughly with various models 3. **update** documentation 4. **build** package with `python build_pypi.py` 5. **upload** to pypi ### for addon developers 1. **study** the [smart floater example](/docs/smart-floater-example/ "complete addon example with detailed code analysis") 2. **follow** the [addon development guide](/docs/addon-development/ "step-by-step addon creation tutorial") 3. **reference** the [api documentation](/docs/addon-api/ "complete api reference for developers") 4. **create** addons in `addons/` directory 5. **test** with gguf loader (see [quick start guide](/docs/quick-start/ "getting started tutorial")) 6. **share** with community ## π package statistics ### size and dependencies - **package size**: ~50mb (includes all dependencies) - **core dependencies**: 5 main packages - **optional dependencies**: gpu acceleration packages - **documentation**: 10+ comprehensive guides ### compatibility - **python**: 3.8+ (tested on 3.8, 3.9, 3.10, 3.11, 3.12) - **operating systems**: windows, macos, linux - **architectures**: x86_64, arm64 (apple silicon) ## π troubleshooting package issues ### common installation issues 1. **python version**: ensure python 3.8+ 2. **dependencies**: install build tools if needed 3. **permissions**: use `--user` flag if needed 4. **virtual environment**: recommended for isolation ### addon loading issues 1. **check logs**: look for addon loading errors 2. **verify structure**: ensure `__init__.py` exists 3. **dependencies**: check addon-specific requirements 4. **permissions**: verify file permissions ### getting help - **documentation**: check `docs/` folder - **github issues**: report bugs and issues - **community**: join discussions and forums - **support**: contact support@ggufloader.com ## π success metrics the package structure is designed to provide: - **easy installation**: single `pip install` command - **immediate functionality**: smart floater works out of the box - **extensibility**: clear addon development path - **maintainability**: well-organized codebase - **user-friendly**: comprehensive documentation --- **this package structure ensures that gguf loader 2.0.0 provides a complete, professional ai text processing solution with the smart floating assistant included by default! π**
addon api reference
complete api reference for developing gguf loader addons
advanced 20 minutescomplete api reference for developing gguf loader addons.
ποΈ core api
addon registration
every addon must implement a
register()function:def register(parent=none): """ register function called by gguf loader when loading the addon. args: parent: the main gguf loader application instance returns: qwidget: the addon's ui widget, or none for background addons """ passmain application interface
the
parentparameter provides access to the main gguf loader application:class ggufloaderapp: """main gguf loader application interface.""" # properties model: optional[any] # currently loaded gguf model ai_chat: aichat # ai chat interface addon_manager: addonmanager # addon management system # methods def get_model_backend(self) -> optional[any]: """get the current model backend for addons.""" def is_model_loaded(self) -> bool: """check if a model is currently loaded.""" # signals model_loaded = signal(object) # emitted when model is loaded model_unloaded = signal() # emitted when model is unloadedπ€ model api
accessing the model
def get_model(self, gguf_app): """get the currently loaded gguf model.""" try: # method 1: direct access if hasattr(gguf_app, 'model') and gguf_app.model: return gguf_app.model # method 2: through ai chat if hasattr(gguf_app, 'ai_chat') and hasattr(gguf_app.ai_chat, 'model'): return gguf_app.ai_chat.model # method 3: backend method if hasattr(gguf_app, 'get_model_backend'): return gguf_app.get_model_backend() return none except exception as e: logging.error(f"error getting model: {e}") return nonemodel interface
class llamamodel: """gguf model interface (llama-cpp-python).""" def __call__(self, prompt: str, max_tokens: int = 256, temperature: float = 0.7, top_p: float = 0.9, top_k: int = 40, repeat_penalty: float = 1.1, stop: list[str] = none, stream: bool = false) -> union[str, dict, iterator]: """generate text from the model.""" pass def tokenize(self, text: str) -> list[int]: """tokenize text.""" pass def detokenize(self, tokens: list[int]) -> str: """detokenize tokens to text.""" passtext generation
def generate_text(self, model, prompt: str, **kwargs) -> str: """generate text using the model.""" try: response = model( prompt, max_tokens=kwargs.get('max_tokens', 200), temperature=kwargs.get('temperature', 0.7), top_p=kwargs.get('top_p', 0.9), repeat_penalty=kwargs.get('repeat_penalty', 1.1), stop=kwargs.get('stop', ["</s>", "\n\n"]), stream=false ) return self.extract_response_text(response) except exception as e: logging.error(f"text generation failed: {e}") return f"error: {str(e)}" def extract_response_text(self, response) -> str: """extract text from model response.""" if isinstance(response, dict) and 'choices' in response: return response['choices'][0].get('text', '').strip() elif isinstance(response, str): return response.strip() else: return str(response).strip()π¨ ui api
widget creation
from pyside6.qtwidgets import qwidget, qvboxlayout, qlabel, qpushbutton from pyside6.qtcore import qtimer, signal class addonwidget(qwidget): """base addon widget class.""" # signals text_processed = signal(str) error_occurred = signal(str) def __init__(self, addon_instance): super().__init__() self.addon = addon_instance self.setup_ui() def setup_ui(self): """setup the widget ui.""" layout = qvboxlayout(self) # title title = qlabel("my addon") title.setstylesheet("font-size: 16px; font-weight: bold;") layout.addwidget(title) # content self.setup_content(layout) def setup_content(self, layout): """override this method to add custom content.""" passcommon ui components
# status indicator def create_status_indicator(self): """create a status indicator widget.""" self.status_label = qlabel("ready") self.status_label.setstylesheet(""" qlabel { padding: 5px; border-radius: 3px; background-color: #4caf50; color: white; } """) return self.status_label def update_status(self, message: str, status_type: str = "info"): """update status indicator.""" colors = { "info": "#2196f3", "success": "#4caf50", "warning": "#ff9800", "error": "#f44336" } self.status_label.settext(message) self.status_label.setstylesheet(f""" qlabel {colors.get(status_type, colors['info'])}; color: white; }} """) # progress indicator def create_progress_indicator(self): """create a progress indicator.""" from pyside6.qtwidgets import qprogressbar self.progress_bar = qprogressbar() self.progress_bar.setvisible(false) return self.progress_bar def show_progress(self, message: str = "processing..."): """show progress indicator.""" self.progress_bar.setvisible(true) self.progress_bar.setrange(0, 0) # indeterminate self.update_status(message, "info") def hide_progress(self): """hide progress indicator.""" self.progress_bar.setvisible(false)floating ui components
from pyside6.qtcore import qt from pyside6.qtgui import qcursor class floatingwidget(qwidget): """create floating widgets like the smart assistant.""" def __init__(self): super().__init__() self.setup_floating_widget() def setup_floating_widget(self): """setup floating widget properties.""" self.setwindowflags( qt.tooltip | qt.framelesswindowhint | qt.windowstaysontophint ) self.setattribute(qt.wa_translucentbackground) def show_near_cursor(self, offset_x: int = 10, offset_y: int = -40): """show widget near cursor position.""" cursor_pos = qcursor.pos() self.move(cursor_pos.x() + offset_x, cursor_pos.y() + offset_y) self.show()π§ system integration api
text selection detection
import pyautogui import pyperclip from pyside6.qtcore import qtimer class textselectionmonitor: """monitor for global text selection.""" def __init__(self, callback): self.callback = callback self.last_clipboard = "" self.selected_text = "" # timer for checking selection self.timer = qtimer() self.timer.timeout.connect(self.check_selection) self.timer.start(300) # check every 300ms def check_selection(self): """check for text selection.""" try: # save current clipboard original_clipboard = pyperclip.paste() # copy selection pyautogui.hotkey('ctrl', 'c') # process after small delay qtimer.singleshot(50, lambda: self.process_selection(original_clipboard)) except exception as e: logging.debug(f"selection check failed: {e}") def process_selection(self, original_clipboard): """process the selection.""" try: current_text = pyperclip.paste() # check if we got new selected text if (current_text != original_clipboard and current_text and len(current_text.strip()) > 3): self.selected_text = current_text.strip() self.callback(self.selected_text) # restore clipboard pyperclip.copy(original_clipboard) except exception as e: logging.debug(f"selection processing failed: {e}") def stop(self): """stop monitoring.""" self.timer.stop()clipboard integration
import pyperclip class clipboardmanager: """manage clipboard operations.""" @staticmethod def get_text() -> str: """get text from clipboard.""" try: return pyperclip.paste() except exception as e: logging.error(f"failed to get clipboard text: {e}") return "" @staticmethod def set_text(text: str) -> bool: """set text to clipboard.""" try: pyperclip.copy(text) return true except exception as e: logging.error(f"failed to set clipboard text: {e}") return false @staticmethod def append_text(text: str) -> bool: """append text to clipboard.""" try: current = clipboardmanager.get_text() new_text = f"{current}\n{text}" if current else text return clipboardmanager.set_text(new_text) except exception as e: logging.error(f"failed to append clipboard text: {e}") return falsehotkey registration
import keyboard class hotkeymanager: """manage global hotkeys.""" def __init__(self): self.registered_hotkeys = {} def register_hotkey(self, hotkey: str, callback, description: str = ""): """register a global hotkey.""" try: keyboard.add_hotkey(hotkey, callback) self.registered_hotkeys[hotkey] = { 'callback': callback, 'description': description } logging.info(f"registered hotkey: {hotkey}") return true except exception as e: logging.error(f"failed to register hotkey {hotkey}: {e}") return false def unregister_hotkey(self, hotkey: str): """unregister a hotkey.""" try: keyboard.remove_hotkey(hotkey) if hotkey in self.registered_hotkeys: del self.registered_hotkeys[hotkey] logging.info(f"unregistered hotkey: {hotkey}") return true except exception as e: logging.error(f"failed to unregister hotkey {hotkey}: {e}") return false def cleanup(self): """clean up all registered hotkeys.""" for hotkey in list(self.registered_hotkeys.keys()): self.unregister_hotkey(hotkey)π configuration api
addon configuration
import json import os from pathlib import path class addonconfig: """manage addon configuration.""" def __init__(self, addon_name: str): self.addon_name = addon_name self.config_dir = path.home() / ".ggufloader" / "addons" / addon_name self.config_file = self.config_dir / "config.json" self.config = {} self.load_config() def load_config(self): """load configuration from file.""" try: if self.config_file.exists(): with open(self.config_file, 'r') as f: self.config = json.load(f) except exception as e: logging.error(f"failed to load config: {e}") self.config = {} def save_config(self): """save configuration to file.""" try: self.config_dir.mkdir(parents=true, exist_ok=true) with open(self.config_file, 'w') as f: json.dump(self.config, f, indent=2) except exception as e: logging.error(f"failed to save config: {e}") def get(self, key: str, default=none): """get configuration value.""" return self.config.get(key, default) def set(self, key: str, value): """set configuration value.""" self.config[key] = value self.save_config() def update(self, updates: dict): """update multiple configuration values.""" self.config.update(updates) self.save_config()π event system api
addon events
from pyside6.qtcore import qobject, signal class addoneventsystem(qobject): """event system for addon communication.""" # core events addon_loaded = signal(str) # addon_name addon_unloaded = signal(str) # addon_name model_changed = signal(object) # model text_selected = signal(str) # selected_text text_processed = signal(str, str) # original_text, processed_text def __init__(self): super().__init__() self.event_handlers = {} def emit_event(self, event_name: str, *args, **kwargs): """emit a custom event.""" if hasattr(self, event_name): signal = getattr(self, event_name) signal.emit(*args, **kwargs) def connect_event(self, event_name: str, handler): """connect to an event.""" if hasattr(self, event_name): signal = getattr(self, event_name) signal.connect(handler)π§ͺ testing api
addon testing utilities
for comprehensive testing examples, see the smart floater example which includes both unit and integration tests.
import unittest from unittest.mock import mock, magicmock class addontestcase(unittest.testcase): """base test case for addon testing.""" def setup(self): """set up test environment.""" self.mock_gguf_app = mock() self.mock_model = mock() self.mock_gguf_app.model = self.mock_model def create_mock_model_response(self, text: str): """create a mock model response.""" return { 'choices': [{'text': text}] } def assert_model_called_with(self, expected_prompt: str): """assert model was called with expected prompt.""" self.mock_model.assert_called() call_args = self.mock_model.call_args self.assertin(expected_prompt, call_args[0][0]) # example test class testmyaddon(addontestcase): def test_text_processing(self): from addons.my_addon.main import myaddon addon = myaddon(self.mock_gguf_app) self.mock_model.return_value = self.create_mock_model_response("processed text") result = addon.process_text("input text") self.assertequal(result, "processed text") self.assert_model_called_with("input text")π logging api
addon logging
import logging from pathlib import path class addonlogger: """logging utilities for addons.""" @staticmethod def setup_logger(addon_name: str, level=logging.info): """setup logger for addon.""" logger = logging.getlogger(f"addon.{addon_name}") logger.setlevel(level) # create file handler log_dir = path.home() / ".ggufloader" / "logs" log_dir.mkdir(parents=true, exist_ok=true) file_handler = logging.filehandler(log_dir / f"{addon_name}.log") file_handler.setlevel(level) # create formatter formatter = logging.formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) file_handler.setformatter(formatter) # add handler to logger logger.addhandler(file_handler) return logger # usage in addon logger = addonlogger.setup_logger("my_addon") logger.info("addon initialized") logger.error("something went wrong")π security api
safe execution
import subprocess import tempfile import os class safeexecution: """utilities for safe code execution.""" @staticmethod def run_command_safely(command: list, timeout: int = 30) -> tuple: """run command safely with timeout.""" try: result = subprocess.run( command, capture_output=true, text=true, timeout=timeout, check=false ) return result.returncode, result.stdout, result.stderr except subprocess.timeoutexpired: return -1, "", "command timed out" except exception as e: return -1, "", str(e) @staticmethod def create_temp_file(content: str, suffix: str = ".tmp") -> str: """create temporary file safely.""" with tempfile.namedtemporaryfile(mode='w', suffix=suffix, delete=false) as f: f.write(content) return f.name @staticmethod def cleanup_temp_file(filepath: str): """clean up temporary file.""" try: if os.path.exists(filepath): os.unlink(filepath) except exception as e: logging.error(f"failed to cleanup temp file: {e}")π additional resources
- smart floater example - complete addon example
- addon development guide - step-by-step development guide
- package structure - technical architecture
- installation guide - development setup
need help with the api? join our community discussions or contact support@ggufloader.com
related documentation
- this guide will teach you how to create custom addons for gguf loader 2.0.0. addons extend the functionality of gguf loader and can provide new features, ui components, and integrations. want to see existing addons in action? [check out the addon showcase on our homepage](/#features "see community addons and examples"). ## ποΈ addon architecture ### what is an addon? an addon is a python package that extends gguf loader's functionality. addons can: - add new ui components and windows - process text and interact with ai models - integrate with external services - provide new workflows and automation - extend the main application's capabilities π― **see real examples**: visit the [homepage community section](/#community "browse community-created addons") to see what others have built and get inspiration for your own addons. ### addon structure every addon must follow this basic structure: ``` addons/ βββ your_addon_name/ βββ __init__.py # addon entry point βββ main.py # main addon logic βββ ui.py # ui components (optional) βββ config.py # configuration (optional) βββ readme.md # addon documentation ``` ## π creating your first addon ### step 1: create the addon directory ```bash mkdir -p addons/my_awesome_addon cd addons/my_awesome_addon ``` ### step 2: create the entry point (`__init__.py`) ```python """ my awesome addon - a sample addon for gguf loader this addon demonstrates the basic structure and capabilities of the gguf loader addon system. """ __version__ = "1.0.0" __author__ = "your name" __description__ = "a sample addon that demonstrates basic functionality" # import the register function from .main import register # export the register function __all__ = ["register"] ``` ### step 3: create the main logic (`main.py`) ```python """ main logic for my awesome addon """ import logging from pyside6.qtwidgets import qwidget, qvboxlayout, qlabel, qpushbutton, qtextedit from pyside6.qtcore import qtimer class myawesomeaddon: """main addon class that handles the addon functionality.""" def __init__(self, gguf_app): """initialize the addon with reference to the main gguf app.""" self.gguf_app = gguf_app self.logger = logging.getlogger(__name__) self.is_running = false # initialize your addon components here self.setup_addon() def setup_addon(self): """setup the addon components.""" self.logger.info("setting up my awesome addon") # add your initialization logic here def get_model(self): """get the currently loaded gguf model.""" try: if hasattr(self.gguf_app, 'model') and self.gguf_app.model: return self.gguf_app.model elif hasattr(self.gguf_app, 'ai_chat') and hasattr(self.gguf_app.ai_chat, 'model'): return self.gguf_app.ai_chat.model return none except exception as e: self.logger.error(f"error getting model: {e}") return none def process_text_with_ai(self, text, prompt_template="process this text: {text}"): """process text using the loaded ai model.""" model = self.get_model() if not model: return "error: no ai model loaded" try: prompt = prompt_template.format(text=text) response = model( prompt, max_tokens=200, temperature=0.7, stop=["", "\n\n"] ) # extract text from response if isinstance(response, dict) and 'choices' in response: return response['choices'][0].get('text', '').strip() elif isinstance(response, str): return response.strip() else: return str(response).strip() except exception as e: self.logger.error(f"error processing text: {e}") return f"error: {str(e)}" def start(self): """start the addon.""" self.is_running = true self.logger.info("my awesome addon started") def stop(self): """stop the addon.""" self.is_running = false self.logger.info("my awesome addon stopped") class myawesomeaddonwidget(qwidget): """ui widget for the addon.""" def __init__(self, addon_instance): super().__init__() self.addon = addon_instance self.setup_ui() def setup_ui(self): """setup the addon ui.""" self.setwindowtitle("my awesome addon") self.setminimumsize(400, 300) layout = qvboxlayout(self) # title title = qlabel("π my awesome addon") title.setstylesheet("font-size: 18px; font-weight: bold; margin: 10px;") layout.addwidget(title) # description description = qlabel("this is a sample addon that demonstrates basic functionality.") description.setwordwrap(true) layout.addwidget(description) # input area layout.addwidget(qlabel("enter text to process:")) self.input_text = qtextedit() self.input_text.setmaximumheight(100) self.input_text.setplaceholdertext("type some text here...") layout.addwidget(self.input_text) # process button self.process_btn = qpushbutton("π€ process with ai") self.process_btn.clicked.connect(self.process_text) layout.addwidget(self.process_btn) # output area layout.addwidget(qlabel("ai response:")) self.output_text = qtextedit() self.output_text.setreadonly(true) layout.addwidget(self.output_text) # status self.status_label = qlabel("ready") self.status_label.setstylesheet("color: green;") layout.addwidget(self.status_label) def process_text(self): """process the input text with ai.""" input_text = self.input_text.toplaintext().strip() if not input_text: self.output_text.settext("please enter some text to process.") return self.status_label.settext("processing...") self.status_label.setstylesheet("color: orange;") self.process_btn.setenabled(false) # process with ai (using qtimer to avoid blocking ui) qtimer.singleshot(100, lambda: self._do_processing(input_text)) def _do_processing(self, text): """actually process the text.""" try: result = self.addon.process_text_with_ai( text, "please provide a helpful and insightful response to: {text}" ) self.output_text.settext(result) self.status_label.settext("complete!") self.status_label.setstylesheet("color: green;") except exception as e: self.output_text.settext(f"error: {str(e)}") self.status_label.settext("error occurred") self.status_label.setstylesheet("color: red;") finally: self.process_btn.setenabled(true) def register(parent=none): """ register function called by gguf loader when loading the addon. args: parent: the main gguf loader application instance returns: qwidget: the addon's ui widget, or none for background addons """ try: # create the addon instance addon = myawesomeaddon(parent) addon.start() # store addon reference in parent for lifecycle management if not hasattr(parent, '_addons'): parent._addons = {} parent._addons['my_awesome_addon'] = addon # create and return the ui widget widget = myawesomeaddonwidget(addon) return widget except exception as e: logging.error(f"failed to register my awesome addon: {e}") return none ``` ### step 4: test your addon 1. **place your addon** in the `addons/` directory (see [package structure](/docs/package-structure/ "understanding addon directory structure") for details) 2. **launch gguf loader**: `ggufloader` (see [installation guide](/docs/installation/ "installation instructions") if needed) 3. **load a gguf model** in the main application (follow the [quick start guide](/docs/quick-start/ "getting started tutorial") if needed) 4. **click your addon** in the addon sidebar 5. **test the functionality** ## π¨ advanced addon features ### background addons some addons don't need a ui and run in the background: ```python def register(parent=none): """register a background addon.""" try: addon = mybackgroundaddon(parent) addon.start() # store reference but return none (no ui) parent._my_background_addon = addon return none except exception as e: logging.error(f"failed to register background addon: {e}") return none ``` ### global hotkeys and text selection learn from the [smart floating assistant addon](/docs/smart-floater-example/ "complete addon example with full source code analysis"): ```python from pyside6.qtcore import qtimer import pyautogui import pyperclip class textselectionaddon: def __init__(self, gguf_app): self.gguf_app = gguf_app self.selected_text = "" # timer for checking text selection self.selection_timer = qtimer() self.selection_timer.timeout.connect(self.check_selection) self.selection_timer.start(500) # check every 500ms def check_selection(self): """check for text selection.""" try: # save current clipboard original_clipboard = pyperclip.paste() # copy selection pyautogui.hotkey('ctrl', 'c') # check if we got new text qtimer.singleshot(50, lambda: self.process_selection(original_clipboard)) except: pass def process_selection(self, original_clipboard): """process the selected text.""" try: current_text = pyperclip.paste() if current_text != original_clipboard and len(current_text.strip()) > 3: self.selected_text = current_text.strip() self.on_text_selected(self.selected_text) # restore clipboard pyperclip.copy(original_clipboard) except: pass def on_text_selected(self, text): """handle text selection event.""" # your custom logic here print(f"text selected: {{% raw %}}{text[:50]}{{% endraw %}}...") ``` ### model integration access and use the loaded gguf model: ```python def use_model_for_processing(self, text): """use the gguf model for text processing.""" model = self.get_model() if not model: return "no model loaded" try: # different processing modes response = model( f"analyze this text: {text}", max_tokens=300, temperature=0.7, top_p=0.9, repeat_penalty=1.1, stop=["", "human:", "user:"] ) return self.extract_response_text(response) except exception as e: return f"error: {str(e)}" def extract_response_text(self, response): """extract text from model response.""" if isinstance(response, dict) and 'choices' in response: return response['choices'][0].get('text', '').strip() elif isinstance(response, str): return response.strip() else: return str(response).strip() ``` ## π addon best practices ### 1. error handling always wrap your code in try-catch blocks: ```python def safe_operation(self): try: # your code here pass except exception as e: self.logger.error(f"operation failed: {e}") return none ``` ### 2. resource cleanup implement proper cleanup: ```python def stop(self): """clean up addon resources.""" if hasattr(self, 'timer'): self.timer.stop() if hasattr(self, 'ui_components'): for component in self.ui_components: component.close() self.logger.info("addon stopped and cleaned up") ``` ### 3. configuration support user configuration: ```python import json import os class addonconfig: def __init__(self, addon_name): self.config_file = f"config/{addon_name}_config.json" self.default_config = { "enabled": true, "hotkey": "ctrl+shift+a", "auto_process": false } self.config = self.load_config() def load_config(self): try: if os.path.exists(self.config_file): with open(self.config_file, 'r') as f: return {**self.default_config, **json.load(f)} except: pass return self.default_config.copy() def save_config(self): os.makedirs(os.path.dirname(self.config_file), exist_ok=true) with open(self.config_file, 'w') as f: json.dump(self.config, f, indent=2) ``` ### 4. logging use proper logging: ```python import logging class myaddon: def __init__(self, gguf_app): self.logger = logging.getlogger(f"addon.{self.__class__.__name__}") self.logger.setlevel(logging.info) # log addon initialization self.logger.info("addon initialized") def process_data(self, data): self.logger.debug(f"processing data: {len(data)} items") try: # process data result = self.do_processing(data) self.logger.info("data processed successfully") return result except exception as e: self.logger.error(f"processing failed: {e}") raise ``` ## π§ testing your addon ### unit testing create tests for your addon: ```python # test_my_addon.py import unittest from unittest.mock import mock, magicmock from addons.my_awesome_addon.main import myawesomeaddon class testmyawesomeaddon(unittest.testcase): def setup(self): self.mock_gguf_app = mock() self.addon = myawesomeaddon(self.mock_gguf_app) def test_addon_initialization(self): self.assertisnotnone(self.addon) self.assertequal(self.addon.gguf_app, self.mock_gguf_app) def test_text_processing(self): # mock the model mock_model = mock() mock_model.return_value = "processed text" self.mock_gguf_app.model = mock_model result = self.addon.process_text_with_ai("test text") self.assertequal(result, "processed text") if __name__ == '__main__': unittest.main() ``` ### integration testing test with the actual gguf loader: ```python # test_integration.py def test_addon_with_gguf_loader(): """test addon integration with gguf loader.""" # this would be run with actual gguf loader instance pass ``` ## π¦ distributing your addon ### 1. create documentation create a `readme.md` for your addon: ```markdown # my awesome addon a powerful addon for gguf loader that provides [functionality]. ## features - feature 1 - feature 2 - feature 3 ## installation 1. copy the addon to `addons/my_awesome_addon/` 2. restart gguf loader 3. click on the addon in the sidebar ## configuration [configuration instructions] ## usage [usage instructions] ``` ### 2. version your addon use semantic versioning in `__init__.py`: ```python __version__ = "1.0.0" # major.minor.patch ``` ### 3. share with community - create a github repository - add installation instructions - include screenshots and examples - submit to the community addon registry π‘ **get featured**: outstanding addons may be featured on our [homepage community showcase](/#community "community addon highlights") - share your creation with the world! ## π€ contributing to core want to contribute to gguf loader itself? check out our [contributing guide](/docs/contributing/ "guidelines for contributing to gguf loader development"). ## π additional resources - [addon api reference](/docs/addon-api/ "complete api documentation for addon developers") - complete api documentation - [smart floater example](/docs/smart-floater-example/ "real-world addon example with detailed code analysis") - learn from the built-in addon - [package structure](/docs/package-structure/ "understanding gguf loader's architecture") - technical architecture details - [installation guide](/docs/installation/ "setup development environment") - development environment setup --- ## π you're ready to build amazing addons! congratulations on completing the addon development guide! you now have the knowledge and tools to create powerful extensions for gguf loader. ### what you've learned - β addon architecture and structure - β creating ui components and background services - β integrating with ai models - β best practices and testing strategies - β distribution and community sharing ### your next steps as an addon developer #### π **start building** - **create your first addon** - put your knowledge into practice with a simple project - **[study real examples](/#features "community addon showcase")** - see how others have implemented creative solutions - **[join developer discussions](/#community "developer community")** - connect with other addon developers #### π **share and grow** - **[showcase your work](/#community "community highlights")** - get your addon featured on our homepage - **[contribute to core](/docs/contributing/ "help improve gguf loader")** - help improve gguf loader itself - **[mentor others](/#community "help newcomers")** - share your expertise with new developers #### π₯ **advanced challenges** - **build complex integrations** - create addons that work with external services - **optimize performance** - learn advanced techniques for efficient addons - **create addon libraries** - build reusable components for the community ### π‘ get inspired visit our [homepage community section](/#community "see what's possible") to see what other developers have created and get inspiration for your next project! **happy addon development! π** need help? join our [community discussions](https://github.com/gguf-loader/gguf-loader/discussions) or contact us at support@ggufloader.com.
- learn how to create addons by studying the built-in smart floating assistant addon. this is a complete, real-world example that demonstrates all the key concepts of addon development. ## π overview the smart floating assistant is gguf loader's flagship addon that provides: - **global text selection detection** across all applications - **floating button interface** that appears near selected text - **ai-powered text processing** (summarize and comment) - **seamless clipboard integration** - **privacy-first local processing** ## ποΈ architecture ### file structure ``` addons/smart_floater/ βββ __init__.py # addon entry point βββ simple_main.py # main addon logic (simplified version) βββ main.py # full-featured version βββ floater_ui.py # ui components βββ comment_engine.py # text processing engine βββ injector.py # text injection utilities βββ error_handler.py # error handling βββ privacy_security.py # privacy and security features βββ performance_optimizer.py # performance optimization ``` ### key components 1. **simplefloatingassistant**: main addon class 2. **smartfloaterstatuswidget**: control panel ui 3. **text selection monitor**: global text detection 4. **ai processing engine**: text summarization and commenting 5. **clipboard manager**: safe clipboard operations ## π code analysis ### entry point (`__init__.py`) ```python """ simple smart floating assistant shows a button when you select text, processes it with ai. that's it. """ # use the simple version instead of the complex one from .simple_main import register __all__ = ["register"] ``` **key lessons:** - keep the entry point simple - export only the `register` function - use clear, descriptive docstrings ### main logic (`simple_main.py`) let's break down the main addon class: ```python class simplefloatingassistant: """simple floating assistant that shows button on text selection.""" def __init__(self, gguf_app_instance: any): """initialize the addon with gguf loader reference.""" self.gguf_app = gguf_app_instance self._is_running = false self._floating_button = none self._popup_window = none self._selected_text = "" self.model = none # store model reference directly # initialize clipboard tracking try: self.last_clipboard = pyperclip.paste() except: self.last_clipboard = "" # button persistence tracking self.button_show_time = 0 self.button_should_stay = false # connect to model loading signals self.connect_to_model_signals() # timer to check for text selection self.timer = qtimer() self.timer.timeout.connect(self.check_selection) self.timer.start(300) # check every 300ms ``` **key lessons:** - store reference to main app (`gguf_app`) - initialize all state variables - connect to model loading signals - use qtimer for periodic tasks - handle initialization errors gracefully ### model integration ```python def connect_to_model_signals(self): """connect to model loading signals from the main app.""" try: # connect to the main app's model_loaded signal if hasattr(self.gguf_app, 'model_loaded'): self.gguf_app.model_loaded.connect(self.on_model_loaded) print("β connected to model_loaded signal") # also try to connect to ai_chat model_loaded signal if hasattr(self.gguf_app, 'ai_chat') and hasattr(self.gguf_app.ai_chat, 'model_loaded'): self.gguf_app.ai_chat.model_loaded.connect(self.on_model_loaded) print("β connected to ai_chat model_loaded signal") except exception as e: print(f"β error connecting to model signals: {e}") def on_model_loaded(self, model): """handle model loaded event.""" self.model = model print(f"β addon received model: {type(model)}") print(f" model methods: {{% raw %}}{[m for m in dir(model) if not m.startswith('_')][:10]}{{% endraw %}}") def get_model(self): """get the loaded model.""" try: # first try our stored model reference if self.model: print("β using stored model reference") return self.model # try multiple fallback methods if hasattr(self.gguf_app, 'model'): if self.gguf_app.model: self.model = self.gguf_app.model return self.gguf_app.model # ... more fallback methods return none except exception as e: print(f"β error getting model: {e}") return none ``` **key lessons:** - connect to model loading signals for real-time updates - implement multiple fallback methods for model access - store model reference locally for performance - use defensive programming with try-catch blocks - provide helpful debug output ### text selection detection ```python def check_selection(self): """check if text is currently selected (without copying).""" try: # save current clipboard content original_clipboard = pyperclip.paste() # temporarily copy selection to check if text is selected pyautogui.hotkey('ctrl', 'c') # small delay to let clipboard update qtimer.singleshot(50, lambda: self._process_selection_check(original_clipboard)) except: pass def _process_selection_check(self, original_clipboard): """process the selection check and restore clipboard.""" try: # get what was copied current_selection = pyperclip.paste() # check if we got new selected text if (current_selection != original_clipboard and current_selection and len(current_selection.strip()) > 3 and len(current_selection) < 5000): # we have selected text! if current_selection.strip() != self.selected_text: self.selected_text = current_selection.strip() self.show_button() self.button_show_time = 0 # reset timer self.button_should_stay = true else: # no text selected - but don't hide immediately if self.button_should_stay: self.button_show_time += 1 # hide after 10 checks (about 3 seconds) if self.button_show_time > 10: self.hide_button() self.button_should_stay = false self.button_show_time = 0 # always restore original clipboard immediately pyperclip.copy(original_clipboard) except: # always try to restore clipboard even if there's an error try: pyperclip.copy(original_clipboard) except: pass ``` **key lessons:** - use non-intrusive text selection detection - always restore the user's clipboard - implement smart button persistence (don't hide immediately) - handle edge cases (empty text, very long text) - use defensive programming for clipboard operations ### floating ui ```python def show_button(self): """show floating button near cursor.""" if self.button: self.button.close() self.button = qpushbutton("β¨") self.button.setfixedsize(40, 40) self.button.setwindowflags(qt.tooltip | qt.framelesswindowhint | qt.windowstaysontophint) self.button.setstylesheet(""" qpushbutton { background-color: #0078d4; border: none; border-radius: 20px; color: white; font-size: 16px; } qpushbutton:hover { background-color: #106ebe; } """) # position near cursor pos = qcursor.pos() self.button.move(pos.x() + 10, pos.y() - 50) self.button.clicked.connect(self.show_popup) self.button.show() # reset persistence tracking self.button_show_time = 0 self.button_should_stay = true ``` **key lessons:** - use appropriate window flags for floating widgets - position relative to cursor for better ux - apply attractive styling with css - connect button clicks to actions - clean up previous instances before creating new ones ### ai text processing ```python def process_text(self, action): """process text with ai using gguf loader's model.""" try: model = self.get_model() if not model: self.result_area.settext("β error: no ai model loaded in gguf loader\n\nplease load a gguf model first!") return self.result_area.settext("π€ processing with ai...") # create appropriate prompt based on action if action == "summarize": prompt = f"please provide a clear and concise summary of the following text:\n\n{self.selected_text}\n\nsummary:" else: # comment prompt = f"please write a thoughtful and insightful comment about the following text:\n\n{self.selected_text}\n\ncomment:" # process with gguf model using the same interface as aichat try: # use the model the same way as chatgenerator does response = model( prompt, max_tokens=300, stream=false, # don't stream for simplicity temperature=0.7, top_p=0.9, repeat_penalty=1.1, top_k=40, stop=["", "human:", "user:", "\n\n\n"] ) # extract text from response if isinstance(response, dict) and 'choices' in response: result_text = response['choices'][0].get('text', '').strip() elif isinstance(response, str): result_text = response.strip() else: result_text = str(response).strip() # clean up the result if result_text: # remove any prompt echoing if "summary:" in result_text: result_text = result_text.split("summary:")[-1].strip() elif "comment:" in result_text: result_text = result_text.split("comment:")[-1].strip() self.result_area.settext(result_text) self.copy_btn.setenabled(true) else: self.result_area.settext("β no response generated. try again.") except exception as e: self.result_area.settext(f"β error processing with ai model:\n{str(e)}\n\nmake sure a compatible gguf model is loaded.") except exception as e: self.result_area.settext(f"β unexpected error: {str(e)}") ``` **key lessons:** - check model availability before processing - create context-appropriate prompts - use consistent model parameters - handle different response formats - clean up ai responses (remove prompt echoing) - provide clear error messages to users ### status widget for addon panel ```python class smartfloaterstatuswidget: def __init__(self, addon_instance): from pyside6.qtwidgets import qwidget, qvboxlayout, qlabel, qpushbutton, qtextedit self.addon = addon_instance self.widget = qwidget() self.widget.setwindowtitle("smart floating assistant") layout = qvboxlayout(self.widget) # status info layout.addwidget(qlabel("π€ smart floating assistant")) layout.addwidget(qlabel("status: running in background")) layout.addwidget(qlabel("")) layout.addwidget(qlabel("how to use:")) layout.addwidget(qlabel("1. select text anywhere on your screen")) layout.addwidget(qlabel("2. click the β¨ button that appears")) layout.addwidget(qlabel("3. choose summarize or comment")) layout.addwidget(qlabel("")) # test button test_btn = qpushbutton("π§ͺ test model connection") test_btn.clicked.connect(self.test_model) layout.addwidget(test_btn) # result area self.result_area = qtextedit() self.result_area.setmaximumheight(100) self.result_area.setreadonly(true) layout.addwidget(self.result_area) # stop/start buttons button_layout = qhboxlayout() stop_btn = qpushbutton("βΉοΈ stop") stop_btn.clicked.connect(self.stop_addon) button_layout.addwidget(stop_btn) start_btn = qpushbutton("βΆοΈ start") start_btn.clicked.connect(self.start_addon) button_layout.addwidget(start_btn) layout.addlayout(button_layout) ``` **key lessons:** - create informative status widgets for addon management - provide clear usage instructions - include testing and control functionality - use emoji and clear labels for better ux - separate ui logic from core addon logic ### registration function ```python def register(parent=none): """register the simple floating assistant.""" try: print(f"π§ register called with parent: {type(parent)}") # stop existing addon if running if hasattr(parent, '_simple_floater'): parent._simple_floater.stop() # create and start simple addon addon = simplefloatingassistant(parent) parent._simple_floater = addon print("β simple floating assistant started!") # return a status widget for the addon panel status_widget = smartfloaterstatuswidget(addon) return status_widget.widget except exception as e: print(f"β failed to start simple addon: {e}") return none ``` **key lessons:** - always handle cleanup of existing instances - store addon reference in parent for lifecycle management - return appropriate ui widget or none for background addons - provide clear success/failure feedback - use defensive programming with try-catch ## π― best practices demonstrated ### 1. **defensive programming** - extensive use of try-catch blocks - graceful handling of missing dependencies - fallback methods for critical operations ### 2. **user experience** - non-intrusive text selection detection - smart button persistence (doesn't disappear immediately) - clear status messages and error handling - attractive, modern ui design ### 3. **performance optimization** - efficient timer-based monitoring - minimal clipboard interference - lazy loading of ui components - resource cleanup on shutdown ### 4. **integration patterns** - signal-based communication with main app - multiple fallback methods for model access - proper lifecycle management - clean separation of concerns ### 5. **error handling** - comprehensive error messages - graceful degradation when model unavailable - user-friendly error reporting - debug information for developers ## π§ customization examples ### adding new processing actions ```python def process_text(self, action): """extended processing with more actions.""" prompts = { "summarize": "please provide a clear and concise summary of: {text}", "comment": "please write a thoughtful comment about: {text}", "explain": "please explain this text in simple terms: {text}", "translate": "please translate this text to english: {text}", "improve": "please improve the writing of this text: {text}" } prompt_template = prompts.get(action, prompts["summarize"]) prompt = prompt_template.format(text=self.selected_text) # ... rest of processing logic ``` ### custom hotkeys ```python def setup_hotkeys(self): """setup custom hotkeys for the addon.""" try: import keyboard # register global hotkey for instant processing keyboard.add_hotkey('ctrl+shift+s', self.quick_summarize) keyboard.add_hotkey('ctrl+shift+c', self.quick_comment) except importerror: print("keyboard library not available for hotkeys") def quick_summarize(self): """quick summarize selected text without ui.""" # get current selection and process immediately pass ``` ### configuration support ```python def load_config(self): """load addon configuration.""" config_file = path.home() / ".ggufloader" / "smart_floater_config.json" default_config = { "check_interval": 300, "button_timeout": 3000, "max_text_length": 5000, "auto_copy_results": true } try: if config_file.exists(): with open(config_file) as f: user_config = json.load(f) return {**default_config, **user_config} except: pass return default_config ``` ## π performance considerations ### memory management - clean up ui components properly - avoid memory leaks in timer callbacks - use weak references where appropriate ### cpu usage - optimize timer intervals - avoid blocking operations in main thread - use qtimer.singleshot for delayed operations ### system integration - minimize clipboard interference - respect user's workflow - handle system sleep/wake events ## π§ͺ testing the smart floater ### manual testing checklist 1. **basic functionality** - [ ] addon loads without errors - [ ] status widget appears in sidebar - [ ] model connection test works 2. **text selection** - [ ] button appears when selecting text - [ ] button stays visible for appropriate time - [ ] works across different applications 3. **ai processing** - [ ] summarize function works correctly - [ ] comment function generates appropriate responses - [ ] error handling when no model loaded 4. **ui/ux** - [ ] floating button positioned correctly - [ ] popup window displays properly - [ ] copy functionality works ### automated testing ```python import unittest from unittest.mock import mock, patch class testsmartfloater(unittest.testcase): def setup(self): self.mock_gguf_app = mock() self.addon = simplefloatingassistant(self.mock_gguf_app) def test_model_connection(self): """test model connection and retrieval.""" mock_model = mock() self.mock_gguf_app.model = mock_model result = self.addon.get_model() self.assertequal(result, mock_model) @patch('pyperclip.paste') @patch('pyperclip.copy') def test_clipboard_operations(self, mock_copy, mock_paste): """test clipboard operations don't interfere.""" mock_paste.return_value = "original text" self.addon.check_selection() # verify clipboard was restored mock_copy.assert_called_with("original text") ``` ## π next steps after studying the smart floater example: 1. **create your own addon** using the patterns shown (follow the [addon development guide](/docs/addon-development/ "step-by-step addon creation tutorial")) 2. **experiment with modifications** to understand the code better 3. **read the full source code** in `addons/smart_floater/` (see [package structure](/docs/package-structure/ "understanding addon file organization")) 4. **reference the api documentation** ([addon api reference](/docs/addon-api/ "complete api documentation")) for detailed method signatures 5. **join the community** to share your addon ideas ## π related documentation - [addon development guide](/docs/addon-development/ "complete tutorial for creating your own addons") - step-by-step development guide - [addon api reference](/docs/addon-api/ "detailed api documentation for addon developers") - complete api documentation - [package structure](/docs/package-structure/ "understanding how addons are integrated") - technical architecture - [quick start guide](/docs/quick-start/ "learn how to use the smart floater as an end user") - how to use the smart floater as an end user --- **the smart floater is a great example of what's possible with gguf loader addons. use it as inspiration for your own creations! π** need help understanding any part of the code? join our [community discussions](https://github.com/gguf-loader/gguf-loader/discussions) or contact support@ggufloader.com.
- this document explains the structure of the gguf loader 2.0.0 pypi package and how the smart floating assistant addon is included. ## π¦ package overview **package name**: `ggufloader` **version**: `2.0.0` **command**: `ggufloader` when users install with `pip install ggufloader`, they get: - complete gguf loader application - smart floating assistant addon (pre-installed) - comprehensive documentation - all necessary dependencies ## ποΈ directory structure ``` ggufloader/ βββ pyproject.toml # package configuration βββ readme_pypi.md # pypi package description βββ build_pypi.py # build script for pypi βββ requirements.txt # dependencies βββ βββ # main application files βββ main.py # basic gguf loader (no addons) βββ gguf_loader_main.py # gguf loader with addon support βββ addon_manager.py # addon management system βββ config.py # configuration βββ utils.py # utilities βββ icon.ico # application icon βββ βββ # ui components βββ ui/ β βββ ai_chat_window.py # main chat interface β βββ apply_style.py # ui styling βββ βββ # core models βββ models/ β βββ model_loader.py # gguf model loading β βββ chat_generator.py # text generation βββ βββ # ui mixins βββ mixins/ β βββ ui_setup_mixin.py # ui setup β βββ model_handler_mixin.py # model handling β βββ chat_handler_mixin.py # chat functionality β βββ event_handler_mixin.py # event handling β βββ utils_mixin.py # utility functions βββ βββ # widgets βββ widgets/ β βββ chat_bubble.py # chat bubble component βββ βββ # pre-installed addons βββ addons/ β βββ smart_floater/ # smart floating assistant β βββ __init__.py # addon entry point β βββ simple_main.py # main addon logic β βββ main.py # full-featured version β βββ floater_ui.py # ui components β βββ comment_engine.py # text processing β βββ injector.py # text injection β βββ error_handler.py # error handling β βββ privacy_security.py # privacy features β βββ performance_optimizer.py # performance βββ βββ # documentation βββ docs/ βββ readme.md # documentation index βββ installation.md # installation guide βββ quick-start.md # quick start guide βββ user-guide.md # complete user manual βββ addon-development.md # addon development guide βββ addon-api.md # api reference βββ smart-floater-example.md # example addon βββ configuration.md # configuration guide βββ troubleshooting.md # troubleshooting βββ contributing.md # contributing guide βββ package-structure.md # this file ``` ## π installation and usage ### installation ```bash pip install ggufloader ``` ### launch application ```bash ggufloader ``` this command launches `gguf_loader_main.py` which includes: - full gguf loader functionality - smart floating assistant addon (automatically loaded) - addon management system - all ui components ## π§ how addons are included ### addon discovery when gguf loader starts, the `addonmanager` automatically: 1. **scans** the `addons/` directory 2. **finds** folders with `__init__.py` files 3. **loads** addons by calling their `register()` function 4. **displays** addon buttons in the sidebar ### smart floater integration the smart floating assistant is included as a pre-installed addon: ```python # addons/smart_floater/__init__.py from .simple_main import register __all__ = ["register"] # when loaded, it provides: # - global text selection detection # - floating button interface # - ai text processing (summarize/comment) # - seamless clipboard integration ``` ### addon lifecycle 1. **package installation**: addon files are installed with the package 2. **application start**: `addonmanager` discovers and loads addons 3. **user interaction**: users can access addons via the sidebar 4. **background operation**: smart floater runs continuously in background ## π package configuration ### pyproject.toml key sections ```toml [project] name = "ggufloader" version = "2.0.0" dependencies = [ "llama-cpp-python>=0.2.72", "pyside6>=6.6.1", "pyautogui>=0.9.54", "pyperclip>=1.8.2", "pywin32>=306; sys_platform == 'win32'", ] [project.scripts] ggufloader = "gguf_loader.gguf_loader_main:main" [tool.setuptools] packages = [ "gguf_loader", "gguf_loader.addons", "gguf_loader.addons.smart_floater" ] include-package-data = true ``` ### package data inclusion all necessary files are included: - python source code - documentation (`.md` files) - icons and images - configuration files - addon files ## π― user experience ### first-time users 1. **install**: `pip install ggufloader` 2. **launch**: `ggufloader` 3. **load model**: click "select gguf model" 4. **use smart floater**: select text anywhere β click β¨ button ### addon discovery - smart floater appears in addon sidebar automatically - users can click to open control panel - no additional installation required - works immediately after model loading ### documentation access users can access documentation: - online: github repository - locally: installed with package in `docs/` folder - in-app: help links and tooltips ## π version updates ### updating the package when releasing new versions: 1. **update version** in `pyproject.toml` 2. **update changelog** and documentation 3. **test addon compatibility** 4. **build and upload** to pypi ### addon updates smart floater updates are included in package updates: - bug fixes and improvements - new features and capabilities - performance optimizations - security enhancements ## π οΈ development workflow ### for package maintainers 1. **develop** new features and addons 2. **test** thoroughly with various models 3. **update** documentation 4. **build** package with `python build_pypi.py` 5. **upload** to pypi ### for addon developers 1. **study** the [smart floater example](/docs/smart-floater-example/ "complete addon example with detailed code analysis") 2. **follow** the [addon development guide](/docs/addon-development/ "step-by-step addon creation tutorial") 3. **reference** the [api documentation](/docs/addon-api/ "complete api reference for developers") 4. **create** addons in `addons/` directory 5. **test** with gguf loader (see [quick start guide](/docs/quick-start/ "getting started tutorial")) 6. **share** with community ## π package statistics ### size and dependencies - **package size**: ~50mb (includes all dependencies) - **core dependencies**: 5 main packages - **optional dependencies**: gpu acceleration packages - **documentation**: 10+ comprehensive guides ### compatibility - **python**: 3.8+ (tested on 3.8, 3.9, 3.10, 3.11, 3.12) - **operating systems**: windows, macos, linux - **architectures**: x86_64, arm64 (apple silicon) ## π troubleshooting package issues ### common installation issues 1. **python version**: ensure python 3.8+ 2. **dependencies**: install build tools if needed 3. **permissions**: use `--user` flag if needed 4. **virtual environment**: recommended for isolation ### addon loading issues 1. **check logs**: look for addon loading errors 2. **verify structure**: ensure `__init__.py` exists 3. **dependencies**: check addon-specific requirements 4. **permissions**: verify file permissions ### getting help - **documentation**: check `docs/` folder - **github issues**: report bugs and issues - **community**: join discussions and forums - **support**: contact support@ggufloader.com ## π success metrics the package structure is designed to provide: - **easy installation**: single `pip install` command - **immediate functionality**: smart floater works out of the box - **extensibility**: clear addon development path - **maintainability**: well-organized codebase - **user-friendly**: comprehensive documentation --- **this package structure ensures that gguf loader 2.0.0 provides a complete, professional ai text processing solution with the smart floating assistant included by default! π**
π related features
π― what's next?
you've completed this guide! here are some suggested next steps to continue your gguf loader journey:
πshare your addon
built something awesome? share it with the community and get featured on our homepage.
share with community βππ€π explore more features
π back to homepage
addon development guide
learn to create custom addons for gguf loader with examples and best practices
advanced 15 minutesthis guide will teach you how to create custom addons for gguf loader 2.0.0. addons extend the functionality of gguf loader and can provide new features, ui components, and integrations. want to see existing addons in action? check out the addon showcase on our homepage.
ποΈ addon architecture
what is an addon?
an addon is a python package that extends gguf loaderβs functionality. addons can:
- add new ui components and windows
- process text and interact with ai models
- integrate with external services
- provide new workflows and automation
- extend the main applicationβs capabilities
π― see real examples: visit the homepage community section to see what others have built and get inspiration for your own addons.
addon structure
every addon must follow this basic structure:
addons/ βββ your_addon_name/ βββ __init__.py # addon entry point βββ main.py # main addon logic βββ ui.py # ui components (optional) βββ config.py # configuration (optional) βββ readme.md # addon documentationπ creating your first addon
step 1: create the addon directory
mkdir -p addons/my_awesome_addon cd addons/my_awesome_addonstep 2: create the entry point (
__init__.py)""" my awesome addon - a sample addon for gguf loader this addon demonstrates the basic structure and capabilities of the gguf loader addon system. """ __version__ = "1.0.0" __author__ = "your name" __description__ = "a sample addon that demonstrates basic functionality" # import the register function from .main import register # export the register function __all__ = ["register"]step 3: create the main logic (
main.py)""" main logic for my awesome addon """ import logging from pyside6.qtwidgets import qwidget, qvboxlayout, qlabel, qpushbutton, qtextedit from pyside6.qtcore import qtimer class myawesomeaddon: """main addon class that handles the addon functionality.""" def __init__(self, gguf_app): """initialize the addon with reference to the main gguf app.""" self.gguf_app = gguf_app self.logger = logging.getlogger(__name__) self.is_running = false # initialize your addon components here self.setup_addon() def setup_addon(self): """setup the addon components.""" self.logger.info("setting up my awesome addon") # add your initialization logic here def get_model(self): """get the currently loaded gguf model.""" try: if hasattr(self.gguf_app, 'model') and self.gguf_app.model: return self.gguf_app.model elif hasattr(self.gguf_app, 'ai_chat') and hasattr(self.gguf_app.ai_chat, 'model'): return self.gguf_app.ai_chat.model return none except exception as e: self.logger.error(f"error getting model: {e}") return none def process_text_with_ai(self, text, prompt_template="process this text: {text}"): """process text using the loaded ai model.""" model = self.get_model() if not model: return "error: no ai model loaded" try: prompt = prompt_template.format(text=text) response = model( prompt, max_tokens=200, temperature=0.7, stop=["</s>", "\n\n"] ) # extract text from response if isinstance(response, dict) and 'choices' in response: return response['choices'][0].get('text', '').strip() elif isinstance(response, str): return response.strip() else: return str(response).strip() except exception as e: self.logger.error(f"error processing text: {e}") return f"error: {str(e)}" def start(self): """start the addon.""" self.is_running = true self.logger.info("my awesome addon started") def stop(self): """stop the addon.""" self.is_running = false self.logger.info("my awesome addon stopped") class myawesomeaddonwidget(qwidget): """ui widget for the addon.""" def __init__(self, addon_instance): super().__init__() self.addon = addon_instance self.setup_ui() def setup_ui(self): """setup the addon ui.""" self.setwindowtitle("my awesome addon") self.setminimumsize(400, 300) layout = qvboxlayout(self) # title title = qlabel("π my awesome addon") title.setstylesheet("font-size: 18px; font-weight: bold; margin: 10px;") layout.addwidget(title) # description description = qlabel("this is a sample addon that demonstrates basic functionality.") description.setwordwrap(true) layout.addwidget(description) # input area layout.addwidget(qlabel("enter text to process:")) self.input_text = qtextedit() self.input_text.setmaximumheight(100) self.input_text.setplaceholdertext("type some text here...") layout.addwidget(self.input_text) # process button self.process_btn = qpushbutton("π€ process with ai") self.process_btn.clicked.connect(self.process_text) layout.addwidget(self.process_btn) # output area layout.addwidget(qlabel("ai response:")) self.output_text = qtextedit() self.output_text.setreadonly(true) layout.addwidget(self.output_text) # status self.status_label = qlabel("ready") self.status_label.setstylesheet("color: green;") layout.addwidget(self.status_label) def process_text(self): """process the input text with ai.""" input_text = self.input_text.toplaintext().strip() if not input_text: self.output_text.settext("please enter some text to process.") return self.status_label.settext("processing...") self.status_label.setstylesheet("color: orange;") self.process_btn.setenabled(false) # process with ai (using qtimer to avoid blocking ui) qtimer.singleshot(100, lambda: self._do_processing(input_text)) def _do_processing(self, text): """actually process the text.""" try: result = self.addon.process_text_with_ai( text, "please provide a helpful and insightful response to: {text}" ) self.output_text.settext(result) self.status_label.settext("complete!") self.status_label.setstylesheet("color: green;") except exception as e: self.output_text.settext(f"error: {str(e)}") self.status_label.settext("error occurred") self.status_label.setstylesheet("color: red;") finally: self.process_btn.setenabled(true) def register(parent=none): """ register function called by gguf loader when loading the addon. args: parent: the main gguf loader application instance returns: qwidget: the addon's ui widget, or none for background addons """ try: # create the addon instance addon = myawesomeaddon(parent) addon.start() # store addon reference in parent for lifecycle management if not hasattr(parent, '_addons'): parent._addons = {} parent._addons['my_awesome_addon'] = addon # create and return the ui widget widget = myawesomeaddonwidget(addon) return widget except exception as e: logging.error(f"failed to register my awesome addon: {e}") return nonestep 4: test your addon
- place your addon in the
addons/directory (see package structure for details) - launch gguf loader:
ggufloader(see installation guide if needed) - load a gguf model in the main application (follow the quick start guide if needed)
- click your addon in the addon sidebar
- test the functionality
π¨ advanced addon features
background addons
some addons donβt need a ui and run in the background:
def register(parent=none): """register a background addon.""" try: addon = mybackgroundaddon(parent) addon.start() # store reference but return none (no ui) parent._my_background_addon = addon return none except exception as e: logging.error(f"failed to register background addon: {e}") return noneglobal hotkeys and text selection
learn from the smart floating assistant addon:
from pyside6.qtcore import qtimer import pyautogui import pyperclip class textselectionaddon: def __init__(self, gguf_app): self.gguf_app = gguf_app self.selected_text = "" # timer for checking text selection self.selection_timer = qtimer() self.selection_timer.timeout.connect(self.check_selection) self.selection_timer.start(500) # check every 500ms def check_selection(self): """check for text selection.""" try: # save current clipboard original_clipboard = pyperclip.paste() # copy selection pyautogui.hotkey('ctrl', 'c') # check if we got new text qtimer.singleshot(50, lambda: self.process_selection(original_clipboard)) except: pass def process_selection(self, original_clipboard): """process the selected text.""" try: current_text = pyperclip.paste() if current_text != original_clipboard and len(current_text.strip()) > 3: self.selected_text = current_text.strip() self.on_text_selected(self.selected_text) # restore clipboard pyperclip.copy(original_clipboard) except: pass def on_text_selected(self, text): """handle text selection event.""" # your custom logic here print(f"text selected: {text[:50]}...")model integration
access and use the loaded gguf model:
def use_model_for_processing(self, text): """use the gguf model for text processing.""" model = self.get_model() if not model: return "no model loaded" try: # different processing modes response = model( f"analyze this text: {text}", max_tokens=300, temperature=0.7, top_p=0.9, repeat_penalty=1.1, stop=["</s>", "human:", "user:"] ) return self.extract_response_text(response) except exception as e: return f"error: {str(e)}" def extract_response_text(self, response): """extract text from model response.""" if isinstance(response, dict) and 'choices' in response: return response['choices'][0].get('text', '').strip() elif isinstance(response, str): return response.strip() else: return str(response).strip()π addon best practices
1. error handling
always wrap your code in try-catch blocks:
def safe_operation(self): try: # your code here pass except exception as e: self.logger.error(f"operation failed: {e}") return none2. resource cleanup
implement proper cleanup:
def stop(self): """clean up addon resources.""" if hasattr(self, 'timer'): self.timer.stop() if hasattr(self, 'ui_components'): for component in self.ui_components: component.close() self.logger.info("addon stopped and cleaned up")3. configuration
support user configuration:
import json import os class addonconfig: def __init__(self, addon_name): self.config_file = f"config/{addon_name}_config.json" self.default_config = { "enabled": true, "hotkey": "ctrl+shift+a", "auto_process": false } self.config = self.load_config() def load_config(self): try: if os.path.exists(self.config_file): with open(self.config_file, 'r') as f: return {**self.default_config, **json.load(f)} except: pass return self.default_config.copy() def save_config(self): os.makedirs(os.path.dirname(self.config_file), exist_ok=true) with open(self.config_file, 'w') as f: json.dump(self.config, f, indent=2)4. logging
use proper logging:
import logging class myaddon: def __init__(self, gguf_app): self.logger = logging.getlogger(f"addon.{self.__class__.__name__}") self.logger.setlevel(logging.info) # log addon initialization self.logger.info("addon initialized") def process_data(self, data): self.logger.debug(f"processing data: {len(data)} items") try: # process data result = self.do_processing(data) self.logger.info("data processed successfully") return result except exception as e: self.logger.error(f"processing failed: {e}") raiseπ§ testing your addon
unit testing
create tests for your addon:
# test_my_addon.py import unittest from unittest.mock import mock, magicmock from addons.my_awesome_addon.main import myawesomeaddon class testmyawesomeaddon(unittest.testcase): def setup(self): self.mock_gguf_app = mock() self.addon = myawesomeaddon(self.mock_gguf_app) def test_addon_initialization(self): self.assertisnotnone(self.addon) self.assertequal(self.addon.gguf_app, self.mock_gguf_app) def test_text_processing(self): # mock the model mock_model = mock() mock_model.return_value = "processed text" self.mock_gguf_app.model = mock_model result = self.addon.process_text_with_ai("test text") self.assertequal(result, "processed text") if __name__ == '__main__': unittest.main()integration testing
test with the actual gguf loader:
# test_integration.py def test_addon_with_gguf_loader(): """test addon integration with gguf loader.""" # this would be run with actual gguf loader instance passπ¦ distributing your addon
1. create documentation
create a
readme.mdfor your addon:# my awesome addon a powerful addon for gguf loader that provides [functionality]. ## features - feature 1 - feature 2 - feature 3 ## installation 1. copy the addon to `addons/my_awesome_addon/` 2. restart gguf loader 3. click on the addon in the sidebar ## configuration [configuration instructions] ## usage [usage instructions]2. version your addon
use semantic versioning in
__init__.py:__version__ = "1.0.0" # major.minor.patch3. share with community
- create a github repository
- add installation instructions
- include screenshots and examples
- submit to the community addon registry
π‘ get featured: outstanding addons may be featured on our homepage community showcase - share your creation with the world!
π€ contributing to core
want to contribute to gguf loader itself? check out our contributing guide.
π additional resources
- addon api reference - complete api documentation
- smart floater example - learn from the built-in addon
- package structure - technical architecture details
- installation guide - development environment setup
π youβre ready to build amazing addons!
congratulations on completing the addon development guide! you now have the knowledge and tools to create powerful extensions for gguf loader.
what youβve learned
- β addon architecture and structure
- β creating ui components and background services
- β integrating with ai models
- β best practices and testing strategies
- β distribution and community sharing
your next steps as an addon developer
π start building
- create your first addon - put your knowledge into practice with a simple project
- study real examples - see how others have implemented creative solutions
- join developer discussions - connect with other addon developers
π share and grow
- showcase your work - get your addon featured on our homepage
- contribute to core - help improve gguf loader itself
- mentor others - share your expertise with new developers
π₯ advanced challenges
- build complex integrations - create addons that work with external services
- optimize performance - learn advanced techniques for efficient addons
- create addon libraries - build reusable components for the community
π‘ get inspired
visit our homepage community section to see what other developers have created and get inspiration for your next project!
happy addon development! π
need help? join our community discussions or contact us at support@ggufloader.com.
related documentation
addon api reference
complete api reference for developing gguf loader addons
advanced 20 minutescomplete api reference for developing gguf loader addons.
ποΈ core api
addon registration
every addon must implement a
register()function:def register(parent=none): """ register function called by gguf loader when loading the addon. args: parent: the main gguf loader application instance returns: qwidget: the addon's ui widget, or none for background addons """ passmain application interface
the
parentparameter provides access to the main gguf loader application:class ggufloaderapp: """main gguf loader application interface.""" # properties model: optional[any] # currently loaded gguf model ai_chat: aichat # ai chat interface addon_manager: addonmanager # addon management system # methods def get_model_backend(self) -> optional[any]: """get the current model backend for addons.""" def is_model_loaded(self) -> bool: """check if a model is currently loaded.""" # signals model_loaded = signal(object) # emitted when model is loaded model_unloaded = signal() # emitted when model is unloadedπ€ model api
accessing the model
def get_model(self, gguf_app): """get the currently loaded gguf model.""" try: # method 1: direct access if hasattr(gguf_app, 'model') and gguf_app.model: return gguf_app.model # method 2: through ai chat if hasattr(gguf_app, 'ai_chat') and hasattr(gguf_app.ai_chat, 'model'): return gguf_app.ai_chat.model # method 3: backend method if hasattr(gguf_app, 'get_model_backend'): return gguf_app.get_model_backend() return none except exception as e: logging.error(f"error getting model: {e}") return nonemodel interface
class llamamodel: """gguf model interface (llama-cpp-python).""" def __call__(self, prompt: str, max_tokens: int = 256, temperature: float = 0.7, top_p: float = 0.9, top_k: int = 40, repeat_penalty: float = 1.1, stop: list[str] = none, stream: bool = false) -> union[str, dict, iterator]: """generate text from the model.""" pass def tokenize(self, text: str) -> list[int]: """tokenize text.""" pass def detokenize(self, tokens: list[int]) -> str: """detokenize tokens to text.""" passtext generation
def generate_text(self, model, prompt: str, **kwargs) -> str: """generate text using the model.""" try: response = model( prompt, max_tokens=kwargs.get('max_tokens', 200), temperature=kwargs.get('temperature', 0.7), top_p=kwargs.get('top_p', 0.9), repeat_penalty=kwargs.get('repeat_penalty', 1.1), stop=kwargs.get('stop', ["</s>", "\n\n"]), stream=false ) return self.extract_response_text(response) except exception as e: logging.error(f"text generation failed: {e}") return f"error: {str(e)}" def extract_response_text(self, response) -> str: """extract text from model response.""" if isinstance(response, dict) and 'choices' in response: return response['choices'][0].get('text', '').strip() elif isinstance(response, str): return response.strip() else: return str(response).strip()π¨ ui api
widget creation
from pyside6.qtwidgets import qwidget, qvboxlayout, qlabel, qpushbutton from pyside6.qtcore import qtimer, signal class addonwidget(qwidget): """base addon widget class.""" # signals text_processed = signal(str) error_occurred = signal(str) def __init__(self, addon_instance): super().__init__() self.addon = addon_instance self.setup_ui() def setup_ui(self): """setup the widget ui.""" layout = qvboxlayout(self) # title title = qlabel("my addon") title.setstylesheet("font-size: 16px; font-weight: bold;") layout.addwidget(title) # content self.setup_content(layout) def setup_content(self, layout): """override this method to add custom content.""" passcommon ui components
# status indicator def create_status_indicator(self): """create a status indicator widget.""" self.status_label = qlabel("ready") self.status_label.setstylesheet(""" qlabel { padding: 5px; border-radius: 3px; background-color: #4caf50; color: white; } """) return self.status_label def update_status(self, message: str, status_type: str = "info"): """update status indicator.""" colors = { "info": "#2196f3", "success": "#4caf50", "warning": "#ff9800", "error": "#f44336" } self.status_label.settext(message) self.status_label.setstylesheet(f""" qlabel {colors.get(status_type, colors['info'])}; color: white; }} """) # progress indicator def create_progress_indicator(self): """create a progress indicator.""" from pyside6.qtwidgets import qprogressbar self.progress_bar = qprogressbar() self.progress_bar.setvisible(false) return self.progress_bar def show_progress(self, message: str = "processing..."): """show progress indicator.""" self.progress_bar.setvisible(true) self.progress_bar.setrange(0, 0) # indeterminate self.update_status(message, "info") def hide_progress(self): """hide progress indicator.""" self.progress_bar.setvisible(false)floating ui components
from pyside6.qtcore import qt from pyside6.qtgui import qcursor class floatingwidget(qwidget): """create floating widgets like the smart assistant.""" def __init__(self): super().__init__() self.setup_floating_widget() def setup_floating_widget(self): """setup floating widget properties.""" self.setwindowflags( qt.tooltip | qt.framelesswindowhint | qt.windowstaysontophint ) self.setattribute(qt.wa_translucentbackground) def show_near_cursor(self, offset_x: int = 10, offset_y: int = -40): """show widget near cursor position.""" cursor_pos = qcursor.pos() self.move(cursor_pos.x() + offset_x, cursor_pos.y() + offset_y) self.show()π§ system integration api
text selection detection
import pyautogui import pyperclip from pyside6.qtcore import qtimer class textselectionmonitor: """monitor for global text selection.""" def __init__(self, callback): self.callback = callback self.last_clipboard = "" self.selected_text = "" # timer for checking selection self.timer = qtimer() self.timer.timeout.connect(self.check_selection) self.timer.start(300) # check every 300ms def check_selection(self): """check for text selection.""" try: # save current clipboard original_clipboard = pyperclip.paste() # copy selection pyautogui.hotkey('ctrl', 'c') # process after small delay qtimer.singleshot(50, lambda: self.process_selection(original_clipboard)) except exception as e: logging.debug(f"selection check failed: {e}") def process_selection(self, original_clipboard): """process the selection.""" try: current_text = pyperclip.paste() # check if we got new selected text if (current_text != original_clipboard and current_text and len(current_text.strip()) > 3): self.selected_text = current_text.strip() self.callback(self.selected_text) # restore clipboard pyperclip.copy(original_clipboard) except exception as e: logging.debug(f"selection processing failed: {e}") def stop(self): """stop monitoring.""" self.timer.stop()clipboard integration
import pyperclip class clipboardmanager: """manage clipboard operations.""" @staticmethod def get_text() -> str: """get text from clipboard.""" try: return pyperclip.paste() except exception as e: logging.error(f"failed to get clipboard text: {e}") return "" @staticmethod def set_text(text: str) -> bool: """set text to clipboard.""" try: pyperclip.copy(text) return true except exception as e: logging.error(f"failed to set clipboard text: {e}") return false @staticmethod def append_text(text: str) -> bool: """append text to clipboard.""" try: current = clipboardmanager.get_text() new_text = f"{current}\n{text}" if current else text return clipboardmanager.set_text(new_text) except exception as e: logging.error(f"failed to append clipboard text: {e}") return falsehotkey registration
import keyboard class hotkeymanager: """manage global hotkeys.""" def __init__(self): self.registered_hotkeys = {} def register_hotkey(self, hotkey: str, callback, description: str = ""): """register a global hotkey.""" try: keyboard.add_hotkey(hotkey, callback) self.registered_hotkeys[hotkey] = { 'callback': callback, 'description': description } logging.info(f"registered hotkey: {hotkey}") return true except exception as e: logging.error(f"failed to register hotkey {hotkey}: {e}") return false def unregister_hotkey(self, hotkey: str): """unregister a hotkey.""" try: keyboard.remove_hotkey(hotkey) if hotkey in self.registered_hotkeys: del self.registered_hotkeys[hotkey] logging.info(f"unregistered hotkey: {hotkey}") return true except exception as e: logging.error(f"failed to unregister hotkey {hotkey}: {e}") return false def cleanup(self): """clean up all registered hotkeys.""" for hotkey in list(self.registered_hotkeys.keys()): self.unregister_hotkey(hotkey)π configuration api
addon configuration
import json import os from pathlib import path class addonconfig: """manage addon configuration.""" def __init__(self, addon_name: str): self.addon_name = addon_name self.config_dir = path.home() / ".ggufloader" / "addons" / addon_name self.config_file = self.config_dir / "config.json" self.config = {} self.load_config() def load_config(self): """load configuration from file.""" try: if self.config_file.exists(): with open(self.config_file, 'r') as f: self.config = json.load(f) except exception as e: logging.error(f"failed to load config: {e}") self.config = {} def save_config(self): """save configuration to file.""" try: self.config_dir.mkdir(parents=true, exist_ok=true) with open(self.config_file, 'w') as f: json.dump(self.config, f, indent=2) except exception as e: logging.error(f"failed to save config: {e}") def get(self, key: str, default=none): """get configuration value.""" return self.config.get(key, default) def set(self, key: str, value): """set configuration value.""" self.config[key] = value self.save_config() def update(self, updates: dict): """update multiple configuration values.""" self.config.update(updates) self.save_config()π event system api
addon events
from pyside6.qtcore import qobject, signal class addoneventsystem(qobject): """event system for addon communication.""" # core events addon_loaded = signal(str) # addon_name addon_unloaded = signal(str) # addon_name model_changed = signal(object) # model text_selected = signal(str) # selected_text text_processed = signal(str, str) # original_text, processed_text def __init__(self): super().__init__() self.event_handlers = {} def emit_event(self, event_name: str, *args, **kwargs): """emit a custom event.""" if hasattr(self, event_name): signal = getattr(self, event_name) signal.emit(*args, **kwargs) def connect_event(self, event_name: str, handler): """connect to an event.""" if hasattr(self, event_name): signal = getattr(self, event_name) signal.connect(handler)π§ͺ testing api
addon testing utilities
for comprehensive testing examples, see the smart floater example which includes both unit and integration tests.
import unittest from unittest.mock import mock, magicmock class addontestcase(unittest.testcase): """base test case for addon testing.""" def setup(self): """set up test environment.""" self.mock_gguf_app = mock() self.mock_model = mock() self.mock_gguf_app.model = self.mock_model def create_mock_model_response(self, text: str): """create a mock model response.""" return { 'choices': [{'text': text}] } def assert_model_called_with(self, expected_prompt: str): """assert model was called with expected prompt.""" self.mock_model.assert_called() call_args = self.mock_model.call_args self.assertin(expected_prompt, call_args[0][0]) # example test class testmyaddon(addontestcase): def test_text_processing(self): from addons.my_addon.main import myaddon addon = myaddon(self.mock_gguf_app) self.mock_model.return_value = self.create_mock_model_response("processed text") result = addon.process_text("input text") self.assertequal(result, "processed text") self.assert_model_called_with("input text")π logging api
addon logging
import logging from pathlib import path class addonlogger: """logging utilities for addons.""" @staticmethod def setup_logger(addon_name: str, level=logging.info): """setup logger for addon.""" logger = logging.getlogger(f"addon.{addon_name}") logger.setlevel(level) # create file handler log_dir = path.home() / ".ggufloader" / "logs" log_dir.mkdir(parents=true, exist_ok=true) file_handler = logging.filehandler(log_dir / f"{addon_name}.log") file_handler.setlevel(level) # create formatter formatter = logging.formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) file_handler.setformatter(formatter) # add handler to logger logger.addhandler(file_handler) return logger # usage in addon logger = addonlogger.setup_logger("my_addon") logger.info("addon initialized") logger.error("something went wrong")π security api
safe execution
import subprocess import tempfile import os class safeexecution: """utilities for safe code execution.""" @staticmethod def run_command_safely(command: list, timeout: int = 30) -> tuple: """run command safely with timeout.""" try: result = subprocess.run( command, capture_output=true, text=true, timeout=timeout, check=false ) return result.returncode, result.stdout, result.stderr except subprocess.timeoutexpired: return -1, "", "command timed out" except exception as e: return -1, "", str(e) @staticmethod def create_temp_file(content: str, suffix: str = ".tmp") -> str: """create temporary file safely.""" with tempfile.namedtemporaryfile(mode='w', suffix=suffix, delete=false) as f: f.write(content) return f.name @staticmethod def cleanup_temp_file(filepath: str): """clean up temporary file.""" try: if os.path.exists(filepath): os.unlink(filepath) except exception as e: logging.error(f"failed to cleanup temp file: {e}")π additional resources
- smart floater example - complete addon example
- addon development guide - step-by-step development guide
- package structure - technical architecture
- installation guide - development setup
need help with the api? join our community discussions or contact support@ggufloader.com
related documentation
- this guide will teach you how to create custom addons for gguf loader 2.0.0. addons extend the functionality of gguf loader and can provide new features, ui components, and integrations. want to see existing addons in action? [check out the addon showcase on our homepage](/#features "see community addons and examples"). ## ποΈ addon architecture ### what is an addon? an addon is a python package that extends gguf loader's functionality. addons can: - add new ui components and windows - process text and interact with ai models - integrate with external services - provide new workflows and automation - extend the main application's capabilities π― **see real examples**: visit the [homepage community section](/#community "browse community-created addons") to see what others have built and get inspiration for your own addons. ### addon structure every addon must follow this basic structure: ``` addons/ βββ your_addon_name/ βββ __init__.py # addon entry point βββ main.py # main addon logic βββ ui.py # ui components (optional) βββ config.py # configuration (optional) βββ readme.md # addon documentation ``` ## π creating your first addon ### step 1: create the addon directory ```bash mkdir -p addons/my_awesome_addon cd addons/my_awesome_addon ``` ### step 2: create the entry point (`__init__.py`) ```python """ my awesome addon - a sample addon for gguf loader this addon demonstrates the basic structure and capabilities of the gguf loader addon system. """ __version__ = "1.0.0" __author__ = "your name" __description__ = "a sample addon that demonstrates basic functionality" # import the register function from .main import register # export the register function __all__ = ["register"] ``` ### step 3: create the main logic (`main.py`) ```python """ main logic for my awesome addon """ import logging from pyside6.qtwidgets import qwidget, qvboxlayout, qlabel, qpushbutton, qtextedit from pyside6.qtcore import qtimer class myawesomeaddon: """main addon class that handles the addon functionality.""" def __init__(self, gguf_app): """initialize the addon with reference to the main gguf app.""" self.gguf_app = gguf_app self.logger = logging.getlogger(__name__) self.is_running = false # initialize your addon components here self.setup_addon() def setup_addon(self): """setup the addon components.""" self.logger.info("setting up my awesome addon") # add your initialization logic here def get_model(self): """get the currently loaded gguf model.""" try: if hasattr(self.gguf_app, 'model') and self.gguf_app.model: return self.gguf_app.model elif hasattr(self.gguf_app, 'ai_chat') and hasattr(self.gguf_app.ai_chat, 'model'): return self.gguf_app.ai_chat.model return none except exception as e: self.logger.error(f"error getting model: {e}") return none def process_text_with_ai(self, text, prompt_template="process this text: {text}"): """process text using the loaded ai model.""" model = self.get_model() if not model: return "error: no ai model loaded" try: prompt = prompt_template.format(text=text) response = model( prompt, max_tokens=200, temperature=0.7, stop=["", "\n\n"] ) # extract text from response if isinstance(response, dict) and 'choices' in response: return response['choices'][0].get('text', '').strip() elif isinstance(response, str): return response.strip() else: return str(response).strip() except exception as e: self.logger.error(f"error processing text: {e}") return f"error: {str(e)}" def start(self): """start the addon.""" self.is_running = true self.logger.info("my awesome addon started") def stop(self): """stop the addon.""" self.is_running = false self.logger.info("my awesome addon stopped") class myawesomeaddonwidget(qwidget): """ui widget for the addon.""" def __init__(self, addon_instance): super().__init__() self.addon = addon_instance self.setup_ui() def setup_ui(self): """setup the addon ui.""" self.setwindowtitle("my awesome addon") self.setminimumsize(400, 300) layout = qvboxlayout(self) # title title = qlabel("π my awesome addon") title.setstylesheet("font-size: 18px; font-weight: bold; margin: 10px;") layout.addwidget(title) # description description = qlabel("this is a sample addon that demonstrates basic functionality.") description.setwordwrap(true) layout.addwidget(description) # input area layout.addwidget(qlabel("enter text to process:")) self.input_text = qtextedit() self.input_text.setmaximumheight(100) self.input_text.setplaceholdertext("type some text here...") layout.addwidget(self.input_text) # process button self.process_btn = qpushbutton("π€ process with ai") self.process_btn.clicked.connect(self.process_text) layout.addwidget(self.process_btn) # output area layout.addwidget(qlabel("ai response:")) self.output_text = qtextedit() self.output_text.setreadonly(true) layout.addwidget(self.output_text) # status self.status_label = qlabel("ready") self.status_label.setstylesheet("color: green;") layout.addwidget(self.status_label) def process_text(self): """process the input text with ai.""" input_text = self.input_text.toplaintext().strip() if not input_text: self.output_text.settext("please enter some text to process.") return self.status_label.settext("processing...") self.status_label.setstylesheet("color: orange;") self.process_btn.setenabled(false) # process with ai (using qtimer to avoid blocking ui) qtimer.singleshot(100, lambda: self._do_processing(input_text)) def _do_processing(self, text): """actually process the text.""" try: result = self.addon.process_text_with_ai( text, "please provide a helpful and insightful response to: {text}" ) self.output_text.settext(result) self.status_label.settext("complete!") self.status_label.setstylesheet("color: green;") except exception as e: self.output_text.settext(f"error: {str(e)}") self.status_label.settext("error occurred") self.status_label.setstylesheet("color: red;") finally: self.process_btn.setenabled(true) def register(parent=none): """ register function called by gguf loader when loading the addon. args: parent: the main gguf loader application instance returns: qwidget: the addon's ui widget, or none for background addons """ try: # create the addon instance addon = myawesomeaddon(parent) addon.start() # store addon reference in parent for lifecycle management if not hasattr(parent, '_addons'): parent._addons = {} parent._addons['my_awesome_addon'] = addon # create and return the ui widget widget = myawesomeaddonwidget(addon) return widget except exception as e: logging.error(f"failed to register my awesome addon: {e}") return none ``` ### step 4: test your addon 1. **place your addon** in the `addons/` directory (see [package structure](/docs/package-structure/ "understanding addon directory structure") for details) 2. **launch gguf loader**: `ggufloader` (see [installation guide](/docs/installation/ "installation instructions") if needed) 3. **load a gguf model** in the main application (follow the [quick start guide](/docs/quick-start/ "getting started tutorial") if needed) 4. **click your addon** in the addon sidebar 5. **test the functionality** ## π¨ advanced addon features ### background addons some addons don't need a ui and run in the background: ```python def register(parent=none): """register a background addon.""" try: addon = mybackgroundaddon(parent) addon.start() # store reference but return none (no ui) parent._my_background_addon = addon return none except exception as e: logging.error(f"failed to register background addon: {e}") return none ``` ### global hotkeys and text selection learn from the [smart floating assistant addon](/docs/smart-floater-example/ "complete addon example with full source code analysis"): ```python from pyside6.qtcore import qtimer import pyautogui import pyperclip class textselectionaddon: def __init__(self, gguf_app): self.gguf_app = gguf_app self.selected_text = "" # timer for checking text selection self.selection_timer = qtimer() self.selection_timer.timeout.connect(self.check_selection) self.selection_timer.start(500) # check every 500ms def check_selection(self): """check for text selection.""" try: # save current clipboard original_clipboard = pyperclip.paste() # copy selection pyautogui.hotkey('ctrl', 'c') # check if we got new text qtimer.singleshot(50, lambda: self.process_selection(original_clipboard)) except: pass def process_selection(self, original_clipboard): """process the selected text.""" try: current_text = pyperclip.paste() if current_text != original_clipboard and len(current_text.strip()) > 3: self.selected_text = current_text.strip() self.on_text_selected(self.selected_text) # restore clipboard pyperclip.copy(original_clipboard) except: pass def on_text_selected(self, text): """handle text selection event.""" # your custom logic here print(f"text selected: {{% raw %}}{text[:50]}{{% endraw %}}...") ``` ### model integration access and use the loaded gguf model: ```python def use_model_for_processing(self, text): """use the gguf model for text processing.""" model = self.get_model() if not model: return "no model loaded" try: # different processing modes response = model( f"analyze this text: {text}", max_tokens=300, temperature=0.7, top_p=0.9, repeat_penalty=1.1, stop=["", "human:", "user:"] ) return self.extract_response_text(response) except exception as e: return f"error: {str(e)}" def extract_response_text(self, response): """extract text from model response.""" if isinstance(response, dict) and 'choices' in response: return response['choices'][0].get('text', '').strip() elif isinstance(response, str): return response.strip() else: return str(response).strip() ``` ## π addon best practices ### 1. error handling always wrap your code in try-catch blocks: ```python def safe_operation(self): try: # your code here pass except exception as e: self.logger.error(f"operation failed: {e}") return none ``` ### 2. resource cleanup implement proper cleanup: ```python def stop(self): """clean up addon resources.""" if hasattr(self, 'timer'): self.timer.stop() if hasattr(self, 'ui_components'): for component in self.ui_components: component.close() self.logger.info("addon stopped and cleaned up") ``` ### 3. configuration support user configuration: ```python import json import os class addonconfig: def __init__(self, addon_name): self.config_file = f"config/{addon_name}_config.json" self.default_config = { "enabled": true, "hotkey": "ctrl+shift+a", "auto_process": false } self.config = self.load_config() def load_config(self): try: if os.path.exists(self.config_file): with open(self.config_file, 'r') as f: return {**self.default_config, **json.load(f)} except: pass return self.default_config.copy() def save_config(self): os.makedirs(os.path.dirname(self.config_file), exist_ok=true) with open(self.config_file, 'w') as f: json.dump(self.config, f, indent=2) ``` ### 4. logging use proper logging: ```python import logging class myaddon: def __init__(self, gguf_app): self.logger = logging.getlogger(f"addon.{self.__class__.__name__}") self.logger.setlevel(logging.info) # log addon initialization self.logger.info("addon initialized") def process_data(self, data): self.logger.debug(f"processing data: {len(data)} items") try: # process data result = self.do_processing(data) self.logger.info("data processed successfully") return result except exception as e: self.logger.error(f"processing failed: {e}") raise ``` ## π§ testing your addon ### unit testing create tests for your addon: ```python # test_my_addon.py import unittest from unittest.mock import mock, magicmock from addons.my_awesome_addon.main import myawesomeaddon class testmyawesomeaddon(unittest.testcase): def setup(self): self.mock_gguf_app = mock() self.addon = myawesomeaddon(self.mock_gguf_app) def test_addon_initialization(self): self.assertisnotnone(self.addon) self.assertequal(self.addon.gguf_app, self.mock_gguf_app) def test_text_processing(self): # mock the model mock_model = mock() mock_model.return_value = "processed text" self.mock_gguf_app.model = mock_model result = self.addon.process_text_with_ai("test text") self.assertequal(result, "processed text") if __name__ == '__main__': unittest.main() ``` ### integration testing test with the actual gguf loader: ```python # test_integration.py def test_addon_with_gguf_loader(): """test addon integration with gguf loader.""" # this would be run with actual gguf loader instance pass ``` ## π¦ distributing your addon ### 1. create documentation create a `readme.md` for your addon: ```markdown # my awesome addon a powerful addon for gguf loader that provides [functionality]. ## features - feature 1 - feature 2 - feature 3 ## installation 1. copy the addon to `addons/my_awesome_addon/` 2. restart gguf loader 3. click on the addon in the sidebar ## configuration [configuration instructions] ## usage [usage instructions] ``` ### 2. version your addon use semantic versioning in `__init__.py`: ```python __version__ = "1.0.0" # major.minor.patch ``` ### 3. share with community - create a github repository - add installation instructions - include screenshots and examples - submit to the community addon registry π‘ **get featured**: outstanding addons may be featured on our [homepage community showcase](/#community "community addon highlights") - share your creation with the world! ## π€ contributing to core want to contribute to gguf loader itself? check out our [contributing guide](/docs/contributing/ "guidelines for contributing to gguf loader development"). ## π additional resources - [addon api reference](/docs/addon-api/ "complete api documentation for addon developers") - complete api documentation - [smart floater example](/docs/smart-floater-example/ "real-world addon example with detailed code analysis") - learn from the built-in addon - [package structure](/docs/package-structure/ "understanding gguf loader's architecture") - technical architecture details - [installation guide](/docs/installation/ "setup development environment") - development environment setup --- ## π you're ready to build amazing addons! congratulations on completing the addon development guide! you now have the knowledge and tools to create powerful extensions for gguf loader. ### what you've learned - β addon architecture and structure - β creating ui components and background services - β integrating with ai models - β best practices and testing strategies - β distribution and community sharing ### your next steps as an addon developer #### π **start building** - **create your first addon** - put your knowledge into practice with a simple project - **[study real examples](/#features "community addon showcase")** - see how others have implemented creative solutions - **[join developer discussions](/#community "developer community")** - connect with other addon developers #### π **share and grow** - **[showcase your work](/#community "community highlights")** - get your addon featured on our homepage - **[contribute to core](/docs/contributing/ "help improve gguf loader")** - help improve gguf loader itself - **[mentor others](/#community "help newcomers")** - share your expertise with new developers #### π₯ **advanced challenges** - **build complex integrations** - create addons that work with external services - **optimize performance** - learn advanced techniques for efficient addons - **create addon libraries** - build reusable components for the community ### π‘ get inspired visit our [homepage community section](/#community "see what's possible") to see what other developers have created and get inspiration for your next project! **happy addon development! π** need help? join our [community discussions](https://github.com/gguf-loader/gguf-loader/discussions) or contact us at support@ggufloader.com.
- learn how to create addons by studying the built-in smart floating assistant addon. this is a complete, real-world example that demonstrates all the key concepts of addon development. ## π overview the smart floating assistant is gguf loader's flagship addon that provides: - **global text selection detection** across all applications - **floating button interface** that appears near selected text - **ai-powered text processing** (summarize and comment) - **seamless clipboard integration** - **privacy-first local processing** ## ποΈ architecture ### file structure ``` addons/smart_floater/ βββ __init__.py # addon entry point βββ simple_main.py # main addon logic (simplified version) βββ main.py # full-featured version βββ floater_ui.py # ui components βββ comment_engine.py # text processing engine βββ injector.py # text injection utilities βββ error_handler.py # error handling βββ privacy_security.py # privacy and security features βββ performance_optimizer.py # performance optimization ``` ### key components 1. **simplefloatingassistant**: main addon class 2. **smartfloaterstatuswidget**: control panel ui 3. **text selection monitor**: global text detection 4. **ai processing engine**: text summarization and commenting 5. **clipboard manager**: safe clipboard operations ## π code analysis ### entry point (`__init__.py`) ```python """ simple smart floating assistant shows a button when you select text, processes it with ai. that's it. """ # use the simple version instead of the complex one from .simple_main import register __all__ = ["register"] ``` **key lessons:** - keep the entry point simple - export only the `register` function - use clear, descriptive docstrings ### main logic (`simple_main.py`) let's break down the main addon class: ```python class simplefloatingassistant: """simple floating assistant that shows button on text selection.""" def __init__(self, gguf_app_instance: any): """initialize the addon with gguf loader reference.""" self.gguf_app = gguf_app_instance self._is_running = false self._floating_button = none self._popup_window = none self._selected_text = "" self.model = none # store model reference directly # initialize clipboard tracking try: self.last_clipboard = pyperclip.paste() except: self.last_clipboard = "" # button persistence tracking self.button_show_time = 0 self.button_should_stay = false # connect to model loading signals self.connect_to_model_signals() # timer to check for text selection self.timer = qtimer() self.timer.timeout.connect(self.check_selection) self.timer.start(300) # check every 300ms ``` **key lessons:** - store reference to main app (`gguf_app`) - initialize all state variables - connect to model loading signals - use qtimer for periodic tasks - handle initialization errors gracefully ### model integration ```python def connect_to_model_signals(self): """connect to model loading signals from the main app.""" try: # connect to the main app's model_loaded signal if hasattr(self.gguf_app, 'model_loaded'): self.gguf_app.model_loaded.connect(self.on_model_loaded) print("β connected to model_loaded signal") # also try to connect to ai_chat model_loaded signal if hasattr(self.gguf_app, 'ai_chat') and hasattr(self.gguf_app.ai_chat, 'model_loaded'): self.gguf_app.ai_chat.model_loaded.connect(self.on_model_loaded) print("β connected to ai_chat model_loaded signal") except exception as e: print(f"β error connecting to model signals: {e}") def on_model_loaded(self, model): """handle model loaded event.""" self.model = model print(f"β addon received model: {type(model)}") print(f" model methods: {{% raw %}}{[m for m in dir(model) if not m.startswith('_')][:10]}{{% endraw %}}") def get_model(self): """get the loaded model.""" try: # first try our stored model reference if self.model: print("β using stored model reference") return self.model # try multiple fallback methods if hasattr(self.gguf_app, 'model'): if self.gguf_app.model: self.model = self.gguf_app.model return self.gguf_app.model # ... more fallback methods return none except exception as e: print(f"β error getting model: {e}") return none ``` **key lessons:** - connect to model loading signals for real-time updates - implement multiple fallback methods for model access - store model reference locally for performance - use defensive programming with try-catch blocks - provide helpful debug output ### text selection detection ```python def check_selection(self): """check if text is currently selected (without copying).""" try: # save current clipboard content original_clipboard = pyperclip.paste() # temporarily copy selection to check if text is selected pyautogui.hotkey('ctrl', 'c') # small delay to let clipboard update qtimer.singleshot(50, lambda: self._process_selection_check(original_clipboard)) except: pass def _process_selection_check(self, original_clipboard): """process the selection check and restore clipboard.""" try: # get what was copied current_selection = pyperclip.paste() # check if we got new selected text if (current_selection != original_clipboard and current_selection and len(current_selection.strip()) > 3 and len(current_selection) < 5000): # we have selected text! if current_selection.strip() != self.selected_text: self.selected_text = current_selection.strip() self.show_button() self.button_show_time = 0 # reset timer self.button_should_stay = true else: # no text selected - but don't hide immediately if self.button_should_stay: self.button_show_time += 1 # hide after 10 checks (about 3 seconds) if self.button_show_time > 10: self.hide_button() self.button_should_stay = false self.button_show_time = 0 # always restore original clipboard immediately pyperclip.copy(original_clipboard) except: # always try to restore clipboard even if there's an error try: pyperclip.copy(original_clipboard) except: pass ``` **key lessons:** - use non-intrusive text selection detection - always restore the user's clipboard - implement smart button persistence (don't hide immediately) - handle edge cases (empty text, very long text) - use defensive programming for clipboard operations ### floating ui ```python def show_button(self): """show floating button near cursor.""" if self.button: self.button.close() self.button = qpushbutton("β¨") self.button.setfixedsize(40, 40) self.button.setwindowflags(qt.tooltip | qt.framelesswindowhint | qt.windowstaysontophint) self.button.setstylesheet(""" qpushbutton { background-color: #0078d4; border: none; border-radius: 20px; color: white; font-size: 16px; } qpushbutton:hover { background-color: #106ebe; } """) # position near cursor pos = qcursor.pos() self.button.move(pos.x() + 10, pos.y() - 50) self.button.clicked.connect(self.show_popup) self.button.show() # reset persistence tracking self.button_show_time = 0 self.button_should_stay = true ``` **key lessons:** - use appropriate window flags for floating widgets - position relative to cursor for better ux - apply attractive styling with css - connect button clicks to actions - clean up previous instances before creating new ones ### ai text processing ```python def process_text(self, action): """process text with ai using gguf loader's model.""" try: model = self.get_model() if not model: self.result_area.settext("β error: no ai model loaded in gguf loader\n\nplease load a gguf model first!") return self.result_area.settext("π€ processing with ai...") # create appropriate prompt based on action if action == "summarize": prompt = f"please provide a clear and concise summary of the following text:\n\n{self.selected_text}\n\nsummary:" else: # comment prompt = f"please write a thoughtful and insightful comment about the following text:\n\n{self.selected_text}\n\ncomment:" # process with gguf model using the same interface as aichat try: # use the model the same way as chatgenerator does response = model( prompt, max_tokens=300, stream=false, # don't stream for simplicity temperature=0.7, top_p=0.9, repeat_penalty=1.1, top_k=40, stop=["", "human:", "user:", "\n\n\n"] ) # extract text from response if isinstance(response, dict) and 'choices' in response: result_text = response['choices'][0].get('text', '').strip() elif isinstance(response, str): result_text = response.strip() else: result_text = str(response).strip() # clean up the result if result_text: # remove any prompt echoing if "summary:" in result_text: result_text = result_text.split("summary:")[-1].strip() elif "comment:" in result_text: result_text = result_text.split("comment:")[-1].strip() self.result_area.settext(result_text) self.copy_btn.setenabled(true) else: self.result_area.settext("β no response generated. try again.") except exception as e: self.result_area.settext(f"β error processing with ai model:\n{str(e)}\n\nmake sure a compatible gguf model is loaded.") except exception as e: self.result_area.settext(f"β unexpected error: {str(e)}") ``` **key lessons:** - check model availability before processing - create context-appropriate prompts - use consistent model parameters - handle different response formats - clean up ai responses (remove prompt echoing) - provide clear error messages to users ### status widget for addon panel ```python class smartfloaterstatuswidget: def __init__(self, addon_instance): from pyside6.qtwidgets import qwidget, qvboxlayout, qlabel, qpushbutton, qtextedit self.addon = addon_instance self.widget = qwidget() self.widget.setwindowtitle("smart floating assistant") layout = qvboxlayout(self.widget) # status info layout.addwidget(qlabel("π€ smart floating assistant")) layout.addwidget(qlabel("status: running in background")) layout.addwidget(qlabel("")) layout.addwidget(qlabel("how to use:")) layout.addwidget(qlabel("1. select text anywhere on your screen")) layout.addwidget(qlabel("2. click the β¨ button that appears")) layout.addwidget(qlabel("3. choose summarize or comment")) layout.addwidget(qlabel("")) # test button test_btn = qpushbutton("π§ͺ test model connection") test_btn.clicked.connect(self.test_model) layout.addwidget(test_btn) # result area self.result_area = qtextedit() self.result_area.setmaximumheight(100) self.result_area.setreadonly(true) layout.addwidget(self.result_area) # stop/start buttons button_layout = qhboxlayout() stop_btn = qpushbutton("βΉοΈ stop") stop_btn.clicked.connect(self.stop_addon) button_layout.addwidget(stop_btn) start_btn = qpushbutton("βΆοΈ start") start_btn.clicked.connect(self.start_addon) button_layout.addwidget(start_btn) layout.addlayout(button_layout) ``` **key lessons:** - create informative status widgets for addon management - provide clear usage instructions - include testing and control functionality - use emoji and clear labels for better ux - separate ui logic from core addon logic ### registration function ```python def register(parent=none): """register the simple floating assistant.""" try: print(f"π§ register called with parent: {type(parent)}") # stop existing addon if running if hasattr(parent, '_simple_floater'): parent._simple_floater.stop() # create and start simple addon addon = simplefloatingassistant(parent) parent._simple_floater = addon print("β simple floating assistant started!") # return a status widget for the addon panel status_widget = smartfloaterstatuswidget(addon) return status_widget.widget except exception as e: print(f"β failed to start simple addon: {e}") return none ``` **key lessons:** - always handle cleanup of existing instances - store addon reference in parent for lifecycle management - return appropriate ui widget or none for background addons - provide clear success/failure feedback - use defensive programming with try-catch ## π― best practices demonstrated ### 1. **defensive programming** - extensive use of try-catch blocks - graceful handling of missing dependencies - fallback methods for critical operations ### 2. **user experience** - non-intrusive text selection detection - smart button persistence (doesn't disappear immediately) - clear status messages and error handling - attractive, modern ui design ### 3. **performance optimization** - efficient timer-based monitoring - minimal clipboard interference - lazy loading of ui components - resource cleanup on shutdown ### 4. **integration patterns** - signal-based communication with main app - multiple fallback methods for model access - proper lifecycle management - clean separation of concerns ### 5. **error handling** - comprehensive error messages - graceful degradation when model unavailable - user-friendly error reporting - debug information for developers ## π§ customization examples ### adding new processing actions ```python def process_text(self, action): """extended processing with more actions.""" prompts = { "summarize": "please provide a clear and concise summary of: {text}", "comment": "please write a thoughtful comment about: {text}", "explain": "please explain this text in simple terms: {text}", "translate": "please translate this text to english: {text}", "improve": "please improve the writing of this text: {text}" } prompt_template = prompts.get(action, prompts["summarize"]) prompt = prompt_template.format(text=self.selected_text) # ... rest of processing logic ``` ### custom hotkeys ```python def setup_hotkeys(self): """setup custom hotkeys for the addon.""" try: import keyboard # register global hotkey for instant processing keyboard.add_hotkey('ctrl+shift+s', self.quick_summarize) keyboard.add_hotkey('ctrl+shift+c', self.quick_comment) except importerror: print("keyboard library not available for hotkeys") def quick_summarize(self): """quick summarize selected text without ui.""" # get current selection and process immediately pass ``` ### configuration support ```python def load_config(self): """load addon configuration.""" config_file = path.home() / ".ggufloader" / "smart_floater_config.json" default_config = { "check_interval": 300, "button_timeout": 3000, "max_text_length": 5000, "auto_copy_results": true } try: if config_file.exists(): with open(config_file) as f: user_config = json.load(f) return {**default_config, **user_config} except: pass return default_config ``` ## π performance considerations ### memory management - clean up ui components properly - avoid memory leaks in timer callbacks - use weak references where appropriate ### cpu usage - optimize timer intervals - avoid blocking operations in main thread - use qtimer.singleshot for delayed operations ### system integration - minimize clipboard interference - respect user's workflow - handle system sleep/wake events ## π§ͺ testing the smart floater ### manual testing checklist 1. **basic functionality** - [ ] addon loads without errors - [ ] status widget appears in sidebar - [ ] model connection test works 2. **text selection** - [ ] button appears when selecting text - [ ] button stays visible for appropriate time - [ ] works across different applications 3. **ai processing** - [ ] summarize function works correctly - [ ] comment function generates appropriate responses - [ ] error handling when no model loaded 4. **ui/ux** - [ ] floating button positioned correctly - [ ] popup window displays properly - [ ] copy functionality works ### automated testing ```python import unittest from unittest.mock import mock, patch class testsmartfloater(unittest.testcase): def setup(self): self.mock_gguf_app = mock() self.addon = simplefloatingassistant(self.mock_gguf_app) def test_model_connection(self): """test model connection and retrieval.""" mock_model = mock() self.mock_gguf_app.model = mock_model result = self.addon.get_model() self.assertequal(result, mock_model) @patch('pyperclip.paste') @patch('pyperclip.copy') def test_clipboard_operations(self, mock_copy, mock_paste): """test clipboard operations don't interfere.""" mock_paste.return_value = "original text" self.addon.check_selection() # verify clipboard was restored mock_copy.assert_called_with("original text") ``` ## π next steps after studying the smart floater example: 1. **create your own addon** using the patterns shown (follow the [addon development guide](/docs/addon-development/ "step-by-step addon creation tutorial")) 2. **experiment with modifications** to understand the code better 3. **read the full source code** in `addons/smart_floater/` (see [package structure](/docs/package-structure/ "understanding addon file organization")) 4. **reference the api documentation** ([addon api reference](/docs/addon-api/ "complete api documentation")) for detailed method signatures 5. **join the community** to share your addon ideas ## π related documentation - [addon development guide](/docs/addon-development/ "complete tutorial for creating your own addons") - step-by-step development guide - [addon api reference](/docs/addon-api/ "detailed api documentation for addon developers") - complete api documentation - [package structure](/docs/package-structure/ "understanding how addons are integrated") - technical architecture - [quick start guide](/docs/quick-start/ "learn how to use the smart floater as an end user") - how to use the smart floater as an end user --- **the smart floater is a great example of what's possible with gguf loader addons. use it as inspiration for your own creations! π** need help understanding any part of the code? join our [community discussions](https://github.com/gguf-loader/gguf-loader/discussions) or contact support@ggufloader.com.
- this document explains the structure of the gguf loader 2.0.0 pypi package and how the smart floating assistant addon is included. ## π¦ package overview **package name**: `ggufloader` **version**: `2.0.0` **command**: `ggufloader` when users install with `pip install ggufloader`, they get: - complete gguf loader application - smart floating assistant addon (pre-installed) - comprehensive documentation - all necessary dependencies ## ποΈ directory structure ``` ggufloader/ βββ pyproject.toml # package configuration βββ readme_pypi.md # pypi package description βββ build_pypi.py # build script for pypi βββ requirements.txt # dependencies βββ βββ # main application files βββ main.py # basic gguf loader (no addons) βββ gguf_loader_main.py # gguf loader with addon support βββ addon_manager.py # addon management system βββ config.py # configuration βββ utils.py # utilities βββ icon.ico # application icon βββ βββ # ui components βββ ui/ β βββ ai_chat_window.py # main chat interface β βββ apply_style.py # ui styling βββ βββ # core models βββ models/ β βββ model_loader.py # gguf model loading β βββ chat_generator.py # text generation βββ βββ # ui mixins βββ mixins/ β βββ ui_setup_mixin.py # ui setup β βββ model_handler_mixin.py # model handling β βββ chat_handler_mixin.py # chat functionality β βββ event_handler_mixin.py # event handling β βββ utils_mixin.py # utility functions βββ βββ # widgets βββ widgets/ β βββ chat_bubble.py # chat bubble component βββ βββ # pre-installed addons βββ addons/ β βββ smart_floater/ # smart floating assistant β βββ __init__.py # addon entry point β βββ simple_main.py # main addon logic β βββ main.py # full-featured version β βββ floater_ui.py # ui components β βββ comment_engine.py # text processing β βββ injector.py # text injection β βββ error_handler.py # error handling β βββ privacy_security.py # privacy features β βββ performance_optimizer.py # performance βββ βββ # documentation βββ docs/ βββ readme.md # documentation index βββ installation.md # installation guide βββ quick-start.md # quick start guide βββ user-guide.md # complete user manual βββ addon-development.md # addon development guide βββ addon-api.md # api reference βββ smart-floater-example.md # example addon βββ configuration.md # configuration guide βββ troubleshooting.md # troubleshooting βββ contributing.md # contributing guide βββ package-structure.md # this file ``` ## π installation and usage ### installation ```bash pip install ggufloader ``` ### launch application ```bash ggufloader ``` this command launches `gguf_loader_main.py` which includes: - full gguf loader functionality - smart floating assistant addon (automatically loaded) - addon management system - all ui components ## π§ how addons are included ### addon discovery when gguf loader starts, the `addonmanager` automatically: 1. **scans** the `addons/` directory 2. **finds** folders with `__init__.py` files 3. **loads** addons by calling their `register()` function 4. **displays** addon buttons in the sidebar ### smart floater integration the smart floating assistant is included as a pre-installed addon: ```python # addons/smart_floater/__init__.py from .simple_main import register __all__ = ["register"] # when loaded, it provides: # - global text selection detection # - floating button interface # - ai text processing (summarize/comment) # - seamless clipboard integration ``` ### addon lifecycle 1. **package installation**: addon files are installed with the package 2. **application start**: `addonmanager` discovers and loads addons 3. **user interaction**: users can access addons via the sidebar 4. **background operation**: smart floater runs continuously in background ## π package configuration ### pyproject.toml key sections ```toml [project] name = "ggufloader" version = "2.0.0" dependencies = [ "llama-cpp-python>=0.2.72", "pyside6>=6.6.1", "pyautogui>=0.9.54", "pyperclip>=1.8.2", "pywin32>=306; sys_platform == 'win32'", ] [project.scripts] ggufloader = "gguf_loader.gguf_loader_main:main" [tool.setuptools] packages = [ "gguf_loader", "gguf_loader.addons", "gguf_loader.addons.smart_floater" ] include-package-data = true ``` ### package data inclusion all necessary files are included: - python source code - documentation (`.md` files) - icons and images - configuration files - addon files ## π― user experience ### first-time users 1. **install**: `pip install ggufloader` 2. **launch**: `ggufloader` 3. **load model**: click "select gguf model" 4. **use smart floater**: select text anywhere β click β¨ button ### addon discovery - smart floater appears in addon sidebar automatically - users can click to open control panel - no additional installation required - works immediately after model loading ### documentation access users can access documentation: - online: github repository - locally: installed with package in `docs/` folder - in-app: help links and tooltips ## π version updates ### updating the package when releasing new versions: 1. **update version** in `pyproject.toml` 2. **update changelog** and documentation 3. **test addon compatibility** 4. **build and upload** to pypi ### addon updates smart floater updates are included in package updates: - bug fixes and improvements - new features and capabilities - performance optimizations - security enhancements ## π οΈ development workflow ### for package maintainers 1. **develop** new features and addons 2. **test** thoroughly with various models 3. **update** documentation 4. **build** package with `python build_pypi.py` 5. **upload** to pypi ### for addon developers 1. **study** the [smart floater example](/docs/smart-floater-example/ "complete addon example with detailed code analysis") 2. **follow** the [addon development guide](/docs/addon-development/ "step-by-step addon creation tutorial") 3. **reference** the [api documentation](/docs/addon-api/ "complete api reference for developers") 4. **create** addons in `addons/` directory 5. **test** with gguf loader (see [quick start guide](/docs/quick-start/ "getting started tutorial")) 6. **share** with community ## π package statistics ### size and dependencies - **package size**: ~50mb (includes all dependencies) - **core dependencies**: 5 main packages - **optional dependencies**: gpu acceleration packages - **documentation**: 10+ comprehensive guides ### compatibility - **python**: 3.8+ (tested on 3.8, 3.9, 3.10, 3.11, 3.12) - **operating systems**: windows, macos, linux - **architectures**: x86_64, arm64 (apple silicon) ## π troubleshooting package issues ### common installation issues 1. **python version**: ensure python 3.8+ 2. **dependencies**: install build tools if needed 3. **permissions**: use `--user` flag if needed 4. **virtual environment**: recommended for isolation ### addon loading issues 1. **check logs**: look for addon loading errors 2. **verify structure**: ensure `__init__.py` exists 3. **dependencies**: check addon-specific requirements 4. **permissions**: verify file permissions ### getting help - **documentation**: check `docs/` folder - **github issues**: report bugs and issues - **community**: join discussions and forums - **support**: contact support@ggufloader.com ## π success metrics the package structure is designed to provide: - **easy installation**: single `pip install` command - **immediate functionality**: smart floater works out of the box - **extensibility**: clear addon development path - **maintainability**: well-organized codebase - **user-friendly**: comprehensive documentation --- **this package structure ensures that gguf loader 2.0.0 provides a complete, professional ai text processing solution with the smart floating assistant included by default! π**
π related features
π― what's next?
you've completed this guide! here are some suggested next steps to continue your gguf loader journey:
πshare your addon
built something awesome? share it with the community and get featured on our homepage.
share with community βππ€π explore more features
π back to homepage
- learn how to create addons by studying the built-in smart floating assistant addon. this is a complete, real-world example that demonstrates all the key concepts of addon development. ## π overview the smart floating assistant is gguf loader's flagship addon that provides: - **global text selection detection** across all applications - **floating button interface** that appears near selected text - **ai-powered text processing** (summarize and comment) - **seamless clipboard integration** - **privacy-first local processing** ## ποΈ architecture ### file structure ``` addons/smart_floater/ βββ __init__.py # addon entry point βββ simple_main.py # main addon logic (simplified version) βββ main.py # full-featured version βββ floater_ui.py # ui components βββ comment_engine.py # text processing engine βββ injector.py # text injection utilities βββ error_handler.py # error handling βββ privacy_security.py # privacy and security features βββ performance_optimizer.py # performance optimization ``` ### key components 1. **simplefloatingassistant**: main addon class 2. **smartfloaterstatuswidget**: control panel ui 3. **text selection monitor**: global text detection 4. **ai processing engine**: text summarization and commenting 5. **clipboard manager**: safe clipboard operations ## π code analysis ### entry point (`__init__.py`) ```python """ simple smart floating assistant shows a button when you select text, processes it with ai. that's it. """ # use the simple version instead of the complex one from .simple_main import register __all__ = ["register"] ``` **key lessons:** - keep the entry point simple - export only the `register` function - use clear, descriptive docstrings ### main logic (`simple_main.py`) let's break down the main addon class: ```python class simplefloatingassistant: """simple floating assistant that shows button on text selection.""" def __init__(self, gguf_app_instance: any): """initialize the addon with gguf loader reference.""" self.gguf_app = gguf_app_instance self._is_running = false self._floating_button = none self._popup_window = none self._selected_text = "" self.model = none # store model reference directly # initialize clipboard tracking try: self.last_clipboard = pyperclip.paste() except: self.last_clipboard = "" # button persistence tracking self.button_show_time = 0 self.button_should_stay = false # connect to model loading signals self.connect_to_model_signals() # timer to check for text selection self.timer = qtimer() self.timer.timeout.connect(self.check_selection) self.timer.start(300) # check every 300ms ``` **key lessons:** - store reference to main app (`gguf_app`) - initialize all state variables - connect to model loading signals - use qtimer for periodic tasks - handle initialization errors gracefully ### model integration ```python def connect_to_model_signals(self): """connect to model loading signals from the main app.""" try: # connect to the main app's model_loaded signal if hasattr(self.gguf_app, 'model_loaded'): self.gguf_app.model_loaded.connect(self.on_model_loaded) print("β connected to model_loaded signal") # also try to connect to ai_chat model_loaded signal if hasattr(self.gguf_app, 'ai_chat') and hasattr(self.gguf_app.ai_chat, 'model_loaded'): self.gguf_app.ai_chat.model_loaded.connect(self.on_model_loaded) print("β connected to ai_chat model_loaded signal") except exception as e: print(f"β error connecting to model signals: {e}") def on_model_loaded(self, model): """handle model loaded event.""" self.model = model print(f"β addon received model: {type(model)}") print(f" model methods: {{% raw %}}{[m for m in dir(model) if not m.startswith('_')][:10]}{{% endraw %}}") def get_model(self): """get the loaded model.""" try: # first try our stored model reference if self.model: print("β using stored model reference") return self.model # try multiple fallback methods if hasattr(self.gguf_app, 'model'): if self.gguf_app.model: self.model = self.gguf_app.model return self.gguf_app.model # ... more fallback methods return none except exception as e: print(f"β error getting model: {e}") return none ``` **key lessons:** - connect to model loading signals for real-time updates - implement multiple fallback methods for model access - store model reference locally for performance - use defensive programming with try-catch blocks - provide helpful debug output ### text selection detection ```python def check_selection(self): """check if text is currently selected (without copying).""" try: # save current clipboard content original_clipboard = pyperclip.paste() # temporarily copy selection to check if text is selected pyautogui.hotkey('ctrl', 'c') # small delay to let clipboard update qtimer.singleshot(50, lambda: self._process_selection_check(original_clipboard)) except: pass def _process_selection_check(self, original_clipboard): """process the selection check and restore clipboard.""" try: # get what was copied current_selection = pyperclip.paste() # check if we got new selected text if (current_selection != original_clipboard and current_selection and len(current_selection.strip()) > 3 and len(current_selection) < 5000): # we have selected text! if current_selection.strip() != self.selected_text: self.selected_text = current_selection.strip() self.show_button() self.button_show_time = 0 # reset timer self.button_should_stay = true else: # no text selected - but don't hide immediately if self.button_should_stay: self.button_show_time += 1 # hide after 10 checks (about 3 seconds) if self.button_show_time > 10: self.hide_button() self.button_should_stay = false self.button_show_time = 0 # always restore original clipboard immediately pyperclip.copy(original_clipboard) except: # always try to restore clipboard even if there's an error try: pyperclip.copy(original_clipboard) except: pass ``` **key lessons:** - use non-intrusive text selection detection - always restore the user's clipboard - implement smart button persistence (don't hide immediately) - handle edge cases (empty text, very long text) - use defensive programming for clipboard operations ### floating ui ```python def show_button(self): """show floating button near cursor.""" if self.button: self.button.close() self.button = qpushbutton("β¨") self.button.setfixedsize(40, 40) self.button.setwindowflags(qt.tooltip | qt.framelesswindowhint | qt.windowstaysontophint) self.button.setstylesheet(""" qpushbutton { background-color: #0078d4; border: none; border-radius: 20px; color: white; font-size: 16px; } qpushbutton:hover { background-color: #106ebe; } """) # position near cursor pos = qcursor.pos() self.button.move(pos.x() + 10, pos.y() - 50) self.button.clicked.connect(self.show_popup) self.button.show() # reset persistence tracking self.button_show_time = 0 self.button_should_stay = true ``` **key lessons:** - use appropriate window flags for floating widgets - position relative to cursor for better ux - apply attractive styling with css - connect button clicks to actions - clean up previous instances before creating new ones ### ai text processing ```python def process_text(self, action): """process text with ai using gguf loader's model.""" try: model = self.get_model() if not model: self.result_area.settext("β error: no ai model loaded in gguf loader\n\nplease load a gguf model first!") return self.result_area.settext("π€ processing with ai...") # create appropriate prompt based on action if action == "summarize": prompt = f"please provide a clear and concise summary of the following text:\n\n{self.selected_text}\n\nsummary:" else: # comment prompt = f"please write a thoughtful and insightful comment about the following text:\n\n{self.selected_text}\n\ncomment:" # process with gguf model using the same interface as aichat try: # use the model the same way as chatgenerator does response = model( prompt, max_tokens=300, stream=false, # don't stream for simplicity temperature=0.7, top_p=0.9, repeat_penalty=1.1, top_k=40, stop=["", "human:", "user:", "\n\n\n"] ) # extract text from response if isinstance(response, dict) and 'choices' in response: result_text = response['choices'][0].get('text', '').strip() elif isinstance(response, str): result_text = response.strip() else: result_text = str(response).strip() # clean up the result if result_text: # remove any prompt echoing if "summary:" in result_text: result_text = result_text.split("summary:")[-1].strip() elif "comment:" in result_text: result_text = result_text.split("comment:")[-1].strip() self.result_area.settext(result_text) self.copy_btn.setenabled(true) else: self.result_area.settext("β no response generated. try again.") except exception as e: self.result_area.settext(f"β error processing with ai model:\n{str(e)}\n\nmake sure a compatible gguf model is loaded.") except exception as e: self.result_area.settext(f"β unexpected error: {str(e)}") ``` **key lessons:** - check model availability before processing - create context-appropriate prompts - use consistent model parameters - handle different response formats - clean up ai responses (remove prompt echoing) - provide clear error messages to users ### status widget for addon panel ```python class smartfloaterstatuswidget: def __init__(self, addon_instance): from pyside6.qtwidgets import qwidget, qvboxlayout, qlabel, qpushbutton, qtextedit self.addon = addon_instance self.widget = qwidget() self.widget.setwindowtitle("smart floating assistant") layout = qvboxlayout(self.widget) # status info layout.addwidget(qlabel("π€ smart floating assistant")) layout.addwidget(qlabel("status: running in background")) layout.addwidget(qlabel("")) layout.addwidget(qlabel("how to use:")) layout.addwidget(qlabel("1. select text anywhere on your screen")) layout.addwidget(qlabel("2. click the β¨ button that appears")) layout.addwidget(qlabel("3. choose summarize or comment")) layout.addwidget(qlabel("")) # test button test_btn = qpushbutton("π§ͺ test model connection") test_btn.clicked.connect(self.test_model) layout.addwidget(test_btn) # result area self.result_area = qtextedit() self.result_area.setmaximumheight(100) self.result_area.setreadonly(true) layout.addwidget(self.result_area) # stop/start buttons button_layout = qhboxlayout() stop_btn = qpushbutton("βΉοΈ stop") stop_btn.clicked.connect(self.stop_addon) button_layout.addwidget(stop_btn) start_btn = qpushbutton("βΆοΈ start") start_btn.clicked.connect(self.start_addon) button_layout.addwidget(start_btn) layout.addlayout(button_layout) ``` **key lessons:** - create informative status widgets for addon management - provide clear usage instructions - include testing and control functionality - use emoji and clear labels for better ux - separate ui logic from core addon logic ### registration function ```python def register(parent=none): """register the simple floating assistant.""" try: print(f"π§ register called with parent: {type(parent)}") # stop existing addon if running if hasattr(parent, '_simple_floater'): parent._simple_floater.stop() # create and start simple addon addon = simplefloatingassistant(parent) parent._simple_floater = addon print("β simple floating assistant started!") # return a status widget for the addon panel status_widget = smartfloaterstatuswidget(addon) return status_widget.widget except exception as e: print(f"β failed to start simple addon: {e}") return none ``` **key lessons:** - always handle cleanup of existing instances - store addon reference in parent for lifecycle management - return appropriate ui widget or none for background addons - provide clear success/failure feedback - use defensive programming with try-catch ## π― best practices demonstrated ### 1. **defensive programming** - extensive use of try-catch blocks - graceful handling of missing dependencies - fallback methods for critical operations ### 2. **user experience** - non-intrusive text selection detection - smart button persistence (doesn't disappear immediately) - clear status messages and error handling - attractive, modern ui design ### 3. **performance optimization** - efficient timer-based monitoring - minimal clipboard interference - lazy loading of ui components - resource cleanup on shutdown ### 4. **integration patterns** - signal-based communication with main app - multiple fallback methods for model access - proper lifecycle management - clean separation of concerns ### 5. **error handling** - comprehensive error messages - graceful degradation when model unavailable - user-friendly error reporting - debug information for developers ## π§ customization examples ### adding new processing actions ```python def process_text(self, action): """extended processing with more actions.""" prompts = { "summarize": "please provide a clear and concise summary of: {text}", "comment": "please write a thoughtful comment about: {text}", "explain": "please explain this text in simple terms: {text}", "translate": "please translate this text to english: {text}", "improve": "please improve the writing of this text: {text}" } prompt_template = prompts.get(action, prompts["summarize"]) prompt = prompt_template.format(text=self.selected_text) # ... rest of processing logic ``` ### custom hotkeys ```python def setup_hotkeys(self): """setup custom hotkeys for the addon.""" try: import keyboard # register global hotkey for instant processing keyboard.add_hotkey('ctrl+shift+s', self.quick_summarize) keyboard.add_hotkey('ctrl+shift+c', self.quick_comment) except importerror: print("keyboard library not available for hotkeys") def quick_summarize(self): """quick summarize selected text without ui.""" # get current selection and process immediately pass ``` ### configuration support ```python def load_config(self): """load addon configuration.""" config_file = path.home() / ".ggufloader" / "smart_floater_config.json" default_config = { "check_interval": 300, "button_timeout": 3000, "max_text_length": 5000, "auto_copy_results": true } try: if config_file.exists(): with open(config_file) as f: user_config = json.load(f) return {**default_config, **user_config} except: pass return default_config ``` ## π performance considerations ### memory management - clean up ui components properly - avoid memory leaks in timer callbacks - use weak references where appropriate ### cpu usage - optimize timer intervals - avoid blocking operations in main thread - use qtimer.singleshot for delayed operations ### system integration - minimize clipboard interference - respect user's workflow - handle system sleep/wake events ## π§ͺ testing the smart floater ### manual testing checklist 1. **basic functionality** - [ ] addon loads without errors - [ ] status widget appears in sidebar - [ ] model connection test works 2. **text selection** - [ ] button appears when selecting text - [ ] button stays visible for appropriate time - [ ] works across different applications 3. **ai processing** - [ ] summarize function works correctly - [ ] comment function generates appropriate responses - [ ] error handling when no model loaded 4. **ui/ux** - [ ] floating button positioned correctly - [ ] popup window displays properly - [ ] copy functionality works ### automated testing ```python import unittest from unittest.mock import mock, patch class testsmartfloater(unittest.testcase): def setup(self): self.mock_gguf_app = mock() self.addon = simplefloatingassistant(self.mock_gguf_app) def test_model_connection(self): """test model connection and retrieval.""" mock_model = mock() self.mock_gguf_app.model = mock_model result = self.addon.get_model() self.assertequal(result, mock_model) @patch('pyperclip.paste') @patch('pyperclip.copy') def test_clipboard_operations(self, mock_copy, mock_paste): """test clipboard operations don't interfere.""" mock_paste.return_value = "original text" self.addon.check_selection() # verify clipboard was restored mock_copy.assert_called_with("original text") ``` ## π next steps after studying the smart floater example: 1. **create your own addon** using the patterns shown (follow the [addon development guide](/docs/addon-development/ "step-by-step addon creation tutorial")) 2. **experiment with modifications** to understand the code better 3. **read the full source code** in `addons/smart_floater/` (see [package structure](/docs/package-structure/ "understanding addon file organization")) 4. **reference the api documentation** ([addon api reference](/docs/addon-api/ "complete api documentation")) for detailed method signatures 5. **join the community** to share your addon ideas ## π related documentation - [addon development guide](/docs/addon-development/ "complete tutorial for creating your own addons") - step-by-step development guide - [addon api reference](/docs/addon-api/ "detailed api documentation for addon developers") - complete api documentation - [package structure](/docs/package-structure/ "understanding how addons are integrated") - technical architecture - [quick start guide](/docs/quick-start/ "learn how to use the smart floater as an end user") - how to use the smart floater as an end user --- **the smart floater is a great example of what's possible with gguf loader addons. use it as inspiration for your own creations! π** need help understanding any part of the code? join our [community discussions](https://github.com/gguf-loader/gguf-loader/discussions) or contact support@ggufloader.com.
- this document explains the structure of the gguf loader 2.0.0 pypi package and how the smart floating assistant addon is included. ## π¦ package overview **package name**: `ggufloader` **version**: `2.0.0` **command**: `ggufloader` when users install with `pip install ggufloader`, they get: - complete gguf loader application - smart floating assistant addon (pre-installed) - comprehensive documentation - all necessary dependencies ## ποΈ directory structure ``` ggufloader/ βββ pyproject.toml # package configuration βββ readme_pypi.md # pypi package description βββ build_pypi.py # build script for pypi βββ requirements.txt # dependencies βββ βββ # main application files βββ main.py # basic gguf loader (no addons) βββ gguf_loader_main.py # gguf loader with addon support βββ addon_manager.py # addon management system βββ config.py # configuration βββ utils.py # utilities βββ icon.ico # application icon βββ βββ # ui components βββ ui/ β βββ ai_chat_window.py # main chat interface β βββ apply_style.py # ui styling βββ βββ # core models βββ models/ β βββ model_loader.py # gguf model loading β βββ chat_generator.py # text generation βββ βββ # ui mixins βββ mixins/ β βββ ui_setup_mixin.py # ui setup β βββ model_handler_mixin.py # model handling β βββ chat_handler_mixin.py # chat functionality β βββ event_handler_mixin.py # event handling β βββ utils_mixin.py # utility functions βββ βββ # widgets βββ widgets/ β βββ chat_bubble.py # chat bubble component βββ βββ # pre-installed addons βββ addons/ β βββ smart_floater/ # smart floating assistant β βββ __init__.py # addon entry point β βββ simple_main.py # main addon logic β βββ main.py # full-featured version β βββ floater_ui.py # ui components β βββ comment_engine.py # text processing β βββ injector.py # text injection β βββ error_handler.py # error handling β βββ privacy_security.py # privacy features β βββ performance_optimizer.py # performance βββ βββ # documentation βββ docs/ βββ readme.md # documentation index βββ installation.md # installation guide βββ quick-start.md # quick start guide βββ user-guide.md # complete user manual βββ addon-development.md # addon development guide βββ addon-api.md # api reference βββ smart-floater-example.md # example addon βββ configuration.md # configuration guide βββ troubleshooting.md # troubleshooting βββ contributing.md # contributing guide βββ package-structure.md # this file ``` ## π installation and usage ### installation ```bash pip install ggufloader ``` ### launch application ```bash ggufloader ``` this command launches `gguf_loader_main.py` which includes: - full gguf loader functionality - smart floating assistant addon (automatically loaded) - addon management system - all ui components ## π§ how addons are included ### addon discovery when gguf loader starts, the `addonmanager` automatically: 1. **scans** the `addons/` directory 2. **finds** folders with `__init__.py` files 3. **loads** addons by calling their `register()` function 4. **displays** addon buttons in the sidebar ### smart floater integration the smart floating assistant is included as a pre-installed addon: ```python # addons/smart_floater/__init__.py from .simple_main import register __all__ = ["register"] # when loaded, it provides: # - global text selection detection # - floating button interface # - ai text processing (summarize/comment) # - seamless clipboard integration ``` ### addon lifecycle 1. **package installation**: addon files are installed with the package 2. **application start**: `addonmanager` discovers and loads addons 3. **user interaction**: users can access addons via the sidebar 4. **background operation**: smart floater runs continuously in background ## π package configuration ### pyproject.toml key sections ```toml [project] name = "ggufloader" version = "2.0.0" dependencies = [ "llama-cpp-python>=0.2.72", "pyside6>=6.6.1", "pyautogui>=0.9.54", "pyperclip>=1.8.2", "pywin32>=306; sys_platform == 'win32'", ] [project.scripts] ggufloader = "gguf_loader.gguf_loader_main:main" [tool.setuptools] packages = [ "gguf_loader", "gguf_loader.addons", "gguf_loader.addons.smart_floater" ] include-package-data = true ``` ### package data inclusion all necessary files are included: - python source code - documentation (`.md` files) - icons and images - configuration files - addon files ## π― user experience ### first-time users 1. **install**: `pip install ggufloader` 2. **launch**: `ggufloader` 3. **load model**: click "select gguf model" 4. **use smart floater**: select text anywhere β click β¨ button ### addon discovery - smart floater appears in addon sidebar automatically - users can click to open control panel - no additional installation required - works immediately after model loading ### documentation access users can access documentation: - online: github repository - locally: installed with package in `docs/` folder - in-app: help links and tooltips ## π version updates ### updating the package when releasing new versions: 1. **update version** in `pyproject.toml` 2. **update changelog** and documentation 3. **test addon compatibility** 4. **build and upload** to pypi ### addon updates smart floater updates are included in package updates: - bug fixes and improvements - new features and capabilities - performance optimizations - security enhancements ## π οΈ development workflow ### for package maintainers 1. **develop** new features and addons 2. **test** thoroughly with various models 3. **update** documentation 4. **build** package with `python build_pypi.py` 5. **upload** to pypi ### for addon developers 1. **study** the [smart floater example](/docs/smart-floater-example/ "complete addon example with detailed code analysis") 2. **follow** the [addon development guide](/docs/addon-development/ "step-by-step addon creation tutorial") 3. **reference** the [api documentation](/docs/addon-api/ "complete api reference for developers") 4. **create** addons in `addons/` directory 5. **test** with gguf loader (see [quick start guide](/docs/quick-start/ "getting started tutorial")) 6. **share** with community ## π package statistics ### size and dependencies - **package size**: ~50mb (includes all dependencies) - **core dependencies**: 5 main packages - **optional dependencies**: gpu acceleration packages - **documentation**: 10+ comprehensive guides ### compatibility - **python**: 3.8+ (tested on 3.8, 3.9, 3.10, 3.11, 3.12) - **operating systems**: windows, macos, linux - **architectures**: x86_64, arm64 (apple silicon) ## π troubleshooting package issues ### common installation issues 1. **python version**: ensure python 3.8+ 2. **dependencies**: install build tools if needed 3. **permissions**: use `--user` flag if needed 4. **virtual environment**: recommended for isolation ### addon loading issues 1. **check logs**: look for addon loading errors 2. **verify structure**: ensure `__init__.py` exists 3. **dependencies**: check addon-specific requirements 4. **permissions**: verify file permissions ### getting help - **documentation**: check `docs/` folder - **github issues**: report bugs and issues - **community**: join discussions and forums - **support**: contact support@ggufloader.com ## π success metrics the package structure is designed to provide: - **easy installation**: single `pip install` command - **immediate functionality**: smart floater works out of the box - **extensibility**: clear addon development path - **maintainability**: well-organized codebase - **user-friendly**: comprehensive documentation --- **this package structure ensures that gguf loader 2.0.0 provides a complete, professional ai text processing solution with the smart floating assistant included by default! π**
π related features
π― what's next?
you've completed this guide! here are some suggested next steps to continue your gguf loader journey:
πshare your addon
built something awesome? share it with the community and get featured on our homepage.
share with community βππ€π explore more features
π back to homepage
π Related Features
π― What's Next?
You've completed this guide! Here are some suggested next steps to continue your GGUF Loader journey:
Explore Homepage
Discover more features, download options, and community resources on our homepage.
Visit Homepage βMore Documentation
Continue learning with our comprehensive documentation library.
All Documentation β