PluginAiOllama.py

Sun, 25 Aug 2024 19:44:24 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 25 Aug 2024 19:44:24 +0200
changeset 8
3118d16e526e
parent 7
eb1dec15b2f0
child 16
cb6af351310b
permissions
-rw-r--r--

Implemented some more menu actions.
- Start and Stop a local 'ollama' server in the background.
- List available models.
- List running models.
- Open the 'ollama' model library in a browser.

# -*- coding: utf-8 -*-

# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the ollama Interface plug-in.
"""

import os

from PyQt6.QtCore import QObject, Qt, QTranslator, pyqtSignal
from PyQt6.QtGui import QKeySequence

from eric7 import Globals, Preferences
from eric7.EricGui import EricPixmapCache
from eric7.EricGui.EricAction import EricAction
from eric7.EricWidgets.EricApplication import ericApp

try:
    from eric7.UI.UserInterface import UserInterfaceSide

    _Side = UserInterfaceSide.Right
except ImportError:
    # backward compatibility for eric < 24.2
    from eric7.UI.UserInterface import UserInterface

    _Side = UserInterface.RightSide

# Start-Of-Header
__header__ = {
    "name": "ollama Interface",
    "author": "Detlev Offenbach <detlev@die-offenbachs.de>",
    "autoactivate": True,
    "deactivateable": True,
    "version": "10.0.0",
    "className": "PluginOllamaInterface",
    "packageName": "OllamaInterface",
    "shortDescription": "Grapgical 'ollama' client for eric-ide.",
    "longDescription": (
        "Plug-in implementing an 'ollama' client and interface widgets."
    ),
    "needsRestart": False,
    "pyqtApi": 2,
}
# End-Of-Header

error = ""  # noqa: U200

ollamaInterfacePluginObject = None


def pageCreationFunction(configDlg):
    """
    Function to create the Translator configuration page.

    @param configDlg reference to the configuration dialog
    @type ConfigurationWidget
    @return reference to the configuration page
    @rtype TranslatorPage
    """
    # TODO: not implemented yet
    page = None  # change this line to create the configuration page
    return page


def getConfigData():
    """
    Function returning data as required by the configuration dialog.

    @return dictionary containing the relevant data
    @rtype dict
    """
    # TODO: not implemented yet
    return {
        "<unique key>": [
            "<display string>",
            "<pixmap filename>",
            pageCreationFunction,
            None,
            None,
        ],
    }


def prepareUninstall():
    """
    Function to prepare for an un-installation.
    """
    Preferences.getSettings().remove(PluginOllamaInterface.PreferencesKey)


def clearPrivateData():
    """
    Function to clear the private data of the plug-in.
    """
    if ollamaInterfacePluginObject is not None:
        widget = ollamaInterfacePluginObject.getWidget()
        if widget is not None:
            widget.clearHistory()


class PluginOllamaInterface(QObject):
    """
    Class implementing the ollama Interface plug-in.

    @signal preferencesChanged() emitted to signal a change of preferences. This
        signal is simply relayed from the main UI.
    """

    PreferencesKey = "Ollama"

    preferencesChanged = pyqtSignal()

    def __init__(self, ui):
        """
        Constructor

        @param ui reference to the user interface object
        @type UI.UserInterface
        """
        super().__init__(ui)
        self.__ui = ui
        self.__initialize()

        self.__defaults = {
            "OllamaScheme": "http",
            "OllamaHost": "localhost",
            "OllamaPort": 11434,
            "OllamaLocalPort": 11435,  # port for locally started ollama server
            "OllamaHeartbeatInterval": 5,  # 5 seconds heartbeat time; 0 = disabled
            "StreamingChatResponse": True,
            "OllamaModelLibraryUrl": "https://ollama.com/library",
        }

        self.__translator = None
        self.__loadTranslator()

    def __initialize(self):
        """
        Private slot to (re)initialize the plugin.
        """
        self.__widget = None

    def activate(self):
        """
        Public method to activate this plug-in.

        @return tuple of None and activation status
        @rtype bool
        """
        from OllamaInterface.OllamaWidget import OllamaWidget

        global error, ollamaInterfacePluginObject
        error = ""  # clear previous error
        ollamaInterfacePluginObject = self

        usesDarkPalette = ericApp().usesDarkPalette()
        iconSuffix = "dark" if usesDarkPalette else "light"

        self.__widget = OllamaWidget(self, fromEric=True)
        iconName = (
            "sbOllama96"
            if self.__ui.getLayoutType() == "Sidebars"
            else "ollama22-{0}".format(iconSuffix)
        )
        self.__ui.addSideWidget(
            _Side,
            self.__widget,
            EricPixmapCache.getIcon(os.path.join("OllamaInterface", "icons", iconName)),
            self.tr("ollama AI Interface"),
        )

        self.__activateAct = EricAction(
            self.tr("ollama AI Interface"),
            self.tr("ollama AI Interface"),
            QKeySequence(self.tr("Ctrl+Alt+Shift+O")),
            0,
            self,
            "ollama_interface_activate",
        )
        self.__activateAct.setStatusTip(
            self.tr("Switch the input focus to the ollama AI window.")
        )
        self.__activateAct.setWhatsThis(
            self.tr(
                """<b>Activate ollama AI Interface</b>"""
                """<p>This switches the input focus to the ollama AI window.</p>"""
            )
        )
        self.__activateAct.triggered.connect(self.__activateWidget)

        self.__ui.addEricActions([self.__activateAct], "ui")
        menu = self.__ui.getMenu("subwindow")
        menu.addAction(self.__activateAct)

        self.__ui.preferencesChanged.connect(self.preferencesChanged)

        return None, True

    def deactivate(self):
        """
        Public method to deactivate this plug-in.
        """
        self.__ui.preferencesChanged.disconnect(self.preferencesChanged)

        menu = self.__ui.getMenu("subwindow")
        menu.removeAction(self.__activateAct)
        self.__ui.removeEricActions([self.__activateAct], "ui")
        self.__ui.removeSideWidget(self.__widget)

        self.__initialize()

    def __loadTranslator(self):
        """
        Private method to load the translation file.
        """
        if self.__ui is not None:
            loc = self.__ui.getLocale()
            if loc and loc != "C":
                locale_dir = os.path.join(
                    os.path.dirname(__file__), "OllamaInterface", "i18n"
                )
                translation = "ollama_{0}".format(loc)
                translator = QTranslator(None)
                loaded = translator.load(translation, locale_dir)
                if loaded:
                    self.__translator = translator
                    ericApp().installTranslator(self.__translator)
                else:
                    print(
                        "Warning: translation file '{0}' could not be"
                        " loaded.".format(translation)
                    )
                    print("Using default.")

    def getWidget(self):
        """
        Public method to get a reference to the 'ollama' widget.

        @return reference to the 'ollama' widget
        @rtype OllamaWidget
        """
        return self.__widget

    def __activateWidget(self):
        """
        Private slot to handle the activation of the pipx interface.
        """
        uiLayoutType = self.__ui.getLayoutType()

        if uiLayoutType == "Toolboxes":
            self.__ui.rToolboxDock.show()
            self.__ui.rToolbox.setCurrentWidget(self.__widget)
        elif uiLayoutType == "Sidebars":
            try:
                self.__ui.activateLeftRightSidebarWidget(self.__widget)
            except AttributeError:
                self.__activateLeftRightSidebarWidget(self.__widget)
        else:
            self.__widget.show()
        self.__widget.setFocus(Qt.FocusReason.ActiveWindowFocusReason)

    def getPreferences(self, key):
        """
        Public method to retrieve the various settings values.

        @param key the key of the value to get
        @type str
        @return the requested setting value
        @rtype Any
        """
        if key in ("OllamaPort", "OllamaLocalPort", "OllamaHeartbeatInterval"):
            return int(
                Preferences.Prefs.settings.value(
                    self.PreferencesKey + "/" + key, self.__defaults[key]
                )
            )
        elif key in ("StreamingChatResponse",):
            return Globals.toBool(
                Preferences.Prefs.settings.value(
                    self.PreferencesKey + "/" + key, self.__defaults[key]
                )
            )
        else:
            return Preferences.Prefs.settings.value(
                self.PreferencesKey + "/" + key, self.__defaults[key]
            )

        return None

    def setPreferences(self, key, value):
        """
        Public method to store the various settings values.

        @param key the key of the setting to be set
        @type str
        @param value the value to be set
        @type Any
        """
        Preferences.Prefs.settings.setValue(self.PreferencesKey + "/" + key, value)

    ############################################################################
    ## Methods for backward compatibility with eric-ide < 24.9
    ############################################################################

    def __activateLeftRightSidebarWidget(self, widget):
        """
        Private method to activate the given widget in the left or right
        sidebar.

        @param widget reference to the widget to be activated
        @type QWidget
        """
        # This is for backward compatibility with eric-ide < 24.9.
        sidebar = (
            self.__ui.leftSidebar
            if Preferences.getUI("CombinedLeftRightSidebar")
            else self.__ui.rightSidebar
        )
        sidebar.show()
        sidebar.setCurrentWidget(widget)


#
# eflag: noqa = M801, U200

eric ide

mercurial