diff --git a/bin/assets/i18n/en.xml b/bin/assets/i18n/en.xml
index 6f9e02b67..ca24ef83d 100644
--- a/bin/assets/i18n/en.xml
+++ b/bin/assets/i18n/en.xml
@@ -21,7 +21,6 @@
This operation cannot be reverted!
AI Conversations History
What can I help with?
- Rename Conversation
Any https or http URL
Any name...
App Output
diff --git a/src/tools/ecode/plugins/aiassistant/chatui.cpp b/src/tools/ecode/plugins/aiassistant/chatui.cpp
index 17acaa0b5..1a6f1c86f 100644
--- a/src/tools/ecode/plugins/aiassistant/chatui.cpp
+++ b/src/tools/ecode/plugins/aiassistant/chatui.cpp
@@ -402,8 +402,8 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
setCmd( "ai-toggle-lock-chat", [this] { renameChat( mSummary, true ); } );
setCmd( "ai-rename-chat", [this] {
- UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::INPUT,
- i18n( "ai_rename_chat", "Rename Conversation" ) );
+ UIMessageBox* msgBox = UIMessageBox::New(
+ UIMessageBox::INPUT, i18n( "rename_conversation", "Rename Conversation" ) );
msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
std::string newName( msgBox->getTextInput()->getText().toUtf8() );
if ( newName.empty() || mSummary == newName )
@@ -420,6 +420,8 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
msgBox->showWhenReady();
} );
+ setCmd( "ai-regenerate-title", [this] { regenerateChatName(); } );
+
setCmd( "new-ai-assistant", [this] {
if ( getPlugin() == nullptr )
return;
@@ -508,6 +510,13 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
menu->addSeparator();
+ if ( chatExistsInDisk() ) {
+ menu->add( i18n( "regenerate_conversation_title", "Regenerate Conversation Title" ),
+ getUISceneNode()->findIconDrawable( "refresh", PixelDensity::dpToPxI( 12 ) ),
+ getKeyBindings().getCommandKeybindString( "ai-regenerate-title" ) )
+ ->setId( "ai-regenerate-title" );
+ }
+
menu->add( i18n( "refresh_model_ui", "Refresh Local Models" ),
getUISceneNode()->findIconDrawable( "refresh", PixelDensity::dpToPxI( 12 ) ),
getKeyBindings().getCommandKeybindString( "ai-refresh-local-models" ) )
@@ -1130,6 +1139,73 @@ static bool allNewLines( const std::string& s ) {
return !s.empty() && std::all_of( s.begin(), s.end(), []( char c ) { return c == '\n'; } );
}
+void LLMChatUI::generateChatName( bool isRenaming ) {
+ auto apiKey = AIAssistantPlugin::getApiKeyFromProvider( mCurModel.provider, getPlugin() );
+ if ( !apiKey ) {
+ showMsg( getUISceneNode()->i18n( "configure_api_key",
+ "You must first configure your provider api key." ) );
+ return;
+ }
+ std::string apiKeyStr{ *apiKey };
+ std::string apiUrl( prepareApiUrl( apiKeyStr ) );
+ auto model = mCurModel;
+
+ 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(), true );
+
+ jchat["messages"].push_back( { { "role", LLMChat::roleToString( LLMChat::Role::User ) },
+ { "content", SummaryPrompt } } );
+
+ auto chatstr = jchat.dump();
+
+ mSummaryRequest =
+ std::make_unique( apiUrl, apiKeyStr, chatstr, model.provider );
+
+ mSummaryRequest->doneCb = [this, isRenaming]( const LLMChatCompletionRequest& req,
+ Http::Response& response ) {
+ auto status = response.getStatus();
+ String oldSummary = std::move( mSummary );
+
+ if ( status == Http::Response::Ok ) {
+ mSummary = String::trim( req.getResponse() );
+ String::trimInPlace( mSummary, '\n' );
+ String::trimInPlace( mSummary, ' ' );
+ String::trimInPlace( mSummary, '"' );
+ } else {
+ // TODO: Implement generating a summary based on the user prompt (take the
+ // first few words)
+ mSummary = i18n( "untitled_conversation", "Untitled Conversation" );
+ }
+
+ if ( isRenaming ) {
+ String newSummary = std::move( mSummary );
+ mSummary = std::move( oldSummary );
+ runOnMainThread(
+ [this, newSummary = std::move( newSummary )] { renameChat( newSummary ); } );
+ } else
+ saveChat();
+
+ runOnMainThread( [this] {
+ updateTabTitle();
+ mSummaryRequest.reset();
+ } );
+ };
+ mSummaryRequest->requestAsync();
+}
+
+bool LLMChatUI::chatExistsInDisk() const {
+ return FileSystem::fileExists( getFilePath() );
+}
+
+void LLMChatUI::regenerateChatName() {
+ if ( chatExistsInDisk() )
+ generateChatName( true );
+}
+
void LLMChatUI::doRequest() {
if ( mRequest )
return;
@@ -1193,86 +1269,50 @@ void LLMChatUI::doRequest() {
removeLastChat();
};
- 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();
+ 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, 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" ) ) {
- showMsg( resp["error"].value( "message", "" ) );
- } else if ( resp.contains( "error" ) && resp["error"].is_string() ) {
- showMsg( URI::decode( resp.value( "error", "" ) ) );
- } else if ( resp.is_array() && !resp.empty() ) {
- const auto& first = resp.at( 0 );
- if ( first.contains( "error" ) && first["error"].contains( "message" ) )
- showMsg( first["error"].value( "message", "" ) );
- else
- showMsg( statusDesc );
- } else {
- showMsg( statusDesc );
- }
-
- removeLastChat();
- }
- mRequest.reset();
- toggleEnableChats( true );
- editor->setEnabled( true );
-
- mChatStop->setVisible( false )->setEnabled( false );
- mChatRun->setVisible( true )->setEnabled( true );
-
- if ( editor->hasFocus() )
- mChatInput->setFocus();
-
- if ( !mChatIsPrivate && !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(), true );
-
- jchat["messages"].push_back(
- { { "role", LLMChat::roleToString( LLMChat::Role::User ) },
- { "content", SummaryPrompt } } );
-
- auto chatstr = jchat.dump();
-
- mSummaryRequest = std::make_unique(
- apiUrl, apiKeyStr, chatstr, model.provider );
-
- mSummaryRequest->doneCb = [this]( const LLMChatCompletionRequest& req,
- Http::Response& response ) {
- auto status = response.getStatus();
- if ( status == Http::Response::Ok ) {
- mSummary = String::trim( req.getResponse() );
- String::trimInPlace( mSummary, '\n' );
- String::trimInPlace( mSummary, ' ' );
- String::trimInPlace( mSummary, '"' );
+ 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" ) ) {
+ showMsg( resp["error"].value( "message", "" ) );
+ } else if ( resp.contains( "error" ) && resp["error"].is_string() ) {
+ showMsg( URI::decode( resp.value( "error", "" ) ) );
+ } else if ( resp.is_array() && !resp.empty() ) {
+ const auto& first = resp.at( 0 );
+ if ( first.contains( "error" ) && first["error"].contains( "message" ) )
+ showMsg( first["error"].value( "message", "" ) );
+ else
+ showMsg( statusDesc );
} else {
- // TODO: Implement generating a summary based on the user prompt (take the
- // first few words)
- mSummary = i18n( "untitled_conversation", "Untitled Conversation" );
+ showMsg( statusDesc );
}
+
+ removeLastChat();
+ }
+ mRequest.reset();
+ toggleEnableChats( true );
+ editor->setEnabled( true );
+
+ mChatStop->setVisible( false )->setEnabled( false );
+ mChatRun->setVisible( true )->setEnabled( true );
+
+ if ( editor->hasFocus() )
+ mChatInput->setFocus();
+
+ if ( !mChatIsPrivate && !mSummaryRequest && mSummary.empty() &&
+ status == Http::Response::Ok ) {
+ generateChatName( false );
+ } else {
saveChat();
- runOnMainThread( [this] {
- updateTabTitle();
- mSummaryRequest.reset();
- } );
- };
- mSummaryRequest->requestAsync();
- } else {
- saveChat();
- }
- } );
- };
+ }
+ } );
+ };
mRequest->requestAsync();
}
@@ -1284,7 +1324,7 @@ void LLMChatUI::toggleEnableChat( UIWidget* chat, bool enabled ) {
chat->findByClass( "erase_but" )->setEnabled( enabled );
chat->findByClass( "move_up" )->setEnabled( enabled );
chat->findByClass( "move_down" )->setEnabled( enabled );
- chat->findByClass( "copy_contents" )->setEnabled( enabled );
+ // chat->findByClass( "copy_contents" )->setEnabled( enabled );
}
void LLMChatUI::toggleEnableChats( bool enabled ) {
diff --git a/src/tools/ecode/plugins/aiassistant/chatui.hpp b/src/tools/ecode/plugins/aiassistant/chatui.hpp
index 39dda0b53..f8e5edec6 100644
--- a/src/tools/ecode/plugins/aiassistant/chatui.hpp
+++ b/src/tools/ecode/plugins/aiassistant/chatui.hpp
@@ -89,6 +89,8 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter {
void setManager( PluginManager* manager ) { mManager = manager; }
+ bool chatExistsInDisk() const;
+
protected:
UUID mUUID;
std::string mSummary;
@@ -195,6 +197,10 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter {
void insertFileToDocument( std::string path, std::shared_ptr cdoc );
void replaceFileLinksToContents( std::string& text );
+
+ void generateChatName( bool isRenaming );
+
+ void regenerateChatName();
};
} // namespace ecode