File indexing completed on 2025-01-05 05:18:57

0001 // SPDX-FileCopyrightText: 2023 Loren Burkholder <computersemiexpert@outlook.com>
0002 // SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
0003 //
0004 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 
0006 #include "KLLMInterface.h"
0007 
0008 #include <KLocalizedString>
0009 
0010 #include <QBuffer>
0011 #include <QJsonDocument>
0012 #include <QJsonObject>
0013 #include <QNetworkAccessManager>
0014 #include <QNetworkReply>
0015 
0016 using namespace Qt::StringLiterals;
0017 using namespace KLLMCore;
0018 
0019 KLLMInterface::KLLMInterface(QObject *parent)
0020     : KLLMInterface{QString{}, parent}
0021 {
0022 }
0023 
0024 KLLMInterface::KLLMInterface(const QString &ollamaUrl, QObject *parent)
0025     : QObject{parent}
0026     , m_manager{new QNetworkAccessManager{this}}
0027     , m_ollamaUrl{ollamaUrl}
0028 {
0029     if (!m_ollamaUrl.isEmpty())
0030         reload();
0031 }
0032 
0033 KLLMInterface::KLLMInterface(const QUrl &ollamaUrl, QObject *parent)
0034     : KLLMInterface{ollamaUrl.toString(), parent}
0035 {
0036 }
0037 
0038 bool KLLMInterface::ready() const
0039 {
0040     return m_ready && !m_hasError;
0041 }
0042 
0043 bool KLLMInterface::hasError() const
0044 {
0045     return m_hasError;
0046 }
0047 
0048 QStringList KLLMInterface::models() const
0049 {
0050     return m_models;
0051 }
0052 
0053 #if 0
0054 void KLLMInterface::deleteModel(const QString &modelName)
0055 {
0056     Q_ASSERT(ready());
0057 
0058     QNetworkRequest req{QUrl::fromUserInput(m_ollamaUrl + QStringLiteral("/api/delete"))};
0059     req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
0060     QJsonObject data;
0061     data["name"_L1] = modelName;
0062 
0063     // Delete resource doesn't take argument. Need to look at how to do it.
0064     auto buf = new QBuffer{this};
0065     buf->setData(QJsonDocument(data).toJson(QJsonDocument::Compact));
0066 
0067     auto reply = new KLLMReply{m_manager->deleteResource(req, buf), this};
0068     connect(reply, &KLLMReply::finished, this, [this, reply, buf] {
0069         Q_EMIT finished(reply->readResponse());
0070         buf->deleteLater();
0071     });
0072 }
0073 #endif
0074 
0075 KLLMReply *KLLMInterface::getCompletion(const KLLMRequest &request)
0076 {
0077     Q_ASSERT(ready());
0078 
0079     QNetworkRequest req{QUrl::fromUserInput(m_ollamaUrl + QStringLiteral("/api/generate"))};
0080     req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
0081 
0082     QJsonObject data;
0083     data["model"_L1] = request.model().isEmpty() ? m_models.constFirst() : request.model();
0084     data["prompt"_L1] = request.message();
0085 
0086     const auto context = request.context().toJson();
0087     if (!context.isNull()) {
0088         data["context"_L1] = context;
0089     }
0090 
0091     if (!m_systemPrompt.isEmpty()) {
0092         data["system"_L1] = m_systemPrompt;
0093     }
0094 
0095     auto buf = new QBuffer{this};
0096     buf->setData(QJsonDocument(data).toJson(QJsonDocument::Compact));
0097 
0098     auto reply = new KLLMReply{m_manager->post(req, buf), this};
0099     connect(reply, &KLLMReply::finished, this, [this, reply, buf] {
0100         Q_EMIT finished(reply->readResponse());
0101         buf->deleteLater();
0102     });
0103     return reply;
0104 }
0105 
0106 void KLLMInterface::reload()
0107 {
0108     if (m_ollamaCheck)
0109         disconnect(m_ollamaCheck);
0110 
0111     QNetworkRequest req{QUrl::fromUserInput(m_ollamaUrl + QStringLiteral("/api/tags"))};
0112     req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
0113     auto rep = m_manager->get(req);
0114     m_ollamaCheck = connect(rep, &QNetworkReply::finished, this, [this, rep] {
0115         if (rep->error() != QNetworkReply::NoError) {
0116             Q_EMIT errorOccurred(i18n("Failed to connect to interface at %1: %2", m_ollamaUrl, rep->errorString()));
0117             m_hasError = true;
0118             Q_EMIT readyChanged();
0119             Q_EMIT hasErrorChanged();
0120             return;
0121         }
0122 
0123         const auto json = QJsonDocument::fromJson(rep->readAll());
0124         const auto models = json["models"_L1].toArray();
0125         for (const QJsonValue &model : models) {
0126             m_models.push_back(model["name"_L1].toString());
0127         }
0128         Q_EMIT modelsChanged();
0129 
0130         m_ready = !m_models.isEmpty();
0131         m_hasError = false;
0132         Q_EMIT readyChanged();
0133         Q_EMIT hasErrorChanged();
0134     });
0135 }
0136 
0137 QString KLLMInterface::ollamaUrl() const
0138 {
0139     return m_ollamaUrl;
0140 }
0141 
0142 void KLLMInterface::setOllamaUrl(const QString &ollamaUrl)
0143 {
0144     if (m_ollamaUrl == ollamaUrl)
0145         return;
0146     m_ollamaUrl = ollamaUrl;
0147     Q_EMIT ollamaUrlChanged();
0148     reload();
0149 }
0150 
0151 void KLLMInterface::setOllamaUrl(const QUrl &ollamaUrl)
0152 {
0153     setOllamaUrl(ollamaUrl.toString());
0154 }
0155 
0156 QString KLLMInterface::systemPrompt() const
0157 {
0158     return m_systemPrompt;
0159 }
0160 
0161 void KLLMInterface::setSystemPrompt(const QString &systemPrompt)
0162 {
0163     if (m_systemPrompt == systemPrompt)
0164         return;
0165     m_systemPrompt = systemPrompt;
0166     Q_EMIT systemPromptChanged();
0167 }
0168 
0169 #include "moc_KLLMInterface.cpp"