From 9e6a29653fcb306c18ccde7b7f2e32206b461546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Fri, 14 Mar 2025 21:56:05 -0300 Subject: [PATCH] AI Assistant UI refactor. Fixes in debugger an autocomplete plugin (from some of the new changes in both cases). Fix UITextView not updating the aligment after padding change. --- src/eepp/ui/uitextview.cpp | 1 + src/tools/ecode/appconfig.cpp | 9 +- src/tools/ecode/appconfig.hpp | 8 +- .../plugins/aiassistant/aiassistantplugin.cpp | 56 ++++- .../plugins/aiassistant/aiassistantplugin.hpp | 5 + .../ecode/plugins/aiassistant/chatui.cpp | 237 +++++++++--------- .../ecode/plugins/aiassistant/chatui.hpp | 24 +- .../autocomplete/autocompleteplugin.cpp | 12 +- .../debugger/debuggerclientlistener.cpp | 3 +- .../ecode/plugins/debugger/debuggerplugin.cpp | 5 +- src/tools/ecode/plugins/pluginmanager.cpp | 7 +- 11 files changed, 224 insertions(+), 143 deletions(-) diff --git a/src/eepp/ui/uitextview.cpp b/src/eepp/ui/uitextview.cpp index 62d45743a..72c0f5341 100644 --- a/src/eepp/ui/uitextview.cpp +++ b/src/eepp/ui/uitextview.cpp @@ -448,6 +448,7 @@ void UITextView::onAlphaChange() { void UITextView::onPaddingChange() { autoWrap(); + alignFix(); UIWidget::onPaddingChange(); } diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp index aaa8e149e..0ed32cab4 100644 --- a/src/tools/ecode/appconfig.cpp +++ b/src/tools/ecode/appconfig.cpp @@ -692,12 +692,15 @@ void AppConfig::loadDocuments( UICodeEditorSplitter* editorSplitter, json j, } else { auto found = tabWidgetTypes.find( file["type"] ); if ( found != tabWidgetTypes.end() ) { - auto widget = found->second.onLoad( file ); + auto [widget, icon, title] = found->second.onLoad( file ); - editorSplitter->createWidgetInTabWidget( - curTabWidget, widget, file.contains( "title" ) ? file["title"] : "" ); + auto [tab, _] = editorSplitter->createWidgetInTabWidget( + curTabWidget, widget, !title.empty() ? title : file.value( "title", "" ) ); editorSplitter->removeUnusedTab( curTabWidget, true, false ); + if ( icon ) + tab->setIcon( icon ); + if ( curTabWidget->getTabCount() == totalToLoad ) curTabWidget->setTabSelected( eeclamp( currentPage, 0, curTabWidget->getTabCount() - 1 ) ); diff --git a/src/tools/ecode/appconfig.hpp b/src/tools/ecode/appconfig.hpp index eca4de4e0..a662e2a22 100644 --- a/src/tools/ecode/appconfig.hpp +++ b/src/tools/ecode/appconfig.hpp @@ -188,9 +188,15 @@ struct SessionSnapshotFile { std::string selection; }; +struct TabWidgetData { + UIWidget* widget{ nullptr }; + Drawable* icon{ nullptr }; + std::string title; +}; + struct TabWidgetCbs { std::function onSave; - std::function onLoad; + std::function onLoad; }; class AppConfig { diff --git a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp index 9bef73db1..a9004c0b3 100644 --- a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp +++ b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp @@ -150,7 +150,8 @@ void AIAssistantPlugin::load( PluginManager* pluginManager ) { TabWidgetCbs config; config.onLoad = [this]( const nlohmann::json& j ) { - return ( eeNew( ChatUI, ( getUISceneNode(), mProviders ) ) )->getChatUI(); + return TabWidgetData{ LLMChatUI::New( mManager ), getPluginContext()->findIcon( "code-ai" ), + i18n( "ai_assistant", "AI Assistant" ) }; }; config.onSave = []( UIWidget* widget ) { nlohmann::json j; @@ -159,6 +160,11 @@ void AIAssistantPlugin::load( PluginManager* pluginManager ) { getPluginContext()->getConfig().addTabWidgetType( "llm_chatui", config ); + if ( getUISceneNode() ) { + UIWidgetCreator::registerWidget( "llmchatui", + [this] { return LLMChatUI::New( mManager ); } ); + } + if ( mReady ) { fireReadyCbs(); setReady( clock.getElapsedTime() ); @@ -223,7 +229,7 @@ void AIAssistantPlugin::loadAIAssistantConfig( const std::string& path, bool upd } if ( mKeyBindings.empty() ) { - mKeyBindings["new-ai-assistant"] = "mod+shift+n"; + mKeyBindings["new-ai-assistant"] = "mod+shift+m"; } auto& kb = j["keybindings"]; @@ -284,12 +290,15 @@ void AIAssistantPlugin::loadAIAssistantConfig( const std::string& path, bool upd void AIAssistantPlugin::newAIAssistant() { auto splitter = getPluginContext()->getSplitter(); - auto chatUI = eeNew( ChatUI, ( getUISceneNode(), mProviders ) ); + auto chatUI = LLMChatUI::New( mManager ); auto tabName( i18n( "ai_assistant", "AI Assistant" ) ); UITabWidget* tabWidget = splitter->getTabWidgets()[splitter->getTabWidgets().size() - 1]; if ( !splitter->hasSplit() ) tabWidget = splitter->splitTabWidget( SplitDirection::Right, tabWidget ); - splitter->createWidgetInTabWidget( tabWidget, chatUI->getChatUI(), tabName ); + auto [tab, _] = splitter->createWidgetInTabWidget( tabWidget, chatUI, tabName ); + auto icon = getPluginContext()->findIcon( "code-ai" ); + if ( icon ) + tab->setIcon( icon ); } void AIAssistantPlugin::onRegisterDocument( TextDocument* doc ) { @@ -347,4 +356,43 @@ void AIAssistantPlugin::initUI() { } } +std::optional AIAssistantPlugin::getApiKeyFromProvider( const std::string& provider, + AIAssistantPlugin* instance ) { + static const char* OPEN_API_KEY = ""; + const char* ret = nullptr; + if ( provider == "openai" ) { + ret = getenv( "OPENAI_API_KEY" ); + } else if ( provider == "anthropic" ) { + ret = getenv( "ANTHROPIC_API_KEY" ); + } else if ( provider == "google" ) { + const char* apiKey = getenv( "GOOGLE_AI_API_KEY" ); + if ( apiKey != nullptr ) + ret = apiKey; + else + ret = getenv( "GEMINI_API_KEY" ); + } else if ( provider == "deepseek" ) { + ret = getenv( "DEEPSEEK_API_KEY" ); + } else if ( provider == "mistral" ) { + ret = getenv( "MISTRAL_API_KEY" ); + } else if ( provider == "lmstudio" || provider == "ollama" ) { + ret = OPEN_API_KEY; + } else if ( provider == "xai" ) { + const char* apiKey = getenv( "XAI_API_KEY" ); + if ( apiKey != nullptr ) + ret = apiKey; + else + ret = getenv( "GROK_API_KEY" ); + } + if ( ret ) + return std::string{ ret }; + + if ( instance ) { + auto providerIt = instance->mApiKeys.find( provider ); + if ( providerIt != instance->mApiKeys.end() ) + return providerIt->second; + } + + return {}; +} + } // namespace ecode diff --git a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.hpp b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.hpp index b281ddbfe..9fc40fb55 100644 --- a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.hpp +++ b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.hpp @@ -19,12 +19,17 @@ class AIAssistantPlugin : public PluginBase { virtual ~AIAssistantPlugin(); + static std::optional getApiKeyFromProvider( const std::string& provider, + AIAssistantPlugin* instance ); + std::string getId() override { return Definition().id; } std::string getTitle() override { return Definition().name; } std::string getDescription() override { return Definition().description; } + const LLMProviders& getProviders() { return mProviders; } + protected: LLMProviders mProviders; bool mUIInit{ false }; diff --git a/src/tools/ecode/plugins/aiassistant/chatui.cpp b/src/tools/ecode/plugins/aiassistant/chatui.cpp index f5bc3fd73..3c5a17e28 100644 --- a/src/tools/ecode/plugins/aiassistant/chatui.cpp +++ b/src/tools/ecode/plugins/aiassistant/chatui.cpp @@ -1,3 +1,4 @@ +#include "aiassistantplugin.hpp" #include "chatui.hpp" #include @@ -45,21 +46,12 @@ LLMChat::Role LLMChat::stringToRole( UIPushButton* userBut ) { static const char* DEFAULT_LAYOUT = R"xml( - - - - - - - - - - - + + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + )xml"; static const char* DEFAULT_CHAT_GLOBE = R"xml( - + @string(user, User) @string(assistant, Assistant) @string(system, System) @@ -158,25 +151,33 @@ static const char* DEFAULT_CHAT_GLOBE = R"xml( )xml"; -ChatUI::ChatUI( UISceneNode* ui, LLMProviders providers ) { - setProviders( std::move( providers ) ); +LLMChatUI::LLMChatUI( PluginManager* manager ) : UILinearLayout(), mManager( manager ) { + setClass( "llm_chatui" ); + setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); - mChatUI = ui->loadLayoutFromString( DEFAULT_LAYOUT ); + getUISceneNode()->loadLayoutFromString( DEFAULT_LAYOUT, this ); - mChatUI->on( Event::OnFocus, [this]( auto ) { mChatInput->setFocus(); } ); + mChatsList = findByClass( "llm_chats" ); + mModelDDL = findByClass( "model_ui" ); - mChatsList = mChatUI->findByClass( "llm_chats" ); - mModelDDL = mChatUI->findByClass( "model_ui" ); + find( "refresh_model_ui" )->onClick( [this]( auto ) { fillApiModels( mModelDDL ); } ); - mChatUI->find( "refresh_model_ui" )->onClick( [this]( auto ) { fillApiModels( mModelDDL ); } ); + find( "settings_but" )->onClick( [this]( auto ) { + if ( getPlugin() ) + getPlugin()->getPluginContext()->focusOrLoadFile( getPlugin()->getFileConfigPath() ); + } ); - fillModelDropDownList( mModelDDL ); - - mChatScrollView = mChatUI->findByClass( "llm_chat_scrollview" )->asType(); + mChatScrollView = findByClass( "llm_chat_scrollview" )->asType(); mChatScrollView->getVerticalScrollBar()->setValue( 1 ); - mChatInput = mChatUI->findByClass( "llm_chat_input" ); - mChatInput->setData( reinterpret_cast( this ) ); + mChatInput = findByClass( "llm_chat_input" ); + + on( Event::OnFocus, [this]( auto ) { mChatInput->setFocus(); } ); + + if ( getPlugin() ) { + mChatInput->setColorScheme( + getPlugin()->getPluginContext()->getSplitter()->getCurrentColorScheme() ); + } mChatInput->getKeyBindings().addKeybindString( "mod+return", "prompt" ); mChatInput->getKeyBindings().addKeybindString( "mod+keypad enter", "prompt" ); @@ -195,7 +196,7 @@ ChatUI::ChatUI( UISceneNode* ui, LLMProviders providers ) { } ); mChatInput->getDocument().setCommand( "prompt", [this] { - auto chats = mChatUI->findAllByClass( "llm_conversation" ); + auto chats = findAllByClass( "llm_conversation" ); if ( chats.empty() && mChatInput->getDocument().isEmpty() ) return; @@ -209,7 +210,7 @@ ChatUI::ChatUI( UISceneNode* ui, LLMProviders providers ) { ->asType() ->getListBox() ->getItemSelectedIndex() != 0 ) { - showMsg( mChatUI->getUISceneNode()->i18n( + showMsg( getUISceneNode()->i18n( "llm_last_message_must_be_from_user", "The last chat message must be from a \"User\" role" ) ); } @@ -224,7 +225,7 @@ ChatUI::ChatUI( UISceneNode* ui, LLMProviders providers ) { mRequest->cancel(); } ); - mChatUI->find( "llm_add_chat" )->onClick( [this]( auto ) { + find( "llm_add_chat" )->onClick( [this]( auto ) { mChatInput->getDocument().execute( "add_chat" ); } ); @@ -236,13 +237,13 @@ ChatUI::ChatUI( UISceneNode* ui, LLMProviders providers ) { mChatInput->setSyntaxDefinition( markdown ); - mChatRun = mChatUI->find( "llm_run" ); + mChatRun = find( "llm_run" ); mChatRun->onClick( [this]( auto ) { mChatInput->getDocument().execute( "prompt" ); } ); - mChatStop = mChatUI->find( "llm_stop" ); + mChatStop = find( "llm_stop" ); mChatStop->onClick( [this]( auto ) { mChatInput->getDocument().execute( "prompt-stop" ); } ); - mChatUserRole = mChatUI->find( "llm_user" ); + mChatUserRole = find( "llm_user" ); mChatUserRole->onClick( [this]( auto ) { if ( mChatUserRole->getText() == mChatUserRole->i18n( "user", "User" ) ) { mChatUserRole->setText( mChatUserRole->i18n( "assistant", "Assistant" ) ); @@ -253,10 +254,16 @@ ChatUI::ChatUI( UISceneNode* ui, LLMProviders providers ) { } } ); - mChatUI->on( Event::OnClose, [this]( auto ) { eeDelete( this ); } ); + if ( getPlugin() == nullptr ) + return; + + auto providers = getPlugin()->getProviders(); + setProviders( std::move( providers ) ); + + fillModelDropDownList( mModelDDL ); } -void ChatUI::fillApiModels( UIDropDownList* modelDDL ) { +void LLMChatUI::fillApiModels( UIDropDownList* modelDDL ) { for ( auto& [name, data] : mProviders ) { if ( !data.enabled || !data.fetchModelsUrl ) continue; @@ -325,7 +332,7 @@ void ChatUI::fillApiModels( UIDropDownList* modelDDL ) { } } -void ChatUI::fillModelDropDownList( UIDropDownList* modelDDL ) { +void LLMChatUI::fillModelDropDownList( UIDropDownList* modelDDL ) { std::vector models; std::size_t selectedIndex = 0; for ( const auto& [name, data] : mProviders ) { @@ -358,7 +365,7 @@ void ChatUI::fillModelDropDownList( UIDropDownList* modelDDL ) { [this, modelDDL] { fillApiModels( modelDDL ); } ); } -void ChatUI::resizeToFit( UICodeEditor* editor ) { +void LLMChatUI::resizeToFit( UICodeEditor* editor ) { Float visibleLineCount = editor->getDocumentView().getVisibleLinesCount(); Float lineHeight = editor->getLineHeight(); Float height = lineHeight * visibleLineCount + editor->getPixelsPadding().Top + @@ -366,9 +373,9 @@ void ChatUI::resizeToFit( UICodeEditor* editor ) { editor->setPixelsSize( editor->getPixelsSize().getWidth(), height ); } -nlohmann::json ChatUI::chatToJson() { +nlohmann::json LLMChatUI::chatToJson() { auto j = nlohmann::json::array(); - auto chats = mChatUI->findAllByClass( "llm_conversation" ); + auto chats = findAllByClass( "llm_conversation" ); for ( const auto& chat : chats ) { UIDropDownList* roleDDL = chat->findByClass( "role_ui" ); UICodeEditor* codeEditor = chat->findByClass( "data_ui" ); @@ -384,7 +391,14 @@ nlohmann::json ChatUI::chatToJson() { return j; } -nlohmann::json ChatUI::serialize() { +Uint32 LLMChatUI::onMessage( const NodeMessage* msg ) { + if ( msg->getMsg() == NodeMessage::Focus ) { + getPlugin()->getPluginContext()->getSplitter()->setCurrentWidget( this ); + } + return 0; +} + +nlohmann::json LLMChatUI::serializeChat() { nlohmann::json j = { { "model", mCurModel.name }, { "stream", true }, { "messages", chatToJson() } }; if ( mCurModel.maxOutputTokens ) @@ -392,36 +406,16 @@ nlohmann::json ChatUI::serialize() { return j; } -void unserialize( const nlohmann::json& /*payload*/ ) {} - -const char* ChatUI::getApiKeyFromProvider( const std::string& provider ) { - static const char* OPEN_API_KEY = ""; - if ( provider == "openai" ) - return getenv( "OPENAI_API_KEY" ); - if ( provider == "anthropic" ) - return getenv( "ANTHROPIC_API_KEY" ); - if ( provider == "google" ) { - const char* apiKey = getenv( "GOOGLE_AI_API_KEY" ); - if ( apiKey != nullptr ) - return apiKey; - return getenv( "GEMINI_API_KEY" ); - } - if ( provider == "deepseek" ) - return getenv( "DEEPSEEK_API_KEY" ); - if ( provider == "mistral" ) - return getenv( "MISTRAL_API_KEY" ); - if ( provider == "lmstudio" || provider == "ollama" ) - return OPEN_API_KEY; - if ( provider == "xai" ) { - const char* apiKey = getenv( "XAI_API_KEY" ); - if ( apiKey != nullptr ) - return apiKey; - return getenv( "GROK_API_KEY" ); - } - return nullptr; +nlohmann::json LLMChatUI::serialize() { + nlohmann::json j; + j["uuid"] = mUUID.toString(); + j["chat"] = serializeChat(); + return j; } -std::string ChatUI::prepareApiUrl( const std::string& apiKey ) { +void unserialize( const nlohmann::json& /*payload*/ ) {} + +std::string LLMChatUI::prepareApiUrl( const std::string& apiKey ) { const auto& provider = mProviders[mCurModel.provider]; std::string url = provider.apiUrl; String::replaceAll( url, "${model}", mCurModel.name ); @@ -429,17 +423,17 @@ std::string ChatUI::prepareApiUrl( const std::string& apiKey ) { return url; } -void ChatUI::doRequest() { +void LLMChatUI::doRequest() { if ( mRequest ) return; - const char* apiKey = getApiKeyFromProvider( mCurModel.provider ); - if ( apiKey == nullptr ) { - showMsg( mChatUI->getUISceneNode()->i18n( - "configure_api_key", "You must first configure your provider api key." ) ); + 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 apiKeyStr{ *apiKey }; mChatRun->setVisible( false )->setEnabled( false ); mChatStop->setVisible( true )->setEnabled( true ); @@ -448,8 +442,8 @@ void ChatUI::doRequest() { toggleEnableChats( false ); auto* editor = chat->findByClass( "data_ui" ); - mRequest = std::make_unique( prepareApiUrl( apiKeyStr ), apiKeyStr, - serialize().dump(), mCurModel.provider ); + mRequest = std::make_unique( + prepareApiUrl( apiKeyStr ), apiKeyStr, serializeChat().dump(), mCurModel.provider ); mRequest->streamedResponseCb = [this, editor]( const std::string& chunk ) { auto conversation = chunk; editor->runOnMainThread( [this, conversation = std::move( conversation ), editor] { @@ -462,7 +456,7 @@ void ChatUI::doRequest() { auto status = response.getStatus(); auto statusDesc = response.getStatusDescription(); - mChatUI->runOnMainThread( [this, editor, status, statusDesc] { + runOnMainThread( [this, editor, status, statusDesc] { if ( status != Http::Response::Ok ) { auto resp = nlohmann::json::parse( mRequest->getStream(), nullptr, false ); if ( resp.contains( "error" ) && resp["error"].contains( "message" ) ) { @@ -488,7 +482,7 @@ void ChatUI::doRequest() { mRequest->requestAsync(); } -void ChatUI::toggleEnableChat( UIWidget* chat, bool enabled ) { +void LLMChatUI::toggleEnableChat( UIWidget* chat, bool enabled ) { chat->findByClass( "role_ui" )->setEnabled( enabled ); UICodeEditor* editor = chat->findByClass( "data_ui" )->asType(); editor->setEnabled( enabled ); @@ -498,22 +492,24 @@ void ChatUI::toggleEnableChat( UIWidget* chat, bool enabled ) { chat->findByClass( "move_down" )->setEnabled( enabled ); } -void ChatUI::toggleEnableChats( bool enabled ) { +void LLMChatUI::toggleEnableChats( bool enabled ) { auto chats = mChatsList->findAllByClass( "llm_conversation" ); for ( auto chat : chats ) toggleEnableChat( chat, enabled ); } -Drawable* ChatUI::findIcon( const std::string& name, const size_t iconSize ) { +Drawable* LLMChatUI::findIcon( const std::string& name, const size_t iconSize ) { if ( name.empty() ) return nullptr; - UIIcon* icon = mChatUI->getUISceneNode()->findIcon( name ); + UIIcon* icon = getUISceneNode()->findIcon( name ); if ( icon ) return icon->getSize( iconSize ); return nullptr; } -UIWidget* ChatUI::addChatUI( LLMChat::Role role ) { +UIWidget* LLMChatUI::addChatUI( LLMChat::Role role ) { + find( "chat_presentation" )->setVisible( false ); + UIWidget* chat = mChatsList->getUISceneNode()->loadLayoutFromString( DEFAULT_CHAT_GLOBE, mChatsList ); auto* roleDDL = chat->findByClass( "role_ui" )->asType(); @@ -549,10 +545,20 @@ UIWidget* ChatUI::addChatUI( LLMChat::Role role ) { editor->setFoldDrawable( findIcon( "chevron-down", PixelDensity::dpToPxI( 12 ) ) ); editor->setFoldedDrawable( findIcon( "chevron-right", PixelDensity::dpToPxI( 12 ) ) ); + if ( getPlugin() ) { + editor->setColorScheme( + getPlugin()->getPluginContext()->getSplitter()->getCurrentColorScheme() ); + } + editor->on( Event::OnSizeChange, [editor, this]( auto ) { resizeToFit( editor ); } ); editor->on( Event::OnVisibleLinesCountChange, [editor, this]( auto ) { resizeToFit( editor ); } ); - chat->findByClass( "erase_but" )->onClick( [chat]( auto ) { chat->close(); } ); + chat->findByClass( "erase_but" )->onClick( [chat, this]( auto ) { + chat->close(); + auto chats = findAllByClass( "llm_conversation" ); + if ( chats.empty() ) + find( "chat_presentation" )->setVisible( true ); + } ); chat->findByClass( "move_up" )->onClick( [chat]( auto ) { if ( chat->getNodeIndex() > 0 ) chat->toPosition( chat->getNodeIndex() - 1 ); @@ -565,7 +571,7 @@ UIWidget* ChatUI::addChatUI( LLMChat::Role role ) { return chat; } -void ChatUI::addChat( LLMChat::Role role, std::string conversation ) { +void LLMChatUI::addChat( LLMChat::Role role, std::string conversation ) { UIWidget* chat = addChatUI( role ); auto* editor = chat->findByClass( "data_ui" ); editor->getDocument().textInput( String::fromUtf8( conversation ) ); @@ -573,7 +579,7 @@ void ChatUI::addChat( LLMChat::Role role, std::string conversation ) { resizeToFit( editor ); } -void ChatUI::removeLastChat() { +void LLMChatUI::removeLastChat() { auto chats = mChatsList->findAllByClass( "llm_conversation" ); if ( !chats.empty() ) { auto* chat = chats[chats.size() - 1]; @@ -583,15 +589,11 @@ void ChatUI::removeLastChat() { } } -void ChatUI::setProviders( LLMProviders&& providers ) { +void LLMChatUI::setProviders( LLMProviders&& providers ) { mProviders = std::move( providers ); } -UIWidget* ChatUI::getChatUI() { - return mChatUI; -} - -void ChatUI::showMsg( String msg ) { +void LLMChatUI::showMsg( String msg ) { auto msgBox = UIMessageBox::New( UIMessageBox::OK, msg ); msgBox->getTextBox()->setTextSelection( true ); msgBox->getTextBox()->onClick( @@ -603,4 +605,11 @@ void ChatUI::showMsg( String msg ) { msgBox->showWhenReady(); } +AIAssistantPlugin* LLMChatUI::getPlugin() { + auto plugin = mManager->get( "aiassistant" ); + if ( plugin ) + return reinterpret_cast( plugin ); + return nullptr; +} + } // namespace ecode diff --git a/src/tools/ecode/plugins/aiassistant/chatui.hpp b/src/tools/ecode/plugins/aiassistant/chatui.hpp index 5db927213..d493b6084 100644 --- a/src/tools/ecode/plugins/aiassistant/chatui.hpp +++ b/src/tools/ecode/plugins/aiassistant/chatui.hpp @@ -3,6 +3,9 @@ #include "llmchatcompletionrequest.hpp" #include "protocol.hpp" +#include "../pluginmanager.hpp" +#include + #include "nlohmann/json_fwd.hpp" namespace EE { namespace UI { @@ -24,6 +27,8 @@ using namespace EE::Graphics; namespace ecode { +class AIAssistantPlugin; + class LLMChat { public: enum class Role { @@ -38,20 +43,17 @@ class LLMChat { static LLMChat::Role stringToRole( UIPushButton* userBut ); }; -class ChatUI { +class LLMChatUI : public UILinearLayout { public: - ChatUI( UISceneNode* ui, LLMProviders providers ); + static LLMChatUI* New( PluginManager* manager ) { return eeNew( LLMChatUI, ( manager ) ); } nlohmann::json serialize(); void unserialize( const nlohmann::json& /*payload*/ ); - const char* getApiKeyFromProvider( const std::string& provider ); - - UIWidget* getChatUI(); - protected: - UIWidget* mChatUI{ nullptr }; + UUID mUUID; + PluginManager* mManager{ nullptr }; UIWidget* mChatsList{ nullptr }; UICodeEditor* mChatInput{ nullptr }; UIPushButton* mChatUserRole{ nullptr }; @@ -64,8 +66,14 @@ class ChatUI { LLMModel mCurModel; std::unordered_map mModelsMap; + LLMChatUI( PluginManager* manager ); + + AIAssistantPlugin* getPlugin(); + void showMsg( String msg ); + nlohmann::json serializeChat(); + nlohmann::json chatToJson(); std::string prepareApiUrl( const std::string& apiKey ); @@ -91,6 +99,8 @@ class ChatUI { void removeLastChat(); void setProviders( LLMProviders&& providers ); + + virtual Uint32 onMessage( const NodeMessage* ); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp index 88671c8d9..e26d6178a 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp @@ -58,7 +58,8 @@ fuzzyMatchSymbols( const std::vector& sy ( score = String::fuzzyMatch( pattern, symbol.text ) ) > std::numeric_limits::min() ) { if ( std::find( matches.begin(), matches.end(), symbol ) == matches.end() ) { - symbol.setScore( score ); + symbol.setScore( score + + ( symbol.kind != LSPCompletionItemKind::Text ? 100 : 0 ) ); matches.push_back( symbol ); if ( matches.size() > max ) @@ -71,11 +72,10 @@ fuzzyMatchSymbols( const std::vector& sy break; } - std::sort( matches.begin(), matches.end(), - []( const AutoCompletePlugin::Suggestion& left, - const AutoCompletePlugin::Suggestion& right ) { - return left.score > right.score && left.kind != LSPCompletionItemKind::Text; - } ); + std::sort( + matches.begin(), matches.end(), + []( const AutoCompletePlugin::Suggestion& left, + const AutoCompletePlugin::Suggestion& right ) { return left.score > right.score; } ); return matches; } diff --git a/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp b/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp index e7c5b581d..fe8ca7045 100644 --- a/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp +++ b/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp @@ -193,8 +193,7 @@ void DebuggerClientListener::stateChanged( DebuggerClient::State state ) { void DebuggerClientListener::sendBreakpoints() { Lock l( mPlugin->mBreakpointsMutex ); for ( const auto& fileBps : mPlugin->mBreakpoints ) { - if ( !fileBps.second.empty() ) - mClient->setBreakpoints( fileBps.first, fromSet( fileBps.second ) ); + mClient->setBreakpoints( fileBps.first, fromSet( fileBps.second ) ); } } diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp index de883c967..e80354fce 100644 --- a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp +++ b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp @@ -1,4 +1,3 @@ -#include "debuggerplugin.hpp" #include "../../notificationcenter.hpp" #include "../../terminalmanager.hpp" #include "../../uistatusbar.hpp" @@ -7,6 +6,7 @@ #include "bussocket.hpp" #include "bussocketprocess.hpp" #include "dap/debuggerclientdap.hpp" +#include "debuggerplugin.hpp" #include "models/breakpointsmodel.hpp" #include "models/processesmodel.hpp" #include "models/variablesmodel.hpp" @@ -243,7 +243,8 @@ void DebuggerPlugin::onLoadProject( const std::string& projectFolder, SourceBreakpointStateful bp; bp.line = jbp.value( "line", 1 ); bp.enabled = jbp.value( "enabled", true ); - set.insert( std::move( bp ) ); + if ( bp.line > 0 ) + set.insert( std::move( bp ) ); } if ( !set.empty() ) breakpoints[key] = std::move( set ); diff --git a/src/tools/ecode/plugins/pluginmanager.cpp b/src/tools/ecode/plugins/pluginmanager.cpp index a0b36a4ab..4424f646a 100644 --- a/src/tools/ecode/plugins/pluginmanager.cpp +++ b/src/tools/ecode/plugins/pluginmanager.cpp @@ -1,6 +1,6 @@ -#include "pluginmanager.hpp" #include "../filesystemlistener.hpp" #include "plugin.hpp" +#include "pluginmanager.hpp" #include #include #include @@ -495,9 +495,8 @@ UIWindow* UIPluginManager::New( UISceneNode* sceneNode, PluginManager* manager, tv->setAutoColumnsWidth( true ); tv->setFitAllColumnsToWidget( true ); tv->setMainColumn( PluginsModel::Description ); - prefs->addEventListener( Event::MouseClick, [tv, manager, loadFileCb]( const Event* event ) { - if ( event->asMouseEvent()->getFlags() & EE_BUTTON_LMASK && - !tv->getSelection().isEmpty() ) { + prefs->onClick( [tv, manager, loadFileCb]( const MouseEvent* event ) { + if ( !tv->getSelection().isEmpty() ) { const PluginDefinition* def = manager->getDefinitionIndex( tv->getSelection().first().row() ); if ( def == nullptr || !manager->isEnabled( def->id ) )