From 03acb3f95bca724ee9c80077ecb258ae6a4ab630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sat, 21 Mar 2026 15:07:32 -0300 Subject: [PATCH] Branched WIP that needs to be merged with the new changes from develop. --- bin/assets/plugins/aiassistant.json | 2 +- .../ecode/plugins/aiassistant/acpclient.cpp | 10 + .../ecode/plugins/aiassistant/acpclient.hpp | 2 + .../ecode/plugins/aiassistant/acpprotocol.cpp | 15 + .../ecode/plugins/aiassistant/acpprotocol.hpp | 16 ++ .../plugins/aiassistant/agentsession.cpp | 41 ++- .../plugins/aiassistant/agentsession.hpp | 6 +- .../ecode/plugins/aiassistant/chatui.cpp | 262 +++++++++++++++--- .../ecode/plugins/aiassistant/chatui.hpp | 12 + 9 files changed, 318 insertions(+), 48 deletions(-) diff --git a/bin/assets/plugins/aiassistant.json b/bin/assets/plugins/aiassistant.json index cce3ba11d..16fc7df81 100644 --- a/bin/assets/plugins/aiassistant.json +++ b/bin/assets/plugins/aiassistant.json @@ -506,7 +506,7 @@ "gemini-cli": { "enabled": true, "command": "gemini", - "args": ["--experimental-acp"] + "args": ["--experimental-acp", "--model=auto"] }, "opencode": { "enabled": true, diff --git a/src/tools/ecode/plugins/aiassistant/acpclient.cpp b/src/tools/ecode/plugins/aiassistant/acpclient.cpp index f91b13271..fb3425158 100644 --- a/src/tools/ecode/plugins/aiassistant/acpclient.cpp +++ b/src/tools/ecode/plugins/aiassistant/acpclient.cpp @@ -216,6 +216,16 @@ void ACPClient::newSession( const NewSessionRequest& req, } ); } +void ACPClient::loadSession( const LoadSessionRequest& req, + const std::function& cb ) { + write( { { "method", "session/load" }, { "params", req.toJson() } }, + [cb]( const IdType&, const json& resp ) { + if ( resp.contains( "result" ) && cb ) { + cb( LoadSessionResponse( resp["result"] ) ); + } + } ); +} + void ACPClient::prompt( const PromptRequest& req, const std::function& cb ) { write( { { "method", "session/prompt" }, { "params", req.toJson() } }, diff --git a/src/tools/ecode/plugins/aiassistant/acpclient.hpp b/src/tools/ecode/plugins/aiassistant/acpclient.hpp index 35227345d..6b8c3571d 100644 --- a/src/tools/ecode/plugins/aiassistant/acpclient.hpp +++ b/src/tools/ecode/plugins/aiassistant/acpclient.hpp @@ -47,6 +47,8 @@ class ACPClient { const std::function& cb ); void newSession( const NewSessionRequest& req, const std::function& cb ); + void loadSession( const LoadSessionRequest& req, + const std::function& cb ); void prompt( const PromptRequest& req, const std::function& cb ); // Notifications to agent diff --git a/src/tools/ecode/plugins/aiassistant/acpprotocol.cpp b/src/tools/ecode/plugins/aiassistant/acpprotocol.cpp index 03bc4c705..2e7e0a8fd 100644 --- a/src/tools/ecode/plugins/aiassistant/acpprotocol.cpp +++ b/src/tools/ecode/plugins/aiassistant/acpprotocol.cpp @@ -60,6 +60,21 @@ NewSessionResponse::NewSessionResponse( const json& body ) { configOptions = body["configOptions"]; } +json LoadSessionRequest::toJson() const { + json j = { { "sessionId", sessionId }, { "cwd", cwd } }; + if ( mcpServers.is_null() ) { + j["mcpServers"] = json::array(); + } else { + j["mcpServers"] = mcpServers; + } + return j; +} + +LoadSessionResponse::LoadSessionResponse( const json& body ) { + if ( body.contains( "configOptions" ) ) + configOptions = body["configOptions"]; +} + json PromptRequest::toJson() const { return { { "sessionId", sessionId }, { "prompt", prompt } }; } diff --git a/src/tools/ecode/plugins/aiassistant/acpprotocol.hpp b/src/tools/ecode/plugins/aiassistant/acpprotocol.hpp index b7873985f..d05d0ce67 100644 --- a/src/tools/ecode/plugins/aiassistant/acpprotocol.hpp +++ b/src/tools/ecode/plugins/aiassistant/acpprotocol.hpp @@ -63,6 +63,22 @@ struct NewSessionResponse { NewSessionResponse( const json& body ); }; +struct LoadSessionRequest { + std::string sessionId; + std::string cwd; + json mcpServers; + + LoadSessionRequest() = default; + json toJson() const; +}; + +struct LoadSessionResponse { + json configOptions; + + LoadSessionResponse() = default; + LoadSessionResponse( const json& body ); +}; + struct PromptRequest { std::string sessionId; json prompt; // Array of ContentBlock diff --git a/src/tools/ecode/plugins/aiassistant/agentsession.cpp b/src/tools/ecode/plugins/aiassistant/agentsession.cpp index 08b6b140c..823f71ff3 100644 --- a/src/tools/ecode/plugins/aiassistant/agentsession.cpp +++ b/src/tools/ecode/plugins/aiassistant/agentsession.cpp @@ -37,6 +37,38 @@ bool AgentSession::start( const std::function& onReady ) { return false; } +bool AgentSession::startLoaded( const std::string& sessionId, + const std::function& onReady ) { + if ( mClient->start() ) { + InitializeRequest req; + req.clientCapabilities.terminal = true; + req.clientCapabilities.fsReadTextFile = true; + req.clientCapabilities.fsWriteTextFile = true; + + mClient->initialize( req, [this, sessionId, onReady]( const InitializeResponse& ires ) { + if ( ires.agentCapabilities.loadSession ) { + LoadSessionRequest lreq; + lreq.sessionId = sessionId; + lreq.cwd = mClient->isReady() ? mClient->getConfig().workingDirectory : ""; + mClient->loadSession( lreq, [this, sessionId, onReady]( const LoadSessionResponse& ) { + mSessionId = sessionId; + if ( onReady ) + onReady( true ); + } ); + } else { + // Agent doesn't support loading, fallback to new session? + // For now let's just fail or call onReady(false) + if ( onReady ) + onReady( false ); + } + } ); + return true; + } + if ( onReady ) + onReady( false ); + return false; +} + void AgentSession::stop() { if ( mClient ) mClient->stop(); @@ -58,6 +90,11 @@ void AgentSession::cancel() { } } +void AgentSession::setTerminalData( const std::string& terminalId, UITerminal* uiTerm ) { + mTerminals[terminalId] = + TermData{ uiTerm->getTerm(), uiTerm->getTerm()->getTerminal(), uiTerm }; +} + void AgentSession::setupClient() { mClient->onSessionUpdate = [this]( const json& msg ) { if ( onSessionUpdate ) @@ -92,10 +129,8 @@ void AgentSession::setupClient() { CreateTerminalResponse res; std::string termId = String::format( "term-%u", String::hash( req.command ) ); res.terminalId = termId; - // Wait for UI? No, ACPClient is running in threads. We just trigger the event - // and return the ID. if ( onTerminalCreated ) { - onTerminalCreated( nullptr, termId ); + onTerminalCreated( req, termId ); } cb( res ); }; diff --git a/src/tools/ecode/plugins/aiassistant/agentsession.hpp b/src/tools/ecode/plugins/aiassistant/agentsession.hpp index 9cb6f1920..f78b84314 100644 --- a/src/tools/ecode/plugins/aiassistant/agentsession.hpp +++ b/src/tools/ecode/plugins/aiassistant/agentsession.hpp @@ -21,6 +21,7 @@ class AgentSession { ~AgentSession(); bool start( const std::function& onReady ); + bool startLoaded( const std::string& sessionId, const std::function& onReady ); void stop(); void prompt( const PromptRequest& req, const std::function& cb ); @@ -32,11 +33,14 @@ class AgentSession { std::function )> onRequestPermission; - std::function onTerminalCreated; + std::function + onTerminalCreated; std::string getSessionId() const { return mSessionId; } ACPClient* getClient() const { return mClient.get(); } + void setTerminalData( const std::string& terminalId, UITerminal* uiTerm ); + protected: std::shared_ptr mThreadPool; std::unique_ptr mClient; diff --git a/src/tools/ecode/plugins/aiassistant/chatui.cpp b/src/tools/ecode/plugins/aiassistant/chatui.cpp index b81a26149..d2ed252fe 100644 --- a/src/tools/ecode/plugins/aiassistant/chatui.cpp +++ b/src/tools/ecode/plugins/aiassistant/chatui.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -239,8 +240,7 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) : mChatAgentMode = find( "llm_agent_mode" ); mChatAgentMode->on( Event::OnValueChange, [this]( auto ) { mIsAgentMode = mChatAgentMode->isSelected(); - mModelDDL->setVisible( !mIsAgentMode ); - mAgentDDL->setVisible( mIsAgentMode ); + updateAgentModeUI(); } ); // mRefreshModels = find( "refresh_model_ui" ); @@ -649,6 +649,7 @@ LLMChatUI::~LLMChatUI() { config.partition = getSplitter()->getSplitPartition(); config.modelProvider = mCurModel.provider; config.modelName = mCurModel.name; + config.agentName = mCurAgent; getPlugin()->setConfig( std::move( config ) ); } } @@ -1025,7 +1026,15 @@ void LLMChatUI::fillAgentDropDownList( UIDropDownList* agentDDL ) { agentDDL->getListBox()->addListBoxItems( std::move( agents ) ); agentDDL->getListBox()->setSelected( selectedIndex ); agentDDL->on( Event::OnValueChange, [this, agentDDL]( auto ) { - mCurAgent = agentDDL->getListBox()->getItemSelectedText().toUtf8(); + auto newAgent = agentDDL->getListBox()->getItemSelectedText().toUtf8(); + if ( newAgent != mCurAgent ) { + mCurAgent = newAgent; + if ( mAgentSession ) { + mAgentSession->stop(); + mAgentSession.reset(); + } + updateTabTitle(); + } } ); } @@ -1047,6 +1056,106 @@ void LLMChatUI::writeToLastChat( const std::string& text ) { } ); } +void LLMChatUI::updateAgentModeUI() { + mModelDDL->setVisible( !mIsAgentMode ); + mAgentDDL->setVisible( mIsAgentMode ); + mChatAdd->setVisible( !mIsAgentMode ); + mChatUserRole->setVisible( !mIsAgentMode ); + + auto chats = mChatsList->findAllByClass( "llm_conversation" ); + for ( auto chat : chats ) { + if ( auto roleUi = chat->findByClass( "role_ui" ) ) + roleUi->setVisible( !mIsAgentMode ); + if ( auto moveUp = chat->findByClass( "move_up" ) ) + moveUp->setVisible( !mIsAgentMode ); + if ( auto moveDown = chat->findByClass( "move_down" ) ) + moveDown->setVisible( !mIsAgentMode ); + if ( auto eraseBut = chat->findByClass( "erase_but" ) ) + eraseBut->setVisible( !mIsAgentMode ); + } +} + +void LLMChatUI::setupAgentSession() { + auto it = mAgents.find( mCurAgent ); + if ( it == mAgents.end() ) + return; + + acp::ACPClient::Config config; + config.command = it->second.command; + config.args = it->second.args; + config.environment = it->second.environment; + config.workingDirectory = getPlugin()->getPluginContext()->getCurrentProject(); + + mAgentSession = + std::make_unique( getUISceneNode()->getThreadPool(), config ); + + mAgentSession->onSessionUpdate = [this]( const nlohmann::json& msg ) { + auto sessionUpdate = msg.value( "sessionUpdate", "" ); + if ( sessionUpdate == "agent_message_chunk" || sessionUpdate == "agent_thought_chunk" ) { + if ( msg.contains( "content" ) && msg["content"].contains( "text" ) ) { + auto chunk = msg["content"].value( "text", "" ); + writeToLastChat( chunk ); + } + } else if ( sessionUpdate == "tool_call" ) { + std::string toolStr = "\n> Tool Call: " + msg.value( "title", "" ) + "\n"; + writeToLastChat( toolStr ); + } else if ( sessionUpdate == "plan" ) { + std::string planStr = "\n> Plan Updated:\n"; + if ( msg.contains( "plan" ) && msg["plan"].contains( "steps" ) && + msg["plan"]["steps"].is_array() ) { + for ( const auto& step : msg["plan"]["steps"] ) { + planStr += "- [" + step.value( "status", "" ) + "] " + + step.value( "title", "" ) + "\n"; + } + } + writeToLastChat( planStr ); + } else if ( sessionUpdate == "available_commands_update" ) { + if ( msg.contains( "availableCommands" ) && msg["availableCommands"].is_array() ) { + mAvailableCommands.clear(); + for ( const auto& cmd : msg["availableCommands"] ) { + mAvailableCommands.push_back( + { cmd.value( "name", "" ), cmd.value( "description", "" ) } ); + } + } + } + }; + + mAgentSession->onRequestPermission = [this]( const auto& req, auto cb ) { + runOnMainThread( [this, req, cb]() { addPermissionUI( req, cb ); } ); + }; + + mAgentSession->onTerminalCreated = [this]( const acp::CreateTerminalRequest& req, + const std::string& termId ) { + runOnMainThread( [this, req, termId] { + find( "chat_presentation" )->setVisible( false ); + UIWidget* bubble = mChatsList->getUISceneNode()->loadLayoutFromString( + R"xml()xml", + mChatsList ); + + std::unordered_map env; + for ( const auto& e : req.env ) + env[e.name] = e.value; + + auto* uiTerm = eterm::UI::UITerminal::New( + getPlugin()->getPluginContext()->getTerminalFont(), + getPlugin() + ->getPluginContext() + ->termConfig() + .fontSize.asPixels( + getUISceneNode()->getPixelsSize().getWidth(), + getUISceneNode()->getPixelsSize(), getUISceneNode()->getDPI(), + getUISceneNode()->getUIThemeManager()->getDefaultFontSize() ), + Sizef( 0, 0 ), req.command, req.args, env, + req.cwd ? *req.cwd : getPlugin()->getPluginContext()->getCurrentProject(), 10000, + nullptr, false, false ); + uiTerm->setParent( bubble ); + uiTerm->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); + + mAgentSession->setTerminalData( termId, uiTerm ); + } ); + }; +} + void LLMChatUI::doAgentRequest() { if ( !mAgentSession ) { auto it = mAgents.find( mCurAgent ); @@ -1058,14 +1167,7 @@ void LLMChatUI::doAgentRequest() { mChatRun->setVisible( false )->setEnabled( false ); mChatStop->setVisible( true )->setEnabled( true ); - acp::ACPClient::Config config; - config.command = it->second.command; - config.args = it->second.args; - config.environment = it->second.environment; - config.workingDirectory = getPlugin()->getPluginContext()->getCurrentProject(); - - mAgentSession = - std::make_unique( getUISceneNode()->getThreadPool(), config ); + setupAgentSession(); UIWidget* chat = addChatUI( LLMChat::Role::Assistant ); toggleEnableChats( false ); @@ -1078,31 +1180,6 @@ void LLMChatUI::doAgentRequest() { thinking->setInterval( [thinking] { thinking->rotate( 360 / 32 ); }, Seconds( 0.125 ), thinkingID ); - mAgentSession->onSessionUpdate = [this]( const nlohmann::json& msg ) { - auto sessionUpdate = msg.value( "sessionUpdate", "" ); - if ( sessionUpdate == "agent_message_chunk" || - sessionUpdate == "agent_thought_chunk" ) { - if ( msg.contains( "content" ) && msg["content"].contains( "text" ) ) { - auto chunk = msg["content"].value( "text", "" ); - writeToLastChat( chunk ); - } - } else if ( sessionUpdate == "tool_call" ) { - std::string toolStr = "\n> Tool Call: " + msg.value( "title", "" ) + "\n"; - writeToLastChat( toolStr ); - } else if ( sessionUpdate == "plan" ) { - std::string planStr = "\n> Plan Updated:\n"; - writeToLastChat( planStr ); - } - }; - - mAgentSession->onRequestPermission = [this]( const auto& req, auto cb ) { - runOnMainThread( [this, req, cb]() { addPermissionUI( req, cb ); } ); - }; - - mAgentSession->onTerminalCreated = []( UITerminal* term, const std::string& termId ) { - // This will be called from AgentSession. - }; - mAgentSession->start( [this]( bool ready ) { runOnMainThread( [this, ready]() { if ( ready ) { @@ -1156,8 +1233,30 @@ void LLMChatUI::sendAgentPrompt() { chats[chats.size() - 2]; // Assistant is the last one (added in doAgentRequest) auto* editor = lastChat->findByClass( "data_ui" ); std::string text = editor->getDocument().getText().toUtf8(); - replaceFileLinksToContents( text ); - req.prompt = { { { "type", "text" }, { "text", text } } }; + + bool isSlashCommand = false; + if ( String::startsWith( text, "/" ) ) { + auto spacePos = text.find( ' ' ); + std::string cmdName = text.substr( 1, spacePos == std::string::npos ? std::string::npos + : spacePos - 1 ); + std::string arg = spacePos == std::string::npos ? "" : text.substr( spacePos + 1 ); + + auto it = std::find_if( mAvailableCommands.begin(), mAvailableCommands.end(), + [&cmdName]( const SlashCommand& c ) { + return c.name == cmdName; + } ); + + if ( it != mAvailableCommands.end() ) { + req.prompt = { { { "type", "slash_command" }, + { "name", cmdName }, + { "argument", arg } } }; + isSlashCommand = true; + } + } + + if ( !isSlashCommand ) { + req.prompt = promptToContentBlocks( text ); + } } else { req.prompt = { { { "type", "text" }, { "text", "" } } }; } @@ -1166,7 +1265,7 @@ void LLMChatUI::sendAgentPrompt() { runOnMainThread( [this, res]() { auto chats = mChatsList->findAllByClass( "llm_conversation" ); if ( !chats.empty() ) { - auto* chat = chats[chats.size() - 1]; + auto* chat = chats.back(); auto* editor = chat->findByClass( "data_ui" ); auto* thinking = editor->findByClass( "thinking" ); if ( thinking ) { @@ -1184,7 +1283,11 @@ void LLMChatUI::sendAgentPrompt() { mChatRun->setVisible( true )->setEnabled( true ); if ( res.stopReason != "cancelled" ) { - saveChat(); + if ( !mChatIsPrivate && !mSummaryRequest && mSummary.empty() ) { + generateChatName( false ); + } else { + saveChat(); + } } } ); } ); @@ -1229,6 +1332,44 @@ void LLMChatUI::replaceFileLinksToContents( std::string& text ) { } } +nlohmann::json LLMChatUI::promptToContentBlocks( std::string text ) { + auto j = nlohmann::json::array(); + LuaPattern ptrn( "\n```file://([^`]*)```\n?" ); + PatternMatcher::Range matches[2]; + size_t lastPos = 0; + while ( ptrn.matches( text, matches, lastPos ) ) { + // Text before the file + if ( (size_t)matches[0].start > lastPos ) { + j.push_back( + { { "type", "text" }, { "text", text.substr( lastPos, matches[0].start - lastPos ) } } ); + } + + std::string path( text.substr( matches[1].start, matches[1].length() ) ); + if ( FileSystem::isRelativePath( path ) ) { + std::string prjPath( getPlugin()->getPluginContext()->getCurrentProject() ); + path = prjPath + path; + } + + std::string fileBuffer; + if ( FileSystem::fileGet( path, fileBuffer ) ) { + j.push_back( { { "type", "resource" }, + { "resource", { { "path", path }, { "content", fileBuffer } } } } ); + } + + lastPos = matches[0].end; + } + + if ( lastPos < text.size() ) { + j.push_back( { { "type", "text" }, { "text", text.substr( lastPos ) } } ); + } + + if ( j.empty() && !text.empty() ) { + j.push_back( { { "type", "text" }, { "text", text } } ); + } + + return j; +} + nlohmann::json LLMChatUI::chatToJson( bool forRequest ) { auto j = nlohmann::json::array(); auto chats = findAllByClass( "llm_conversation" ); @@ -1278,6 +1419,10 @@ nlohmann::json LLMChatUI::serialize() { std::string inputText( mChatInput->getDocument().getText().toUtf8() ); j["input"] = std::move( inputText ); j["locked"] = mChatLocked; + j["agent_mode"] = mIsAgentMode; + j["agent_name"] = mCurAgent; + if ( mAgentSession && !mAgentSession->getSessionId().empty() ) + j["session_id"] = mAgentSession->getSessionId(); return j; } @@ -1288,6 +1433,8 @@ std::string LLMChatUI::unserialize( const nlohmann::json& payload ) { mTimestamp = payload.value( "timestamp", 0 ); mSummary = payload.value( "summary", "" ); mChatLocked = payload.value( "locked", false ); + mIsAgentMode = payload.value( "agent_mode", false ); + mCurAgent = payload.value( "agent_name", "" ); std::string provider = payload.value( "provider", "" ); if ( payload.contains( "chat" ) && payload["chat"].is_object() ) { @@ -1296,11 +1443,30 @@ std::string LLMChatUI::unserialize( const nlohmann::json& payload ) { mCurModel = findModel( provider, model ); } - if ( mCurModel.name.empty() ) + if ( mCurModel.name.empty() && !mIsAgentMode ) return payload.value( "input", "" ); - if ( !selectModel( mModelDDL, mCurModel ) ) - fillModelDropDownList( mModelDDL ); + if ( mIsAgentMode ) { + mChatAgentMode->setSelected( true ); + updateAgentModeUI(); + if ( !mCurAgent.empty() ) { + auto index = mAgentDDL->getListBox()->getItemIndex( mCurAgent ); + if ( index != eeINDEX_NOT_FOUND ) + mAgentDDL->getListBox()->setSelected( index ); + } + + std::string sessionId = payload.value( "session_id", "" ); + if ( !sessionId.empty() ) { + auto it = mAgents.find( mCurAgent ); + if ( it != mAgents.end() ) { + setupAgentSession(); + mAgentSession->startLoaded( sessionId, []( bool ) {} ); + } + } + } else { + if ( !selectModel( mModelDDL, mCurModel ) ) + fillModelDropDownList( mModelDDL ); + } if ( payload.contains( "chat" ) && payload["chat"].is_object() ) { const auto& chat = payload["chat"]; @@ -1696,6 +1862,13 @@ UIWidget* LLMChatUI::addChatUI( LLMChat::Role role ) { i18n( "chat_copied", "Chat Copied" ) ); } ); resizeToFit( editor ); + if ( mIsAgentMode ) { + chat->findByClass( "role_ui" )->setVisible( false ); + chat->findByClass( "erase_but" )->setVisible( false ); + chat->findByClass( "move_up" )->setVisible( false ); + chat->findByClass( "move_down" )->setVisible( false ); + } + return chat; } @@ -1770,6 +1943,9 @@ void LLMChatUI::updateTabTitle() { return; UITab* tab = reinterpret_cast( getData() ); auto title = i18n( "ai_assistant", "AI Assistant" ); + if ( mIsAgentMode && !mCurAgent.empty() ) { + title = i18n( "agent", "Agent" ) + ": " + mCurAgent; + } if ( !mSummary.empty() ) title += " - " + mSummary; tab->setText( title ); diff --git a/src/tools/ecode/plugins/aiassistant/chatui.hpp b/src/tools/ecode/plugins/aiassistant/chatui.hpp index 2a7dc1864..bdd03c242 100644 --- a/src/tools/ecode/plugins/aiassistant/chatui.hpp +++ b/src/tools/ecode/plugins/aiassistant/chatui.hpp @@ -129,6 +129,12 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter { std::map mAgents; std::string mCurAgent; + struct SlashCommand { + std::string name; + std::string description; + }; + std::vector mAvailableCommands; + std::unique_ptr mAgentSession; int mPendingModelsToLoad{ 0 }; @@ -180,6 +186,10 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter { void fillAgentDropDownList( UIDropDownList* agentDDL ); + void updateAgentModeUI(); + + void setupAgentSession(); + void resizeToFit( UICodeEditor* editor ); void addChat( LLMChat::Role role, std::string conversation ); @@ -221,6 +231,8 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter { void replaceFileLinksToContents( std::string& text ); + nlohmann::json promptToContentBlocks( std::string text ); + void generateChatName( bool isRenaming ); void regenerateChatName();