Skip to content

Commit

Permalink
Fixing Wingman to work using QNetwork on Win (which fixes Win build) #…
Browse files Browse the repository at this point in the history
  • Loading branch information
dvorka committed Feb 2, 2024
1 parent 4cf7cbb commit cff521b
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 46 deletions.
2 changes: 1 addition & 1 deletion lib/lib.pro
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ TEMPLATE = lib
CONFIG += staticlib
CONFIG -= qt

# Qt Network as CURL replacement on Windows
# Qt Network as CURL replacement on Win - add Qt to libmindforger!
win32 {
CONFIG += qt
QT += network
Expand Down
3 changes: 0 additions & 3 deletions lib/src/debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@
// - configuration: DEFINES = DO_MF_DEBUG
// - command line: CONFIG+=mfunits

// use Qt Network instead of CURL for the communication with OpenAI
// #define MF_OPENAI_QT_NETWORK

#ifdef DO_MF_DEBUG
#include <chrono>
#include <iostream>
Expand Down
42 changes: 42 additions & 0 deletions lib/src/gear/string_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,48 @@ char** stringSplit(const char* s, const char delimiter);
char** stringSplit(const char* s, const char delimiter, u_int16_t resultBaseSize, u_int16_t resultIncSize);
std::vector<std::string> stringSplit(const std::string s, const std::string regexDelimiter);

#if defined(__APPLE__) || defined(_WIN32)
static inline std::string stringToUtf8(std::string& codepage_str)
{
int size = MultiByteToWideChar(
CP_ACP, MB_COMPOSITE,
codepage_str.c_str(),
codepage_str.length(),
nullptr,
0);

std::wstring utf16_str(size, '\0');
MultiByteToWideChar(
CP_ACP,
MB_COMPOSITE,
codepage_str.c_str(),
codepage_str.length(),
&utf16_str[0],
size);

int utf8_size = WideCharToMultiByte(
CP_UTF8,
0,
utf16_str.c_str(),
utf16_str.length(),
nullptr,
0,
nullptr,
nullptr);
std::string utf8_str(utf8_size, '\0');
WideCharToMultiByte(
CP_UTF8,
0, utf16_str.c_str(),
utf16_str.length(),
&utf8_str[0],
utf8_size,
nullptr,
nullptr);

return utf8_str;
}
#endif

/**
* @brief Normalizes a string to NCName.
*
Expand Down
2 changes: 1 addition & 1 deletion lib/src/mind/ai/llm/mock_wingman.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class MockWingman: Wingman
MockWingman(const MockWingman&&) = delete;
MockWingman& operator =(const MockWingman&) = delete;
MockWingman& operator =(const MockWingman&&) = delete;
~MockWingman();
~MockWingman() override;

std::string getWingmanLlmModel() const { return llmModel; }

Expand Down
90 changes: 56 additions & 34 deletions lib/src/mind/ai/llm/openai_wingman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,10 @@ OpenAiWingman::~OpenAiWingman()
* @see https://json.nlohmann.me/
*/
void OpenAiWingman::curlGet(CommandWingmanChat& command) {
#if !defined(__APPLE__) && !defined(_WIN32)
CURL* curl = curl_easy_init();
if (curl) {
#endif
string escapedPrompt{command.prompt};
replaceAll("\n", " ", escapedPrompt);
replaceAll("\"", "\\\"", escapedPrompt);
Expand Down Expand Up @@ -100,45 +102,56 @@ void OpenAiWingman::curlGet(CommandWingmanChat& command) {
<< "<<<"
<< endl);

#ifdef MF_OPENAI_QT_NETWORK
// Set up Qt networking options
QNetworkRequest request;
request.setUrl(QUrl("https://api.openai.com/v1/chat/completions"));

QByteArray requestBody(requestJSonStr.toUtf8());
request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
request.setRawHeader("Authorization", "Bearer " + apiKey.toUtf8());

// Create a network access manager
#ifdef WIN32
/* Qt Networking examples:
*
* - https://gist.github.com/FONQRI/d8fb13150c1e6760f1b1617730559418
*/

// request
QNetworkRequest request{};

// request: headers
request.setUrl(
QUrl("https://api.openai.com/v1/chat/completions"));
request.setHeader(
QNetworkRequest::ContentTypeHeader,
QVariant("application/json"));
string apiKeyUtf8{stringToUtf8(apiKey)};
request.setRawHeader(
"Authorization",
("Bearer " + apiKeyUtf8).c_str());

// request body
string requestJSonStrUtf8{stringToUtf8(requestJSonStr)};
QByteArray requestBody(
requestJSonStrUtf8.c_str());

// create a network access manager
QNetworkAccessManager manager;

// Send the request
QNetworkReply *reply = manager.post(request, requestBody);
// request: POST
QNetworkReply* reply = manager.post(request, requestBody);

// Connect to the finished signal to handle the response
connect(reply, &QNetworkReply::finished, [&]() {
// connect to the finished signal to handle the response
QObject::connect(
reply, &QNetworkReply::finished,
[&]()
{
if (reply->error() == QNetworkReply::NoError) {
command.httpResponse = reply->readAll().toUtf8();
command.answerHtml = parseHtmlFromJson(command.httpResponse);
command.answerTokens = countTokens(command.answerHtml);
command.answerLlmModel = llmModel;
command.status = WingmanStatusCode::WINGMAN_STATUS_CODE_SUCCESS;
command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_OK;
command.httpResponse = QString(reply->readAll()).toStdString();
} else {
command.status = WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR;
command.errorMessage = reply->errorString();
std::cerr << "Error: Wingman OpenAI Qt networking request failed: " << command.errorMessage << std::endl;

command.httpResponse.clear();
command.answerHtml.clear();
command.answerTokens = 0;
command.answerLlmModel = llmModel;
return;
command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR;
command.errorMessage = QString(reply->readAll()).toStdString();
}
});

// Delete the network reply when it's finished
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
#else
// delete the network reply when it's finished
QObject::connect(
reply, &QNetworkReply::finished,
reply, &QNetworkReply::deleteLater);
#else
// set up cURL options
command.httpResponse.clear();
curl_easy_setopt(
Expand Down Expand Up @@ -169,7 +182,14 @@ void OpenAiWingman::curlGet(CommandWingmanChat& command) {
if (res != CURLE_OK) {
command.status = WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR;
command.errorMessage = curl_easy_strerror(res);
std::cerr << "Error: Wingman OpenAI cURL request failed: " << command.errorMessage << std::endl;
} else {
command.status = WingmanStatusCode::WINGMAN_STATUS_CODE_OK;
}
#endif

// finish error handling (shared by QNetwork/CURL)
if(command.status == WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR) {
std::cerr << "Error: Wingman OpenAI cURL request failed: " << command.errorMessage << endl;

command.httpResponse.clear();
command.answerHtml.clear();
Expand All @@ -178,7 +198,6 @@ void OpenAiWingman::curlGet(CommandWingmanChat& command) {

return;
}
#endif

// parse JSon
/*
Expand Down Expand Up @@ -272,11 +291,14 @@ void OpenAiWingman::curlGet(CommandWingmanChat& command) {
"No choices in the OpenAI API HTTP response");
}
}
} else {
#if !defined(__APPLE__) && !defined(_WIN32)
}
else {
command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR;
command.errorMessage.assign(
"OpenAI API HTTP request failed: unable to initialize cURL");
}
#endif
}

void OpenAiWingman::chat(CommandWingmanChat& command) {
Expand Down
13 changes: 7 additions & 6 deletions lib/src/mind/ai/llm/openai_wingman.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@

#include <string>

#include "curl/curl.h"
// HTTP client: CURL on Linux, Qt Network on macOS and Win
#ifdef _WIN32
#include <QtNetwork>
#else
#include "curl/curl.h"
#endif

#include "wingman.h"

#ifdef MF_OPENAI_QT_NETWORK
#include <QtNetwork>
#endif

namespace m8r {

/**
Expand All @@ -48,7 +49,7 @@ class OpenAiWingman: Wingman
OpenAiWingman(const OpenAiWingman&&) = delete;
OpenAiWingman& operator =(const OpenAiWingman&) = delete;
OpenAiWingman& operator =(const OpenAiWingman&&) = delete;
~OpenAiWingman();
~OpenAiWingman() override;

virtual void chat(CommandWingmanChat& command) override;
};
Expand Down
2 changes: 1 addition & 1 deletion lib/src/model/outline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ void Outline::addNote(Note* note, int offset)
void Outline::addNotes(std::vector<Note*>& notesToAdd, int offset)
{
if(notesToAdd.size()) {
for(int i=notesToAdd.size()-1; i>=0; i--) {
for(size_t i=notesToAdd.size()-1; i>=0; i--) {
notesToAdd[i]->makeModified();
addNote(notesToAdd[i], offset);
}
Expand Down

0 comments on commit cff521b

Please sign in to comment.