PluginAiOllama.py

Wed, 18 Sep 2024 13:11:38 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 18 Sep 2024 13:11:38 +0200
changeset 49
a68c5da858e3
parent 47
178d52749f25
child 51
f8d0b051bee9
permissions
-rw-r--r--

Prepared new release.

# -*- 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 QCoreApplication, 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.1.1",
    "className": "PluginOllamaInterface",
    "packageName": "OllamaInterface",
    "shortDescription": "Graphical 'ollama' client for eric-ide.",
    "longDescription": (
        "Plug-in implementing an 'ollama' client and interface widgets."
    ),
    "needsRestart": False,
    "hasCompiledForms": True,
    "pyqtApi": 2,
}
# End-Of-Header

error = ""  # noqa: U200

ollamaInterfacePluginObject = None


def createOllamaPage(_configDlg):
    """
    Function to create the 'ollama' interface' configuration page.

    @param _configDlg reference to the configuration dialog
    @type ConfigurationWidget
    @return reference to the configuration page
    @rtype OllamaPage
    """
    from OllamaInterface.ConfigurationPage.OllamaPage import OllamaPage

    global ollamaInterfacePluginObject

    page = OllamaPage(ollamaInterfacePluginObject)
    return page


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

    @return dictionary containing the relevant data
    @rtype dict
    """
    usesDarkPalette = ericApp().usesDarkPalette()
    iconSuffix = "dark" if usesDarkPalette else "light"

    return {
        "ollamaPage": [
            QCoreApplication.translate("PluginOllamaInterface", "ollama AI Interface"),
            os.path.join("OllamaInterface", "icons", "ollama22-{0}".format(iconSuffix)),
            createOllamaPage,
            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",
            "OllamaDownloadUrl": "https://ollama.com/download",
            "OllamaBlogUrl": "https://ollama.com/blog",
        }

        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]
                )
            )
        elif key in (
            "OllamaHost",
            "OllamaModelLibraryUrl",
            "OllamaDownloadUrl",
            "OllamaBlogUrl",
        ):
            value = Preferences.Prefs.settings.value(
                self.PreferencesKey + "/" + key, self.__defaults[key]
            )
            if not value:
                # use default value if empty
                value = self.__defaults[key]
            return value
        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