diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 9377b130ab..3d88e2e4df 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -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
diff --git a/src/QOwnNotes.pro b/src/QOwnNotes.pro
index 405427d9a3..05cdf5886a 100644
--- a/src/QOwnNotes.pro
+++ b/src/QOwnNotes.pro
@@ -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 \
@@ -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 \
diff --git a/src/dialogs/settingsdialog.h b/src/dialogs/settingsdialog.h
index 454115475d..f8f32ade7c 100644
--- a/src/dialogs/settingsdialog.h
+++ b/src/dialogs/settingsdialog.h
@@ -60,7 +60,8 @@ class SettingsDialog : public MasterDialog {
LayoutPage,
WebCompanionPage,
WebApplicationPage,
- ExperimentalPage
+ ExperimentalPage,
+ AiPage
};
explicit SettingsDialog(int page = 0, QWidget *parent = 0);
diff --git a/src/dialogs/settingsdialog.ui b/src/dialogs/settingsdialog.ui
index 43e19f97bf..7481cc53fd 100644
--- a/src/dialogs/settingsdialog.ui
+++ b/src/dialogs/settingsdialog.ui
@@ -295,6 +295,18 @@
:/icons/breeze-qownnotes/16x16/text-html.svg:/icons/breeze-qownnotes/16x16/text-html.svg
+ -
+
+ AI
+
+
+ 22
+
+
+
+ :/icons/breeze-dark-qownnotes/16x16/text-html.svg:/icons/breeze-dark-qownnotes/16x16/text-html.svg
+
+
-
General
@@ -421,7 +433,7 @@
-
- 7
+ 22
@@ -6843,6 +6855,78 @@ Just test yourself if you get sync conflicts and set a higher value if so.
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ API keys
+
+
+
+ 3
+
+
-
+
+
+ Create API Key
+
+
+ …
+
+
+
+ :/icons/breeze-qownnotes/16x16/text-html.svg:/icons/breeze-qownnotes/16x16/text-html.svg
+
+
+
+ -
+
+
+ Groq API key:
+
+
+ Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter
+
+
+
+ -
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index e790a37b32..7b586adb76 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -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"
@@ -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)
}
//
diff --git a/src/services/openaiservice.cpp b/src/services/openaiservice.cpp
new file mode 100644
index 0000000000..a42d99432b
--- /dev/null
+++ b/src/services/openaiservice.cpp
@@ -0,0 +1,125 @@
+/*
+* Copyright (c) 2014-2024 Patrizio Bekerle --
+*
+* 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
+#include
+#include
+#include
+#include
+
+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();
+}
diff --git a/src/services/openaiservice.h b/src/services/openaiservice.h
new file mode 100644
index 0000000000..a52605af1e
--- /dev/null
+++ b/src/services/openaiservice.h
@@ -0,0 +1,50 @@
+/*
+* Copyright (c) 2014-2024 Patrizio Bekerle --
+*
+* 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
+#include
+#include
+
+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
+};