Skip to content

Commit

Permalink
#3026 add: basic auto-completion api and settings dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
pbek committed May 10, 2024
1 parent 464ac9d commit 9219d34
Show file tree
Hide file tree
Showing 7 changed files with 270 additions and 2 deletions.
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@ set(SOURCE_FILES
services/cryptoservice.h
services/scriptingservice.cpp
services/scriptingservice.h
services/openaiservice.cpp
services/openaiservice.h
libraries/piwiktracker/piwiktracker.h
libraries/piwiktracker/piwiktracker.cpp
libraries/qkeysequencewidget/qkeysequencewidget/src/qkeysequencewidget_p.h
Expand Down
2 changes: 2 additions & 0 deletions src/QOwnNotes.pro
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ SOURCES += main.cpp\
services/scriptingservice.cpp \
services/websocketserverservice.cpp \
services/webappclientservice.cpp \
services/openaiservice.cpp \
dialogs/masterdialog.cpp \
utils/misc.cpp \
utils/git.cpp \
Expand Down Expand Up @@ -272,6 +273,7 @@ HEADERS += mainwindow.h \
dialogs/passworddialog.h \
services/metricsservice.h \
services/cryptoservice.h \
services/openaiservice.h \
dialogs/masterdialog.h \
utils/misc.h \
utils/git.h \
Expand Down
3 changes: 2 additions & 1 deletion src/dialogs/settingsdialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ class SettingsDialog : public MasterDialog {
LayoutPage,
WebCompanionPage,
WebApplicationPage,
ExperimentalPage
ExperimentalPage,
AiPage
};

explicit SettingsDialog(int page = 0, QWidget *parent = 0);
Expand Down
86 changes: 85 additions & 1 deletion src/dialogs/settingsdialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,18 @@
<normaloff>:/icons/breeze-qownnotes/16x16/text-html.svg</normaloff>:/icons/breeze-qownnotes/16x16/text-html.svg</iconset>
</property>
</item>
<item>
<property name="text">
<string>AI</string>
</property>
<property name="whatsThis">
<string notr="true">22</string>
</property>
<property name="icon">
<iconset resource="../breeze-dark-qownnotes.qrc">
<normaloff>:/icons/breeze-dark-qownnotes/16x16/text-html.svg</normaloff>:/icons/breeze-dark-qownnotes/16x16/text-html.svg</iconset>
</property>
</item>
<item>
<property name="text">
<string>General</string>
Expand Down Expand Up @@ -421,7 +433,7 @@
<item>
<widget class="QStackedWidget" name="settingsStackedWidget">
<property name="currentIndex">
<number>7</number>
<number>22</number>
</property>
<widget class="QWidget" name="noteFoldersPage">
<layout class="QGridLayout" name="gridLayout_19">
Expand Down Expand Up @@ -6843,6 +6855,78 @@ Just test yourself if you get sync conflicts and set a higher value if so.</stri
</item>
</layout>
</widget>
<widget class="QWidget" name="aiPage">
<layout class="QGridLayout" name="gridLayout_86">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_40">
<property name="title">
<string>API keys</string>
</property>
<layout class="QGridLayout" name="gridLayout_85">
<property name="rightMargin">
<number>3</number>
</property>
<item row="0" column="2">
<widget class="QToolButton" name="groqApiKeyWebButton">
<property name="toolTip">
<string>Create API Key</string>
</property>
<property name="text">
<string notr="true">…</string>
</property>
<property name="icon">
<iconset theme="text-html" resource="../breeze-qownnotes.qrc">
<normaloff>:/icons/breeze-qownnotes/16x16/text-html.svg</normaloff>:/icons/breeze-qownnotes/16x16/text-html.svg</iconset>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="groqApiKeyHeadlineLabel">
<property name="text">
<string>Groq API key:</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="groqApiKeyLineEdit">
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<spacer name="verticalSpacer_21">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
Expand Down
4 changes: 4 additions & 0 deletions src/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
#include "services/databaseservice.h"
#include "services/metricsservice.h"
#include "services/nextclouddeckservice.h"
#include "services/openaiservice.h"
#include "services/owncloudservice.h"
#include "services/updateservice.h"
#include "services/webappclientservice.h"
Expand Down Expand Up @@ -5817,6 +5818,9 @@ void MainWindow::on_action_Find_note_triggered() {
changeDistractionFreeMode(false);
this->ui->searchLineEdit->setFocus();
this->ui->searchLineEdit->selectAll();

// auto openAiService = new OpenAiService(this);
// Q_UNUSED(openAiService)
}

//
Expand Down
125 changes: 125 additions & 0 deletions src/services/openaiservice.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright (c) 2014-2024 Patrizio Bekerle -- <patrizio@bekerle.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
*/

#include "openaiservice.h"
#include <QJsonObject>
#include <QJsonDocument>
#include <QJsonArray>
#include <QNetworkRequest>
#include <utility>

using namespace std;

QT_USE_NAMESPACE

OpenAiService::OpenAiService(QObject* parent)
: QObject(parent) {
auto apiKey = "secret";
auto _completer = new OpenAiCompleter(
apiKey,
"llama3-8b-8192",
"https://api.groq.com/openai/v1/chat/completions",
parent);

_completer->complete("Who am I?");

QObject::connect(_completer, &OpenAiCompleter::completed, this, [this](const QString& result) {
qDebug() << "'result': " << result;
});

QObject::connect(_completer, &OpenAiCompleter::errorOccurred, this, [this](const QString& errorString) {
qDebug() << "'errorString': " << errorString;
});
}

OpenAiCompleter::OpenAiCompleter(QString apiKey, QString modelId, QString apiBaseUrl, QObject* parent)
: QObject(parent), apiKey(std::move(apiKey)), apiBaseUrl(std::move(apiBaseUrl)), modelId(std::move(modelId))
{
networkManager = new QNetworkAccessManager(this);
connect(networkManager, &QNetworkAccessManager::finished, this, &OpenAiCompleter::replyFinished);
}

void OpenAiCompleter::complete(const QString& prompt)
{
QUrl url(apiBaseUrl);
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Authorization", ("Bearer " + apiKey).toUtf8());

QJsonObject json;
json["model"] = modelId; // Use the modelId set in the constructor or by setModelId

QJsonArray messagesArray;
QJsonObject messageObject;
messageObject["role"] = "user";
messageObject["content"] = prompt;
messagesArray.append(messageObject);

json["messages"] = messagesArray;

qDebug() << __func__ << " - 'json': " << json;

QJsonDocument doc(json);
QByteArray data = doc.toJson();

networkManager->post(request, data);
}

void OpenAiCompleter::setApiBaseUrl(const QString& url) {
this->apiBaseUrl = url;
}

void OpenAiCompleter::setModelId(const QString& id) {
this->modelId = id;
}

void OpenAiCompleter::replyFinished(QNetworkReply* reply)
{
if (reply->error()) {
emit errorOccurred(reply->errorString());
return;
}

QByteArray response_data = reply->readAll();
QJsonDocument json = QJsonDocument::fromJson(response_data);
QJsonObject jsonObject = json.object();
QJsonArray choices = jsonObject["choices"].toArray();

// Initializing an empty result string to hold the eventual content.
QString text = "";

// Look through the choices (though typically there's only one)
// and parse the nested message content.
if(!choices.isEmpty()) {
QJsonObject firstChoice = choices[0].toObject();

// Check if 'message' field exists, ensuring compatibility with the new structure
if(firstChoice.contains("message")) {
QJsonObject message = firstChoice["message"].toObject();
// Check if the role is "assistant" before extracting content
if(message["role"].toString() == "assistant") {
text = message["content"].toString();
}
} else { // Fallback to directly parse 'text' if present, for backward compatibility or other responses
text = firstChoice["text"].toString();
}

emit completed(text.trimmed());
}
else {
emit errorOccurred("No choices found in the response");
}

reply->deleteLater();
}
50 changes: 50 additions & 0 deletions src/services/openaiservice.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2014-2024 Patrizio Bekerle -- <patrizio@bekerle.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
*/

#pragma once

#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>

class OpenAiService : public QObject
{
Q_OBJECT
public:
explicit OpenAiService(QObject* parent = nullptr);
};


class OpenAiCompleter : public QObject
{
Q_OBJECT
public:
explicit OpenAiCompleter(QString apiKey, QString modelId, QString apiBaseUrl = "https://api.openai.com/v1/completions", QObject* parent = nullptr);
void complete(const QString& prompt);
void setApiBaseUrl(const QString& url);
void setModelId(const QString& id);

signals:
void completed(QString result);
void errorOccurred(QString errorString);

private slots:
void replyFinished(QNetworkReply* reply);

private:
QNetworkAccessManager* networkManager;
QString apiKey;
QString apiBaseUrl; // Store the API base URL
QString modelId; // Model ID used for API requests
};

0 comments on commit 9219d34

Please sign in to comment.