diff --git a/chat.cpp b/chat.cpp index 0db67f4b..697f74a4 100644 --- a/chat.cpp +++ b/chat.cpp @@ -21,6 +21,8 @@ Chat::Chat(QObject *parent) connect(this, &Chat::promptRequested, m_llmodel, &ChatLLM::prompt, Qt::QueuedConnection); connect(this, &Chat::modelNameChangeRequested, m_llmodel, &ChatLLM::modelNameChangeRequested, Qt::QueuedConnection); + connect(this, &Chat::unloadRequested, m_llmodel, &ChatLLM::unload, Qt::QueuedConnection); + connect(this, &Chat::reloadRequested, m_llmodel, &ChatLLM::reload, Qt::QueuedConnection); // The following are blocking operations and will block the gui thread, therefore must be fast // to respond to @@ -115,3 +117,14 @@ bool Chat::isRecalc() const { return m_llmodel->isRecalc(); } + +void Chat::unload() +{ + stopGenerating(); + emit unloadRequested(); +} + +void Chat::reload() +{ + emit reloadRequested(); +} diff --git a/chat.h b/chat.h index abc60897..79c61aa9 100644 --- a/chat.h +++ b/chat.h @@ -11,7 +11,7 @@ class Chat : public QObject { Q_OBJECT Q_PROPERTY(QString id READ id NOTIFY idChanged) - Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(ChatModel *chatModel READ chatModel NOTIFY chatModelChanged) Q_PROPERTY(bool isModelLoaded READ isModelLoaded NOTIFY isModelLoadedChanged) Q_PROPERTY(QString response READ response NOTIFY responseChanged) @@ -26,7 +26,12 @@ public: explicit Chat(QObject *parent = nullptr); QString id() const { return m_id; } - QString name() const { return m_name; } + QString name() const { return m_userName.isEmpty() ? m_name : m_userName; } + void setName(const QString &name) + { + m_userName = name; + emit nameChanged(); + } ChatModel *chatModel() { return m_chatModel; } Q_INVOKABLE void reset(); @@ -46,6 +51,9 @@ public: void setModelName(const QString &modelName); bool isRecalc() const; + void unload(); + void reload(); + Q_SIGNALS: void idChanged(); void nameChanged(); @@ -63,6 +71,8 @@ Q_SIGNALS: void threadCountChanged(); void setThreadCountRequested(int32_t threadCount); void recalcChanged(); + void unloadRequested(); + void reloadRequested(); private Q_SLOTS: void responseStarted(); @@ -72,6 +82,7 @@ private: ChatLLM *m_llmodel; QString m_id; QString m_name; + QString m_userName; ChatModel *m_chatModel; bool m_responseInProgress; int32_t m_desiredThreadCount; diff --git a/chatlistmodel.h b/chatlistmodel.h index db635ba4..bb0bd1b2 100644 --- a/chatlistmodel.h +++ b/chatlistmodel.h @@ -14,8 +14,6 @@ public: explicit ChatListModel(QObject *parent = nullptr) : QAbstractListModel(parent) { - if (m_chats.isEmpty()) - addChat(); } enum Roles { @@ -56,8 +54,8 @@ public: Q_INVOKABLE Chat* addChat() { Chat *newChat = new Chat(this); - beginInsertRows(QModelIndex(), m_chats.size(), m_chats.size()); - m_chats.append(newChat); + beginInsertRows(QModelIndex(), 0, 0); + m_chats.prepend(newChat); endInsertRows(); emit countChanged(); setCurrentChat(newChat); @@ -97,12 +95,15 @@ public: } if (m_currentChat) { - Q_ASSERT(m_currentChat); + if (m_currentChat->isModelLoaded()) + m_currentChat->unload(); emit disconnect(m_currentChat); } emit connectChat(chat); m_currentChat = chat; + if (!m_currentChat->isModelLoaded()) + m_currentChat->reload(); emit currentChatChanged(); } @@ -117,8 +118,8 @@ public: Q_SIGNALS: void countChanged(); - void connectChat(Chat *); - void disconnectChat(Chat *); + void connectChat(Chat*); + void disconnectChat(Chat*); void currentChatChanged(); private: diff --git a/chatllm.cpp b/chatllm.cpp index 51e36aaa..2612042e 100644 --- a/chatllm.cpp +++ b/chatllm.cpp @@ -288,3 +288,15 @@ bool ChatLLM::prompt(const QString &prompt, const QString &prompt_template, int3 emit responseStopped(); return true; } + +void ChatLLM::unload() +{ + delete m_llmodel; + m_llmodel = nullptr; + emit isModelLoadedChanged(); +} + +void ChatLLM::reload() +{ + loadModel(); +} diff --git a/chatllm.h b/chatllm.h index 192f90eb..ae888f0f 100644 --- a/chatllm.h +++ b/chatllm.h @@ -39,6 +39,8 @@ public Q_SLOTS: float temp, int32_t n_batch, float repeat_penalty, int32_t repeat_penalty_tokens); bool loadModel(); void modelNameChangeRequested(const QString &modelName); + void unload(); + void reload(); Q_SIGNALS: void isModelLoadedChanged(); diff --git a/llm.cpp b/llm.cpp index eabcb004..6aa17ff3 100644 --- a/llm.cpp +++ b/llm.cpp @@ -21,12 +21,16 @@ LLM::LLM() : QObject{nullptr} , m_chatListModel(new ChatListModel(this)) { + // Should be in the same thread connect(Download::globalInstance(), &Download::modelListChanged, - this, &LLM::modelListChanged, Qt::QueuedConnection); + this, &LLM::modelListChanged, Qt::DirectConnection); connect(m_chatListModel, &ChatListModel::connectChat, - this, &LLM::connectChat, Qt::QueuedConnection); + this, &LLM::connectChat, Qt::DirectConnection); connect(m_chatListModel, &ChatListModel::disconnectChat, - this, &LLM::disconnectChat, Qt::QueuedConnection); + this, &LLM::disconnectChat, Qt::DirectConnection); + + if (!m_chatListModel->count()) + m_chatListModel->addChat(); } QList LLM::modelList() const @@ -117,12 +121,10 @@ bool LLM::isRecalc() const void LLM::connectChat(Chat *chat) { - connect(chat, &Chat::modelNameChanged, - this, &LLM::modelListChanged, Qt::QueuedConnection); - connect(chat, &Chat::recalcChanged, - this, &LLM::recalcChanged, Qt::QueuedConnection); - connect(chat, &Chat::responseChanged, - this, &LLM::responseChanged, Qt::QueuedConnection); + // Should be in the same thread + connect(chat, &Chat::modelNameChanged, this, &LLM::modelListChanged, Qt::DirectConnection); + connect(chat, &Chat::recalcChanged, this, &LLM::recalcChanged, Qt::DirectConnection); + connect(chat, &Chat::responseChanged, this, &LLM::responseChanged, Qt::DirectConnection); } void LLM::disconnectChat(Chat *chat) diff --git a/llm.h b/llm.h index 174f4a5e..e291ebbf 100644 --- a/llm.h +++ b/llm.h @@ -29,8 +29,8 @@ Q_SIGNALS: void chatListModelChanged(); private Q_SLOTS: - void connectChat(Chat *chat); - void disconnectChat(Chat *chat); + void connectChat(Chat*); + void disconnectChat(Chat*); private: ChatListModel *m_chatListModel; diff --git a/qml/ChatDrawer.qml b/qml/ChatDrawer.qml index e628cde7..284d9780 100644 --- a/qml/ChatDrawer.qml +++ b/qml/ChatDrawer.qml @@ -58,32 +58,93 @@ Drawer { } } - ListView { - id: conversationList + ScrollView { anchors.left: parent.left anchors.right: parent.right + anchors.rightMargin: -10 anchors.topMargin: 10 anchors.top: newChat.bottom anchors.bottom: checkForUpdatesButton.top - model: LLM.chatListModel + anchors.bottomMargin: 10 + ScrollBar.vertical.policy: ScrollBar.AlwaysOn - delegate: Label { - id: chatLabel - anchors.left: parent.left - anchors.right: parent.right - color: theme.textColor - padding: 15 - font.pixelSize: theme.fontSizeLarger - text: name - background: Rectangle { + ListView { + id: conversationList + anchors.fill: parent + anchors.rightMargin: 10 + + model: LLM.chatListModel + + delegate: Rectangle { + id: chatRectangle + anchors.left: parent.left + anchors.right: parent.right + height: chatName.height + opacity: 0.9 + property bool isCurrent: LLM.chatListModel.currentChat === LLM.chatListModel.get(index) color: index % 2 === 0 ? theme.backgroundLight : theme.backgroundLighter + border.width: isCurrent + border.color: theme.backgroundLightest + TextArea { + id: chatName + anchors.left: parent.left + anchors.right: editButton.left + color: theme.textColor + padding: 15 + focus: false + readOnly: true + wrapMode: Text.NoWrap + hoverEnabled: false // Disable hover events on the TextArea + selectByMouse: false // Disable text selection in the TextArea + font.pixelSize: theme.fontSizeLarger + text: name + horizontalAlignment: TextInput.AlignLeft + background: Rectangle { + color: "transparent" + } + Keys.onReturnPressed: (event)=> { + changeName(); + } + onEditingFinished: { + changeName(); + } + function changeName() { + LLM.chatListModel.get(index).name = chatName.text + chatName.focus = false + chatName.readOnly = true + } + TapHandler { + onTapped: { + if (isCurrent) + return; + LLM.chatListModel.currentChat = LLM.chatListModel.get(index); + } + } + } + Button { + id: editButton + anchors.verticalCenter: chatName.verticalCenter + anchors.right: chatRectangle.right + anchors.rightMargin: 10 + width: 30 + height: 30 + visible: isCurrent + background: Image { + width: 30 + height: 30 + source: "qrc:/gpt4all/icons/edit.svg" + } + onClicked: { + chatName.focus = true + chatName.readOnly = false + } + } } - horizontalAlignment: TextInput.AlignLeft - } - Accessible.role: Accessible.List - Accessible.name: qsTr("List of chats") - Accessible.description: qsTr("List of chats in the drawer dialog") + Accessible.role: Accessible.List + Accessible.name: qsTr("List of chats") + Accessible.description: qsTr("List of chats in the drawer dialog") + } } /*Label {