From 5cd4bae80bdb7fb3c680e657868e0e2bb78c0561 Mon Sep 17 00:00:00 2001 From: Jan Sundermeyer Date: Sat, 20 Apr 2024 15:44:20 +0200 Subject: [PATCH] add search on old conversations --- images-ng/edit-find.svg | 10 +++ images-ng/edit-find_dm.svg | 10 +++ images.qrc | 2 + src/aichatassistant.cpp | 34 ++++++++- src/aichatassistant.h | 4 ++ src/aiquerystoragemodel.cpp | 134 ++++++++++++++++++++++++++++-------- src/aiquerystoragemodel.h | 8 ++- 7 files changed, 172 insertions(+), 30 deletions(-) create mode 100644 images-ng/edit-find.svg create mode 100644 images-ng/edit-find_dm.svg diff --git a/images-ng/edit-find.svg b/images-ng/edit-find.svg new file mode 100644 index 0000000000..e53891e7c6 --- /dev/null +++ b/images-ng/edit-find.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/images-ng/edit-find_dm.svg b/images-ng/edit-find_dm.svg new file mode 100644 index 0000000000..76eeeacb93 --- /dev/null +++ b/images-ng/edit-find_dm.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/images.qrc b/images.qrc index 4f6194fc4b..60aba17249 100644 --- a/images.qrc +++ b/images.qrc @@ -80,6 +80,8 @@ images-ng/down.svgz images-ng/edit-copy.svgz images-ng/edit-cut.svgz +images-ng/edit-find_dm.svg +images-ng/edit-find.svg images-ng/edit-paste.svgz images-ng/edit-redo.svg images-ng/edit-undo.svg diff --git a/src/aichatassistant.cpp b/src/aichatassistant.cpp index 346471b329..bfe26cb6df 100644 --- a/src/aichatassistant.cpp +++ b/src/aichatassistant.cpp @@ -10,6 +10,18 @@ AIChatAssistant::AIChatAssistant(QWidget *parent) { config=dynamic_cast(ConfigManagerInterface::getInstance()); + m_btSearch=new QToolButton(); + m_actSearch=new QAction(); + m_actSearch->setIcon(getRealIcon("edit-find")); + m_actSearch->setToolTip(tr("Search in previous conversations")); + connect(m_actSearch,&QAction::triggered,this,&AIChatAssistant::slotSearch); + m_btSearch->setDefaultAction(m_actSearch); + m_leSearch=new QLineEdit(); + m_leSearch->setPlaceholderText(tr("Search in conversations")); + auto *hlayoutSearch=new QHBoxLayout(); + hlayoutSearch->addWidget(m_leSearch); + hlayoutSearch->addWidget(m_btSearch); + AIQueryStorageModel *model=new AIQueryStorageModel(this); QString path=config->configBaseDir+QString("/ai_conversation"); model->setStoragePath(path); @@ -17,12 +29,19 @@ AIChatAssistant::AIChatAssistant(QWidget *parent) treeView->setModel(model); connect(treeView, &QTreeView::clicked, this, &AIChatAssistant::onTreeViewClicked); + auto *vtreeLayout=new QVBoxLayout(); + vtreeLayout->addLayout(hlayoutSearch); + vtreeLayout->addWidget(treeView); + QWidget *wdgtTree=new QWidget(); + wdgtTree->setLayout(vtreeLayout); + textBrowser=new QTextBrowser(); auto *hlBrowser=new QSplitter(); - hlBrowser->addWidget(treeView); + hlBrowser->addWidget(wdgtTree); hlBrowser->addWidget(textBrowser); leEntry=new QTextEdit(); + leEntry->setPlaceholderText(tr("Enter your query here")); m_actSend=new QAction(); m_actSend->setIcon(getRealIcon("document-send")); m_actSend->setToolTip(tr("Send Query to AI provider")); @@ -42,6 +61,9 @@ AIChatAssistant::AIChatAssistant(QWidget *parent) connect(m_actOptions,&QAction::triggered,this,&AIChatAssistant::slotOptions); m_btOptions=new QToolButton(); m_btOptions->setDefaultAction(m_actOptions); + + + auto *hlayout=new QHBoxLayout(); hlayout->addWidget(leEntry); auto *vl=new QVBoxLayout(); @@ -269,6 +291,15 @@ void AIChatAssistant::slotOptions() dlg.setLayout(ly); dlg.exec(); } +/*! + * \brief filter old conversations + */ +void AIChatAssistant::slotSearch() +{ + QString text=m_leSearch->text(); + AIQueryStorageModel *model=dynamic_cast(treeView->model()); + model->setFilter(text); +} /*! * \brief handle communication error with ai provider */ @@ -433,4 +464,5 @@ QString AIChatAssistant::getConversationForBrowser() * - modeltree for conversations * + show summary of conversation in titles ? * - search in questions/answers + * - overlay buttons insert/etc. */ diff --git a/src/aichatassistant.h b/src/aichatassistant.h index e3e5e1ac99..8dcf84b6bf 100644 --- a/src/aichatassistant.h +++ b/src/aichatassistant.h @@ -25,6 +25,7 @@ private slots: void slotSend(); void slotInsert(); void slotOptions(); + void slotSearch(); void onRequestError(QNetworkReply::NetworkError code); void onRequestCompleted(QNetworkReply *nreply); void onTreeViewClicked(const QModelIndex &index); @@ -39,6 +40,9 @@ private slots: QToolButton *m_btOptions; QAction *m_actOptions; QTextEdit *leEntry; + QLineEdit *m_leSearch; + QToolButton *m_btSearch; + QAction *m_actSearch; QString m_response; QString m_selectedText; diff --git a/src/aiquerystoragemodel.cpp b/src/aiquerystoragemodel.cpp index f2ec5dc89f..e3a481fb6a 100644 --- a/src/aiquerystoragemodel.cpp +++ b/src/aiquerystoragemodel.cpp @@ -1,5 +1,8 @@ #include "aiquerystoragemodel.h" +#include +#include + AIQueryStorageModel::AIQueryStorageModel(QObject *parent) : QAbstractItemModel{parent} {} @@ -20,9 +23,9 @@ QVariant AIQueryStorageModel::data(const QModelIndex &index, int role) const previous=m_segments.at(parent_row-2).index; } int r=index.row(); - return m_files.value(previous+r); + return m_shownFiles.value(previous+r); } - return m_files.at(index.row()); + return m_shownFiles.at(index.row()); } } return QVariant{}; @@ -67,7 +70,7 @@ int AIQueryStorageModel::rowCount(const QModelIndex &parent) const return m_segments.at(row).index; } } - return m_files.size(); + return m_shownFiles.size(); } return 0; } @@ -93,29 +96,8 @@ void AIQueryStorageModel::setStoragePath(const QString &path) if(m_files.isEmpty()) { return; } - auto lst_names = std::vector{tr("Today"),tr("Last Week"),tr("Last Month")}; - auto lst_date = std::vector{QDate::currentDate(),QDate::currentDate().addDays(-7),QDate::currentDate().addMonths(-1)}; - auto last_it=m_files.cbegin(); - for(size_t i=0;i()); - if (it-last_it > 0) { - TimeFrame tf; - tf.name=lst_names.at(i); - tf.index=it-m_files.constBegin(); - m_segments.append(tf); - last_it=it; - } - if(it==m_files.cend()){ - break; - } - } - if(last_it!=m_files.cend()){ - TimeFrame tf; - tf.name=tr("Older"); - tf.index=last_it-m_files.constBegin(); - m_segments.append(tf); - } + m_shownFiles=m_files; + generateSegments(); } QString AIQueryStorageModel::getFileName(const QModelIndex &index) const @@ -124,15 +106,27 @@ QString AIQueryStorageModel::getFileName(const QModelIndex &index) const if (row == 0) { return QString{}; } + QString fn; if(m_segments.size()>0){ int previous=0; if(row>1 && row<=m_segments.size()){ previous=m_segments.at(row-2).index; } int r=index.row(); - return m_storageDirectory.absoluteFilePath(m_files.value(previous+r)); + if(m_filterActive){ + fn=m_filteredFiles.value(previous+r); + }else{ + fn=m_files.value(previous+r); + } + } + if(fn.isEmpty()){ + if(m_filterActive){ + fn=m_filteredFiles.at(index.row()); + }else{ + fn=m_files.at(index.row()); + } } - return m_storageDirectory.absoluteFilePath(m_files.at(index.row())); + return m_storageDirectory.absoluteFilePath(fn); } void AIQueryStorageModel::addFileName(const QString &name) @@ -152,3 +146,87 @@ void AIQueryStorageModel::addFileName(const QString &name) } endInsertRows(); } + +void AIQueryStorageModel::setFilter(const QString &filter) +{ + beginResetModel(); + if(filter.isEmpty()){ + m_shownFiles=m_files; + generateSegments(); + endResetModel(); + m_filterActive=false; + return; + } + m_filteredFiles.clear(); + for (auto &elem : m_files) { + if (fileContains(m_storageDirectory.absoluteFilePath(elem), filter)) { + m_filteredFiles.append(elem); + } + } + if (m_filteredFiles.isEmpty()) { + m_shownFiles=m_files; + m_filterActive=false; + } else { + m_shownFiles=m_filteredFiles; + m_filterActive=true; + } + generateSegments(); + endResetModel(); +} +/*! + * \brief segment files by date + */ +void AIQueryStorageModel::generateSegments() +{ + m_segments.clear(); + auto lst_names = std::vector{tr("Today"),tr("Last Week"),tr("Last Month")}; + auto lst_date = std::vector{QDate::currentDate(),QDate::currentDate().addDays(-7),QDate::currentDate().addMonths(-1)}; + auto last_it=m_shownFiles.cbegin(); + for(size_t i=0;i()); + if (it-last_it > 0) { + TimeFrame tf; + tf.name=lst_names.at(i); + tf.index=it-m_shownFiles.constBegin(); + m_segments.append(tf); + last_it=it; + } + if(it==m_shownFiles.cend()){ + break; + } + } + if(last_it!=m_shownFiles.cend()){ + TimeFrame tf; + tf.name=tr("Older"); + tf.index=last_it-m_files.constBegin(); + m_segments.append(tf); + } +} +/*! + * \brief open json file and check if user query or response contains filter + * \param filename + * \param filter + * \return + */ +bool AIQueryStorageModel::fileContains(const QString &filename, const QString &filter) const +{ + QFile file{filename}; + if (!file.open(QIODevice::ReadOnly)) { + return false; + } + QJsonDocument doc{QJsonDocument::fromJson(file.readAll())}; + file.close(); + if (doc.isNull()) { + return false; + } + auto obj = doc.object(); + QJsonArray ja = obj["messages"].toArray(); + for (auto elem : ja) { + auto msg = elem.toObject(); + if (msg.contains("content") && msg["content"].toString().contains(filter)) { + return true; + } + } + return false; +} diff --git a/src/aiquerystoragemodel.h b/src/aiquerystoragemodel.h index 595222c078..d0eec8e20d 100644 --- a/src/aiquerystoragemodel.h +++ b/src/aiquerystoragemodel.h @@ -20,9 +20,14 @@ class AIQueryStorageModel : public QAbstractItemModel void setStoragePath(const QString &path); QString getFileName(const QModelIndex &index) const; void addFileName(const QString &name); + void setFilter(const QString &filter); private: QDir m_storageDirectory; QStringList m_files; + QStringList m_filteredFiles; + QStringList m_shownFiles; + bool m_filterActive=false; + struct TimeFrame { @@ -31,7 +36,8 @@ class AIQueryStorageModel : public QAbstractItemModel }; QListm_segments; - + void generateSegments(); + bool fileContains(const QString &filename, const QString &filter) const; // QAbstractItemModel interface };