Addon API Reference

Complete API reference for developing GGUF Loader addons

Advanced 20 minutes

Complete 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
    """
    pass

Main Application Interface

The parent parameter 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 None

Model 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."""
        pass

Text 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."""
        pass

Common 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 False

Hotkey 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


Need help with the API? Join our community discussions or contact support@ggufloader.com

๐ŸŽฏ 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 โ†’
๐Ÿ“–

Study Examples

Learn from real-world addon examples and see what's possible.

Addon Showcase โ†’
๐Ÿค

Get Support

Need help with your addon? Our community is here to support you.

Get Help โ†’

๐Ÿ  Back to Homepage