Generate summary and save conversation WIP.

This commit is contained in:
Martín Lucas Golini
2025-03-15 01:25:05 -03:00
parent 9e6a29653f
commit 129057ee03
8 changed files with 115 additions and 24 deletions
+12 -6
View File
@@ -38,7 +38,8 @@
"display_name": "Claude 3.5 Haiku",
"max_output_tokens": 8192,
"max_tokens": 200000,
"name": "claude-3-5-haiku-latest"
"name": "claude-3-5-haiku-latest",
"cheapest": true
},
{
"cache_configuration": null,
@@ -79,7 +80,8 @@
"display_name": "DeepSeek Chat",
"max_output_tokens": 8192,
"max_tokens": 64000,
"name": "deepseek-chat"
"name": "deepseek-chat",
"cheapest": true
},
{
"display_name": "DeepSeek Reasoner",
@@ -121,7 +123,8 @@
{
"display_name": "Gemini 2.0 Flash Lite",
"max_tokens": 1000000,
"name": "gemini-2.0-flash-lite-preview"
"name": "gemini-2.0-flash-lite-preview",
"cheapest": true
}
]
},
@@ -148,7 +151,8 @@
{
"display_name": "mistral-small-latest",
"max_tokens": 32000,
"name": "mistral-small-latest"
"name": "mistral-small-latest",
"cheapest": true
},
{
"display_name": "open-mistral-nemo",
@@ -190,7 +194,8 @@
},
{
"max_tokens": 128000,
"name": "gpt-4o-mini"
"name": "gpt-4o-mini",
"cheapest": true
},
{
"max_tokens": 200000,
@@ -216,7 +221,8 @@
"display_name": "xAI",
"models": [
{
"name": "grok-2-latest"
"name": "grok-2-latest",
"cheapest": true
},
{
"name": "grok-3-latest"
+11 -6
View File
@@ -1109,9 +1109,12 @@ Http::Response Http::downloadRequest( const Http::Request& request, IOStream& wr
// Close the connection
if ( !mConnection->isKeepAlive() ) {
mConnection->disconnect();
HttpConnection* connection = mConnection;
eeSAFE_DELETE( connection );
mConnection = NULL;
if ( mConnection ) {
HttpConnection* connection = mConnection;
eeSAFE_DELETE( connection );
mConnection = NULL;
}
}
}
@@ -1177,9 +1180,11 @@ void Http::AsyncRequest::run() {
}
// The Async Request destroys the socket used to create the request
HttpConnection* connection = mHttp->mConnection;
eeSAFE_DELETE( connection );
mHttp->mConnection = NULL;
if ( mHttp->mConnection ) {
HttpConnection* connection = mHttp->mConnection;
eeSAFE_DELETE( connection );
mHttp->mConnection = NULL;
}
mRunning = false;
}
@@ -57,15 +57,23 @@ static std::map<std::string, LLMProvider> parseLLMProviders( const nlohmann::jso
if ( modelJson.contains( "display_name" ) ) {
model.displayName = modelJson["display_name"].get<std::string>();
}
if ( modelJson.contains( "max_tokens" ) ) {
model.maxTokens = modelJson["max_tokens"].get<std::size_t>();
}
if ( modelJson.contains( "max_output_tokens" ) ) {
model.maxOutputTokens = modelJson["max_output_tokens"].get<std::size_t>();
}
if ( modelJson.contains( "default_temperature" ) ) {
model.defaultTemperature = modelJson["default_temperature"].get<double>();
}
if ( modelJson.contains( "cheapest" ) ) {
model.cheapest = modelJson.value( "cheapest", false );
}
if ( modelJson.contains( "cache_configuration" ) &&
!modelJson["cache_configuration"].is_null() ) {
const auto& cacheJson = modelJson["cache_configuration"];
+72 -9
View File
@@ -1,6 +1,7 @@
#include "aiassistantplugin.hpp"
#include "chatui.hpp"
#include <eepp/system/filesystem.hpp>
#include <eepp/ui/doc/syntaxdefinitionmanager.hpp>
#include <eepp/ui/uicodeeditor.hpp>
#include <eepp/ui/uidropdownlist.hpp>
@@ -14,6 +15,7 @@
#include <nlohmann/json.hpp>
using namespace EE::System;
using namespace EE::Window;
namespace ecode {
@@ -398,22 +400,36 @@ Uint32 LLMChatUI::onMessage( const NodeMessage* msg ) {
return 0;
}
nlohmann::json LLMChatUI::serializeChat() {
nlohmann::json LLMChatUI::serializeChat( const LLMModel& model ) {
nlohmann::json j = {
{ "model", mCurModel.name }, { "stream", true }, { "messages", chatToJson() } };
if ( mCurModel.maxOutputTokens )
j["max_tokens"] = *mCurModel.maxOutputTokens;
{ "model", model.name }, { "stream", true }, { "messages", chatToJson() } };
if ( model.maxOutputTokens )
j["max_tokens"] = *model.maxOutputTokens;
return j;
}
nlohmann::json LLMChatUI::serialize() {
nlohmann::json j;
j["uuid"] = mUUID.toString();
j["chat"] = serializeChat();
j["chat"] = serializeChat( mCurModel );
return j;
}
void unserialize( const nlohmann::json& /*payload*/ ) {}
void LLMChatUI::unserialize( const nlohmann::json& /*payload*/ ) {}
void LLMChatUI::saveChat() {
auto plugin = getPlugin();
if ( plugin == nullptr )
return;
std::string pluginsStatePath( plugin->getManager()->getPluginsPath() + "plugins_state" +
FileSystem::getOSSlash() + "aiassistant" +
FileSystem::getOSSlash() );
if ( !FileSystem::fileExists( pluginsStatePath ) )
FileSystem::makeDir( pluginsStatePath, true );
std::string path( pluginsStatePath + mSummary + ".json" );
FileSystem::fileWrite( path, serialize().dump( 2 ) );
}
std::string LLMChatUI::prepareApiUrl( const std::string& apiKey ) {
const auto& provider = mProviders[mCurModel.provider];
@@ -441,9 +457,11 @@ void LLMChatUI::doRequest() {
UIWidget* chat = addChatUI( LLMChat::Role::Assistant );
toggleEnableChats( false );
auto model = mCurModel;
auto* editor = chat->findByClass<UICodeEditor>( "data_ui" );
std::string apiUrl( prepareApiUrl( apiKeyStr ) );
mRequest = std::make_unique<LLMChatCompletionRequest>(
prepareApiUrl( apiKeyStr ), apiKeyStr, serializeChat().dump(), mCurModel.provider );
apiUrl, apiKeyStr, serializeChat( model ).dump(), model.provider );
mRequest->streamedResponseCb = [this, editor]( const std::string& chunk ) {
auto conversation = chunk;
editor->runOnMainThread( [this, conversation = std::move( conversation ), editor] {
@@ -452,11 +470,14 @@ void LLMChatUI::doRequest() {
resizeToFit( editor );
} );
};
mRequest->doneCb = [this, editor]( const LLMChatCompletionRequest&, Http::Response& response ) {
mRequest->doneCb = [this, editor, apiUrl = std::move( apiUrl ),
apiKeyStr = std::move( apiKeyStr ), model = std::move( model )](
const LLMChatCompletionRequest&, Http::Response& response ) {
auto status = response.getStatus();
auto statusDesc = response.getStatusDescription();
runOnMainThread( [this, editor, status, statusDesc] {
runOnMainThread( [this, editor, status, statusDesc, apiUrl = std::move( apiUrl ),
apiKeyStr = std::move( apiKeyStr ), model = std::move( model )] {
if ( status != Http::Response::Ok ) {
auto resp = nlohmann::json::parse( mRequest->getStream(), nullptr, false );
if ( resp.contains( "error" ) && resp["error"].contains( "message" ) ) {
@@ -477,6 +498,38 @@ void LLMChatUI::doRequest() {
if ( editor->hasFocus() )
mChatInput->setFocus();
if ( !mSummaryRequest && mSummary.empty() && status == Http::Response::Ok ) {
static const std::string SummaryPrompt =
"Generate a concise 3-7 word title for this conversation, omitting "
"punctuation. Go "
"straight to the title, without any preamble and prefix like `Here's a concise "
"suggestion:...` or `Title:`. Ignore this message for the summary generation.";
auto jchat = serializeChat( getCheapestModelFromCurrentProvider() );
jchat["messages"].push_back(
{ { "role", LLMChat::roleToString( LLMChat::Role::User ) },
{ "content", SummaryPrompt } } );
auto chatstr = jchat.dump();
mSummaryRequest = std::make_unique<LLMChatCompletionRequest>(
apiUrl, apiKeyStr, chatstr, model.provider );
mSummaryRequest->doneCb = [this]( const LLMChatCompletionRequest& req,
Http::Response& response ) {
auto status = response.getStatus();
if ( status == Http::Response::Ok ) {
mSummary = req.getResponse();
saveChat();
}
runOnMainThread( [this] { mSummaryRequest.reset(); } );
};
mSummaryRequest->requestAsync();
} else {
saveChat();
}
} );
};
mRequest->requestAsync();
@@ -612,4 +665,14 @@ AIAssistantPlugin* LLMChatUI::getPlugin() {
return nullptr;
}
const LLMModel& LLMChatUI::getCheapestModelFromCurrentProvider() const {
auto providerIt = mProviders.find( mCurModel.provider );
if ( providerIt != mProviders.end() ) {
for ( const auto& model : providerIt->second.models )
if ( model.cheapest )
return model;
}
return mCurModel;
}
} // namespace ecode
@@ -53,6 +53,7 @@ class LLMChatUI : public UILinearLayout {
protected:
UUID mUUID;
std::string mSummary;
PluginManager* mManager{ nullptr };
UIWidget* mChatsList{ nullptr };
UICodeEditor* mChatInput{ nullptr };
@@ -62,6 +63,7 @@ class LLMChatUI : public UILinearLayout {
UIScrollView* mChatScrollView{ nullptr };
UIDropDownList* mModelDDL{ nullptr };
std::unique_ptr<LLMChatCompletionRequest> mRequest;
std::unique_ptr<LLMChatCompletionRequest> mSummaryRequest;
LLMProviders mProviders;
LLMModel mCurModel;
std::unordered_map<String::HashType, LLMModel> mModelsMap;
@@ -72,7 +74,7 @@ class LLMChatUI : public UILinearLayout {
void showMsg( String msg );
nlohmann::json serializeChat();
nlohmann::json serializeChat( const LLMModel& model );
nlohmann::json chatToJson();
@@ -101,6 +103,10 @@ class LLMChatUI : public UILinearLayout {
void setProviders( LLMProviders&& providers );
virtual Uint32 onMessage( const NodeMessage* );
const LLMModel& getCheapestModelFromCurrentProvider() const;
void saveChat();
};
} // namespace ecode
@@ -103,7 +103,7 @@ void LLMChatCompletionRequest::cancel() {
mCancel = true;
}
const std::string& LLMChatCompletionRequest::getStream() {
const std::string& LLMChatCompletionRequest::getStream() const {
return mStream.getStream();
}
@@ -32,7 +32,9 @@ class LLMChatCompletionRequest {
void cancel();
const std::string& getStream();
const std::string& getStream() const;
const std::string& getResponse() const { return mResponse; }
protected:
URI mUrl;
@@ -22,6 +22,7 @@ struct LLMModel {
std::optional<double> defaultTemperature;
std::optional<LLMCacheConfiguration> cacheConfiguration;
bool isEphemeral{ false };
bool cheapest{ false };
};
struct LLMProvider {