From cfd66fa43846b58a1f5deeaf60a136ba260752f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 23 Mar 2025 03:37:34 -0300 Subject: [PATCH] Chat history WIP. Reverted completion fuzzy search. --- .../eepp/ui/abstract/uiabstracttableview.hpp | 7 +- include/eepp/ui/uilinearlayout.hpp | 5 + src/eepp/ui/abstract/uiabstracttableview.cpp | 21 ++- src/eepp/ui/iconmanager.cpp | 1 + src/eepp/ui/uilinearlayout.cpp | 2 +- src/eepp/ui/uitreeview.cpp | 4 + .../plugins/aiassistant/aiassistantplugin.cpp | 8 +- .../plugins/aiassistant/aiassistantplugin.hpp | 5 +- .../ecode/plugins/aiassistant/chathistory.cpp | 115 ++++++++++++++ .../ecode/plugins/aiassistant/chathistory.hpp | 55 +++++++ .../ecode/plugins/aiassistant/chatui.cpp | 149 +++++++++++++++++- .../ecode/plugins/aiassistant/chatui.hpp | 6 + .../autocomplete/autocompleteplugin.cpp | 11 +- 13 files changed, 366 insertions(+), 23 deletions(-) create mode 100644 src/tools/ecode/plugins/aiassistant/chathistory.cpp create mode 100644 src/tools/ecode/plugins/aiassistant/chathistory.hpp diff --git a/include/eepp/ui/abstract/uiabstracttableview.hpp b/include/eepp/ui/abstract/uiabstracttableview.hpp index 00c7d9ab3..9195deda2 100644 --- a/include/eepp/ui/abstract/uiabstracttableview.hpp +++ b/include/eepp/ui/abstract/uiabstracttableview.hpp @@ -136,10 +136,14 @@ class EE_API UIAbstractTableView : public UIAbstractView { void setRowHeaderWidth( Float rowHeaderWidth ); - bool hasOnUpdateCellCb(); + bool hasOnUpdateCellCb() const; void setOnUpdateCellCb( const std::function& onUpdateCellCb ); + bool hasSetupCellCb() const; + + void setSetupCellCb( const std::function& onSetupCellCb ); + protected: friend class EE::UI::UITableHeaderColumn; @@ -172,6 +176,7 @@ class EE_API UIAbstractTableView : public UIAbstractView { size_t mMainColumn{ 0 }; std::unordered_map> mWidgetsClickCbId; std::function mOnUpdateCellCb; + std::function mSetupCellCb; Float mRowHeaderWidth{ 0 }; virtual ~UIAbstractTableView(); diff --git a/include/eepp/ui/uilinearlayout.hpp b/include/eepp/ui/uilinearlayout.hpp index 7232a261e..de11b9315 100644 --- a/include/eepp/ui/uilinearlayout.hpp +++ b/include/eepp/ui/uilinearlayout.hpp @@ -32,8 +32,13 @@ class EE_API UILinearLayout : public UILayout { void updateLayout(); + void setUpdateLayoutEvenIfNotVisible( bool update ) { mUpdateLayoutEvenIfNotVisible = update; } + + bool updatesLayoutEvenIfNotVisible() const { return mUpdateLayoutEvenIfNotVisible; } + protected: UIOrientation mOrientation; + bool mUpdateLayoutEvenIfNotVisible{ false }; UILinearLayout(); diff --git a/src/eepp/ui/abstract/uiabstracttableview.cpp b/src/eepp/ui/abstract/uiabstracttableview.cpp index 137f5029f..7e9d86a50 100644 --- a/src/eepp/ui/abstract/uiabstracttableview.cpp +++ b/src/eepp/ui/abstract/uiabstracttableview.cpp @@ -21,6 +21,7 @@ UIAbstractTableView::UIAbstractTableView( const std::string& tag ) : mHeader = UILinearLayout::NewWithTag( mTag + "::header", UIOrientation::Horizontal ); mHeader->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); mHeader->setParent( this )->setVisible( true )->setEnabled( true ); + mHeader->setUpdateLayoutEvenIfNotVisible( true ); mVScroll->on( Event::OnAlphaChange, [this]( const Event* ) { if ( mVScroll->getAlpha() == 0.f || mVScroll->getAlpha() == 1.f ) updateColumnsWidth(); @@ -599,6 +600,10 @@ UIWidget* UIAbstractTableView::setupCell( UITableCell* widget, UIWidget* rowWidg widget->setTextAlign( UI_HALIGN_LEFT ); widget->setCurIndex( index ); bindNavigationClick( widget ); + + if ( mSetupCellCb ) + mSetupCellCb( widget ); + return widget; } @@ -778,7 +783,7 @@ void UIAbstractTableView::setRowHeaderWidth( Float rowHeaderWidth ) { buildRowHeader(); } -bool UIAbstractTableView::hasOnUpdateCellCb() { +bool UIAbstractTableView::hasOnUpdateCellCb() const { return mOnUpdateCellCb != nullptr; } @@ -787,6 +792,15 @@ void UIAbstractTableView::setOnUpdateCellCb( mOnUpdateCellCb = onUpdateCellCb; } +bool UIAbstractTableView::hasSetupCellCb() const { + return mSetupCellCb != nullptr; +} + +void UIAbstractTableView::setSetupCellCb( + const std::function& onSetupCellCb ) { + mSetupCellCb = onSetupCellCb; +} + void UIAbstractTableView::buildRowHeader() { if ( mRowHeaderWidth == 0 ) { if ( mRowHeader ) @@ -996,7 +1010,10 @@ const size_t& UIAbstractTableView::getMainColumn() const { } void UIAbstractTableView::setMainColumn( const size_t& mainColumn ) { - mMainColumn = mainColumn; + if ( mMainColumn != mainColumn ) { + mMainColumn = mainColumn; + createOrUpdateColumns( false ); + } } bool UIAbstractTableView::getSingleClickNavigation() const { diff --git a/src/eepp/ui/iconmanager.cpp b/src/eepp/ui/iconmanager.cpp index b55300700..96f5416ee 100644 --- a/src/eepp/ui/iconmanager.cpp +++ b/src/eepp/ui/iconmanager.cpp @@ -98,6 +98,7 @@ UIIconTheme* IconManager::init( const std::string& iconThemeName, FontTrueType* { "play-filled", 0xF00A }, { "code-ai", 0xF56F }, { "robot-2", 0xF3D9 }, + { "chat-history", 0xEB61 }, }; for ( const auto& icon : icons ) diff --git a/src/eepp/ui/uilinearlayout.cpp b/src/eepp/ui/uilinearlayout.cpp index 1e40b3af6..73c94def8 100644 --- a/src/eepp/ui/uilinearlayout.cpp +++ b/src/eepp/ui/uilinearlayout.cpp @@ -51,7 +51,7 @@ UILinearLayout* UILinearLayout::setOrientation( const UIOrientation& orientation } void UILinearLayout::updateLayout() { - if ( !mVisible ) { + if ( !mVisible && !mUpdateLayoutEvenIfNotVisible ) { if ( mPacking ) return; mPacking = true; diff --git a/src/eepp/ui/uitreeview.cpp b/src/eepp/ui/uitreeview.cpp index 20a25e391..a4707cf1f 100644 --- a/src/eepp/ui/uitreeview.cpp +++ b/src/eepp/ui/uitreeview.cpp @@ -206,6 +206,10 @@ UIWidget* UITreeView::setupCell( UITableCell* widget, UIWidget* rowWidget, } } ); } + + if ( mSetupCellCb ) + mSetupCellCb( widget ); + return widget; } diff --git a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp index fab2e1bd3..c02cba657 100644 --- a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp +++ b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp @@ -177,11 +177,6 @@ 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() ); @@ -305,7 +300,7 @@ void AIAssistantPlugin::loadAIAssistantConfig( const std::string& path, bool upd initUI(); } -void AIAssistantPlugin::newAIAssistant() { +LLMChatUI* AIAssistantPlugin::newAIAssistant() { auto splitter = getPluginContext()->getSplitter(); auto chatUI = LLMChatUI::New( mManager ); auto tabName( i18n( "ai_assistant", "AI Assistant" ) ); @@ -316,6 +311,7 @@ void AIAssistantPlugin::newAIAssistant() { auto icon = getPluginContext()->findIcon( "code-ai" ); if ( icon ) tab->setIcon( icon ); + return chatUI; } void AIAssistantPlugin::onRegisterDocument( TextDocument* doc ) { diff --git a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.hpp b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.hpp index e04dd7257..1ca6f4c49 100644 --- a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.hpp +++ b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.hpp @@ -6,6 +6,8 @@ namespace ecode { +class LLMChatUI; + class AIAssistantPlugin : public PluginBase { public: static PluginDefinition Definition() { @@ -34,6 +36,8 @@ class AIAssistantPlugin : public PluginBase { std::string getConversationsPath() const; + LLMChatUI* newAIAssistant(); + protected: LLMProviders mProviders; bool mUIInit{ false }; @@ -57,7 +61,6 @@ class AIAssistantPlugin : public PluginBase { void initUI(); - void newAIAssistant(); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/aiassistant/chathistory.cpp b/src/tools/ecode/plugins/aiassistant/chathistory.cpp new file mode 100644 index 000000000..fef24546a --- /dev/null +++ b/src/tools/ecode/plugins/aiassistant/chathistory.cpp @@ -0,0 +1,115 @@ +#include "chathistory.hpp" +#include +#include + +namespace ecode { + +std::vector ChatHistory::getHistory( const std::string& historyFolder ) { + auto files = FileSystem::filesInfoGetInPath( historyFolder, false, false, false, true ); + std::vector history; + history.reserve( files.size() ); + for ( auto& file : files ) { + auto filename( file.getFileName() ); + auto firstSpace = filename.find_first_of( " " ); + if ( firstSpace == std::string::npos ) + continue; + auto secondSpace = filename.find_first_of( " ", firstSpace + 1 ); + if ( secondSpace == std::string::npos ) + continue; + auto uuid = UUID::fromString( filename.substr( 0, firstSpace ) ); + if ( !uuid ) + continue; + auto summary = filename.substr( secondSpace + 1 ); + if ( !String::endsWith( summary, ".json" ) || summary.size() < 5 ) + continue; + summary = summary.substr( 0, summary.size() - 5 ); + history.emplace_back( std::move( *uuid ), std::move( summary ), std::move( file ) ); + } + + std::sort( history.begin(), history.end(), []( const ChatHistory& a, const ChatHistory& b ) { + return a.file.getModificationTime() > b.file.getModificationTime(); + } ); + + return history; +} + +ChatHistoryModel::ChatHistoryModel( std::vector&& history, UISceneNode* uiSceneNode ) : + mHistory( std::move( history ) ), mUISceneNode( uiSceneNode ) { + mCurHistory.reserve( mHistory.size() ); + for ( const auto& item : mHistory ) + mCurHistory.emplace_back( &item ); +} + +void ChatHistoryModel::setFilter( const std::string& filter ) { + mCurFilter = filter; + mCurHistory.clear(); + for ( const auto& item : mHistory ) { + if ( filter.empty() || String::icontains( item.summary, filter ) ) + mCurHistory.emplace_back( &item ); + } + invalidate(); +} + +size_t ChatHistoryModel::rowCount( const ModelIndex& ) const { + return mCurHistory.size(); +} + +size_t ChatHistoryModel::columnCount( const ModelIndex& ) const { + return 5; +} + +std::string ChatHistoryModel::columnName( const size_t& column ) const { + switch ( column ) { + case Columns::Id: + return mUISceneNode->i18n( "id", "Id" ); + case Columns::Summary: + return mUISceneNode->i18n( "summary", "Summary" ); + case Columns::DateTime: + return mUISceneNode->i18n( "datetime", "Date" ); + case Columns::Path: + return mUISceneNode->i18n( "path", "Path" ); + case Columns::Remove: + return mUISceneNode->i18n( "remove", "Remove" ); + } + return ""; +} + +Variant ChatHistoryModel::data( const ModelIndex& index, ModelRole role ) const { + if ( role == ModelRole::Display ) { + auto& item = *mCurHistory[index.row()]; + switch ( index.column() ) { + case Columns::Id: + return Variant( item.uuid.toString() ); + case Columns::Summary: + return Variant( item.summary.c_str() ); + case Columns::DateTime: + return Variant( Sys::epochToString( item.file.getModificationTime() ) ); + case Columns::Path: + return Variant( item.file.getFilepath().c_str() ); + } + } else if ( role == ModelRole::Icon ) { + if ( index.column() == Columns::Remove ) { + static UIIcon* eraseIcon = mUISceneNode->findIcon( "chrome-close" ); + return Variant( eraseIcon ); + } + } + return {}; +} + +void ChatHistoryModel::remove( const ModelIndex& index ) { + if ( !index.isValid() ) + return; + + auto& item = *mCurHistory[index.row()]; + + FileSystem::fileRemove( item.file.getFilepath() ); + + mHistory.erase( + std::remove_if( mHistory.begin(), mHistory.end(), [&item]( const ChatHistory& history ) { + return item.uuid == history.uuid; + } ) ); + + setFilter( mCurFilter ); +} + +} // namespace ecode diff --git a/src/tools/ecode/plugins/aiassistant/chathistory.hpp b/src/tools/ecode/plugins/aiassistant/chathistory.hpp new file mode 100644 index 000000000..ab4c399d6 --- /dev/null +++ b/src/tools/ecode/plugins/aiassistant/chathistory.hpp @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include +#include +#include + +using namespace EE; +using namespace EE::System; +using namespace EE::UI; +using namespace EE::UI::Models; + +namespace EE::UI { +class UISceneNode; +} + +namespace ecode { + +struct ChatHistory { + UUID uuid; + std::string summary; + FileInfo file; + + ChatHistory( UUID&& uuid, std::string&& summary, FileInfo&& file ) : + uuid( std::move( uuid ) ), summary( std::move( summary ) ), file( std::move( file ) ) {} + + static std::vector getHistory( const std::string& historyFolder ); +}; + +class ChatHistoryModel : public Model { + public: + enum Columns { Id, Summary, DateTime, Path, Remove }; + + ChatHistoryModel( std::vector&&, UISceneNode* ); + + virtual size_t rowCount( const ModelIndex& = ModelIndex() ) const; + + virtual size_t columnCount( const ModelIndex& = ModelIndex() ) const; + + virtual std::string columnName( const size_t& /*column*/ ) const; + + virtual Variant data( const ModelIndex& index, ModelRole role = ModelRole::Display ) const; + + void setFilter( const std::string& filter ); + + void remove( const ModelIndex& index ); + + protected: + std::vector mCurHistory; + std::vector mHistory; + UISceneNode* mUISceneNode; + std::string mCurFilter; +}; + +} // namespace ecode diff --git a/src/tools/ecode/plugins/aiassistant/chatui.cpp b/src/tools/ecode/plugins/aiassistant/chatui.cpp index d3ea728bc..aae5d844c 100644 --- a/src/tools/ecode/plugins/aiassistant/chatui.cpp +++ b/src/tools/ecode/plugins/aiassistant/chatui.cpp @@ -1,5 +1,7 @@ #include "aiassistantplugin.hpp" +#include "chathistory.hpp" #include "chatui.hpp" +#include "eepp/ui/uiloader.hpp" #include #include @@ -10,6 +12,7 @@ #include #include #include +#include #include #include @@ -63,7 +66,7 @@ static const char* DEFAULT_LAYOUT = R"xml( border: 0; background-color: var(--tab-back); } -.llm_chatui #settings_but { +.llm_chatui #llm_settings_but { tint: var(--floating-icon); } .llm_conversation { @@ -136,8 +139,9 @@ DropDownList.role_ui { + + - @@ -176,7 +180,7 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) : UILinearLayout(), mManager( man find( "refresh_model_ui" )->onClick( [this]( auto ) { fillApiModels( mModelDDL ); } ); - find( "settings_but" )->onClick( [this]( auto ) { + find( "llm_settings_but" )->onClick( [this]( auto ) { if ( getPlugin() ) getPlugin()->getPluginContext()->focusOrLoadFile( getPlugin()->getFileConfigPath() ); } ); @@ -268,6 +272,9 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) : UILinearLayout(), mManager( man } } ); + mChatHistory = find( "llm_chat_history" ); + mChatHistory->onClick( [this]( auto ) { showChatHistory(); } ); + if ( getPlugin() == nullptr ) return; @@ -278,6 +285,119 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) : UILinearLayout(), mManager( man fillModelDropDownList( mModelDDL ); } +void LLMChatUI::showChatHistory() { + auto plugin = getPlugin(); + if ( plugin == nullptr ) + return; + + UIWindow* win = + UIWindow::NewOpt( UIMessageBox::WindowBaseContainerType::VERTICAL_LINEAR_LAYOUT ); + win->setMinWindowSize( mDpSize.getWidth(), getUISceneNode()->getSize().getHeight() * 0.7f ); + win->setKeyBindingCommand( "closeWindow", [win, this] { + win->closeWindow(); + setFocus(); + } ); + win->getKeyBindings().addKeybind( { KEY_ESCAPE }, "closeWindow" ); + win->setWindowFlags( UI_WIN_NO_DECORATION | UI_WIN_SHADOW | + UI_WIN_MODAL | UI_WIN_EPHEMERAL ); + win->center(); + win->setId( UUID().toString() ); + + UITextInput* input = UITextInput::New(); + input->setParent( win->getContainer() ); + input->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::WrapContent ); + input->setHint( i18n( "search_chat_ellipsis", "Search Chat..." ) ); + input->setVisible( false ); + + UILoader* loader = UILoader::New(); + loader->setId( "loader" ); + loader->setRadius( 48 ); + loader->setOutlineThickness( 6 ); + loader->setParent( win->getContainer() ); + loader->setIndeterminate( true ); + + UITableView* tv = UITableView::New(); + tv->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::Fixed ); + tv->setLayoutWeight( 1 ); + tv->setParent( win->getContainer() ); + tv->setVisible( false ); + tv->setAutoColumnsWidth( true ); + tv->setFitAllColumnsToWidget( true ); + tv->setSetupCellCb( [this, tv]( UITableCell* cell ) { + if ( cell->getCurIndex().column() != ChatHistoryModel::Remove ) + return; + + cell->onClick( [this, tv, cell]( const MouseEvent* event ) { + ChatHistoryModel* model = static_cast( tv->getModel() ); + auto summary = + model->data( model->index( cell->getCurIndex().row(), ChatHistoryModel::Summary ) ) + .toString(); + auto msgBox = + UIMessageBox::New( UIMessageBox::OK_CANCEL, + String::format( i18n( "confirm_del_llm_chat", + "Are you sure you want to remove \"%s?\"" ) + .toUtf8(), + summary ) ); + msgBox->showWhenReady(); + msgBox->on( Event::OnConfirm, + [model, cell]( auto ) { model->remove( cell->getCurIndex() ); } ); + } ); + } ); + + input->on( Event::KeyDown, [tv, input]( const Event* event ) { + tv->forceKeyDown( *event->asKeyEvent() ); + input->setFocus(); + } ); + + input->on( Event::OnTextChanged, [tv, input]( const Event* ) { + ChatHistoryModel* model = static_cast( tv->getModel() ); + model->setFilter( input->getText().toUtf8() ); + } ); + + input->on( Event::OnPressEnter, + [win]( const Event* ) { win->executeKeyBindingCommand( "closeWindow" ); } ); + + tv->on( Event::OnModelEvent, [win, this]( const Event* event ) { + const ModelEvent* modelEvent = static_cast( event ); + if ( modelEvent->getModelEventType() == ModelEventType::Open ) { + if ( getPlugin() != nullptr ) { + auto* chatUI = getPlugin()->newAIAssistant(); + auto* model = static_cast( modelEvent->getModel() ); + auto path = model->data( + model->index( modelEvent->getModelIndex().row(), ChatHistoryModel::Path ) ); + std::string data; + FileSystem::fileGet( path.toString(), data ); + nlohmann::json j = nlohmann::json::parse( data, nullptr, false ); + chatUI->unserialize( j ); + } + win->executeKeyBindingCommand( "closeWindow" ); + } + } ); + + win->showWhenReady(); + + std::string winId = win->getId(); + UISceneNode* uiSceneNode = getUISceneNode(); + getUISceneNode()->getThreadPool()->run( + [plugin, loader, input, tv, winId = std::move( winId ), uiSceneNode] { + std::string conversationsPath = plugin->getConversationsPath(); + auto model = std::make_shared( + ChatHistory::getHistory( conversationsPath ), uiSceneNode ); + if ( uiSceneNode->find( winId ) == nullptr ) // Window closed? + return; + tv->runOnMainThread( [tv, loader, input, model] { + loader->setVisible( false ); + input->setVisible( true ); + tv->setVisible( true ); + tv->setModel( model ); + tv->setMainColumn( ChatHistoryModel::Summary ); + tv->setColumnsVisible( { ChatHistoryModel::Summary, ChatHistoryModel::DateTime, + ChatHistoryModel::Remove } ); + input->setFocus(); + } ); + } ); +} + void LLMChatUI::fillApiModels( UIDropDownList* modelDDL ) { mPendingModelsToLoad = 0; for ( auto& [name, data] : mProviders ) { @@ -370,7 +490,7 @@ String LLMChatUI::getModelDisplayName( const LLMModel& model ) const { bool LLMChatUI::selectModel( UIDropDownList* modelDDL, const LLMModel& model ) { auto modelName = getModelDisplayName( model ); auto index = modelDDL->getListBox()->getItemIndex( modelName ); - if ( index != eeINDEX_NOT_FOUND ){ + if ( index != eeINDEX_NOT_FOUND ) { modelDDL->getListBox()->setSelected( index ); return true; } @@ -469,7 +589,8 @@ void LLMChatUI::unserialize( const nlohmann::json& payload ) { std::string provider = payload.value( "provider", "" ); if ( payload.contains( "chat" ) && payload["chat"].is_object() ) { - std::string model = payload.value( "model", "" ); + const auto& chat = payload["chat"]; + std::string model = chat.value( "model", "" ); mCurModel = findModel( provider, model ); } @@ -479,13 +600,16 @@ void LLMChatUI::unserialize( const nlohmann::json& payload ) { if ( !selectModel( mModelDDL, mCurModel ) ) fillModelDropDownList( mModelDDL ); - if ( payload.contains( "messages" ) && payload["messages"].is_array() ) { - const auto& messages = payload["messages"]; + if ( payload.contains( "chat" ) && payload["chat"].is_object() ) { + const auto& chat = payload["chat"]; + const auto& messages = chat["messages"]; for ( const auto& chat : messages ) { addChat( LLMChat::stringToRole( chat.value( "role", "" ) ), chat.value( "content", "" ) ); } } + + updateTabTitle(); } LLMModel LLMChatUI::findModel( const std::string& provider, const std::string& model ) { @@ -609,6 +733,7 @@ void LLMChatUI::doRequest() { auto status = response.getStatus(); if ( status == Http::Response::Ok ) { mSummary = req.getResponse(); + runOnMainThread( [this] { updateTabTitle(); } ); saveChat(); } runOnMainThread( [this] { mSummaryRequest.reset(); } ); @@ -769,4 +894,14 @@ void LLMChatUI::onInit() { selectModel( mModelDDL, mCurModel ); } +void LLMChatUI::updateTabTitle() { + if ( getData() == 0 ) + return; + UITab* tab = reinterpret_cast( getData() ); + auto title = i18n( "ai_assistant", "AI Assistant" ); + if ( !mSummary.empty() ) + title += " - " + mSummary; + tab->setText( title ); +} + } // namespace ecode diff --git a/src/tools/ecode/plugins/aiassistant/chatui.hpp b/src/tools/ecode/plugins/aiassistant/chatui.hpp index 1998121a9..53d43bd2a 100644 --- a/src/tools/ecode/plugins/aiassistant/chatui.hpp +++ b/src/tools/ecode/plugins/aiassistant/chatui.hpp @@ -63,6 +63,7 @@ class LLMChatUI : public UILinearLayout { UIPushButton* mChatUserRole{ nullptr }; UIPushButton* mChatRun{ nullptr }; UIPushButton* mChatStop{ nullptr }; + UIPushButton* mChatHistory{ nullptr }; UIScrollView* mChatScrollView{ nullptr }; UIDropDownList* mModelDDL{ nullptr }; std::unique_ptr mRequest; @@ -121,6 +122,11 @@ class LLMChatUI : public UILinearLayout { void saveChat(); void onInit(); + + void showChatHistory(); + + void updateTabTitle(); + }; } // namespace ecode diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp index e26d6178a..0a7c90456 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp @@ -55,20 +55,21 @@ fuzzyMatchSymbols( const std::vector& sy for ( const auto& symbols : symbolsVec ) { for ( const auto& symbol : *symbols ) { if ( symbol.kind == LSPCompletionItemKind::Snippet || - ( score = String::fuzzyMatch( pattern, symbol.text ) ) > - std::numeric_limits::min() ) { + ( score = String::fuzzyMatchSimple( + pattern, symbol.text, false, symbol.kind != LSPCompletionItemKind::Text ) ) > + 0 ) { if ( std::find( matches.begin(), matches.end(), symbol ) == matches.end() ) { symbol.setScore( score + - ( symbol.kind != LSPCompletionItemKind::Text ? 100 : 0 ) ); + ( symbol.kind != LSPCompletionItemKind::Text ? score : 0 ) ); matches.push_back( symbol ); - if ( matches.size() > max ) + if ( matches.size() >= max ) break; } } } - if ( matches.size() > max ) + if ( matches.size() >= max ) break; }