Floating Chat Example
Complete walkthrough of the built-in floating chat addon - signals, windows, and lifecycle
This page walks through floating_chat, GGUF Loaderβs built-in addon β a Facebook Messenger-style floating button that opens a chat window connected to the loaded model. Itβs the best real-world reference for writing a background addon with its own windows.
addons/floating_chat/
βββ __init__.py # exposes register()
βββ main.py # FloatingChatAddon (QObject) + register()
βββ floating_button.py # FloatingChatButton - draggable always-on-top button
βββ chat_window.py # FloatingChatWindow - the chat UI
βββ status_widget.py # small sidebar widget returned by register()
π§ The Design
The addon is a QObject (FloatingChatAddon) that owns two windows:
FloatingChatButtonβ a frameless, always-on-top tool window that can be dragged anywhere and remembers its position viaQSettings.FloatingChatWindowβ a regular top-level window (400Γ600) positioned next to the button, with a message list, streaming replies, and Copy All / Clear buttons.
πͺ Registration
__init__.py simply re-exports register:
from .main import register
__all__ = ['register']
register() finds the main window (parent β parent chain β topLevelWidgets() scan), stops any previous instance, starts the addon, and returns a status widget for the sidebar:
def register(parent=None):
gguf_app = _find_main_window(parent) # model + model_loaded check
if gguf_app is None:
return None
if getattr(gguf_app, '_floating_chat_addon', None):
gguf_app._floating_chat_addon.stop()
addon = FloatingChatAddon(gguf_app)
if addon.start():
gguf_app._floating_chat_addon = addon
return FloatingChatStatusWidget(addon)
return None
The returned FloatingChatStatusWidget shows up in the Addons menu, and because register() runs at startup, the button appears automatically β no user action needed.
π‘ Connecting to the App
FloatingChatAddon.__init__ subscribes to model lifecycle signals:
if hasattr(self.gguf_app, 'model_loaded'):
self.gguf_app.model_loaded.connect(self._on_model_loaded)
if hasattr(self.gguf_app, 'generation_finished'):
self.gguf_app.generation_finished.connect(self._on_generation_finished)
if hasattr(self.gguf_app, 'generation_error'):
self.gguf_app.generation_error.connect(self._on_generation_error)
_on_model_loaded flips the chat windowβs status to π’ Model: Ready; generation events update the streaming bubble / error state.
π Lifecycle: start() / stop()
def start(self):
self._floating_button = FloatingChatButton()
self._floating_button.clicked.connect(self._on_button_clicked)
self._load_button_position() # QSettings restore, clamped to screen
self._floating_button.show()
self.addon_started.emit()
return True
def stop(self):
self._save_button_position() # persist for next session
if self._chat_window: self._chat_window.close()
if self._floating_button: self._floating_button.close()
self.addon_stopped.emit()
stop() is idempotent and called on app close via MainWindow.closeEvent, so thereβs never a dangling button.
π±οΈ Button Click β Window Toggle
The click handler distinguishes three states (a minimized window is βvisibleβ to Qt but off-screen, so it must be restored, not hidden):
def _on_button_clicked(self):
chat = self._chat_window
if chat and chat.isVisible():
if chat.isMinimized():
chat.showNormal(); chat.raise_(); chat.activateWindow()
else:
chat.hide()
else:
self._show_chat_window()
_show_chat_window creates the window lazily, positions it next to the button (flipping to the left / up when it would overflow the screenβs available geometry, which excludes taskbars and docks), then showNormal() + raise_() + activateWindow().
πͺ Window Flags (the platform subtleties)
flags = (Qt.WindowType.FramelessWindowHint |
Qt.WindowType.WindowStaysOnTopHint |
Qt.WindowType.X11BypassWindowManagerHint) # Linux/X11
if sys.platform != "darwin":
flags |= Qt.WindowType.Tool # keeps it out of the taskbar
- Windows/Linux:
Toolkeeps the button out of the taskbar.X11BypassWindowManagerHintgives true always-on-top under X11. - macOS: the
Toolflag would turn the button into a utility window that auto-hides when the app loses focus β so itβs deliberately dropped. Trade-off: the button appears in Mission Control. - Wayland: compositors donβt allow floating above other appsβ windows; the button is confined to the app window (documented, and X11 is recommended for the full experience).
The chat window drops the minimize button entirely β a minimized companion window is a trap state that canβt be reliably restored.
π Resiliancy Touches
changeEventbounces the button straight back to normal if a system shortcut (e.g. Win+D) minimizes every window including the tool:if event.type() == QEvent.Type.WindowStateChange and self.isMinimized(): QTimer.singleShot(0, self.showNormal)- Dragging clamps to
screen.availableGeometry()(not(0,0)), so the button canβt slide under the macOS menu bar or a Windows taskbar. - Saved positions are clamped the same way on restore.
π§© What You Can Reuse
- The window-finding pattern in
register()β works for any addon. - The QSettings position persistence with on-screen clamping.
- The platform-conditional window flags β copy it for any always-on-top addon window.
- The show/hide/restore toggle logic.
π See Also
Related Documentation
addon development guide
learn to create custom addons for gguf loader with examples and best practices
advanced 15 minutesgguf loaderβs addon system lets you extend the app without touching its source. addons are python packages dropped into the
addons/folder; the app discovers, loads, and manages them automatically.π¦ what an addon looks like
addons/ βββ my_addon/ βββ __init__.py # must expose register() βββ main.py # your logic (any structure you like) βββ ... # widgets, resources, etc.minimum viable addon
# addons/my_addon/__init__.py from pyside6.qtwidgets import qlabel def register(parent=none): """called by gguf loader when the addon loads.""" return qlabel("hello from my addon!")thatβs it β drop the folder in
addons/, restart (or addons β refresh addons), and your widget appears in the addons menu.π how loading works
addonmanager(addon_manager.py):scan_addons()β lists subdirectories of the addons folder that contain__init__.py.load_addon(name, path)β dynamically imports the module (importlib.util.spec_from_file_location) and requires aregisterattribute.load_all_addons()β loads every addon and records success/failure in the console log.get_addon_widget(name, parent)β callsregister(parent)and returns the widget.open_addon_dialog(name, parent)β shows the addon widget in a non-modal dialog from the addons menu.
the main window calls
get_addon_widgetfor each loaded addon at startup, soregister()runs even before the user opens the menu β this is how background addons (like the floating chat button) start automatically.π‘ talking to the app
your
register(parent)receives the parent widget. the main window exposes an addon-facing api (see the addon api reference). the robust pattern used by the built-in addon:def register(parent=none): gguf_app = none # 1. parent might already be the main window if parent and hasattr(parent, 'model') and hasattr(parent, 'model_loaded'): gguf_app = parent else: # 2. walk up the parent chain current = parent while current is not none: if hasattr(current, 'model') and hasattr(current, 'model_loaded'): gguf_app = current break current = current.parent() # 3. fall back to scanning top-level widgets if gguf_app is none: app = qapplication.instance() for widget in app.toplevelwidgets(): if hasattr(widget, 'model') and hasattr(widget, 'model_loaded'): gguf_app = widget break ...key api members:
model(callable backend),model_loaded/model_unloaded/generation_finished/generation_errorsignals,addon_manager, and_floating_chat_addon.π€ calling the model
# streaming for token in gguf_app.model(prompt, stream=true, max_tokens=512): print(token["choices"][0]["text"]) # or use the backend directly response = gguf_app.model.generate(prompt, max_tokens=512, temperature=0.7)modelisnoneuntil the user loads a model β always check, and subscribe tomodel_loadedto react.π§© widget vs. background addons
- return a widget from
register()β appears in the addons menu, opens in a dialog. - return
none(or also keep your own top-level windows) β runs in the background. the floating chat addon returns a small status widget but drives its own always-on-top windows.
π‘ best practices
- guard everything β wrap registration in
try/exceptand log failures; a broken addon must never crash the app. - clean up state β if you keep references on the main window (e.g.
gguf_app._my_addon = ...), stop them on app close (mainwindow.closeeventstops_floating_chat_addonβ follow that pattern). - use qsettings for persistence β
qsettings("ggufloader", "youraddon"). - prefer signals β let your widgets emit signals; donβt reach into other widgetsβ internals.
- qt-free logic β if you have non-ui logic (parsing, computation), put it in a module with no qt imports so itβs unit-testable.
β debugging
addon load errors print to the console with a traceback (
failed to load addon <name>: ...). run the app from a terminal to see them:python main.pyπ next steps
- addon api reference β every hook and member
- floating chat example β full walkthrough of the built-in addon
related documentation
addon api reference
complete api reference for developing gguf loader addons
advanced 20 minutescomplete reference for the hooks, signals, and objects available to gguf loader addons.
πͺ addon contract
an addon is a folder under
addons/with an__init__.pyexposing:register(parent=none) -> qwidget | nonecalled by
addonmanagerwhen the addon loads. returns an optional widget shown in the addons menu (opened in a non-modal dialog), ornonefor background-only addons.parameters β
parent: the widget the addon is being attached to (may be the main window, a dialog, or another widget).return β a widget, or
none.def register(parent=none): return qlabel("my addon") # widget addonπ₯οΈ main window api
addons receive a reference to the main window (via
parentor by scanningqapplication.instance().toplevelwidgets()). it exposes:properties
| member | type | description | |β|β|β| |
model|modelbackend \| none| the loaded model backend; callable.noneuntil a model is loaded. | |chat_generator|none| legacy hook; alwaysnone. addons should callmodelinstead. | |addon_manager|addonmanager| the addon manager instance. | |_floating_chat_addon|floatingchataddon \| none| the built-in floating chat addon (if running). |signals
| signal | signature | description | |β|β|β| |
model_loaded|signal(object)| emitted with themodelbackendafter a model loads. | |model_unloaded|signal()| emitted after the model is released. | |generation_finished|signal()| emitted when a chat generation completes. | |generation_error|signal(str)| emitted with an error message when generation fails. | |theme_changed|signal(bool)| emitted when dark mode toggles (true= dark). |gguf_app.model_loaded.connect(self._on_model_loaded) gguf_app.generation_finished.connect(self._on_finished)π§ modelbackend
gguf_app.modelis acore.llm.model_backend.modelbackend. it is callable and thread-safe (all access serialized through a lock).calling convention
# raw llama-cpp shape (addon-compatible) result = gguf_app.model(prompt, stream=false, max_tokens=512, temperature=0.7) text = result["choices"][0]["text"] # streaming for token_data in gguf_app.model(prompt, stream=true, max_tokens=512): text = token_data["choices"][0]["text"]methods & attributes
| member | description | |β|β| |
model_path| path of the loaded gguf file. | |use_gpu| whether gpu acceleration is active. | |n_ctx| context length. | |is_loaded|trueonce the runtime exists. | |load()| create the underlyingllamaruntime (raises on failure). | |unload()| release the model and free memory. | |generate(prompt, **kwargs)| complete non-streamed response string. | |generate_stream(prompt, **kwargs)| iterator of token strings. | |__call__(prompt, **kwargs)| llama-cpp-compatible callable (stream returns token-dict generator). |kwargs are passed straight to llama_cpp:
max_tokens,temperature,top_p,top_k,repeat_penalty,stop, etc.π§° agent tools (for agent-capable addons)
the agent engine (
core/agent/) exposes sandboxed tools throughtoolregistry. tools are not qt-bound and are safe to call from any thread.tool params result list_directorypath(default.){status, result: [{name, type, size}]}read_filepath,max_size,encoding{status, result: content, lines, encoding}write_filepath,content{status, path, bytes_written}edit_filepath,operation(replace/insert_line/delete_line),find,replace,line_number,content{status, operation, changes_made}search_filespattern/query,path{status, result: [paths], total_matches}every tool result includes
status("success"/"error") andtool_name. all paths are sandboxed to the workspace β escaping paths raise.from core.agent.tool_registry import create_default_registry registry = create_default_registry("./workspace") result = registry.execute("list_directory", {"path": "."})π§© addonmanager
method description scan_addons(){name: path}of addons with__init__.py.load_addon(name, path)import + validate register;true/false.load_all_addons()load all; {name: success}.get_addon_widget(name, parent)widget from register(parent).open_addon_dialog(name, parent)show addon in a non-modal dialog. get_loaded_addons()list of successfully loaded names. πΎ persistence
use qtβs
qsettingswith a scoped org/app key to avoid collisions:from pyside6.qtcore import qsettings settings = qsettings("ggufloader", "myaddon") settings.setvalue("position", point) pos = settings.value("position", qpoint(100, 100))β‘ lifecycle
register()is called at startup and on addons β refresh addons.- the main windowβs
closeeventstops the floating chat addon; follow that pattern to stop your own background objects:if hasattr(gguf_app, '_my_addon') and gguf_app._my_addon: gguf_app._my_addon.stop()
π see also
- addon development guide β tutorial
- floating chat example β full real-world addon
related documentation
- gguf loader's addon system lets you extend the app without touching its source. addons are python packages dropped into the `addons/` folder; the app discovers, loads, and manages them automatically.
## π¦ what an addon looks like
```
addons/
βββ my_addon/
βββ __init__.py # must expose register()
βββ main.py # your logic (any structure you like)
βββ ... # widgets, resources, etc.
```
### minimum viable addon
```python
# addons/my_addon/__init__.py
from pyside6.qtwidgets import qlabel
def register(parent=none):
"""called by gguf loader when the addon loads."""
return qlabel("hello from my addon!")
```
that's it β drop the folder in `addons/`, restart (or **addons β refresh addons**), and your widget appears in the **addons** menu.
## π how loading works
`addonmanager` (`addon_manager.py`):
1. `scan_addons()` β lists subdirectories of the addons folder that contain `__init__.py`.
2. `load_addon(name, path)` β dynamically imports the module (`importlib.util.spec_from_file_location`) and requires a `register` attribute.
3. `load_all_addons()` β loads every addon and records success/failure in the console log.
4. `get_addon_widget(name, parent)` β calls `register(parent)` and returns the widget.
5. `open_addon_dialog(name, parent)` β shows the addon widget in a non-modal dialog from the addons menu.
the main window calls `get_addon_widget` for each loaded addon at startup, so `register()` runs even before the user opens the menu β this is how background addons (like the floating chat button) start automatically.
## π‘ talking to the app
your `register(parent)` receives the parent widget. the **main window** exposes an addon-facing api (see the [addon api reference](/docs/addon-api/)). the robust pattern used by the built-in addon:
```python
def register(parent=none):
gguf_app = none
# 1. parent might already be the main window
if parent and hasattr(parent, 'model') and hasattr(parent, 'model_loaded'):
gguf_app = parent
else:
# 2. walk up the parent chain
current = parent
while current is not none:
if hasattr(current, 'model') and hasattr(current, 'model_loaded'):
gguf_app = current
break
current = current.parent()
# 3. fall back to scanning top-level widgets
if gguf_app is none:
app = qapplication.instance()
for widget in app.toplevelwidgets():
if hasattr(widget, 'model') and hasattr(widget, 'model_loaded'):
gguf_app = widget
break
...
```
key api members: `model` (callable backend), `model_loaded` / `model_unloaded` / `generation_finished` / `generation_error` signals, `addon_manager`, and `_floating_chat_addon`.
## π€ calling the model
```python
# streaming
for token in gguf_app.model(prompt, stream=true, max_tokens=512):
print(token["choices"][0]["text"])
# or use the backend directly
response = gguf_app.model.generate(prompt, max_tokens=512, temperature=0.7)
```
`model` is `none` until the user loads a model β always check, and subscribe to `model_loaded` to react.
## π§© widget vs. background addons
- **return a widget** from `register()` β appears in the addons menu, opens in a dialog.
- **return `none`** (or also keep your own top-level windows) β runs in the background. the floating chat addon returns a small status widget but drives its own always-on-top windows.
## π‘ best practices
1. **guard everything** β wrap registration in `try/except` and log failures; a broken addon must never crash the app.
2. **clean up state** β if you keep references on the main window (e.g. `gguf_app._my_addon = ...`), stop them on app close (`mainwindow.closeevent` stops `_floating_chat_addon` β follow that pattern).
3. **use qsettings for persistence** β `qsettings("ggufloader", "youraddon")`.
4. **prefer signals** β let your widgets emit signals; don't reach into other widgets' internals.
5. **qt-free logic** β if you have non-ui logic (parsing, computation), put it in a module with no qt imports so it's unit-testable.
## β
debugging
addon load errors print to the console with a traceback (`failed to load addon
: ...`). run the app from a terminal to see them: ```bash python main.py ``` ## π next steps - [addon api reference](/docs/addon-api/) β every hook and member - [floating chat example](/docs/floating-chat-example/) β full walkthrough of the built-in addon - this page walks through **floating_chat**, gguf loader's built-in addon β a facebook messenger-style floating button that opens a chat window connected to the loaded model. it's the best real-world reference for writing a background addon with its own windows. ``` addons/floating_chat/ βββ __init__.py # exposes register() βββ main.py # floatingchataddon (qobject) + register() βββ floating_button.py # floatingchatbutton - draggable always-on-top button βββ chat_window.py # floatingchatwindow - the chat ui βββ status_widget.py # small sidebar widget returned by register() ``` ## π§ the design the addon is a `qobject` (`floatingchataddon`) that owns two windows: - **`floatingchatbutton`** β a frameless, always-on-top tool window that can be dragged anywhere and remembers its position via `qsettings`. - **`floatingchatwindow`** β a regular top-level window (`400Γ600`) positioned next to the button, with a message list, streaming replies, and copy all / clear buttons. ## πͺ registration `__init__.py` simply re-exports `register`: ```python from .main import register __all__ = ['register'] ``` `register()` finds the main window (parent β parent chain β `toplevelwidgets()` scan), stops any previous instance, starts the addon, and returns a **status widget** for the sidebar: ```python def register(parent=none): gguf_app = _find_main_window(parent) # model + model_loaded check if gguf_app is none: return none if getattr(gguf_app, '_floating_chat_addon', none): gguf_app._floating_chat_addon.stop() addon = floatingchataddon(gguf_app) if addon.start(): gguf_app._floating_chat_addon = addon return floatingchatstatuswidget(addon) return none ``` the returned `floatingchatstatuswidget` shows up in the **addons** menu, and because `register()` runs at startup, the button appears automatically β no user action needed. ## π‘ connecting to the app `floatingchataddon.__init__` subscribes to model lifecycle signals: ```python if hasattr(self.gguf_app, 'model_loaded'): self.gguf_app.model_loaded.connect(self._on_model_loaded) if hasattr(self.gguf_app, 'generation_finished'): self.gguf_app.generation_finished.connect(self._on_generation_finished) if hasattr(self.gguf_app, 'generation_error'): self.gguf_app.generation_error.connect(self._on_generation_error) ``` `_on_model_loaded` flips the chat window's status to **π’ model: ready**; generation events update the streaming bubble / error state. ## π lifecycle: start() / stop() ```python def start(self): self._floating_button = floatingchatbutton() self._floating_button.clicked.connect(self._on_button_clicked) self._load_button_position() # qsettings restore, clamped to screen self._floating_button.show() self.addon_started.emit() return true def stop(self): self._save_button_position() # persist for next session if self._chat_window: self._chat_window.close() if self._floating_button: self._floating_button.close() self.addon_stopped.emit() ``` `stop()` is idempotent and called on app close via `mainwindow.closeevent`, so there's never a dangling button. ## π±οΈ button click β window toggle the click handler distinguishes three states (a minimized window is "visible" to qt but off-screen, so it must be restored, not hidden): ```python def _on_button_clicked(self): chat = self._chat_window if chat and chat.isvisible(): if chat.isminimized(): chat.shownormal(); chat.raise_(); chat.activatewindow() else: chat.hide() else: self._show_chat_window() ``` `_show_chat_window` creates the window lazily, positions it next to the button (flipping to the left / up when it would overflow the screen's **available geometry**, which excludes taskbars and docks), then `shownormal()` + `raise_()` + `activatewindow()`. ## πͺ window flags (the platform subtleties) ```python flags = (qt.windowtype.framelesswindowhint | qt.windowtype.windowstaysontophint | qt.windowtype.x11bypasswindowmanagerhint) # linux/x11 if sys.platform != "darwin": flags |= qt.windowtype.tool # keeps it out of the taskbar ``` - **windows/linux**: `tool` keeps the button out of the taskbar. `x11bypasswindowmanagerhint` gives true always-on-top under x11. - **macos**: the `tool` flag would turn the button into a utility window that **auto-hides when the app loses focus** β so it's deliberately dropped. trade-off: the button appears in mission control. - **wayland**: compositors don't allow floating above *other* apps' windows; the button is confined to the app window (documented, and x11 is recommended for the full experience). the chat window drops the minimize button entirely β a minimized companion window is a trap state that can't be reliably restored. ## π resiliancy touches - `changeevent` bounces the button straight back to normal if a system shortcut (e.g. win+d) minimizes *every* window including the tool: ```python if event.type() == qevent.type.windowstatechange and self.isminimized(): qtimer.singleshot(0, self.shownormal) ``` - dragging clamps to `screen.availablegeometry()` (not `(0,0)`), so the button can't slide under the macos menu bar or a windows taskbar. - saved positions are clamped the same way on restore. ## π§© what you can reuse - the **window-finding pattern** in `register()` β works for any addon. - the **qsettings position persistence** with on-screen clamping. - the **platform-conditional window flags** β copy it for any always-on-top addon window. - the **show/hide/restore toggle** logic. ## π see also - [addon development guide](/docs/addon-development/) - [addon api reference](/docs/addon-api/)
- this page documents how the gguf loader codebase is organized. the app is a pyside6 desktop application; a single `main.py` bootstraps everything, and logic is split into `core/` (pure logic), `services/` (qt threading bridges), and `ui/` + `widgets/` (presentation). ## ποΈ top-level layout ``` gguf-loader/ βββ main.py # entry point: logging, dll paths, qapplication, mainwindow βββ config.py # central configuration constants βββ resource_manager.py # resource/path discovery (dev, package, or frozen) βββ addon_manager.py # loads and manages addons βββ requirements.txt # python dependencies βββ build_exe.spec # pyinstaller spec used for windows & linux builds β βββ core/ # pure, testable logic (no qt) β βββ llm/ β β βββ model_backend.py # thread-safe llama-cpp-python wrapper β β βββ prompt_builder.py # system-prompt & conversation assembly β βββ agent/ β βββ agent_engine.py # tool-use agent loop (no qt, no llama_cpp) β βββ tool_registry.py # sandboxed workspace tools β βββ services/ # qobject bridges that run work on threads β βββ model_service.py # load/unload models on a qthread β βββ chat_service.py # streaming generation on a qthread β βββ agent_service.py # runs agentengine on a worker thread β βββ environment_service.py # venv/dependency checks & pip tasks β βββ launcher_service.py # launches scripts/ utilities β βββ ui/ # main window & panels β βββ main_window.py # composition root + addon-facing api β βββ chat_panel.py # chat display, input, agent controls β βββ sidebar_panel.py # model settings sidebar β βββ theme.py # dark/light qss token system β βββ widgets/ # reusable widgets β βββ chat_bubble.py # chatgpt-style bubble β βββ feedback_dialog.py # feedback form dialog β βββ addons/ # addon packages (each has __init__.py with register()) β βββ floating_chat/ # built-in floating chat addon β βββ scripts/ # utility & release scripts β βββ capture_screenshots.py # regenerates readme/site screenshots β βββ install_linux.sh # linux installer/uninstaller β βββ package_linux.sh # builds the linux .tar.gz release β βββ ... # gpu install/monitor helpers β βββ build_hooks/ # pyinstaller hook modules ``` ## π key design rules - **`core/` never imports qt or llama_cpp.** it receives plain callables, so it can be unit-tested in isolation. - **`services/` are the only place qt threads are created.** ui never spins up threads directly. - **`ui/` widgets are "dumb"** β they render state and emit signals; `mainwindow` owns all logic. - **`resource_manager.py`** makes paths work identically in dev, as an installed package, and in a pyinstaller bundle (`sys._meipass`). ## π§΅ threading model ``` ui thread (mainwindow) worker thread β β βββ modelservice.load() βββββββ qthread: llama_cpp loads model ββββββββ loaded(modelbackend) β βββ chatservice.generate() ββββ qthread: streams tokens ββββββββ token_received(text) β βββ agentservice.process() ββββ qthread: agentengine tool loop ββββββββ status/tool/response β ``` a fresh `qthread` + worker is created per request (the professional qt pattern β `qthread` is never subclassed). ## π entry point flow `main.py` β `setup_library_path()` (finds bundled llama.cpp libs) β `qapplication` β `mainwindow()` β builds ui, wires services, checks environment, loads addons β `app.exec()`. ## π¦ packaging - **windows**: `build_exe.bat` / `build_exe.spec` β `ggufloader_vx.y.z.exe` - **linux**: build the spec inside a linux environment, then `scripts/package_linux.sh` wraps the binary + installer + icon into a `.tar.gz` - github actions publishes both to every release automatically. see the [architecture overview](/docs/architecture/) for deeper design rationale.
π 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
- this page walks through **floating_chat**, gguf loader's built-in addon β a facebook messenger-style floating button that opens a chat window connected to the loaded model. it's the best real-world reference for writing a background addon with its own windows. ``` addons/floating_chat/ βββ __init__.py # exposes register() βββ main.py # floatingchataddon (qobject) + register() βββ floating_button.py # floatingchatbutton - draggable always-on-top button βββ chat_window.py # floatingchatwindow - the chat ui βββ status_widget.py # small sidebar widget returned by register() ``` ## π§ the design the addon is a `qobject` (`floatingchataddon`) that owns two windows: - **`floatingchatbutton`** β a frameless, always-on-top tool window that can be dragged anywhere and remembers its position via `qsettings`. - **`floatingchatwindow`** β a regular top-level window (`400Γ600`) positioned next to the button, with a message list, streaming replies, and copy all / clear buttons. ## πͺ registration `__init__.py` simply re-exports `register`: ```python from .main import register __all__ = ['register'] ``` `register()` finds the main window (parent β parent chain β `toplevelwidgets()` scan), stops any previous instance, starts the addon, and returns a **status widget** for the sidebar: ```python def register(parent=none): gguf_app = _find_main_window(parent) # model + model_loaded check if gguf_app is none: return none if getattr(gguf_app, '_floating_chat_addon', none): gguf_app._floating_chat_addon.stop() addon = floatingchataddon(gguf_app) if addon.start(): gguf_app._floating_chat_addon = addon return floatingchatstatuswidget(addon) return none ``` the returned `floatingchatstatuswidget` shows up in the **addons** menu, and because `register()` runs at startup, the button appears automatically β no user action needed. ## π‘ connecting to the app `floatingchataddon.__init__` subscribes to model lifecycle signals: ```python if hasattr(self.gguf_app, 'model_loaded'): self.gguf_app.model_loaded.connect(self._on_model_loaded) if hasattr(self.gguf_app, 'generation_finished'): self.gguf_app.generation_finished.connect(self._on_generation_finished) if hasattr(self.gguf_app, 'generation_error'): self.gguf_app.generation_error.connect(self._on_generation_error) ``` `_on_model_loaded` flips the chat window's status to **π’ model: ready**; generation events update the streaming bubble / error state. ## π lifecycle: start() / stop() ```python def start(self): self._floating_button = floatingchatbutton() self._floating_button.clicked.connect(self._on_button_clicked) self._load_button_position() # qsettings restore, clamped to screen self._floating_button.show() self.addon_started.emit() return true def stop(self): self._save_button_position() # persist for next session if self._chat_window: self._chat_window.close() if self._floating_button: self._floating_button.close() self.addon_stopped.emit() ``` `stop()` is idempotent and called on app close via `mainwindow.closeevent`, so there's never a dangling button. ## π±οΈ button click β window toggle the click handler distinguishes three states (a minimized window is "visible" to qt but off-screen, so it must be restored, not hidden): ```python def _on_button_clicked(self): chat = self._chat_window if chat and chat.isvisible(): if chat.isminimized(): chat.shownormal(); chat.raise_(); chat.activatewindow() else: chat.hide() else: self._show_chat_window() ``` `_show_chat_window` creates the window lazily, positions it next to the button (flipping to the left / up when it would overflow the screen's **available geometry**, which excludes taskbars and docks), then `shownormal()` + `raise_()` + `activatewindow()`. ## πͺ window flags (the platform subtleties) ```python flags = (qt.windowtype.framelesswindowhint | qt.windowtype.windowstaysontophint | qt.windowtype.x11bypasswindowmanagerhint) # linux/x11 if sys.platform != "darwin": flags |= qt.windowtype.tool # keeps it out of the taskbar ``` - **windows/linux**: `tool` keeps the button out of the taskbar. `x11bypasswindowmanagerhint` gives true always-on-top under x11. - **macos**: the `tool` flag would turn the button into a utility window that **auto-hides when the app loses focus** β so it's deliberately dropped. trade-off: the button appears in mission control. - **wayland**: compositors don't allow floating above *other* apps' windows; the button is confined to the app window (documented, and x11 is recommended for the full experience). the chat window drops the minimize button entirely β a minimized companion window is a trap state that can't be reliably restored. ## π resiliancy touches - `changeevent` bounces the button straight back to normal if a system shortcut (e.g. win+d) minimizes *every* window including the tool: ```python if event.type() == qevent.type.windowstatechange and self.isminimized(): qtimer.singleshot(0, self.shownormal) ``` - dragging clamps to `screen.availablegeometry()` (not `(0,0)`), so the button can't slide under the macos menu bar or a windows taskbar. - saved positions are clamped the same way on restore. ## π§© what you can reuse - the **window-finding pattern** in `register()` β works for any addon. - the **qsettings position persistence** with on-screen clamping. - the **platform-conditional window flags** β copy it for any always-on-top addon window. - the **show/hide/restore toggle** logic. ## π see also - [addon development guide](/docs/addon-development/) - [addon api reference](/docs/addon-api/)
- this page documents how the gguf loader codebase is organized. the app is a pyside6 desktop application; a single `main.py` bootstraps everything, and logic is split into `core/` (pure logic), `services/` (qt threading bridges), and `ui/` + `widgets/` (presentation). ## ποΈ top-level layout ``` gguf-loader/ βββ main.py # entry point: logging, dll paths, qapplication, mainwindow βββ config.py # central configuration constants βββ resource_manager.py # resource/path discovery (dev, package, or frozen) βββ addon_manager.py # loads and manages addons βββ requirements.txt # python dependencies βββ build_exe.spec # pyinstaller spec used for windows & linux builds β βββ core/ # pure, testable logic (no qt) β βββ llm/ β β βββ model_backend.py # thread-safe llama-cpp-python wrapper β β βββ prompt_builder.py # system-prompt & conversation assembly β βββ agent/ β βββ agent_engine.py # tool-use agent loop (no qt, no llama_cpp) β βββ tool_registry.py # sandboxed workspace tools β βββ services/ # qobject bridges that run work on threads β βββ model_service.py # load/unload models on a qthread β βββ chat_service.py # streaming generation on a qthread β βββ agent_service.py # runs agentengine on a worker thread β βββ environment_service.py # venv/dependency checks & pip tasks β βββ launcher_service.py # launches scripts/ utilities β βββ ui/ # main window & panels β βββ main_window.py # composition root + addon-facing api β βββ chat_panel.py # chat display, input, agent controls β βββ sidebar_panel.py # model settings sidebar β βββ theme.py # dark/light qss token system β βββ widgets/ # reusable widgets β βββ chat_bubble.py # chatgpt-style bubble β βββ feedback_dialog.py # feedback form dialog β βββ addons/ # addon packages (each has __init__.py with register()) β βββ floating_chat/ # built-in floating chat addon β βββ scripts/ # utility & release scripts β βββ capture_screenshots.py # regenerates readme/site screenshots β βββ install_linux.sh # linux installer/uninstaller β βββ package_linux.sh # builds the linux .tar.gz release β βββ ... # gpu install/monitor helpers β βββ build_hooks/ # pyinstaller hook modules ``` ## π key design rules - **`core/` never imports qt or llama_cpp.** it receives plain callables, so it can be unit-tested in isolation. - **`services/` are the only place qt threads are created.** ui never spins up threads directly. - **`ui/` widgets are "dumb"** β they render state and emit signals; `mainwindow` owns all logic. - **`resource_manager.py`** makes paths work identically in dev, as an installed package, and in a pyinstaller bundle (`sys._meipass`). ## π§΅ threading model ``` ui thread (mainwindow) worker thread β β βββ modelservice.load() βββββββ qthread: llama_cpp loads model ββββββββ loaded(modelbackend) β βββ chatservice.generate() ββββ qthread: streams tokens ββββββββ token_received(text) β βββ agentservice.process() ββββ qthread: agentengine tool loop ββββββββ status/tool/response β ``` a fresh `qthread` + worker is created per request (the professional qt pattern β `qthread` is never subclassed). ## π entry point flow `main.py` β `setup_library_path()` (finds bundled llama.cpp libs) β `qapplication` β `mainwindow()` β builds ui, wires services, checks environment, loads addons β `app.exec()`. ## π¦ packaging - **windows**: `build_exe.bat` / `build_exe.spec` β `ggufloader_vx.y.z.exe` - **linux**: build the spec inside a linux environment, then `scripts/package_linux.sh` wraps the binary + installer + icon into a `.tar.gz` - github actions publishes both to every release automatically. see the [architecture overview](/docs/architecture/) for deeper design rationale.
π 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 api reference
complete api reference for developing gguf loader addons
advanced 20 minutescomplete reference for the hooks, signals, and objects available to gguf loader addons.
πͺ addon contract
an addon is a folder under
addons/with an__init__.pyexposing:register(parent=none) -> qwidget | nonecalled by
addonmanagerwhen the addon loads. returns an optional widget shown in the addons menu (opened in a non-modal dialog), ornonefor background-only addons.parameters β
parent: the widget the addon is being attached to (may be the main window, a dialog, or another widget).return β a widget, or
none.def register(parent=none): return qlabel("my addon") # widget addonπ₯οΈ main window api
addons receive a reference to the main window (via
parentor by scanningqapplication.instance().toplevelwidgets()). it exposes:properties
| member | type | description | |β|β|β| |
model|modelbackend \| none| the loaded model backend; callable.noneuntil a model is loaded. | |chat_generator|none| legacy hook; alwaysnone. addons should callmodelinstead. | |addon_manager|addonmanager| the addon manager instance. | |_floating_chat_addon|floatingchataddon \| none| the built-in floating chat addon (if running). |signals
| signal | signature | description | |β|β|β| |
model_loaded|signal(object)| emitted with themodelbackendafter a model loads. | |model_unloaded|signal()| emitted after the model is released. | |generation_finished|signal()| emitted when a chat generation completes. | |generation_error|signal(str)| emitted with an error message when generation fails. | |theme_changed|signal(bool)| emitted when dark mode toggles (true= dark). |gguf_app.model_loaded.connect(self._on_model_loaded) gguf_app.generation_finished.connect(self._on_finished)π§ modelbackend
gguf_app.modelis acore.llm.model_backend.modelbackend. it is callable and thread-safe (all access serialized through a lock).calling convention
# raw llama-cpp shape (addon-compatible) result = gguf_app.model(prompt, stream=false, max_tokens=512, temperature=0.7) text = result["choices"][0]["text"] # streaming for token_data in gguf_app.model(prompt, stream=true, max_tokens=512): text = token_data["choices"][0]["text"]methods & attributes
| member | description | |β|β| |
model_path| path of the loaded gguf file. | |use_gpu| whether gpu acceleration is active. | |n_ctx| context length. | |is_loaded|trueonce the runtime exists. | |load()| create the underlyingllamaruntime (raises on failure). | |unload()| release the model and free memory. | |generate(prompt, **kwargs)| complete non-streamed response string. | |generate_stream(prompt, **kwargs)| iterator of token strings. | |__call__(prompt, **kwargs)| llama-cpp-compatible callable (stream returns token-dict generator). |kwargs are passed straight to llama_cpp:
max_tokens,temperature,top_p,top_k,repeat_penalty,stop, etc.π§° agent tools (for agent-capable addons)
the agent engine (
core/agent/) exposes sandboxed tools throughtoolregistry. tools are not qt-bound and are safe to call from any thread.tool params result list_directorypath(default.){status, result: [{name, type, size}]}read_filepath,max_size,encoding{status, result: content, lines, encoding}write_filepath,content{status, path, bytes_written}edit_filepath,operation(replace/insert_line/delete_line),find,replace,line_number,content{status, operation, changes_made}search_filespattern/query,path{status, result: [paths], total_matches}every tool result includes
status("success"/"error") andtool_name. all paths are sandboxed to the workspace β escaping paths raise.from core.agent.tool_registry import create_default_registry registry = create_default_registry("./workspace") result = registry.execute("list_directory", {"path": "."})π§© addonmanager
method description scan_addons(){name: path}of addons with__init__.py.load_addon(name, path)import + validate register;true/false.load_all_addons()load all; {name: success}.get_addon_widget(name, parent)widget from register(parent).open_addon_dialog(name, parent)show addon in a non-modal dialog. get_loaded_addons()list of successfully loaded names. πΎ persistence
use qtβs
qsettingswith a scoped org/app key to avoid collisions:from pyside6.qtcore import qsettings settings = qsettings("ggufloader", "myaddon") settings.setvalue("position", point) pos = settings.value("position", qpoint(100, 100))β‘ lifecycle
register()is called at startup and on addons β refresh addons.- the main windowβs
closeeventstops the floating chat addon; follow that pattern to stop your own background objects:if hasattr(gguf_app, '_my_addon') and gguf_app._my_addon: gguf_app._my_addon.stop()
π see also
- addon development guide β tutorial
- floating chat example β full real-world addon
related documentation
- gguf loader's addon system lets you extend the app without touching its source. addons are python packages dropped into the `addons/` folder; the app discovers, loads, and manages them automatically.
## π¦ what an addon looks like
```
addons/
βββ my_addon/
βββ __init__.py # must expose register()
βββ main.py # your logic (any structure you like)
βββ ... # widgets, resources, etc.
```
### minimum viable addon
```python
# addons/my_addon/__init__.py
from pyside6.qtwidgets import qlabel
def register(parent=none):
"""called by gguf loader when the addon loads."""
return qlabel("hello from my addon!")
```
that's it β drop the folder in `addons/`, restart (or **addons β refresh addons**), and your widget appears in the **addons** menu.
## π how loading works
`addonmanager` (`addon_manager.py`):
1. `scan_addons()` β lists subdirectories of the addons folder that contain `__init__.py`.
2. `load_addon(name, path)` β dynamically imports the module (`importlib.util.spec_from_file_location`) and requires a `register` attribute.
3. `load_all_addons()` β loads every addon and records success/failure in the console log.
4. `get_addon_widget(name, parent)` β calls `register(parent)` and returns the widget.
5. `open_addon_dialog(name, parent)` β shows the addon widget in a non-modal dialog from the addons menu.
the main window calls `get_addon_widget` for each loaded addon at startup, so `register()` runs even before the user opens the menu β this is how background addons (like the floating chat button) start automatically.
## π‘ talking to the app
your `register(parent)` receives the parent widget. the **main window** exposes an addon-facing api (see the [addon api reference](/docs/addon-api/)). the robust pattern used by the built-in addon:
```python
def register(parent=none):
gguf_app = none
# 1. parent might already be the main window
if parent and hasattr(parent, 'model') and hasattr(parent, 'model_loaded'):
gguf_app = parent
else:
# 2. walk up the parent chain
current = parent
while current is not none:
if hasattr(current, 'model') and hasattr(current, 'model_loaded'):
gguf_app = current
break
current = current.parent()
# 3. fall back to scanning top-level widgets
if gguf_app is none:
app = qapplication.instance()
for widget in app.toplevelwidgets():
if hasattr(widget, 'model') and hasattr(widget, 'model_loaded'):
gguf_app = widget
break
...
```
key api members: `model` (callable backend), `model_loaded` / `model_unloaded` / `generation_finished` / `generation_error` signals, `addon_manager`, and `_floating_chat_addon`.
## π€ calling the model
```python
# streaming
for token in gguf_app.model(prompt, stream=true, max_tokens=512):
print(token["choices"][0]["text"])
# or use the backend directly
response = gguf_app.model.generate(prompt, max_tokens=512, temperature=0.7)
```
`model` is `none` until the user loads a model β always check, and subscribe to `model_loaded` to react.
## π§© widget vs. background addons
- **return a widget** from `register()` β appears in the addons menu, opens in a dialog.
- **return `none`** (or also keep your own top-level windows) β runs in the background. the floating chat addon returns a small status widget but drives its own always-on-top windows.
## π‘ best practices
1. **guard everything** β wrap registration in `try/except` and log failures; a broken addon must never crash the app.
2. **clean up state** β if you keep references on the main window (e.g. `gguf_app._my_addon = ...`), stop them on app close (`mainwindow.closeevent` stops `_floating_chat_addon` β follow that pattern).
3. **use qsettings for persistence** β `qsettings("ggufloader", "youraddon")`.
4. **prefer signals** β let your widgets emit signals; don't reach into other widgets' internals.
5. **qt-free logic** β if you have non-ui logic (parsing, computation), put it in a module with no qt imports so it's unit-testable.
## β
debugging
addon load errors print to the console with a traceback (`failed to load addon
: ...`). run the app from a terminal to see them: ```bash python main.py ``` ## π next steps - [addon api reference](/docs/addon-api/) β every hook and member - [floating chat example](/docs/floating-chat-example/) β full walkthrough of the built-in addon - this page walks through **floating_chat**, gguf loader's built-in addon β a facebook messenger-style floating button that opens a chat window connected to the loaded model. it's the best real-world reference for writing a background addon with its own windows. ``` addons/floating_chat/ βββ __init__.py # exposes register() βββ main.py # floatingchataddon (qobject) + register() βββ floating_button.py # floatingchatbutton - draggable always-on-top button βββ chat_window.py # floatingchatwindow - the chat ui βββ status_widget.py # small sidebar widget returned by register() ``` ## π§ the design the addon is a `qobject` (`floatingchataddon`) that owns two windows: - **`floatingchatbutton`** β a frameless, always-on-top tool window that can be dragged anywhere and remembers its position via `qsettings`. - **`floatingchatwindow`** β a regular top-level window (`400Γ600`) positioned next to the button, with a message list, streaming replies, and copy all / clear buttons. ## πͺ registration `__init__.py` simply re-exports `register`: ```python from .main import register __all__ = ['register'] ``` `register()` finds the main window (parent β parent chain β `toplevelwidgets()` scan), stops any previous instance, starts the addon, and returns a **status widget** for the sidebar: ```python def register(parent=none): gguf_app = _find_main_window(parent) # model + model_loaded check if gguf_app is none: return none if getattr(gguf_app, '_floating_chat_addon', none): gguf_app._floating_chat_addon.stop() addon = floatingchataddon(gguf_app) if addon.start(): gguf_app._floating_chat_addon = addon return floatingchatstatuswidget(addon) return none ``` the returned `floatingchatstatuswidget` shows up in the **addons** menu, and because `register()` runs at startup, the button appears automatically β no user action needed. ## π‘ connecting to the app `floatingchataddon.__init__` subscribes to model lifecycle signals: ```python if hasattr(self.gguf_app, 'model_loaded'): self.gguf_app.model_loaded.connect(self._on_model_loaded) if hasattr(self.gguf_app, 'generation_finished'): self.gguf_app.generation_finished.connect(self._on_generation_finished) if hasattr(self.gguf_app, 'generation_error'): self.gguf_app.generation_error.connect(self._on_generation_error) ``` `_on_model_loaded` flips the chat window's status to **π’ model: ready**; generation events update the streaming bubble / error state. ## π lifecycle: start() / stop() ```python def start(self): self._floating_button = floatingchatbutton() self._floating_button.clicked.connect(self._on_button_clicked) self._load_button_position() # qsettings restore, clamped to screen self._floating_button.show() self.addon_started.emit() return true def stop(self): self._save_button_position() # persist for next session if self._chat_window: self._chat_window.close() if self._floating_button: self._floating_button.close() self.addon_stopped.emit() ``` `stop()` is idempotent and called on app close via `mainwindow.closeevent`, so there's never a dangling button. ## π±οΈ button click β window toggle the click handler distinguishes three states (a minimized window is "visible" to qt but off-screen, so it must be restored, not hidden): ```python def _on_button_clicked(self): chat = self._chat_window if chat and chat.isvisible(): if chat.isminimized(): chat.shownormal(); chat.raise_(); chat.activatewindow() else: chat.hide() else: self._show_chat_window() ``` `_show_chat_window` creates the window lazily, positions it next to the button (flipping to the left / up when it would overflow the screen's **available geometry**, which excludes taskbars and docks), then `shownormal()` + `raise_()` + `activatewindow()`. ## πͺ window flags (the platform subtleties) ```python flags = (qt.windowtype.framelesswindowhint | qt.windowtype.windowstaysontophint | qt.windowtype.x11bypasswindowmanagerhint) # linux/x11 if sys.platform != "darwin": flags |= qt.windowtype.tool # keeps it out of the taskbar ``` - **windows/linux**: `tool` keeps the button out of the taskbar. `x11bypasswindowmanagerhint` gives true always-on-top under x11. - **macos**: the `tool` flag would turn the button into a utility window that **auto-hides when the app loses focus** β so it's deliberately dropped. trade-off: the button appears in mission control. - **wayland**: compositors don't allow floating above *other* apps' windows; the button is confined to the app window (documented, and x11 is recommended for the full experience). the chat window drops the minimize button entirely β a minimized companion window is a trap state that can't be reliably restored. ## π resiliancy touches - `changeevent` bounces the button straight back to normal if a system shortcut (e.g. win+d) minimizes *every* window including the tool: ```python if event.type() == qevent.type.windowstatechange and self.isminimized(): qtimer.singleshot(0, self.shownormal) ``` - dragging clamps to `screen.availablegeometry()` (not `(0,0)`), so the button can't slide under the macos menu bar or a windows taskbar. - saved positions are clamped the same way on restore. ## π§© what you can reuse - the **window-finding pattern** in `register()` β works for any addon. - the **qsettings position persistence** with on-screen clamping. - the **platform-conditional window flags** β copy it for any always-on-top addon window. - the **show/hide/restore toggle** logic. ## π see also - [addon development guide](/docs/addon-development/) - [addon api reference](/docs/addon-api/)
- this page documents how the gguf loader codebase is organized. the app is a pyside6 desktop application; a single `main.py` bootstraps everything, and logic is split into `core/` (pure logic), `services/` (qt threading bridges), and `ui/` + `widgets/` (presentation). ## ποΈ top-level layout ``` gguf-loader/ βββ main.py # entry point: logging, dll paths, qapplication, mainwindow βββ config.py # central configuration constants βββ resource_manager.py # resource/path discovery (dev, package, or frozen) βββ addon_manager.py # loads and manages addons βββ requirements.txt # python dependencies βββ build_exe.spec # pyinstaller spec used for windows & linux builds β βββ core/ # pure, testable logic (no qt) β βββ llm/ β β βββ model_backend.py # thread-safe llama-cpp-python wrapper β β βββ prompt_builder.py # system-prompt & conversation assembly β βββ agent/ β βββ agent_engine.py # tool-use agent loop (no qt, no llama_cpp) β βββ tool_registry.py # sandboxed workspace tools β βββ services/ # qobject bridges that run work on threads β βββ model_service.py # load/unload models on a qthread β βββ chat_service.py # streaming generation on a qthread β βββ agent_service.py # runs agentengine on a worker thread β βββ environment_service.py # venv/dependency checks & pip tasks β βββ launcher_service.py # launches scripts/ utilities β βββ ui/ # main window & panels β βββ main_window.py # composition root + addon-facing api β βββ chat_panel.py # chat display, input, agent controls β βββ sidebar_panel.py # model settings sidebar β βββ theme.py # dark/light qss token system β βββ widgets/ # reusable widgets β βββ chat_bubble.py # chatgpt-style bubble β βββ feedback_dialog.py # feedback form dialog β βββ addons/ # addon packages (each has __init__.py with register()) β βββ floating_chat/ # built-in floating chat addon β βββ scripts/ # utility & release scripts β βββ capture_screenshots.py # regenerates readme/site screenshots β βββ install_linux.sh # linux installer/uninstaller β βββ package_linux.sh # builds the linux .tar.gz release β βββ ... # gpu install/monitor helpers β βββ build_hooks/ # pyinstaller hook modules ``` ## π key design rules - **`core/` never imports qt or llama_cpp.** it receives plain callables, so it can be unit-tested in isolation. - **`services/` are the only place qt threads are created.** ui never spins up threads directly. - **`ui/` widgets are "dumb"** β they render state and emit signals; `mainwindow` owns all logic. - **`resource_manager.py`** makes paths work identically in dev, as an installed package, and in a pyinstaller bundle (`sys._meipass`). ## π§΅ threading model ``` ui thread (mainwindow) worker thread β β βββ modelservice.load() βββββββ qthread: llama_cpp loads model ββββββββ loaded(modelbackend) β βββ chatservice.generate() ββββ qthread: streams tokens ββββββββ token_received(text) β βββ agentservice.process() ββββ qthread: agentengine tool loop ββββββββ status/tool/response β ``` a fresh `qthread` + worker is created per request (the professional qt pattern β `qthread` is never subclassed). ## π entry point flow `main.py` β `setup_library_path()` (finds bundled llama.cpp libs) β `qapplication` β `mainwindow()` β builds ui, wires services, checks environment, loads addons β `app.exec()`. ## π¦ packaging - **windows**: `build_exe.bat` / `build_exe.spec` β `ggufloader_vx.y.z.exe` - **linux**: build the spec inside a linux environment, then `scripts/package_linux.sh` wraps the binary + installer + icon into a `.tar.gz` - github actions publishes both to every release automatically. see the [architecture overview](/docs/architecture/) for deeper design rationale.
π 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
- Get up and running with gguf loader 2.1.2 in just a few minutes! new to gguf loader? [check out the homepage](/) to see what makes it special. ## π step 1: install gguf loader follow the [installation guide](/docs/installation/) for your platform, or download directly: - **windows**: [ggufloader_v2.1.2.exe](https://github.com/ggufloader/gguf-loader/releases/download/v2.1.2/ggufloader_v2.1.2.exe) - **linux**: [ggufloader_v2.1.2_linux_x86_64.tar.gz](https://github.com/ggufloader/gguf-loader/releases/download/v2.1.2/ggufloader_v2.1.2_linux_x86_64.tar.gz) ## π₯ step 2: get a gguf model gguf loader runs any gguf-format model. good starter models on hugging face: - **small (fast)**: [llama-3-8b-instruct gguf (q4_0)](https://huggingface.co/thebloke/llama-3-8b-instruct-gguf) β ~4.7 gb, runs great on cpu - **medium**: [mistral-7b-instruct gguf (q4_k_m)](https://huggingface.co/thebloke/mistral-7b-instruct-v0.2-gguf) β ~4.1 gb - **larger**: [phi-3.5-mini-instruct gguf](https://huggingface.co/microsoft/phi-3.5-mini-instruct-gguf) or any qwen/llama gguf you like download the `.gguf` file and remember where you saved it. ## π₯οΈ step 3: load the model 1. launch gguf loader. 2. in the **model settings** sidebar, choose **processing** mode β `cpu only` (default, works everywhere) or `gpu accelerated` (nvidia cuda). 3. set a **context length** (8192 is a safe default for most models; 32768 uses more ram). 4. click **load gguf model** and select your `.gguf` file. 5. wait for the status to show **"model ready!"** β the header chip turns green with the model name. ## π¬ step 4: chat type a message in the input box at the bottom and press **enter** (shift+enter inserts a newline). responses stream in as chatgpt-style bubbles β your messages on the right (amber), the ai's on the left. use the **view β text size** menu to adjust bubble font size. ## π€ step 5 (optional): try agent mode click **π€ agent mode: off** in the input area to toggle it on: 1. pick a **workspace folder** (defaults to `./agent_workspace`). 2. ask the agent to do file work β e.g. *"create a file called hello.py that prints 'hi'"*. 3. the agent plans tool calls, executes them (read/write/edit/search files inside the workspace only), and reports back with live status updates. ## β next steps - [user guide](/docs/user-guide/) β everything the app can do - [addon development](/docs/addon-development/) β extend gguf loader with addons
π 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 β