diff --git a/Conversation.cpp b/Conversation.cpp index 6cd6810..0906d6b 100644 --- a/Conversation.cpp +++ b/Conversation.cpp @@ -37,17 +37,16 @@ void Conversation::sendReply(BMessage message) { } std::string Conversation::buildHistoryInfoLine() { - - std::string info = "" + std::to_string(_messageHistory.size()) + " messages"; - - return info; - + + std::string info = "" + std::to_string(_messageHistory.size()) + " messages"; + + return info; } void Conversation::ClearHistory() { - - printf("Cleared history\n"); - _messageHistory.clear(); + + printf("Cleared history\n"); + _messageHistory.clear(); } std::vector @@ -71,7 +70,8 @@ Conversation::FilterTextModels(const json &modelsJson) { } } - std::sort(result.begin(), result.end(), std::greater<>()); // inverse alphabetical to get gpt-4 on top + std::sort(result.begin(), result.end(), + std::greater<>()); // inverse alphabetical to get gpt-4 on top return result; } @@ -111,24 +111,36 @@ void Conversation::MessageReceived(BMessage *message) { } 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()) { - - - try { - //printf("full Reply as text:%s",body.text.value().String()); - auto fullBody =body.text.value().String(); + // The following call will not block, because we have been notified + // that the request is done. + try { + + printf("RequestCompleted\n"); + + if (!_lastResult) { + printf("No Last Result (no connection?)..\n"); + return; + } + + auto identifier = message->GetInt32(UrlEventData::Id, -1); + if (_lastResult->Identity() == identifier) { + + if (!_lastResult->HasBody()) { + printf("NO Body (no connection)..\n"); + return; + } + + BHttpBody body = _lastResult->Body(); + if (body.text.has_value()) { + + // printf("full Reply as text:%s",body.text.value().String()); + auto fullBody = body.text.value().String(); json parsed = json::parse(fullBody); printf("Parsed..\n"); std::string objType = parsed["object"]; - // printf("Reply of type object :%s\n", objType.c_str()); + // printf("Reply of type object :%s\n", objType.c_str()); if (objType == "list") { // printf("full Reply as text:%s",body.text.value().String()); @@ -143,19 +155,15 @@ void Conversation::MessageReceived(BMessage *message) { sendReply(msg); // std::string content = - //parsed["choices"][0]["message"]["content"]; + // parsed["choices"][0]["message"]["content"]; } else if (objType == "chat.completion") { std::string content = parsed["choices"][0]["message"]["content"]; - - _messageHistory.push_back({ - {"role", "assistant"}, - {"content", content} - }); - + _messageHistory.push_back( + {{"role", "assistant"}, {"content", content}}); // printf("we got content:%s",content.c_str()); BMessage message(kSendReply); @@ -164,23 +172,21 @@ void Conversation::MessageReceived(BMessage *message) { sendReply(message); } - } catch (const std::exception &e) { - fprintf(stderr, "Error parsing JSON: %s\n", e.what()); - std::string content = "Error parsing JSON, wrong model ?"; + } else { BMessage message(kSendReply); - message.AddString("text", BString(content.c_str())); + message.AddString("text", "EMPTY BODY"); sendReply(message); } - - } else { - BMessage message(kSendReply); - message.AddString("text", "EMPTY BODY"); - sendReply(message); } + } catch (const std::exception &e) { + fprintf(stderr, "Error parsing JSON: %s\n", e.what()); + std::string content = "Error parsing JSON, wrong model ?"; + BMessage message(kSendReply); + message.AddString("text", BString(content.c_str())); + sendReply(message); } - } - break; + } break; case UrlEvent::HttpStatus: { @@ -192,24 +198,24 @@ void Conversation::MessageReceived(BMessage *message) { 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); - } + // 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.."); - } + // 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: @@ -259,11 +265,8 @@ void Conversation::setModel(const std::string &model) { void Conversation::ask(const std::string &prompt) { - _messageHistory.push_back({ - {"role", "user"}, - {"content", prompt} - }); - + _messageHistory.push_back({{"role", "user"}, {"content", prompt}}); + // printf("Asking prompt: %s",prompt.c_str()); if (_lastResult) @@ -272,23 +275,22 @@ void Conversation::ask(const std::string &prompt) { auto url = BUrl("https://api.openai.com/v1/chat/completions"); BHttpRequest request = BHttpRequest(url); request.SetMethod(BHttpMethod::Post); - //Allow up to 2 minute before timeout, it can be long depending on load or complexity of prompt - request.SetTimeout(120*1000000); - + // Allow up to 2 minute before timeout, it can be long depending on load or + // complexity of prompt + request.SetTimeout(120 * 1000000); + BHttpFields fields = BHttpFields(); fields.AddField("Authorization", buildBearerKey()); // fields.AddField("Content-Type", "application/json"); //NO, this will // crash, we set it in request request.SetFields(fields); -// WE PASS THE WHOLE HISTORY to keep context, as recommended for this stateless API! -// json bodyJson = {{"model", _activeModel}, + // WE PASS THE WHOLE HISTORY to keep context, as recommended for this + // stateless API! + // json bodyJson = {{"model", _activeModel}, // {"messages", {{{"role", "user"}, {"content", prompt}}}}}; - json bodyJson = { - {"model", _activeModel}, - {"messages", _messageHistory} - }; + json bodyJson = {{"model", _activeModel}, {"messages", _messageHistory}}; std::string body = bodyJson.dump(); @@ -313,7 +315,7 @@ BString Conversation::ReadOpenAIKey() { // /boot/home/config/openai_key configPath.Append("openai_key"); - + BFile file(configPath.Path(), B_READ_ONLY); printf("full path:%s\n", configPath.Path()); @@ -323,10 +325,10 @@ BString Conversation::ReadOpenAIKey() { } off_t size; - file.GetSize(&size); + file.GetSize(&size); char *buffer = new char[size + 1]; - file.Read(buffer, size); + file.Read(buffer, size); buffer[size] = '\0'; // null-terminate BString result(buffer); diff --git a/DumBer b/DumBer index 1197207..cadeb35 100755 Binary files a/DumBer and b/DumBer differ diff --git a/MainWindow.cpp b/MainWindow.cpp index 3bc70ee..dca23a8 100644 --- a/MainWindow.cpp +++ b/MainWindow.cpp @@ -5,7 +5,6 @@ #include "MainWindow.h" - static int progressAnim = 0; static int progressColor = 0; static bool progressColorUp = false; @@ -14,6 +13,7 @@ static bool progressColorUp = false; #include #include #include +#include #include #include #include @@ -30,13 +30,13 @@ static bool progressColorUp = false; #include #include "Conversation.h" -#include +#include #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "Window" MainWindow::MainWindow() - : BWindow(BRect(50, 50, 600, 400), B_TRANSLATE("DumBer"), B_TITLED_WINDOW, + : BWindow(BRect(50, 50, 600, 400), B_TRANSLATE("DumBer"), B_DOCUMENT_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE) { // Without this conversation would never get bmessages from HttpRequest @@ -63,7 +63,6 @@ MainWindow::MainWindow() _progress->SetTo(0); _progress->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); - // Info view, only one line high _infoView = new BTextView("info"); _infoView->SetText("..."); @@ -95,7 +94,6 @@ MainWindow::MainWindow() _sendButton->MakeDefault(true); - _answerView = new BTextView("answer", B_WILL_DRAW); _answerView->MakeEditable(false); // Disable editing _answerView->MakeSelectable(true); // Enable text selection @@ -103,95 +101,126 @@ MainWindow::MainWindow() _answerView->SetExplicitMinSize(BSize(B_SIZE_UNSET, askH * 2)); - _answerScrollView = - new BScrollView("scroll_view", _answerView, B_FRAME_EVENTS | B_WILL_DRAW, 0, - false, true, B_FANCY_BORDER); // horizontal and vertical scrollbars + _answerScrollView = new BScrollView( + "scroll_view", _answerView, B_FRAME_EVENTS | B_WILL_DRAW, 0, false, true, + B_FANCY_BORDER); // horizontal and vertical scrollbars - // Enable correct resizing behavior, otherwise we get no correct scrollbar after resizing - _answerView->SetFlags(_answerView->Flags() | B_FULL_UPDATE_ON_RESIZE); - _answerScrollView->SetFlags(_answerView->Flags() | B_FULL_UPDATE_ON_RESIZE); + // Enable correct resizing behavior, otherwise we get no correct scrollbar + // after resizing + _answerView->SetFlags(_answerView->Flags() | B_FULL_UPDATE_ON_RESIZE); + _answerScrollView->SetFlags(_answerView->Flags() | B_FULL_UPDATE_ON_RESIZE); /* - BView *imageView = new BView("icon_view", B_WILL_DRAW | B_FOLLOW_NONE); - imageView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + //TRYING TO load image from resources and failing... + BView *imageView = new BView("icon_view", B_WILL_DRAW | B_FOLLOW_ALL); + imageView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); - BBitmap* oneImage = BTranslationUtils::GetBitmap('RAWT', 77, NULL); - imageView->SetViewColor(ui_color(B_CONTROL_BORDER_COLOR)); +// Open the resource file + BFile resourceFile("Resources.rdef", B_READ_ONLY); + if (resourceFile.InitCheck() != B_OK) { + fprintf(stderr, "Failed to open the resource file.\n"); + return; + } - if (oneImage) { - imageView->SetViewBitmap(oneImage,B_FOLLOW_LEFT_TOP, B_TILE_BITMAP); - printf("Image loaded!\n"); - } -else { - printf("Image NOT loaded!\n"); - } -*/ + BResources resources(&resourceFile); + + // Load the icon resource + size_t theSize; + const uint8 *iconData = + (const uint8 *)resources.LoadResource('VICN', 55, &theSize); + if (iconData == nullptr) { + fprintf(stderr, "Failed to load the icon resource.\n"); + } else { + printf("loaded size:%zu", theSize); + } + // Create a BBitmap + BBitmap *bitmap = + new BBitmap(BRect(0, 0, 96, 96), B_RGBA32); // Adjust size as needed + if (bitmap == nullptr) { + fprintf(stderr, "Failed to create a BBitmap.\n"); + } + // Convert the icon data to the Bitmap + if (!BIconUtils::GetVectorIcon(iconData, theSize, bitmap)) { + fprintf(stderr, "Failed to convert the icon to a BBitmap.\n"); + // delete bitmap; // Clean up in case of failure + } + // Now you can use the bitmap as needed... + + // delete bitmap; // Clean up when done + */ BStringView *headerQuestion = new BStringView("questionLabel", "Your question: "); BStringView *headerAnswer = new BStringView("questionAnswer", "Answer: "); -rgb_color colorQuestion = {100, 100,150, 255}; -//rgb_color colorAnswer = {100, 100,150, 255}; + rgb_color colorQuestion = {100, 100, 150, 255}; + // rgb_color colorAnswer = {100, 100,150, 255}; -//high color = txt -headerQuestion->SetHighColor(colorQuestion); -headerAnswer->SetHighColor(colorQuestion); + // high color = txt + headerQuestion->SetHighColor(colorQuestion); + headerAnswer->SetHighColor(colorQuestion); -BLayoutBuilder::Group<>(this, B_VERTICAL, 0) + BLayoutBuilder::Group<>(this, B_VERTICAL, 0) - .AddGlue(0.1) - .Add(headerQuestion) - .AddGroup(B_HORIZONTAL, 0, 1) + .AddGlue(0.1) + .Add(headerQuestion) + .AddGroup(B_HORIZONTAL, 0, 1) - .Add(_inputField) - .AddGlue(0.01) -// .Add(imageView) + .Add(_inputField) + .AddGlue(0.01) +// .Add(imageView) - .AddGroup(B_VERTICAL, B_USE_DEFAULT_SPACING, 0) + .AddGroup(B_VERTICAL, B_USE_DEFAULT_SPACING, 0) - .AddGroup(B_HORIZONTAL, 0) - .Add(_infoConversation) - .AddGlue(0.1) - .End() - .AddGroup(B_HORIZONTAL, 0) - .Add(_modelField) - .AddGlue(0.1) - .End() - .AddGroup(B_HORIZONTAL, 0) - .Add(_sendButton) - .AddGlue(0.1) - .End() + .AddGroup(B_HORIZONTAL, 0) + .Add(_infoConversation) + .AddGlue(0.1) + .End() + .AddGroup(B_HORIZONTAL, 0) + .Add(_modelField) + .AddGlue(0.1) + .End() + .AddGroup(B_HORIZONTAL, 0) + .Add(_sendButton) + .AddGlue(0.1) + .End() - .End() + .End() - .End() + .End() - .AddGlue(0.1) - .Add(headerAnswer) - .AddGroup(B_VERTICAL, B_USE_DEFAULT_SPACING, 1.0) + .AddGlue(0.1) + .Add(headerAnswer) + .AddGroup(B_VERTICAL, B_USE_DEFAULT_SPACING, 1.0) - .Add(_answerScrollView, 1) - .Add(_progress, 0.1) - .Add(_infoView, 0.1) - .End() - .SetInsets(6, 6, 6, 6) + .Add(_answerScrollView, 1) + .Add(_progress, 0.1) + .Add(_infoView, 0.1) + .End() + .SetInsets(6, 6, 6, 6) - .End(); + .End(); -// Loop Just to animate progress in Bar -BMessageRunner *runner = new BMessageRunner(this, // target BHandler - new BMessage(kPulse), - 100000 // interval in μs (0 ms) - + //imageView->SetViewColor(ui_color(B_CONTROL_BORDER_COLOR)); -); + //if (bitmap) { + // imageView->SetViewBitmap(bitmap, 0, 0); + // printf("Image loaded!\n"); + //} else { + // printf("Image NOT loaded!\n"); + //} -updateHistoryInfo(); + // Loop Just to animate progress in Bar + BMessageRunner *runner = new BMessageRunner(this, // target BHandler + new BMessage(kPulse), + 100000 // interval in μs (0 ms) + ); -PostMessage(kCheckKey); + updateHistoryInfo(); + + PostMessage(kCheckKey); } void MainWindow::checkValidKey() { @@ -377,9 +406,9 @@ void MainWindow::MessageReceived(BMessage *message) { case kViewJSON: { _infoView->SetText("Showing JSON"); - _answerView->SetText(lastJSON.c_str()); - progressAnim=0; - } break; + _answerView->SetText(lastJSON.c_str()); + progressAnim = 0; + } break; case kSendReply: { @@ -395,14 +424,11 @@ void MainWindow::MessageReceived(BMessage *message) { const char *text; const char *fullJSON; - - - if (message->FindString("json", &fullJSON) == B_OK) { - - lastJSON = std::string(fullJSON); - - } + if (message->FindString("json", &fullJSON) == B_OK) { + + lastJSON = std::string(fullJSON); + } if (message->FindString("text", &text) == B_OK) { // printf("Received text: %s\n", text); @@ -471,28 +497,26 @@ BMenuBar *MainWindow::_BuildMenu() { item = new BMenuItem(B_TRANSLATE("Send Prompt" B_UTF8_ELLIPSIS), new BMessage(kSendPrompt)); - item->SetShortcut('S', B_COMMAND_KEY); + item->SetShortcut('S', B_COMMAND_KEY); item->SetTarget(this); menu->AddItem(item); menu->AddSeparatorItem(); - + item = new BMenuItem(B_TRANSLATE("Clear History" B_UTF8_ELLIPSIS), new BMessage(kClearHistory)); item->SetTarget(this); - item->SetShortcut('K', B_COMMAND_KEY | B_SHIFT_KEY); + item->SetShortcut('K', B_COMMAND_KEY | B_SHIFT_KEY); menu->AddItem(item); menuBar->AddItem(menu); - menu->AddSeparatorItem(); - item = new BMenuItem(B_TRANSLATE("View full json" B_UTF8_ELLIPSIS), + 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); + item->SetShortcut('J', B_COMMAND_KEY | B_SHIFT_KEY); menu->AddItem(item); - - return menuBar; } diff --git a/Resources.rdef b/Resources.rdef index 61a8def..e36654d 100644 --- a/Resources.rdef +++ b/Resources.rdef @@ -17,6 +17,10 @@ resource app_version { long_info = "A simple native Haiku client for ChatGPT that uses simple text and very little memory. It requires having your own API Key." }; + + +resource(55) #'VICN' import "artwork/icons/kConsoleApp"; + resource vector_icon { $"6E6369660804016B0500020006023CADEB3D1051BD10513CADEB4C3551C6C7FF" @@ -48,4 +52,3 @@ resource vector_icon { - diff --git a/artwork/icons/kConsoleApp b/artwork/icons/kConsoleApp new file mode 100644 index 0000000..a8c0cd2 Binary files /dev/null and b/artwork/icons/kConsoleApp differ