diff --git a/.genio b/.genio index c79fcc3..c0af45a 100644 Binary files a/.genio and b/.genio differ diff --git a/App.cpp b/App.cpp index 4dacdb2..079d265 100644 --- a/App.cpp +++ b/App.cpp @@ -9,12 +9,25 @@ #include #include +#include "Utils.h" + #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "Application" const char *kApplicationSignature = "application/x-vnd.SLema-DumBer"; App::App() : BApplication(kApplicationSignature) { + + BMessage s = LoadMessageFromFile(kSettingsFileName); + + if (s.what=='STNG') + settingsMessage = s; + else + { + settingsMessage.what = 'STNG'; + settingsMessage.AddInt32("settings_format_version", 1); // Initial data + } + MainWindow *m = new MainWindow(); m->SetLook(B_DOCUMENT_WINDOW_LOOK); m->SetFeel(B_NORMAL_WINDOW_FEEL); @@ -44,8 +57,62 @@ void App::AboutRequested() { +void App::MessageReceived(BMessage *message) { + + switch (message->what) { + + case kSettingsUpdate: { + + // NO DO NOT CLEAR message, we can just overwrite one field and save the whole thing each time + //settingsMessage.MakeEmpty(); + + std::cout << "Received settings update: " ; + + + int32 count = message->CountNames(B_ANY_TYPE); + for (int32 index = 0; index < count; ++index) { + char* name; + uint32 type; + int32 itemCount; + + std::cout << "Received settings update - key: " << name; + + // Retrieve information about the item at the index + if (message->GetInfo(B_ANY_TYPE, index, &name, &type, &itemCount) == B_NO_ERROR) { + + if (type == B_STRING_TYPE) { + BString value; + if (message->FindString(name, &value) == B_NO_ERROR) { + settingsMessage.AddString(name, value); + } + } + + } + } + + + SaveMessageToFile(settingsMessage,kSettingsFileName); + } break; + + + default: { + // message->PrintToStream(); + BHandler::MessageReceived( + message); // call the parent handler for other messages + // _infoView->SetText(message->FindMessage()); + break; + } + + } // end switch + +} // end function + + + int main() { App *app = new App(); + + app->Run(); delete app; return 0; diff --git a/App.h b/App.h index 2760df9..6bc2c09 100644 --- a/App.h +++ b/App.h @@ -5,7 +5,7 @@ #ifndef APP_H #define APP_H -#include "MainWindow.h" +#include "MainWindow.h" #include @@ -16,10 +16,14 @@ public: App(); virtual ~App(); + virtual void AboutRequested(); MainWindow* mainWindow = nullptr; + BMessage settingsMessage; + BMessage* getSettingsMessage() { return &settingsMessage; } + virtual void MessageReceived(BMessage *msg); void ReadyToRun() { diff --git a/Conversation.cpp b/Conversation.cpp index 037b363..a50d04f 100644 --- a/Conversation.cpp +++ b/Conversation.cpp @@ -12,6 +12,10 @@ #include +#include "Utils.h" +#include "App.h" + + Conversation::Conversation(BHandler *replyTo) { replyTarget = replyTo; @@ -49,8 +53,7 @@ void Conversation::ClearHistory() { _messageHistory.clear(); } -std::vector -Conversation::FilterTextModels(const json &modelsJson) { +status_t Conversation::FilterTextModels(const json &modelsJson) { std::vector result; std::regex pattern("gpt|text|curie|babbage|ada"); @@ -69,10 +72,27 @@ Conversation::FilterTextModels(const json &modelsJson) { result.push_back(id); } } + + if (result.empty()) + return B_ERROR; + std::sort(result.begin(), result.end(), std::greater<>()); // inverse alphabetical to get gpt-4 on top - return result; + + PrintAsJsonArray(result); + + BMessage msg(kModelsReceived); + for (const auto &model : result) { + msg.AddString("model", model.c_str()); + } + sendReply(msg); + + + + + + return B_OK; } void Conversation::MessageReceived(BMessage *message) { @@ -148,18 +168,13 @@ void Conversation::MessageReceived(BMessage *message) { if (objType == "list") { // printf("full Reply as text:%s",body.text.value().String()); - - std::vector validModels = FilterTextModels(parsed); - PrintAsJsonArray(validModels); - BMessage msg(kModelsReceived); - - for (const auto &model : validModels) { - msg.AddString("model", model.c_str()); - } - sendReply(msg); - - // std::string content = - // parsed["choices"][0]["message"]["content"]; + if (FilterTextModels(parsed)==B_OK) + { + //Save models in settings so we don't have to do one request at start each time + BMessage message(kSettingsUpdate); + message.AddString("models_json", BString(fullBody)); + be_app->PostMessage(&message); + } } @@ -236,20 +251,20 @@ void Conversation::MessageReceived(BMessage *message) { sendReply(msgr); - BMessage msg(kShowError); + BMessage msg(kShowStatus); msg.AddString("text", e.DebugMessage()); sendReply(msg); } catch (const std::exception& e) { std::cout << "Caught a standard exception: " << e.what() << std::endl; - BMessage msg(kShowError); + BMessage msg(kShowStatus); msg.AddString("text", e.what()); sendReply(msg); } catch (...) { std::cout << "Caught an unknown exception!" << std::endl; - BMessage msg(kShowError); + BMessage msg(kShowStatus); msg.AddString("text", "unknown exception"); sendReply(msg); } @@ -268,7 +283,40 @@ std::string Conversation::buildBearerKey() { return bearer; } -void Conversation::loadModels() { +void Conversation::loadModelsForced(bool force) { + + // TRY first to get models from settings files + if (force==false) { + App* app = dynamic_cast(be_app); + if (app) { + BMessage* settings = app->getSettingsMessage(); + const char* jsonString; + + if (settings->FindString("models_json", &jsonString)==B_NO_ERROR) + { + json parsed = json::parse(jsonString); + std::string objType = parsed["object"]; + if (objType == "list") { + //printf("loadModelFromSettings :%s",jsonString); + + if(FilterTextModels(parsed)==B_OK) + return; + } + + } + else + std::cout << "no models in settings object...\n"; + + } + } + + //MODELS not in settings, ask them. + + + BMessage msg(kShowStatus); + msg.AddString("text", "Asking server for list of models..."); + sendReply(msg); + auto url = BUrl("https://api.openai.com/v1/models", true); BHttpRequest request = BHttpRequest(url); diff --git a/Conversation.h b/Conversation.h index bae0260..b982dba 100644 --- a/Conversation.h +++ b/Conversation.h @@ -14,7 +14,7 @@ #include #include -static const uint32 kShowError = 'serr'; + @@ -63,11 +63,11 @@ public: virtual void MessageReceived(BMessage *msg); - std::vector FilterTextModels(const json& modelsJson); + status_t FilterTextModels(const json& modelsJson); void ask(const std::string& prompt); void setModel(const std::string& prompt); - void loadModels(); + void loadModelsForced(bool force); void PrintAsJsonArray(const std::vector& models) ; void ClearHistory(); std::string buildHistoryInfoLine(); diff --git a/MainWindow.cpp b/MainWindow.cpp index 638d79d..93fbe7e 100644 --- a/MainWindow.cpp +++ b/MainWindow.cpp @@ -28,9 +28,10 @@ static bool progressColorUp = false; #include #include +#include #include "Conversation.h" -#include +#include "Utils.h" #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "Window" @@ -236,11 +237,12 @@ void MainWindow::checkValidKey() { progressColor = 0; progressAnim = 1; - _conversation->loadModels(); - _infoView->SetText("Requesting model lists..."); + + _conversation->loadModelsForced(false); } } + void MainWindow::ShowMissingKeyAlertAndQuit() { BAlert *alert = new BAlert( @@ -302,15 +304,27 @@ void MainWindow::MessageReceived(BMessage *message) { updateHistoryInfo(); } break; + + case kRequestModels: { - case kShowError: { + printf("will Request models"); + //_infoView->SetText("Cleared conversation history. Starting new context"); + //_inputField->SetText(""); + //_answerView->SetText(""); + //_conversation->ClearHistory(); + _conversation->loadModelsForced(true); + //updateHistoryInfo(); - printf("error received:"); + } break; + + case kShowStatus: { + + printf("status or error received:"); const char *text; if (message->FindString("text", &text) == B_OK) { - _infoView->SetText(BString("ERROR: ") << text); - printf("ERROR: %s\n", text); + _infoView->SetText(BString("Status: ") << text); + printf("Status: %s\n", text); } } break; @@ -517,6 +531,8 @@ BMenuBar *MainWindow::_BuildMenu() { menu->AddSeparatorItem(); + + //-------------------------------- item = new BMenuItem(B_TRANSLATE("Clear History" B_UTF8_ELLIPSIS), new BMessage(kClearHistory)); item->SetTarget(this); @@ -525,12 +541,22 @@ BMenuBar *MainWindow::_BuildMenu() { menuBar->AddItem(menu); + menu->AddSeparatorItem(); + + //-------------------------------- item = new BMenuItem(B_TRANSLATE("View full json" B_UTF8_ELLIPSIS), new BMessage(kViewJSON)); item->SetTarget(this); item->SetShortcut('J', B_COMMAND_KEY | B_SHIFT_KEY); menu->AddItem(item); + + //-------------------------------- + item = new BMenuItem(B_TRANSLATE("Update models..." B_UTF8_ELLIPSIS), + new BMessage(kRequestModels)); + item->SetTarget(this); + //item->SetShortcut('J', B_COMMAND_KEY | B_SHIFT_KEY); + menu->AddItem(item); return menuBar; } diff --git a/MainWindow.h b/MainWindow.h index df3dec0..64bc183 100644 --- a/MainWindow.h +++ b/MainWindow.h @@ -17,18 +17,6 @@ #include "Conversation.h" -static const uint32 kCheckKey = 'chkk'; -static const uint32 kMsgNewFile = 'fnew'; -static const uint32 kMsgOpenFile = 'fopn'; -static const uint32 kMsgSaveFile = 'fsav'; -static const uint32 kModelSelected = 'msel'; -static const uint32 kViewJSON = 'vjso'; - -static const uint32 kPulse = 'plse'; - -static const uint32 kSendPrompt = 'kspt'; -static const uint32 kQuestionChanged = 'kqch'; - class MainWindow : public BWindow { public: MainWindow(); @@ -71,6 +59,7 @@ private: BMenuField *_modelField; BPopUpMenu *_modelMenu; BButton *_sendButton; + BMessage *settings; // BMenuItem *fSaveMenuItem; }; diff --git a/Makefile b/Makefile index 9ee3af6..164f202 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,7 @@ 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 \ + Utils.cpp \ MainWindow.cpp \ Conversation.cpp diff --git a/Utils.cpp b/Utils.cpp new file mode 100644 index 0000000..3d0fa65 --- /dev/null +++ b/Utils.cpp @@ -0,0 +1,52 @@ +/* + * Copyright 2024, My Name + * All rights reserved. Distributed under the terms of the MIT license. + */ + + +#include "Utils.h" + +#include +#include +#include +#include +#include +#include + + +status_t SaveMessageToFile(const BMessage& message, const char* fileName) { + BPath path; + if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) { + return B_ERROR; + } + path.Append(fileName); + + BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE); + if (file.InitCheck() != B_OK) { + return file.InitCheck(); + } + return message.Flatten(&file); +} + +BMessage LoadMessageFromFile(const char* fileName) { + BPath path; + + if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) { + return BMessage(B_ERROR); // Return a message indicating an error + } + + path.Append(fileName); + + BFile file(path.Path(), B_READ_ONLY); + if (file.InitCheck() != B_OK) { + return BMessage(file.InitCheck()); // Return a message with the error status + } + + BMessage message; + status_t status = message.Unflatten(&file); + if (status != B_OK) { + return BMessage(status); // Return a message indicating unflattening error + } + return message; +} + diff --git a/Utils.h b/Utils.h new file mode 100644 index 0000000..c72a5d4 --- /dev/null +++ b/Utils.h @@ -0,0 +1,40 @@ +/* + * Copyright 2024, My Name + * All rights reserved. Distributed under the terms of the MIT license. + */ +#ifndef UTILS_H +#define UTILS_H + + +#include + +#include +#include +#include +#include +#include + +static const uint32 kShowStatus = 'stat'; +static const uint32 kCheckKey = 'chkK'; +static const uint32 kMsgNewFile = 'fNEW'; +static const uint32 kMsgOpenFile = 'fOPN'; +static const uint32 kMsgSaveFile = 'fSAV'; +static const uint32 kModelSelected = 'mSEL'; +static const uint32 kRequestModels = 'mREQ'; + +static const uint32 kViewJSON = 'vJSN'; +static const uint32 kSettingsUpdate = 'sUPD'; +static const char* kSettingsFileName = "bedumber_settings.bmessage"; + + +static const uint32 kPulse = 'plse'; + +static const uint32 kSendPrompt = 'kspt'; +static const uint32 kQuestionChanged = 'kqch'; + + + +status_t SaveMessageToFile(const BMessage& message, const char* fileName); +BMessage LoadMessageFromFile(const char* fileName); + +#endif // UTILS_H