diff --git a/App.cpp b/App.cpp index 134d6ad..d033be2 100644 --- a/App.cpp +++ b/App.cpp @@ -15,10 +15,16 @@ const char *kApplicationSignature = "application/x-vnd.SLema-DumBer"; App::App() : BApplication(kApplicationSignature) { - MainWindow *mainWindow = new MainWindow(); + MainWindow *m = new MainWindow(); + mainWindow = m; mainWindow->Show(); } + + + + + App::~App() {} void App::AboutRequested() { diff --git a/App.h b/App.h index ef868fa..7147f2a 100644 --- a/App.h +++ b/App.h @@ -5,6 +5,7 @@ #ifndef APP_H #define APP_H +#include "MainWindow.h" #include @@ -17,8 +18,13 @@ public: virtual void AboutRequested(); + MainWindow* mainWindow = nullptr; + + private: }; + + #endif // APP_H diff --git a/Conversation.cpp b/Conversation.cpp new file mode 100644 index 0000000..f15d958 --- /dev/null +++ b/Conversation.cpp @@ -0,0 +1,228 @@ +// Conversation.cpp +#include "Conversation.h" + + +#include +#include +#include +#include + +#include +#include + +#include + +Conversation::Conversation(BHandler* replyTo) { + + replyTarget = replyTo; + _apiKey = ReadOpenAIKey(); + printf("key is: %s", _apiKey.String()); + +} +Conversation::~Conversation() { + } + + +void Conversation::MessageReceived(BMessage* message) { + switch (message->what) { +//.. case B_HTTP_DATA_RECEIVED: { + // break; + // } + + case UrlEvent::HostNameResolved: { + printf("Host name resolved\n"); + auto name = message->GetString(UrlEventData::HostName); + message->PrintToStream(); + + //_infoView->SetText("Hostname resolve..."); + //_infoView->SetText(name); + //_progress->SetTo(5); + + } break; + + case UrlEvent::ConnectionOpened: { + printf("ConnectionOpened\n"); + //_progress->SetTo(10); + //_infoView->SetText("connection opened..."); + } break; + + case UrlEvent::ResponseStarted: { + printf("ResponseStarted\n"); + //_progress->SetTo(14); + //_infoView->SetText("ResponseStarted..."); + } break; + + case UrlEvent::HttpRedirect: { + printf("HttpRedirect\n"); + //_progress->SetTo(16); + //_infoView->SetText("HttpRedirect..."); + } break; + + case UrlEvent::RequestCompleted: { + printf("RequestCompleted\n"); + auto identifier = message->GetInt32(UrlEventData::Id, -1); + if (_lastResult->Identity() == identifier) { + // The following call will not block, because we have been notified + // that the request is done. + BHttpBody body = _lastResult->Body(); + if (body.text.has_value()) + { + + json parsed = json::parse(body.text.value().String()); + + + + std::string content = parsed["choices"][0]["message"]["content"]; + +// printf("we got content:%s",content.c_str()); + + + BMessage message(kSendReply); + message.AddString("text", BString(content.c_str())); + + + BLooper* looper = replyTarget->Looper(); // get the looper it's attached to + + if (looper != nullptr) { + BMessenger messenger(replyTarget, looper); + messenger.SendMessage(&message); + } else { + printf("Handler not attached to a looper.\n"); + } + + + // status_t err = appMessenger.SendMessage(&message); + //if (err != B_OK) + // printf("SendMessage failed: %s\n", strerror(err)); + //else + //printf("SendMessage OK"); + + // _answerView->SetText(content.c_str()); + + // _answerView->SetText(body.text.value()); + + } + // else + //_answerView->SetText("nuthin'"); + } + + //_infoView->SetText("Completed"); + //_progress->SetMaxValue(100); + //_progress->SetTo(100); + + } break; + + case UrlEvent::HttpStatus: { + + printf("HttpStatus\n"); + //_infoView->SetText("HttpStatus received"); + //_progress->SetTo(20); + + } break; + + case UrlEvent::BytesWritten: { + // _infoView->SetText("Some bytes written.."); + auto identifier = message->GetInt32(UrlEventData::Id, -1); + if (_lastResult->Identity() == identifier) { + off_t numBytes = message->GetInt64(UrlEventData::NumBytes, 0); + off_t totalBytes = message->GetInt64(UrlEventData::TotalBytes, 0); + // _progress->SetTo(numBytes); + //_progress->SetMaxValue(totalBytes); + } + } break; + + case UrlEvent::DownloadProgress: { + auto identifier = message->GetInt32(UrlEventData::Id, -1); + if (_lastResult->Identity() == identifier) { + off_t nn = message->GetInt64(UrlEventData::NumBytes, 0); + off_t totalBytes = message->GetInt64(UrlEventData::TotalBytes, 0); + //_progress->SetTo(nn); + //_progress->SetMaxValue(totalBytes); + //_infoView->SetText("Download Progress.."); + } + } break; + + default: + BHandler::MessageReceived(message); + break; + } +} + +void Conversation::ask(const std::string& prompt) { + + printf("Asking prompt: %s",prompt.c_str()); + + if (_lastResult) + + _sharedSession.Cancel(_lastResult->Identity()); + + + auto url = BUrl("https://api.openai.com/v1/chat/completions"); + BHttpRequest request = BHttpRequest(url); + request.SetMethod(BHttpMethod::Post); + + // if the API key file contains a new line bhttpfields will crash with invalid + // content .end() requires include algorithm + std::string key = _apiKey.String(); + key.erase(std::remove(key.begin(), key.end(), '\n'), key.end()); + key.erase(std::remove(key.begin(), key.end(), '\r'), key.end()); + + std::string bearer = std::string("Bearer ") + std::string(key); + + BHttpFields fields = BHttpFields(); + fields.AddField("Authorization", bearer); + // fields.AddField("Content-Type", "application/json"); //NO, this will + // crash, we set it in request + request.SetFields(fields); + + json bodyJson = { + {"model", "gpt-4o"}, + {"messages", {{{"role", "user"}, {"content", prompt}}}}}; + + std::string body = bodyJson.dump(); + + BString mime = BString("application/json"); + + auto memoryIO = std::make_unique(body.c_str(), body.size()); + request.SetRequestBody(std::move(memoryIO), "application/json", body.size()); + + printf("Sending Prompt to server: %s\n", url.UrlString().String()); + _lastResult = _sharedSession.Execute(std::move(request), nullptr, this); + + if (_lastResult) { + printf("Result has identity: %d\n", _lastResult->Identity()); + } +} + + +BString Conversation::ReadOpenAIKey() { + + // /boot/home/config/openai_key + // or ~/.config/openai_key on linux + + BPath configPath; + if (find_directory(B_USER_SETTINGS_DIRECTORY, &configPath) != B_OK) + return "error: couldn't find config directory"; + + configPath.Append("openai_key"); + + BFile file(configPath.Path(), B_READ_ONLY); + + printf("full path:%s\n", configPath.Path()); + if (file.InitCheck() != B_OK) + return "error: couldn't open key file "; + + off_t size; + file.GetSize(&size); + + char *buffer = new char[size + 1]; + file.Read(buffer, size); + buffer[size] = '\0'; // null-terminate + + BString result(buffer); + delete[] buffer; + + return result; +} + + diff --git a/Conversation.h b/Conversation.h new file mode 100644 index 0000000..89b09ba --- /dev/null +++ b/Conversation.h @@ -0,0 +1,61 @@ +// Conversation.h +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + + + + +//NEED THIS IN MAKE FILE TO USE PRIVATE HEADERS +//CXXFLAGS = -std=c++17 -Wall -I/boot/system/develop/headers/private + + +//Build works but Genio doesn't see class definitions unless I use full path ? +#include "/boot/system/develop/headers/private/netservices2/NetServicesDefs.h" +#include "/boot/system/develop/headers/private/netservices2/HttpSession.h" +#include "/boot/system/develop/headers/private/netservices2/HttpRequest.h" +#include "/boot/system/develop/headers/private/netservices2/HttpResult.h" +#include "/boot/system/develop/headers/private/netservices2/HttpFields.h" +#include "/boot/system/develop/headers/private/netservices2/ErrorsExt.h" + +//From private headers ! +//#include +//#include +//#include +//#include +//#include +//#include + + +static const uint32 kSendReply = 'krpl'; + +#include "include/json.hpp" + +using json = nlohmann::json; +using namespace BPrivate::Network; + + +using namespace BPrivate::Network; + +class Conversation : public BHandler { +public: + + Conversation(BHandler* replyTo); + virtual ~Conversation() ; + virtual void ask(const std::string& prompt); + virtual void MessageReceived(BMessage *msg); + +private: + BHandler* replyTarget; + BString ReadOpenAIKey(); + BString _apiKey; + BHttpSession _sharedSession = BHttpSession (); + std::optional _lastResult; +}; \ No newline at end of file diff --git a/DumBer b/DumBer index 8caff78..0999288 100755 Binary files a/DumBer and b/DumBer differ diff --git a/MainWindow.cpp b/MainWindow.cpp index 4ef4615..7e0c9dc 100644 --- a/MainWindow.cpp +++ b/MainWindow.cpp @@ -5,9 +5,7 @@ #include "MainWindow.h" -#include -#include #include #include @@ -18,23 +16,10 @@ #include #include #include -#include - + #include -#include -#include -#include -#include - -#include -#include -#include - -#include "include/json.hpp" - -using json = nlohmann::json; -using namespace BPrivate::Network; +#include "Conversation.h" #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "Window" @@ -50,6 +35,13 @@ MainWindow::MainWindow() : BWindow(BRect(100, 100, 600, 400), B_TRANSLATE("BeDumb"), B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE) { + + //Without this conversation would never get bmessages from HttpRequest + LockLooper(); + AddHandler(_conversation); + UnlockLooper(); + + BMenuBar *menuBar = _BuildMenu(); BLayoutBuilder::Group<>(this, B_VERTICAL, 0).Add(menuBar).AddGlue().End(); @@ -87,8 +79,6 @@ MainWindow::MainWindow() float askH = lineHeight * 5; _inputField->SetExplicitMinSize(BSize(B_SIZE_UNSET, askH)); - _apiKey = ReadOpenAIKey(); - printf("key is: %s", _apiKey.String()); BButton *sendButton = new BButton("send", B_TRANSLATE("Send"), new BMessage(kSendPrompt), @@ -130,7 +120,7 @@ MainWindow::~MainWindow() {} void MainWindow::MessageReceived(BMessage *message) { - switch (message->what) { + switch (message->what) { // case kMsgNewFile: { // fSaveMenuItem->SetEnabled(false); @@ -148,101 +138,38 @@ void MainWindow::MessageReceived(BMessage *message) { case kSendPrompt: { - printf("Button Pressed\n"); - sendQuery(); - - } break; - - case UrlEvent::HostNameResolved: { - printf("Host name resolved\n"); - auto name = message->GetString(UrlEventData::HostName); - message->PrintToStream(); - - _infoView->SetText("Hostname resolve..."); - _infoView->SetText(name); + _progress->SetMaxValue(100); _progress->SetTo(5); + printf("Button Pressed\n"); + _infoView->SetText("Asking..."); + _conversation->ask(std::string(_inputField->Text())); + } break; - case UrlEvent::ConnectionOpened: { - printf("ConnectionOpened\n"); - _progress->SetTo(10); - _infoView->SetText("connection opened..."); - } break; + case kSendReply: { + printf("Conversation returned!\n"); + _infoView->SetText("Answer Received"); - case UrlEvent::ResponseStarted: { - printf("ResponseStarted\n"); - _progress->SetTo(14); - _infoView->SetText("ResponseStarted..."); - } break; - case UrlEvent::HttpRedirect: { - printf("HttpRedirect\n"); - _progress->SetTo(16); - _infoView->SetText("HttpRedirect..."); - } break; - - case UrlEvent::RequestCompleted: { - printf("RequestCompleted\n"); - auto identifier = message->GetInt32(UrlEventData::Id, -1); - if (_lastResult->Identity() == identifier) { - // The following call will not block, because we have been notified - // that the request is done. - BHttpBody body = _lastResult->Body(); - if (body.text.has_value()) - { - - json parsed = json::parse(body.text.value().String()); - - std::string content = parsed["choices"][0]["message"]["content"]; - _answerView->SetText(content.c_str()); - - // _answerView->SetText(body.text.value()); - - } - else - _answerView->SetText("nuthin'"); - } - - _infoView->SetText("Completed"); + const char* text; + if (message->FindString("text", &text) == B_OK) { + printf("Received text: %s\n", text); + // Do something with text (e.g., set it to a BTextView) + _answerView->SetText(text); + } else { + printf("No text found in message.\n"); + _answerView->SetText("NO TEXT IN REPLY"); + } _progress->SetMaxValue(100); _progress->SetTo(100); - + } break; - case UrlEvent::HttpStatus: { - - printf("HttpStatus\n"); - _infoView->SetText("HttpStatus received"); - _progress->SetTo(20); - - } break; - - case UrlEvent::BytesWritten: { - _infoView->SetText("Some bytes written.."); - auto identifier = message->GetInt32(UrlEventData::Id, -1); - if (_lastResult->Identity() == identifier) { - off_t numBytes = message->GetInt64(UrlEventData::NumBytes, 0); - off_t totalBytes = message->GetInt64(UrlEventData::TotalBytes, 0); - _progress->SetTo(numBytes); - _progress->SetMaxValue(totalBytes); - } - } break; - - case UrlEvent::DownloadProgress: { - auto identifier = message->GetInt32(UrlEventData::Id, -1); - if (_lastResult->Identity() == identifier) { - off_t nn = message->GetInt64(UrlEventData::NumBytes, 0); - off_t totalBytes = message->GetInt64(UrlEventData::TotalBytes, 0); - _progress->SetTo(nn); - _progress->SetMaxValue(totalBytes); - _infoView->SetText("Download Progress.."); - } - } break; default: { // message->PrintToStream(); - BWindow::MessageReceived( + BHandler::MessageReceived( message); // call the parent handler for other messages // _infoView->SetText(message->FindMessage()); break; @@ -252,51 +179,6 @@ void MainWindow::MessageReceived(BMessage *message) { } // end function -void MainWindow::sendQuery() { - - if (_lastResult) - _sharedSession.Cancel(_lastResult->Identity()); - - _progress->SetMaxValue(100); - _progress->SetTo(0); - - auto url = BUrl("https://api.openai.com/v1/chat/completions"); - BHttpRequest request = BHttpRequest(url); - request.SetMethod(BHttpMethod::Post); - - // if the API key file contains a new line bhttpfields will crash with invalid - // content .end() requires include algorithm - std::string key = _apiKey.String(); - key.erase(std::remove(key.begin(), key.end(), '\n'), key.end()); - key.erase(std::remove(key.begin(), key.end(), '\r'), key.end()); - - std::string bearer = std::string("Bearer ") + std::string(key); - - BHttpFields fields = BHttpFields(); - fields.AddField("Authorization", bearer); - // fields.AddField("Content-Type", "application/json"); //NO, this will - // crash, we set it in request - request.SetFields(fields); - - json bodyJson = { - {"model", "gpt-3.5-turbo"}, - {"messages", {{{"role", "user"}, {"content", _inputField->Text()}}}}}; - - std::string body = bodyJson.dump(); - - BString mime = BString("application/json"); - // request.SetRequestBody(body.c_str(),mime); - - auto memoryIO = std::make_unique(body.c_str(), body.size()); - request.SetRequestBody(std::move(memoryIO), "application/json", body.size()); - - printf("Sending Prompt to server: %s\n", url.UrlString().String()); - _lastResult = _sharedSession.Execute(std::move(request), nullptr, this); - - if (_lastResult) { - printf("Result has identity: %d\n", _lastResult->Identity()); - } -} BMenuBar *MainWindow::_BuildMenu() { @@ -336,32 +218,3 @@ BMenuBar *MainWindow::_BuildMenu() { } -BString MainWindow::ReadOpenAIKey() { - - // /boot/home/config/openai_key - // or ~/.config/openai_key on linux - - BPath configPath; - if (find_directory(B_USER_SETTINGS_DIRECTORY, &configPath) != B_OK) - return "error: couldn't find config directory"; - - configPath.Append("openai_key"); - - BFile file(configPath.Path(), B_READ_ONLY); - - printf("full path:%s\n", configPath.Path()); - if (file.InitCheck() != B_OK) - return "error: couldn't open key file "; - - off_t size; - file.GetSize(&size); - - char *buffer = new char[size + 1]; - file.Read(buffer, size); - buffer[size] = '\0'; // null-terminate - - BString result(buffer); - delete[] buffer; - - return result; -} diff --git a/MainWindow.h b/MainWindow.h index e5de61b..0703ea6 100644 --- a/MainWindow.h +++ b/MainWindow.h @@ -12,30 +12,9 @@ #include #include +#include "Conversation.h" -//NEED THIS IN MAKE FILE TO USE PRIVATE HEADERS -//CXXFLAGS = -std=c++17 -Wall -I/boot/system/develop/headers/private - - -//Build works but Genio doesn't see class definitions unless I use full path ? -#include "/boot/system/develop/headers/private/netservices2/NetServicesDefs.h" -#include "/boot/system/develop/headers/private/netservices2/HttpSession.h" -#include "/boot/system/develop/headers/private/netservices2/HttpRequest.h" -#include "/boot/system/develop/headers/private/netservices2/HttpResult.h" -#include "/boot/system/develop/headers/private/netservices2/HttpFields.h" -#include "/boot/system/develop/headers/private/netservices2/ErrorsExt.h" - -//From private headers ! -//#include -//#include -//#include -//#include -//#include -//#include - - -using namespace BPrivate::Network; class MainWindow : public BWindow { public: @@ -44,25 +23,18 @@ public: virtual void MessageReceived(BMessage *msg); - void sendQuery(); - BString ReadOpenAIKey(); + Conversation* _conversation = new Conversation(this); private: - - BString _apiKey; - BHttpSession _sharedSession = BHttpSession (); - std::optional _lastResult; - BMenuBar *_BuildMenu(); BTextView * _answerView; BTextView * _infoView; BTextView* _inputField; - //BTextControl* _inputField; BStatusBar* _progress; - BMenuItem *fSaveMenuItem; + // BMenuItem *fSaveMenuItem; }; #endif diff --git a/Makefile b/Makefile index 0f51cdf..dce2539 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,8 @@ APP_MIME_SIG = application/x-vnd.SLema-DumBer # same name (source.c or source.cpp) are included from different directories. # Also note that spaces in folder names do not work well with this Makefile. SRCS = App.cpp \ - MainWindow.cpp + MainWindow.cpp \ + Conversation.cpp # Specify the resource definition files to use. Full or relative paths can be # used.