Skip to content

Commit

Permalink
Upgrade to version 0.4.4
Browse files Browse the repository at this point in the history
* feat: Add attachments for message
* feat: Support QtC color palette for chat view
* feat: Improve code completion from non-FIM models
* refactor: Removed trimming messages
* chore: Bump version to 0.4.4
  • Loading branch information
Palm1r authored Jan 8, 2025
1 parent 3501286 commit 511f5b3
Show file tree
Hide file tree
Showing 36 changed files with 734 additions and 147 deletions.
10 changes: 9 additions & 1 deletion ChatView/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
qt_add_library(QodeAssistChatView STATIC)

qt_policy(SET QTP0001 NEW)
qt_policy(SET QTP0004 NEW)

# URI name should match the subdirectory name to suppress the warning
qt_add_qml_module(QodeAssistChatView
URI ChatView
VERSION 1.0
Expand All @@ -13,6 +13,14 @@ qt_add_qml_module(QodeAssistChatView
qml/Badge.qml
qml/dialog/CodeBlock.qml
qml/dialog/TextBlock.qml
qml/controls/QoAButton.qml
qml/parts/TopBar.qml
qml/parts/BottomBar.qml
qml/parts/AttachedFilesPlace.qml
RESOURCES
icons/attach-file.svg
icons/close-dark.svg
icons/close-light.svg
SOURCES
ChatWidget.hpp ChatWidget.cpp
ChatModel.hpp ChatModel.cpp
Expand Down
62 changes: 42 additions & 20 deletions ChatView/ChatModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ QVariant ChatModel::data(const QModelIndex &index, int role) const
case Roles::Content: {
return message.content;
}
case Roles::Attachments: {
QStringList filenames;
for (const auto &attachment : message.attachments) {
filenames << attachment.filename;
}
return filenames;
}
default:
return QVariant();
}
Expand All @@ -65,28 +72,43 @@ QHash<int, QByteArray> ChatModel::roleNames() const
QHash<int, QByteArray> roles;
roles[Roles::RoleType] = "roleType";
roles[Roles::Content] = "content";
roles[Roles::Attachments] = "attachments";
return roles;
}

void ChatModel::addMessage(const QString &content, ChatRole role, const QString &id)
void ChatModel::addMessage(
const QString &content,
ChatRole role,
const QString &id,
const QList<Context::ContentFile> &attachments)
{
int tokenCount = estimateTokenCount(content);
QString fullContent = content;
if (!attachments.isEmpty()) {
fullContent += "\n\nAttached files list:";
for (const auto &attachment : attachments) {
fullContent += QString("\nname: %1\nfile content:\n%2")
.arg(attachment.filename, attachment.content);
}
}
int tokenCount = estimateTokenCount(fullContent);

if (!m_messages.isEmpty() && !id.isEmpty() && m_messages.last().id == id) {
Message &lastMessage = m_messages.last();
int oldTokenCount = lastMessage.tokenCount;
lastMessage.content = content;
lastMessage.attachments = attachments;
lastMessage.tokenCount = tokenCount;
m_totalTokens += (tokenCount - oldTokenCount);
emit dataChanged(index(m_messages.size() - 1), index(m_messages.size() - 1));
} else {
beginInsertRows(QModelIndex(), m_messages.size(), m_messages.size());
m_messages.append({role, content, tokenCount, id});
Message newMessage{role, content, tokenCount, id};
newMessage.attachments = attachments;
m_messages.append(newMessage);
m_totalTokens += tokenCount;
endInsertRows();
}

trim();
emit totalTokensChanged();
}

Expand All @@ -95,20 +117,6 @@ QVector<ChatModel::Message> ChatModel::getChatHistory() const
return m_messages;
}

void ChatModel::trim()
{
while (m_totalTokens > tokensThreshold()) {
if (!m_messages.isEmpty()) {
m_totalTokens -= m_messages.first().tokenCount;
beginRemoveRows(QModelIndex(), 0, 0);
m_messages.removeFirst();
endRemoveRows();
} else {
break;
}
}
}

int ChatModel::estimateTokenCount(const QString &text) const
{
return text.length() / 4;
Expand Down Expand Up @@ -156,7 +164,6 @@ QList<MessagePart> ChatModel::processMessageContent(const QString &content) cons
QJsonArray ChatModel::prepareMessagesForRequest(const QString &systemPrompt) const
{
QJsonArray messages;

messages.append(QJsonObject{{"role", "system"}, {"content", systemPrompt}});

for (const auto &message : m_messages) {
Expand All @@ -171,7 +178,22 @@ QJsonArray ChatModel::prepareMessagesForRequest(const QString &systemPrompt) con
default:
continue;
}
messages.append(QJsonObject{{"role", role}, {"content", message.content}});

QString content
= message.attachments.isEmpty()
? message.content
: message.content + "\n\nAttached files list:"
+ std::accumulate(
message.attachments.begin(),
message.attachments.end(),
QString(),
[](QString acc, const Context::ContentFile &attachment) {
return acc
+ QString("\nname: %1\nfile content:\n%2")
.arg(attachment.filename, attachment.content);
});

messages.append(QJsonObject{{"role", role}, {"content", content}});
}

return messages;
Expand Down
15 changes: 11 additions & 4 deletions ChatView/ChatModel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
#include <QJsonArray>
#include <QtQmlIntegration>

#include "context/ContentFile.hpp"

namespace QodeAssist::Chat {

class ChatModel : public QAbstractListModel
Expand All @@ -36,17 +38,19 @@ class ChatModel : public QAbstractListModel
QML_ELEMENT

public:
enum Roles { RoleType = Qt::UserRole, Content };

enum ChatRole { System, User, Assistant };
Q_ENUM(ChatRole)

enum Roles { RoleType = Qt::UserRole, Content, Attachments };

struct Message
{
ChatRole role;
QString content;
int tokenCount;
QString id;

QList<Context::ContentFile> attachments;
};

explicit ChatModel(QObject *parent = nullptr);
Expand All @@ -55,7 +59,11 @@ class ChatModel : public QAbstractListModel
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;

Q_INVOKABLE void addMessage(const QString &content, ChatRole role, const QString &id);
Q_INVOKABLE void addMessage(
const QString &content,
ChatRole role,
const QString &id,
const QList<Context::ContentFile> &attachments = {});
Q_INVOKABLE void clear();
Q_INVOKABLE QList<MessagePart> processMessageContent(const QString &content) const;

Expand All @@ -74,7 +82,6 @@ class ChatModel : public QAbstractListModel
void modelReseted();

private:
void trim();
int estimateTokenCount(const QString &text) const;

QVector<Message> m_messages;
Expand Down
48 changes: 46 additions & 2 deletions ChatView/ChatRootView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include <QClipboard>
#include <QFileDialog>
#include <QMessageBox>

#include <coreplugin/icore.h>
#include <projectexplorer/project.h>
Expand Down Expand Up @@ -75,9 +76,26 @@ QColor ChatRootView::backgroundColor() const
return Utils::creatorColor(Utils::Theme::BackgroundColorNormal);
}

void ChatRootView::sendMessage(const QString &message, bool sharingCurrentFile) const
void ChatRootView::sendMessage(const QString &message, bool sharingCurrentFile)
{
m_clientInterface->sendMessage(message, sharingCurrentFile);
if (m_chatModel->totalTokens() > m_chatModel->tokensThreshold()) {
QMessageBox::StandardButton reply = QMessageBox::question(
Core::ICore::dialogParent(),
tr("Token Limit Exceeded"),
tr("The chat history has exceeded the token limit.\n"
"Would you like to create new chat?"),
QMessageBox::Yes | QMessageBox::No);

if (reply == QMessageBox::Yes) {
autosave();
m_chatModel->clear();
m_recentFilePath = QString{};
return;
}
}

m_clientInterface->sendMessage(message, m_attachmentFiles, sharingCurrentFile);
clearAttachmentFiles();
}

void ChatRootView::copyToClipboard(const QString &text)
Expand All @@ -90,6 +108,14 @@ void ChatRootView::cancelRequest()
m_clientInterface->cancelRequest();
}

void ChatRootView::clearAttachmentFiles()
{
if (!m_attachmentFiles.isEmpty()) {
m_attachmentFiles.clear();
emit attachmentFilesChanged();
}
}

void ChatRootView::generateColors()
{
QColor baseColor = backgroundColor();
Expand Down Expand Up @@ -293,4 +319,22 @@ QString ChatRootView::getAutosaveFilePath() const
return QDir(dir).filePath(getSuggestedFileName() + ".json");
}

void ChatRootView::showAttachFilesDialog()
{
QFileDialog dialog(nullptr, tr("Select Files to Attach"));
dialog.setFileMode(QFileDialog::ExistingFiles);

if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
dialog.setDirectory(project->projectDirectory().toString());
}

if (dialog.exec() == QDialog::Accepted) {
QStringList filePaths = dialog.selectedFiles();
if (!filePaths.isEmpty()) {
m_attachmentFiles = filePaths;
emit attachmentFilesChanged();
}
}
}

} // namespace QodeAssist::Chat
10 changes: 8 additions & 2 deletions ChatView/ChatRootView.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class ChatRootView : public QQuickItem
Q_PROPERTY(QColor codeColor READ codeColor CONSTANT FINAL)
Q_PROPERTY(bool isSharingCurrentFile READ isSharingCurrentFile NOTIFY
isSharingCurrentFileChanged FINAL)
Q_PROPERTY(QStringList attachmentFiles MEMBER m_attachmentFiles NOTIFY attachmentFilesChanged)

QML_ELEMENT

public:
Expand All @@ -62,16 +64,19 @@ class ChatRootView : public QQuickItem
void autosave();
QString getAutosaveFilePath() const;

Q_INVOKABLE void showAttachFilesDialog();

public slots:
void sendMessage(const QString &message, bool sharingCurrentFile = false) const;
void sendMessage(const QString &message, bool sharingCurrentFile = false);
void copyToClipboard(const QString &text);
void cancelRequest();
void clearAttachmentFiles();

signals:
void chatModelChanged();
void currentTemplateChanged();

void isSharingCurrentFileChanged();
void attachmentFilesChanged();

private:
void generateColors();
Expand All @@ -90,6 +95,7 @@ public slots:
QColor m_secondaryColor;
QColor m_codeColor;
QString m_recentFilePath;
QStringList m_attachmentFiles;
};

} // namespace QodeAssist::Chat
7 changes: 5 additions & 2 deletions ChatView/ClientInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <texteditor/texteditor.h>

#include "ChatAssistantSettings.hpp"
#include "ContextManager.hpp"
#include "GeneralSettings.hpp"
#include "Logger.hpp"
#include "PromptTemplateManager.hpp"
Expand Down Expand Up @@ -64,11 +65,13 @@ ClientInterface::ClientInterface(ChatModel *chatModel, QObject *parent)

ClientInterface::~ClientInterface() = default;

void ClientInterface::sendMessage(const QString &message, bool includeCurrentFile)
void ClientInterface::sendMessage(
const QString &message, const QList<QString> &attachments, bool includeCurrentFile)
{
cancelRequest();

m_chatModel->addMessage(message, ChatModel::ChatRole::User, "");
auto attachFiles = Context::ContextManager::instance().getContentFiles(attachments);
m_chatModel->addMessage(message, ChatModel::ChatRole::User, "", attachFiles);

auto &chatAssistantSettings = Settings::chatAssistantSettings();

Expand Down
5 changes: 4 additions & 1 deletion ChatView/ClientInterface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ class ClientInterface : public QObject
explicit ClientInterface(ChatModel *chatModel, QObject *parent = nullptr);
~ClientInterface();

void sendMessage(const QString &message, bool includeCurrentFile = false);
void sendMessage(
const QString &message,
const QList<QString> &attachments = {},
bool includeCurrentFile = false);
void clearMessages();
void cancelRequest();

Expand Down
10 changes: 10 additions & 0 deletions ChatView/icons/attach-file.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions ChatView/icons/close-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions ChatView/icons/close-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions ChatView/qml/Badge.qml
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@ Rectangle {
id: root

property alias text: badgeText.text
property alias fontColor: badgeText.color

implicitWidth: badgeText.implicitWidth + root.radius
implicitHeight: badgeText.implicitHeight + 6
color: "lightgreen"
color: palette.button
radius: root.height / 2
border.color: palette.mid
border.width: 1
border.color: "gray"

Text {
id: badgeText

anchors.centerIn: parent
color: palette.buttonText
}
}
Loading

0 comments on commit 511f5b3

Please sign in to comment.