saves models to settings to avoid reload, with option to force from menu

This commit is contained in:
Santiago Lema 2025-09-29 00:50:41 -03:00
parent dd033a7896
commit 177cdb925f
10 changed files with 269 additions and 42 deletions

BIN
.genio

Binary file not shown.

67
App.cpp
View file

@ -9,12 +9,25 @@
#include <AboutWindow.h>
#include <Catalog.h>
#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;

6
App.h
View file

@ -5,7 +5,7 @@
#ifndef APP_H
#define APP_H
#include "MainWindow.h"
#include "MainWindow.h"
#include <Application.h>
@ -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()
{

View file

@ -12,6 +12,10 @@
#include <Application.h>
#include "Utils.h"
#include "App.h"
Conversation::Conversation(BHandler *replyTo) {
replyTarget = replyTo;
@ -49,8 +53,7 @@ void Conversation::ClearHistory() {
_messageHistory.clear();
}
std::vector<std::string>
Conversation::FilterTextModels(const json &modelsJson) {
status_t Conversation::FilterTextModels(const json &modelsJson) {
std::vector<std::string> 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<App*>(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);

View file

@ -14,7 +14,7 @@
#include <string>
#include <regex>
static const uint32 kShowError = 'serr';
@ -63,11 +63,11 @@ public:
virtual void MessageReceived(BMessage *msg);
std::vector<std::string> 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<std::string>& models) ;
void ClearHistory();
std::string buildHistoryInfoLine();

View file

@ -28,9 +28,10 @@ static bool progressColorUp = false;
#include <View.h>
#include <Path.h>
#include <FindDirectory.h>
#include "Conversation.h"
#include <FindDirectory.h>
#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;
}

View file

@ -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;
};

View file

@ -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

52
Utils.cpp Normal file
View file

@ -0,0 +1,52 @@
/*
* Copyright 2024, My Name <my@email.address>
* All rights reserved. Distributed under the terms of the MIT license.
*/
#include "Utils.h"
#include <File.h>
#include <Path.h>
#include <Directory.h>
#include <FindDirectory.h>
#include <Message.h>
#include <String.h>
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;
}

40
Utils.h Normal file
View file

@ -0,0 +1,40 @@
/*
* Copyright 2024, My Name <my@email.address>
* All rights reserved. Distributed under the terms of the MIT license.
*/
#ifndef UTILS_H
#define UTILS_H
#include <SupportDefs.h>
#include <Message.h>
#include <String.h>
#include <Path.h>
#include <Directory.h>
#include <File.h>
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