From dbcefd8f4c093a458987a13adb2466946108d93c Mon Sep 17 00:00:00 2001 From: Martin Dvorak Date: Sat, 3 Feb 2024 22:08:56 +0100 Subject: [PATCH 1/3] WIP: macOS wingman @ QNetwork #1514 --- app/app.pro | 2 + app/src/qt/main_window_presenter.cpp | 2 +- lib/lib.pro | 13 +-- lib/src/gear/string_utils.h | 7 +- lib/src/mind/ai/llm/openai_wingman.cpp | 141 +++++++++++++++++++++---- lib/src/mind/ai/llm/openai_wingman.h | 2 +- 6 files changed, 139 insertions(+), 28 deletions(-) diff --git a/app/app.pro b/app/app.pro index 26d73a65..223cb609 100644 --- a/app/app.pro +++ b/app/app.pro @@ -22,6 +22,7 @@ message("= MindForger QMake configuration ==========================") message("Qt version: $$QT_VERSION") QT += widgets +QT += network mfdebug|mfunits { DEFINES += DO_MF_DEBUG @@ -88,6 +89,7 @@ win32 { else:CONFIG(debug, debug|release): LIBS += -L$$PWD/../lib/debug -lmindforger } else { # Linux and macOS + # TODO split macOS LIBS += -L$$OUT_PWD/../lib -lmindforger -lcurl } diff --git a/app/src/qt/main_window_presenter.cpp b/app/src/qt/main_window_presenter.cpp index 900c69fb..c1c178fd 100644 --- a/app/src/qt/main_window_presenter.cpp +++ b/app/src/qt/main_window_presenter.cpp @@ -2187,7 +2187,7 @@ void MainWindowPresenter::handleActionWingman(bool showDialog) void MainWindowPresenter::slotRunWingmanFromDialog(bool showDialog) { - bool runAsynchronously = true; + bool runAsynchronously = false; // pull prompt from the dialog & prepare prompt from the dialog string prompt = this->wingmanDialog->getPrompt(); diff --git a/lib/lib.pro b/lib/lib.pro index 1447ff13..dbaebfdf 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -18,13 +18,14 @@ TARGET = mindforger TEMPLATE = lib CONFIG += staticlib -CONFIG -= qt -# Qt Network as CURL replacement on Win - add Qt to libmindforger! -win32 { - CONFIG += qt - QT += network -} +#win32|macx { + # Qt Network as CURL replacement on Win - add Qt to libmindforger! + CONFIG += qt + QT += network +#} else { +# CONFIG -= qt +#} # Dependencies: # - INCLUDEPATH is used during compilation to find included header files. diff --git a/lib/src/gear/string_utils.h b/lib/src/gear/string_utils.h index dcc84b3a..6d01fc50 100644 --- a/lib/src/gear/string_utils.h +++ b/lib/src/gear/string_utils.h @@ -49,7 +49,7 @@ 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 stringSplit(const std::string s, const std::string regexDelimiter); -#if defined(__APPLE__) || defined(_WIN32) +#if defined(_WIN32) static inline std::string stringToUtf8(std::string& codepage_str) { int size = MultiByteToWideChar( @@ -89,6 +89,11 @@ static inline std::string stringToUtf8(std::string& codepage_str) return utf8_str; } +#elif defined(__APPLE__) +static inline std::string stringToUtf8(std::string& codepage_str) +{ + return codepage_str; +} #endif /** diff --git a/lib/src/mind/ai/llm/openai_wingman.cpp b/lib/src/mind/ai/llm/openai_wingman.cpp index 0ad4e19f..2353e854 100644 --- a/lib/src/mind/ai/llm/openai_wingman.cpp +++ b/lib/src/mind/ai/llm/openai_wingman.cpp @@ -102,12 +102,64 @@ void OpenAiWingman::curlGet(CommandWingmanChat& command) { << "<<<" << endl); -#ifdef WIN32 +#if defined(_WIN32) || defined(__APPLE__) /* Qt Networking examples: * + * - https://community.openai.com/t/qt-interface-w-chatgpt-api/354900 * - https://gist.github.com/FONQRI/d8fb13150c1e6760f1b1617730559418 */ + QNetworkAccessManager networkManager; + string prompt{"Write a simple 'Hello World' program in Python."}; + int maxTokens = 300; + + QString qApiKey = QString::fromStdString(apiKey); + + //QUrl apiEndpoint("https://api.openai.com/v1/chat/completions"); + QUrl apiEndpoint("https://api.openai.com/v1/engines/davinci/completions"); + QNetworkRequest request(apiEndpoint); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setRawHeader("Authorization", "Bearer " + qApiKey.toUtf8()); + + QJsonObject jsonPayload; + jsonPayload.insert("prompt", QJsonValue(QString::fromStdString(prompt))); + jsonPayload.insert("max_tokens", maxTokens); + + // Send API request + QNetworkReply* reply = networkManager.post(request, QJsonDocument(jsonPayload).toJson()); + /* + QObject::connect( + reply, &QNetworkReply::finished, + [reply]() + { + if (reply->error() != QNetworkReply::NoError) { + MF_DEBUG("Error: " << reply->errorString().toStdString() << endl); + } else { + QJsonObject jsonResponse = QJsonDocument::fromJson(reply->readAll()).object(); + QString code = jsonResponse.value("choices").toArray().first().toObject().value("text").toString().trimmed(); + MF_DEBUG("Received code:" << endl); + MF_DEBUG(code.toStdString()); + } + reply->deleteLater(); + }); + */ + for(int i=0; i<120; i++) { + MF_DEBUG("Step " << i << endl); + if(reply->isRunning()) { + MF_DEBUG(" IS RUNNING " << i << endl); + } else { + if(reply->error() == QNetworkReply::NoError) { + MF_DEBUG(" NO error! " << i << endl); + } else { + MF_DEBUG(" An ERROR! " << reply->error() << endl); + } + } + QThread::msleep(1000); + } + + + +#ifdef DISABLED_DEBUG_BLAH // request QNetworkRequest request{}; @@ -116,41 +168,69 @@ void OpenAiWingman::curlGet(CommandWingmanChat& command) { QUrl("https://api.openai.com/v1/chat/completions")); request.setHeader( QNetworkRequest::ContentTypeHeader, - QVariant("application/json")); - string apiKeyUtf8{stringToUtf8(apiKey)}; + "application/json"); + // TODO removed string apiKeyUtf8{stringToUtf8(apiKey)}; request.setRawHeader( "Authorization", - ("Bearer " + apiKeyUtf8).c_str()); + ("Bearer " + apiKey).c_str()); // request body - string requestJSonStrUtf8{stringToUtf8(requestJSonStr)}; - QByteArray requestBody( - requestJSonStrUtf8.c_str()); + MF_DEBUG("Building OpenAI request body..." << endl); + // TODO remove string requestJSonStrUtf8{stringToUtf8(requestJSonStr)}; + QByteArray requestBody(requestJSonStr.c_str()); + MF_DEBUG(" OpenAI request body bytearray DONE" << endl); // create a network access manager - QNetworkAccessManager manager; + QNetworkAccessManager manager{}; - // request: POST + MF_DEBUG("POSTing OpenAI request..." << endl); QNetworkReply* reply = manager.post(request, requestBody); + MF_DEBUG(" OpenAI reply handle: " << reply << endl); // connect to the finished signal to handle the response + /* QObject::connect( reply, &QNetworkReply::finished, - [&]() + [=]() { if (reply->error() == QNetworkReply::NoError) { - command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_OK; - command.httpResponse = QString(reply->readAll()).toStdString(); + auto commandhttpResponse = QString::fromUtf8(reply->readAll()).toStdString(); + MF_DEBUG("Request to OpenAI successful: '" << commandhttpResponse << endl); + //command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_OK; + //command.httpResponse = QString::fromUtf8(reply->readAll()).toStdString(); + MF_DEBUG(" Response from OpenAI: '" << commandhttpResponse << endl); } else { - command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR; - command.errorMessage = QString(reply->readAll()).toStdString(); + auto commanderrorMessage = reply->errorString().toStdString(); + MF_DEBUG("Request to OpenAI FAILED: '" << commanderrorMessage << endl); + //command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR; + //command.errorMessage = reply->errorString().toStdString(); + //command.httpResponse.clear(); + //MF_DEBUG(" ERROR response from OpenAI: '" << command.httpResponse << endl); } + reply->deleteLater(); }); + */ + + for(int i=0; i<30; i++) { + MF_DEBUG("Step " << i << endl); + if(reply->isRunning()) { + MF_DEBUG(" IS RUNNING " << i << endl); + } else { + if(reply->error() == QNetworkReply::NoError) { + MF_DEBUG(" NO error! " << i << endl); + } else { + MF_DEBUG(" An ERROR! " << reply->error() << endl); + } + } + QThread::msleep(1000); + } // delete the network reply when it's finished - QObject::connect( - reply, &QNetworkReply::finished, - reply, &QNetworkReply::deleteLater); + //QObject::connect( + // reply, &QNetworkReply::finished, + // reply, &QNetworkReply::deleteLater); +#endif // disabled blah + #else // set up cURL options command.httpResponse.clear(); @@ -189,7 +269,11 @@ void OpenAiWingman::curlGet(CommandWingmanChat& command) { // 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; + // if(true) { + std::cerr << + "Error: Wingman OpenAI cURL/QtNetwork request failed (error message/HTTP response):" << endl << + " '" << command.errorMessage << "'" << endl << + " '" << command.httpResponse << "'" << endl; command.httpResponse.clear(); command.answerHtml.clear(); @@ -226,7 +310,26 @@ void OpenAiWingman::curlGet(CommandWingmanChat& command) { "system_fingerprint": null } */ - auto httpResponseJSon = nlohmann::json::parse(command.httpResponse); + + // parse response string to JSon object + nlohmann::json httpResponseJSon; + try { + auto httpResponseJSon = nlohmann::json::parse(command.httpResponse); + } catch (...) { + // catch ALL exceptions + MF_DEBUG( + "Error: unable to parse OpenAI JSon response:" << endl << + "'" << command.httpResponse << "'" << endl + ); + + command.status = WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR; + command.errorMessage = "Error: unable to parse OpenAI JSon response: '" + command.httpResponse + "'"; + command.answerHtml.clear(); + command.answerTokens = 0; + command.answerLlmModel = llmModel; + + return; + } MF_DEBUG( "OpenAiWingman::curlGet() parsed response:" << endl diff --git a/lib/src/mind/ai/llm/openai_wingman.h b/lib/src/mind/ai/llm/openai_wingman.h index 64b6f913..64fd80c9 100644 --- a/lib/src/mind/ai/llm/openai_wingman.h +++ b/lib/src/mind/ai/llm/openai_wingman.h @@ -22,7 +22,7 @@ #include // HTTP client: CURL on Linux, Qt Network on macOS and Win -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) #include #else #include "curl/curl.h" From b1a4ea0a2852ee71dd74c41368edaac829ba83ec Mon Sep 17 00:00:00 2001 From: Martin Dvorak Date: Sun, 4 Feb 2024 00:10:58 +0100 Subject: [PATCH 2/3] Ugly code, but Wingman works @ QNetwork on Win for OpenAi #1514 --- app/app.pro | 1 + app/qnetwork-get-test.html | 23 ++++++ lib/lib.pro | 1 + lib/src/mind/ai/llm/openai_wingman.cpp | 101 +++++++++++++++++++++++-- 4 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 app/qnetwork-get-test.html diff --git a/app/app.pro b/app/app.pro index 223cb609..fdd64662 100644 --- a/app/app.pro +++ b/app/app.pro @@ -189,6 +189,7 @@ INCLUDEPATH += ./src/qt/spelling # win32{ QMAKE_CXXFLAGS += /MP + QMAKE_CXX = ccache $$QMAKE_CXX } else { # linux and macos mfnoccache { diff --git a/app/qnetwork-get-test.html b/app/qnetwork-get-test.html new file mode 100644 index 00000000..7b04333d --- /dev/null +++ b/app/qnetwork-get-test.html @@ -0,0 +1,23 @@ +{ + "id": "chatcmpl-8oIy1BN3YeHaGgc3AIaYcEsOWHXbG", + "object": "chat.completion", + "created": 1707000001, + "model": "gpt-3.5-turbo-0613", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Hello! How can I assist you today?" + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 18, + "completion_tokens": 9, + "total_tokens": 27 + }, + "system_fingerprint": null +} diff --git a/lib/lib.pro b/lib/lib.pro index dbaebfdf..fce0fed8 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -56,6 +56,7 @@ mfdebug|mfunits { # compiler options (qmake CONFIG+=mfnoccache ...) win32{ QMAKE_CXXFLAGS += /MP + QMAKE_CXX = ccache $$QMAKE_CXX } else { # linux and macos mfnoccache { diff --git a/lib/src/mind/ai/llm/openai_wingman.cpp b/lib/src/mind/ai/llm/openai_wingman.cpp index 2353e854..c913e27c 100644 --- a/lib/src/mind/ai/llm/openai_wingman.cpp +++ b/lib/src/mind/ai/llm/openai_wingman.cpp @@ -74,7 +74,19 @@ void OpenAiWingman::curlGet(CommandWingmanChat& command) { /* OpenAI API JSon request example (see unit test): - ... + { + "messages": [ + { + "content": "You are a helpful assistant.", + "role": "system" + }, + { + "content": "Hey hello! I'm MindForger user - how can you help me?", + "role": "user" + } + ], + "model": "gpt-3.5-turbo" + } */ nlohmann::json messageSystemJSon{}; @@ -106,8 +118,43 @@ void OpenAiWingman::curlGet(CommandWingmanChat& command) { /* Qt Networking examples: * * - https://community.openai.com/t/qt-interface-w-chatgpt-api/354900 + * - https://forum.qt.io/topic/116601/qnetworkaccessmanager-reply-is-always-empty/7 * - https://gist.github.com/FONQRI/d8fb13150c1e6760f1b1617730559418 */ +#ifdef THIS_WORKS_GET + QNetworkAccessManager nam; + + QNetworkRequest request(QUrl("https://www.walkman-pictures.com/")); + auto reply = nam.get(request); + + QEventLoop loop; + QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + loop.exec(); + + reply->deleteLater(); + auto error = reply->error(); + if(error != QNetworkReply::NoError) { + qDebug() << "network error:" << error << reply->errorString(); + return; + } + QByteArray read = reply->readAll(); + if(read.isEmpty()) { + qDebug() << "response is empty"; + return; + } + + QString fileName(QDir::currentPath() + "/" + "qnetwork-get-test.html"); + QFile file(fileName); + //[CHANGED]Remove QIODevice::Text to prevent line terminator mis-translating + if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + qDebug() << "file open error:" << file.error() << file.errorString(); + return; + } + //NOTE: Don't use QTextStream to write QByteArray to file, that's not the right way even if the result is correct!!! + file.write(read); + file.close(); +#endif + QNetworkAccessManager networkManager; string prompt{"Write a simple 'Hello World' program in Python."}; @@ -115,18 +162,58 @@ void OpenAiWingman::curlGet(CommandWingmanChat& command) { QString qApiKey = QString::fromStdString(apiKey); - //QUrl apiEndpoint("https://api.openai.com/v1/chat/completions"); - QUrl apiEndpoint("https://api.openai.com/v1/engines/davinci/completions"); + QUrl apiEndpoint("https://api.openai.com/v1/chat/completions"); + //QUrl apiEndpoint("https://api.openai.com/v1/engines/davinci/completions"); QNetworkRequest request(apiEndpoint); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + qApiKey.toUtf8()); + /* QJsonObject jsonPayload; jsonPayload.insert("prompt", QJsonValue(QString::fromStdString(prompt))); jsonPayload.insert("max_tokens", maxTokens); + */ // Send API request - QNetworkReply* reply = networkManager.post(request, QJsonDocument(jsonPayload).toJson()); + QNetworkReply* reply = networkManager.post( + request, + requestJSonStr.c_str() + //QJsonDocument(jsonPayload).toJson() + ); + + QEventLoop loop; + QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + loop.exec(); + + reply->deleteLater(); + auto error = reply->error(); + if(error != QNetworkReply::NoError) { + qDebug() << "network error:" << error << reply->errorString(); + return; + } + QByteArray read = reply->readAll(); + if(read.isEmpty()) { + qDebug() << "response is empty"; + return; + } + + QString qCommandResponse = QString{read}; + qDebug() << "Response is: '" << qCommandResponse << "'"; + command.httpResponse = qCommandResponse.toStdString(); + + /* + QString fileName(QDir::currentPath() + "/" + "qnetwork-get-test.html"); + QFile file(fileName); + //[CHANGED]Remove QIODevice::Text to prevent line terminator mis-translating + if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + qDebug() << "file open error:" << file.error() << file.errorString(); + return; + } + //NOTE: Don't use QTextStream to write QByteArray to file, that's not the right way even if the result is correct!!! + file.write(read); + file.close(); + */ + /* QObject::connect( reply, &QNetworkReply::finished, @@ -142,7 +229,7 @@ void OpenAiWingman::curlGet(CommandWingmanChat& command) { } reply->deleteLater(); }); - */ + for(int i=0; i<120; i++) { MF_DEBUG("Step " << i << endl); if(reply->isRunning()) { @@ -156,7 +243,7 @@ void OpenAiWingman::curlGet(CommandWingmanChat& command) { } QThread::msleep(1000); } - + */ #ifdef DISABLED_DEBUG_BLAH @@ -314,7 +401,7 @@ void OpenAiWingman::curlGet(CommandWingmanChat& command) { // parse response string to JSon object nlohmann::json httpResponseJSon; try { - auto httpResponseJSon = nlohmann::json::parse(command.httpResponse); + httpResponseJSon = nlohmann::json::parse(command.httpResponse); } catch (...) { // catch ALL exceptions MF_DEBUG( From 3409c1c6e98aa97ba2ed32b28e79bcbbcd4b052f Mon Sep 17 00:00:00 2001 From: Martin Dvorak Date: Sun, 4 Feb 2024 00:52:36 +0100 Subject: [PATCH 3/3] Wingman: working version polish + async on #1514; ccache @ win --- app/app.pro | 5 +- app/src/qt/main_window_presenter.cpp | 2 +- lib/lib.pro | 4 +- lib/src/mind/ai/llm/openai_wingman.cpp | 223 +++++-------------------- 4 files changed, 50 insertions(+), 184 deletions(-) diff --git a/app/app.pro b/app/app.pro index fdd64662..8fe505c2 100644 --- a/app/app.pro +++ b/app/app.pro @@ -189,7 +189,9 @@ INCLUDEPATH += ./src/qt/spelling # win32{ QMAKE_CXXFLAGS += /MP - QMAKE_CXX = ccache $$QMAKE_CXX + !mfnoccache { + QMAKE_CXX = ccache $$QMAKE_CXX + } } else { # linux and macos mfnoccache { @@ -504,5 +506,6 @@ win32 { message(DEFINES of app.pro build: $$DEFINES) message(QMAKE_EXTRA_TARGETS of app.pro build: $$QMAKE_EXTRA_TARGETS) message(QT of app.pro build: $$QT) +message(PATH is: $$(PATH)) # eof diff --git a/app/src/qt/main_window_presenter.cpp b/app/src/qt/main_window_presenter.cpp index c1c178fd..900c69fb 100644 --- a/app/src/qt/main_window_presenter.cpp +++ b/app/src/qt/main_window_presenter.cpp @@ -2187,7 +2187,7 @@ void MainWindowPresenter::handleActionWingman(bool showDialog) void MainWindowPresenter::slotRunWingmanFromDialog(bool showDialog) { - bool runAsynchronously = false; + bool runAsynchronously = true; // pull prompt from the dialog & prepare prompt from the dialog string prompt = this->wingmanDialog->getPrompt(); diff --git a/lib/lib.pro b/lib/lib.pro index fce0fed8..035291d0 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -56,7 +56,9 @@ mfdebug|mfunits { # compiler options (qmake CONFIG+=mfnoccache ...) win32{ QMAKE_CXXFLAGS += /MP - QMAKE_CXX = ccache $$QMAKE_CXX + !mfnoccache { + QMAKE_CXX = ccache $$QMAKE_CXX + } } else { # linux and macos mfnoccache { diff --git a/lib/src/mind/ai/llm/openai_wingman.cpp b/lib/src/mind/ai/llm/openai_wingman.cpp index c913e27c..13442d11 100644 --- a/lib/src/mind/ai/llm/openai_wingman.cpp +++ b/lib/src/mind/ai/llm/openai_wingman.cpp @@ -117,208 +117,64 @@ void OpenAiWingman::curlGet(CommandWingmanChat& command) { #if defined(_WIN32) || defined(__APPLE__) /* Qt Networking examples: * - * - https://community.openai.com/t/qt-interface-w-chatgpt-api/354900 * - https://forum.qt.io/topic/116601/qnetworkaccessmanager-reply-is-always-empty/7 + * - https://community.openai.com/t/qt-interface-w-chatgpt-api/354900 * - https://gist.github.com/FONQRI/d8fb13150c1e6760f1b1617730559418 */ -#ifdef THIS_WORKS_GET - QNetworkAccessManager nam; - - QNetworkRequest request(QUrl("https://www.walkman-pictures.com/")); - auto reply = nam.get(request); - - QEventLoop loop; - QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - loop.exec(); - - reply->deleteLater(); - auto error = reply->error(); - if(error != QNetworkReply::NoError) { - qDebug() << "network error:" << error << reply->errorString(); - return; - } - QByteArray read = reply->readAll(); - if(read.isEmpty()) { - qDebug() << "response is empty"; - return; - } - - QString fileName(QDir::currentPath() + "/" + "qnetwork-get-test.html"); - QFile file(fileName); - //[CHANGED]Remove QIODevice::Text to prevent line terminator mis-translating - if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - qDebug() << "file open error:" << file.error() << file.errorString(); - return; - } - //NOTE: Don't use QTextStream to write QByteArray to file, that's not the right way even if the result is correct!!! - file.write(read); - file.close(); -#endif - QNetworkAccessManager networkManager; - string prompt{"Write a simple 'Hello World' program in Python."}; - int maxTokens = 300; - - QString qApiKey = QString::fromStdString(apiKey); - - QUrl apiEndpoint("https://api.openai.com/v1/chat/completions"); - //QUrl apiEndpoint("https://api.openai.com/v1/engines/davinci/completions"); - QNetworkRequest request(apiEndpoint); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Authorization", "Bearer " + qApiKey.toUtf8()); - /* - QJsonObject jsonPayload; - jsonPayload.insert("prompt", QJsonValue(QString::fromStdString(prompt))); - jsonPayload.insert("max_tokens", maxTokens); - */ + QNetworkRequest request(QUrl("https://api.openai.com/v1/chat/completions")); + request.setHeader( + QNetworkRequest::ContentTypeHeader, + "application/json"); + request.setRawHeader( + "Authorization", + "Bearer " + QString::fromStdString(apiKey).toUtf8()); - // Send API request QNetworkReply* reply = networkManager.post( request, requestJSonStr.c_str() - //QJsonDocument(jsonPayload).toJson() ); - QEventLoop loop; QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); loop.exec(); - reply->deleteLater(); - auto error = reply->error(); - if(error != QNetworkReply::NoError) { - qDebug() << "network error:" << error << reply->errorString(); - return; - } - QByteArray read = reply->readAll(); - if(read.isEmpty()) { - qDebug() << "response is empty"; - return; - } - QString qCommandResponse = QString{read}; - qDebug() << "Response is: '" << qCommandResponse << "'"; - command.httpResponse = qCommandResponse.toStdString(); + command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_OK; - /* - QString fileName(QDir::currentPath() + "/" + "qnetwork-get-test.html"); - QFile file(fileName); - //[CHANGED]Remove QIODevice::Text to prevent line terminator mis-translating - if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - qDebug() << "file open error:" << file.error() << file.errorString(); - return; + // response: error handling + auto error = reply->error(); + if(error != QNetworkReply::NoError) { + command.errorMessage = + "Error: request to OpenAI Wingman provider failed due a network error - " + + reply->errorString().toStdString(); + MF_DEBUG(command.errorMessage << endl); + command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR; } - //NOTE: Don't use QTextStream to write QByteArray to file, that's not the right way even if the result is correct!!! - file.write(read); - file.close(); - */ - - /* - QObject::connect( - reply, &QNetworkReply::finished, - [reply]() - { - if (reply->error() != QNetworkReply::NoError) { - MF_DEBUG("Error: " << reply->errorString().toStdString() << endl); - } else { - QJsonObject jsonResponse = QJsonDocument::fromJson(reply->readAll()).object(); - QString code = jsonResponse.value("choices").toArray().first().toObject().value("text").toString().trimmed(); - MF_DEBUG("Received code:" << endl); - MF_DEBUG(code.toStdString()); + QByteArray read; + if(command.status == m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_OK) { + read = reply->readAll(); + + if(read.isEmpty()) { + command.errorMessage = + "Error: Request to OpenAI Wingman provider failed - response is empty'"; + MF_DEBUG(command.errorMessage << endl); + command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR; } - reply->deleteLater(); - }); - - for(int i=0; i<120; i++) { - MF_DEBUG("Step " << i << endl); - if(reply->isRunning()) { - MF_DEBUG(" IS RUNNING " << i << endl); - } else { - if(reply->error() == QNetworkReply::NoError) { - MF_DEBUG(" NO error! " << i << endl); - } else { - MF_DEBUG(" An ERROR! " << reply->error() << endl); - } - } - QThread::msleep(1000); } - */ - - -#ifdef DISABLED_DEBUG_BLAH - // request - QNetworkRequest request{}; - - // request: headers - request.setUrl( - QUrl("https://api.openai.com/v1/chat/completions")); - request.setHeader( - QNetworkRequest::ContentTypeHeader, - "application/json"); - // TODO removed string apiKeyUtf8{stringToUtf8(apiKey)}; - request.setRawHeader( - "Authorization", - ("Bearer " + apiKey).c_str()); - - // request body - MF_DEBUG("Building OpenAI request body..." << endl); - // TODO remove string requestJSonStrUtf8{stringToUtf8(requestJSonStr)}; - QByteArray requestBody(requestJSonStr.c_str()); - MF_DEBUG(" OpenAI request body bytearray DONE" << endl); - - // create a network access manager - QNetworkAccessManager manager{}; - - MF_DEBUG("POSTing OpenAI request..." << endl); - QNetworkReply* reply = manager.post(request, requestBody); - MF_DEBUG(" OpenAI reply handle: " << reply << endl); - // connect to the finished signal to handle the response - /* - QObject::connect( - reply, &QNetworkReply::finished, - [=]() - { - if (reply->error() == QNetworkReply::NoError) { - auto commandhttpResponse = QString::fromUtf8(reply->readAll()).toStdString(); - MF_DEBUG("Request to OpenAI successful: '" << commandhttpResponse << endl); - //command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_OK; - //command.httpResponse = QString::fromUtf8(reply->readAll()).toStdString(); - MF_DEBUG(" Response from OpenAI: '" << commandhttpResponse << endl); - } else { - auto commanderrorMessage = reply->errorString().toStdString(); - MF_DEBUG("Request to OpenAI FAILED: '" << commanderrorMessage << endl); - //command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR; - //command.errorMessage = reply->errorString().toStdString(); - //command.httpResponse.clear(); - //MF_DEBUG(" ERROR response from OpenAI: '" << command.httpResponse << endl); - } - reply->deleteLater(); - }); - */ - - for(int i=0; i<30; i++) { - MF_DEBUG("Step " << i << endl); - if(reply->isRunning()) { - MF_DEBUG(" IS RUNNING " << i << endl); - } else { - if(reply->error() == QNetworkReply::NoError) { - MF_DEBUG(" NO error! " << i << endl); - } else { - MF_DEBUG(" An ERROR! " << reply->error() << endl); - } - } - QThread::msleep(1000); + // response: successful response processing + if(command.status == m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_OK) { + QString qCommandResponse = QString{read}; + command.httpResponse = qCommandResponse.toStdString(); + command.errorMessage.clear(); + command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_OK; + MF_DEBUG( + "Successful OpenAI Wingman provider response:" << endl << + " '" << command.httpResponse << "'" << endl); } - - // delete the network reply when it's finished - //QObject::connect( - // reply, &QNetworkReply::finished, - // reply, &QNetworkReply::deleteLater); -#endif // disabled blah - -#else +#else // set up cURL options command.httpResponse.clear(); curl_easy_setopt( @@ -356,7 +212,6 @@ void OpenAiWingman::curlGet(CommandWingmanChat& command) { // finish error handling (shared by QNetwork/CURL) if(command.status == WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR) { - // if(true) { std::cerr << "Error: Wingman OpenAI cURL/QtNetwork request failed (error message/HTTP response):" << endl << " '" << command.errorMessage << "'" << endl << @@ -466,11 +321,17 @@ void OpenAiWingman::curlGet(CommandWingmanChat& command) { command.errorMessage.assign( "OpenAI API HTTP required failed with finish_reason: " + statusStr); + command.answerHtml.clear(); + command.answerTokens = 0; + command.answerLlmModel = llmModel; } MF_DEBUG(" status: " << command.status << endl); } } else { command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR; + command.answerHtml.clear(); + command.answerTokens = 0; + command.answerLlmModel = llmModel; if( httpResponseJSon.contains("error") && httpResponseJSon["error"].contains("message")