handles history

This commit is contained in:
Santiago Lema 2025-05-08 01:11:46 -03:00
parent dfb077be70
commit 85be8d6ef4
7 changed files with 452 additions and 212 deletions

View file

@ -1,71 +1,86 @@
// Conversation.cpp
#include "Conversation.h"
#include <Alert.h>
#include <File.h>
#include <FindDirectory.h>
#include <Path.h>
#include <String.h>
#include <Alert.h>
#include <Url.h>
#include <MimeType.h>
#include <Url.h>
#include <Application.h>
Conversation::Conversation(BHandler* replyTo) {
Conversation::Conversation(BHandler *replyTo) {
replyTarget = replyTo;
_apiKey = ReadOpenAIKey();
printf("key is: %s", _apiKey.String());
}
Conversation::~Conversation() {
}
Conversation::~Conversation() {}
void Conversation::PrintAsJsonArray(const std::vector<std::string>& models) {
json output = models; // implicit conversion to JSON array
std::cout << output.dump(2) << std::endl; // pretty-print with 2-space indent
void Conversation::PrintAsJsonArray(const std::vector<std::string> &models) {
json output = models; // implicit conversion to JSON array
std::cout << output.dump(2) << std::endl; // pretty-print with 2-space indent
}
void Conversation::sendReply(BMessage message) {
BLooper* looper = replyTarget->Looper(); // get the looper it's attached to
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");
}
if (looper != nullptr) {
BMessenger messenger(replyTarget, looper);
messenger.SendMessage(&message);
} else {
printf("Handler not attached to a looper.\n");
}
}
std::vector<std::string> Conversation::FilterTextModels(const json& modelsJson) {
std::vector<std::string> result;
std::regex pattern("gpt|text|davinci|curie|babbage|ada");
for (const auto& model : modelsJson["data"]) {
std::string id = model["id"];
if (std::regex_search(id, pattern) &&
id.find("audio") == std::string::npos &&
id.find("vision") == std::string::npos &&
id.find("dall-e") == std::string::npos) {
result.push_back(id);
}
}
return result;
}
void Conversation::MessageReceived(BMessage* message) {
switch (message->what) {
//.. case B_HTTP_DATA_RECEIVED: {
// break;
// }
std::string Conversation::buildHistoryInfoLine() {
std::string info = "" + std::to_string(_messageHistory.size()) + " messages";
return info;
}
void Conversation::ClearHistory() {
printf("Cleared history\n");
_messageHistory.clear();
}
std::vector<std::string>
Conversation::FilterTextModels(const json &modelsJson) {
std::vector<std::string> result;
std::regex pattern("gpt|text|curie|babbage|ada");
for (const auto &model : modelsJson["data"]) {
std::string id = model["id"];
if (std::regex_search(id, pattern) &&
id.find("audio") == std::string::npos &&
id.find("image") == std::string::npos &&
id.find("image") == std::string::npos &&
id.find("tts") == std::string::npos &&
id.find("embed") == std::string::npos &&
id.find("-20") == std::string::npos &&
id.find("preview") == std::string::npos &&
id.find("transcribe") == std::string::npos &&
id.find("dall-e") == std::string::npos) {
result.push_back(id);
}
}
std::sort(result.begin(), result.end(), std::greater<>()); // inverse alphabetical to get gpt-4 on top
return result;
}
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);
@ -94,7 +109,7 @@ void Conversation::MessageReceived(BMessage* message) {
//_progress->SetTo(16);
//_infoView->SetText("HttpRedirect...");
} break;
case UrlEvent::RequestCompleted: {
printf("RequestCompleted\n");
auto identifier = message->GetInt32(UrlEventData::Id, -1);
@ -102,45 +117,65 @@ void Conversation::MessageReceived(BMessage* message) {
// 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())
{
//printf("full Reply as text:%s",body.text.value().String());
json parsed = json::parse(body.text.value().String());
printf("Parsed..\n");
if (body.text.has_value()) {
try {
std::string objType = parsed["object"];
printf("Reply of type object :%s\n",objType.c_str());
if (objType == "list")
{
//printf("full Reply as text:%s",body.text.value().String());
std::vector validModels = FilterTextModels(parsed);
PrintAsJsonArray(validModels);
// printf("full Reply as text:%s",body.text.value().String());
json parsed = json::parse(body.text.value().String());
printf("Parsed..\n");
std::string objType = parsed["object"];
printf("Reply of type object :%s\n", objType.c_str());
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"];
}
else if (objType == "chat.completion") {
std::string content = parsed["choices"][0]["message"]["content"];
// std::string content = parsed["choices"][0]["message"]["content"];
}
_messageHistory.push_back({
{"role", "assistant"},
{"content", content}
});
else
if (objType == "chat.completion")
{
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()));
sendReply(message);
}
}
else
{
BMessage message(kSendReply);
message.AddString("text", "EMPTY BODY");
// printf("we got content:%s",content.c_str());
BMessage message(kSendReply);
message.AddString("text", BString(content.c_str()));
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);
}
} else {
BMessage message(kSendReply);
message.AddString("text", "EMPTY BODY");
sendReply(message);
}
}
}
}
}
break;
@ -154,12 +189,12 @@ void Conversation::MessageReceived(BMessage* message) {
} break;
case UrlEvent::BytesWritten: {
// _infoView->SetText("Some bytes written..");
// _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->SetTo(numBytes);
//_progress->SetMaxValue(totalBytes);
}
} break;
@ -174,11 +209,11 @@ void Conversation::MessageReceived(BMessage* message) {
//_infoView->SetText("Download Progress..");
}
} break;
default:
BHandler::MessageReceived(message);
break;
}
default:
BHandler::MessageReceived(message);
break;
}
}
std::string Conversation::buildBearerKey() {
@ -191,7 +226,6 @@ std::string Conversation::buildBearerKey() {
std::string bearer = std::string("Bearer ") + std::string(key);
return bearer;
}
void Conversation::loadModels() {
@ -199,7 +233,6 @@ void Conversation::loadModels() {
auto url = BUrl("https://api.openai.com/v1/models");
BHttpRequest request = BHttpRequest(url);
request.SetMethod(BHttpMethod::Get);
BHttpFields fields = BHttpFields();
fields.AddField("Authorization", buildBearerKey());
@ -213,33 +246,45 @@ void Conversation::loadModels() {
if (_lastResult) {
printf("Result has identity: %d\n", _lastResult->Identity());
}
}
void Conversation::ask(const std::string& prompt) {
// printf("Asking prompt: %s",prompt.c_str());
void Conversation::setModel(const std::string &model) {
_activeModel = model;
printf("Conversation will use model:%s\n", _activeModel.c_str());
}
void Conversation::ask(const std::string &prompt) {
_messageHistory.push_back({
{"role", "user"},
{"content", 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);
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);
json bodyJson = {
{"model", "gpt-4o"},
{"messages", {{{"role", "user"}, {"content", prompt}}}}};
// 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}
};
std::string body = bodyJson.dump();
@ -256,28 +301,22 @@ void Conversation::ask(const std::string& prompt) {
}
}
BString Conversation::ReadOpenAIKey() {
BPath configPath;
if (find_directory(B_USER_SETTINGS_DIRECTORY, &configPath) != B_OK)
return "error: couldn't find config directory";
// /boot/home/config/openai_key
configPath.Append("openai_key");
BFile file(configPath.Path(), B_READ_ONLY);
printf("full path:%s\n", configPath.Path());
if (file.InitCheck() != B_OK)
{
validKey=false;
if (file.InitCheck() != B_OK) {
validKey = false;
return "error: couldn't open key file ";
}
}
off_t size;
file.GetSize(&size);
@ -288,10 +327,8 @@ BString Conversation::ReadOpenAIKey() {
BString result(buffer);
delete[] buffer;
validKey=true;
validKey = true;
return result;
}