mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-02-23 07:23:02 -05:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 31145f191b | |||
| 9096adde6f | |||
| b8e578d2d7 | |||
| 4e45774bce | |||
| 928490d31f | |||
| 97163cf6c9 | |||
| f85c162692 | |||
| 258053d826 | |||
| bf63ae5714 | |||
| ae76850e78 | |||
| bf3c0b3aa0 | |||
| 9add61c805 |
4
.github/workflows/build_cmake.yml
vendored
4
.github/workflows/build_cmake.yml
vendored
@ -13,8 +13,8 @@ on:
|
|||||||
env:
|
env:
|
||||||
PLUGIN_NAME: QodeAssist
|
PLUGIN_NAME: QodeAssist
|
||||||
QT_VERSION: 6.8.1
|
QT_VERSION: 6.8.1
|
||||||
QT_CREATOR_VERSION: 15.0.0
|
QT_CREATOR_VERSION: 15.0.1
|
||||||
QT_CREATOR_VERSION_INTERNAL: 15.0.0
|
QT_CREATOR_VERSION_INTERNAL: 15.0.1
|
||||||
MACOS_DEPLOYMENT_TARGET: "11.0"
|
MACOS_DEPLOYMENT_TARGET: "11.0"
|
||||||
CMAKE_VERSION: "3.29.6"
|
CMAKE_VERSION: "3.29.6"
|
||||||
NINJA_VERSION: "1.12.1"
|
NINJA_VERSION: "1.12.1"
|
||||||
|
|||||||
@ -18,9 +18,12 @@ qt_add_qml_module(QodeAssistChatView
|
|||||||
qml/parts/BottomBar.qml
|
qml/parts/BottomBar.qml
|
||||||
qml/parts/AttachedFilesPlace.qml
|
qml/parts/AttachedFilesPlace.qml
|
||||||
RESOURCES
|
RESOURCES
|
||||||
icons/attach-file.svg
|
icons/attach-file-light.svg
|
||||||
|
icons/attach-file-dark.svg
|
||||||
icons/close-dark.svg
|
icons/close-dark.svg
|
||||||
icons/close-light.svg
|
icons/close-light.svg
|
||||||
|
icons/link-file-light.svg
|
||||||
|
icons/link-file-dark.svg
|
||||||
SOURCES
|
SOURCES
|
||||||
ChatWidget.hpp ChatWidget.cpp
|
ChatWidget.hpp ChatWidget.cpp
|
||||||
ChatModel.hpp ChatModel.cpp
|
ChatModel.hpp ChatModel.cpp
|
||||||
|
|||||||
@ -28,7 +28,6 @@ namespace QodeAssist::Chat {
|
|||||||
|
|
||||||
ChatModel::ChatModel(QObject *parent)
|
ChatModel::ChatModel(QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
, m_totalTokens(0)
|
|
||||||
{
|
{
|
||||||
auto &settings = Settings::chatAssistantSettings();
|
auto &settings = Settings::chatAssistantSettings();
|
||||||
|
|
||||||
@ -90,26 +89,19 @@ void ChatModel::addMessage(
|
|||||||
.arg(attachment.filename, attachment.content);
|
.arg(attachment.filename, attachment.content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int tokenCount = estimateTokenCount(fullContent);
|
|
||||||
|
|
||||||
if (!m_messages.isEmpty() && !id.isEmpty() && m_messages.last().id == id) {
|
if (!m_messages.isEmpty() && !id.isEmpty() && m_messages.last().id == id) {
|
||||||
Message &lastMessage = m_messages.last();
|
Message &lastMessage = m_messages.last();
|
||||||
int oldTokenCount = lastMessage.tokenCount;
|
|
||||||
lastMessage.content = content;
|
lastMessage.content = content;
|
||||||
lastMessage.attachments = attachments;
|
lastMessage.attachments = attachments;
|
||||||
lastMessage.tokenCount = tokenCount;
|
|
||||||
m_totalTokens += (tokenCount - oldTokenCount);
|
|
||||||
emit dataChanged(index(m_messages.size() - 1), index(m_messages.size() - 1));
|
emit dataChanged(index(m_messages.size() - 1), index(m_messages.size() - 1));
|
||||||
} else {
|
} else {
|
||||||
beginInsertRows(QModelIndex(), m_messages.size(), m_messages.size());
|
beginInsertRows(QModelIndex(), m_messages.size(), m_messages.size());
|
||||||
Message newMessage{role, content, tokenCount, id};
|
Message newMessage{role, content, id};
|
||||||
newMessage.attachments = attachments;
|
newMessage.attachments = attachments;
|
||||||
m_messages.append(newMessage);
|
m_messages.append(newMessage);
|
||||||
m_totalTokens += tokenCount;
|
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
emit totalTokensChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<ChatModel::Message> ChatModel::getChatHistory() const
|
QVector<ChatModel::Message> ChatModel::getChatHistory() const
|
||||||
@ -117,18 +109,11 @@ QVector<ChatModel::Message> ChatModel::getChatHistory() const
|
|||||||
return m_messages;
|
return m_messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ChatModel::estimateTokenCount(const QString &text) const
|
|
||||||
{
|
|
||||||
return text.length() / 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatModel::clear()
|
void ChatModel::clear()
|
||||||
{
|
{
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
m_messages.clear();
|
m_messages.clear();
|
||||||
m_totalTokens = 0;
|
|
||||||
endResetModel();
|
endResetModel();
|
||||||
emit totalTokensChanged();
|
|
||||||
emit modelReseted();
|
emit modelReseted();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,11 +184,6 @@ QJsonArray ChatModel::prepareMessagesForRequest(const QString &systemPrompt) con
|
|||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ChatModel::totalTokens() const
|
|
||||||
{
|
|
||||||
return m_totalTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ChatModel::tokensThreshold() const
|
int ChatModel::tokensThreshold() const
|
||||||
{
|
{
|
||||||
auto &settings = Settings::chatAssistantSettings();
|
auto &settings = Settings::chatAssistantSettings();
|
||||||
|
|||||||
@ -33,7 +33,6 @@ namespace QodeAssist::Chat {
|
|||||||
class ChatModel : public QAbstractListModel
|
class ChatModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(int totalTokens READ totalTokens NOTIFY totalTokensChanged FINAL)
|
|
||||||
Q_PROPERTY(int tokensThreshold READ tokensThreshold NOTIFY tokensThresholdChanged FINAL)
|
Q_PROPERTY(int tokensThreshold READ tokensThreshold NOTIFY tokensThresholdChanged FINAL)
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
@ -47,7 +46,6 @@ public:
|
|||||||
{
|
{
|
||||||
ChatRole role;
|
ChatRole role;
|
||||||
QString content;
|
QString content;
|
||||||
int tokenCount;
|
|
||||||
QString id;
|
QString id;
|
||||||
|
|
||||||
QList<Context::ContentFile> attachments;
|
QList<Context::ContentFile> attachments;
|
||||||
@ -70,22 +68,17 @@ public:
|
|||||||
QVector<Message> getChatHistory() const;
|
QVector<Message> getChatHistory() const;
|
||||||
QJsonArray prepareMessagesForRequest(const QString &systemPrompt) const;
|
QJsonArray prepareMessagesForRequest(const QString &systemPrompt) const;
|
||||||
|
|
||||||
int totalTokens() const;
|
|
||||||
int tokensThreshold() const;
|
int tokensThreshold() const;
|
||||||
|
|
||||||
QString currentModel() const;
|
QString currentModel() const;
|
||||||
QString lastMessageId() const;
|
QString lastMessageId() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void totalTokensChanged();
|
|
||||||
void tokensThresholdChanged();
|
void tokensThresholdChanged();
|
||||||
void modelReseted();
|
void modelReseted();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int estimateTokenCount(const QString &text) const;
|
|
||||||
|
|
||||||
QVector<Message> m_messages;
|
QVector<Message> m_messages;
|
||||||
int m_totalTokens = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
|||||||
@ -29,12 +29,15 @@
|
|||||||
#include <projectexplorer/projectmanager.h>
|
#include <projectexplorer/projectmanager.h>
|
||||||
#include <utils/theme/theme.h>
|
#include <utils/theme/theme.h>
|
||||||
#include <utils/utilsicons.h>
|
#include <utils/utilsicons.h>
|
||||||
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
|
|
||||||
#include "ChatAssistantSettings.hpp"
|
#include "ChatAssistantSettings.hpp"
|
||||||
#include "ChatSerializer.hpp"
|
#include "ChatSerializer.hpp"
|
||||||
#include "GeneralSettings.hpp"
|
#include "GeneralSettings.hpp"
|
||||||
#include "Logger.hpp"
|
#include "Logger.hpp"
|
||||||
#include "ProjectSettings.hpp"
|
#include "ProjectSettings.hpp"
|
||||||
|
#include "context/TokenUtils.hpp"
|
||||||
|
#include "context/ContextManager.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
@ -43,6 +46,13 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
|||||||
, m_chatModel(new ChatModel(this))
|
, m_chatModel(new ChatModel(this))
|
||||||
, m_clientInterface(new ClientInterface(m_chatModel, this))
|
, m_clientInterface(new ClientInterface(m_chatModel, this))
|
||||||
{
|
{
|
||||||
|
m_isSyncOpenFiles = Settings::chatAssistantSettings().linkOpenFiles();
|
||||||
|
connect(&Settings::chatAssistantSettings().linkOpenFiles, &Utils::BaseAspect::changed,
|
||||||
|
this,
|
||||||
|
[this](){
|
||||||
|
setIsSyncOpenFiles(Settings::chatAssistantSettings().linkOpenFiles());
|
||||||
|
});
|
||||||
|
|
||||||
auto &settings = Settings::generalSettings();
|
auto &settings = Settings::generalSettings();
|
||||||
|
|
||||||
connect(&settings.caModel,
|
connect(&settings.caModel,
|
||||||
@ -50,18 +60,33 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
|||||||
this,
|
this,
|
||||||
&ChatRootView::currentTemplateChanged);
|
&ChatRootView::currentTemplateChanged);
|
||||||
|
|
||||||
connect(&Settings::chatAssistantSettings().sharingCurrentFile,
|
|
||||||
&Utils::BaseAspect::changed,
|
|
||||||
this,
|
|
||||||
&ChatRootView::isSharingCurrentFileChanged);
|
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
m_clientInterface,
|
m_clientInterface,
|
||||||
&ClientInterface::messageReceivedCompletely,
|
&ClientInterface::messageReceivedCompletely,
|
||||||
this,
|
this,
|
||||||
&ChatRootView::autosave);
|
&ChatRootView::autosave);
|
||||||
|
|
||||||
connect(m_chatModel, &ChatModel::modelReseted, [this]() { m_recentFilePath = QString(); });
|
connect(
|
||||||
|
m_clientInterface,
|
||||||
|
&ClientInterface::messageReceivedCompletely,
|
||||||
|
this,
|
||||||
|
&ChatRootView::updateInputTokensCount);
|
||||||
|
|
||||||
|
connect(m_chatModel, &ChatModel::modelReseted, [this]() { setRecentFilePath(QString{}); });
|
||||||
|
connect(this, &ChatRootView::attachmentFilesChanged, &ChatRootView::updateInputTokensCount);
|
||||||
|
connect(this, &ChatRootView::linkedFilesChanged, &ChatRootView::updateInputTokensCount);
|
||||||
|
connect(&Settings::chatAssistantSettings().useSystemPrompt, &Utils::BaseAspect::changed,
|
||||||
|
this, &ChatRootView::updateInputTokensCount);
|
||||||
|
connect(&Settings::chatAssistantSettings().systemPrompt, &Utils::BaseAspect::changed,
|
||||||
|
this, &ChatRootView::updateInputTokensCount);
|
||||||
|
|
||||||
|
auto editors = Core::EditorManager::instance();
|
||||||
|
|
||||||
|
connect(editors, &Core::EditorManager::editorOpened, this, &ChatRootView::onEditorOpened);
|
||||||
|
connect(editors, &Core::EditorManager::editorAboutToClose, this, &ChatRootView::onEditorAboutToClose);
|
||||||
|
connect(editors, &Core::EditorManager::editorsClosed, this, &ChatRootView::onEditorsClosed);
|
||||||
|
|
||||||
|
updateInputTokensCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatModel *ChatRootView::chatModel() const
|
ChatModel *ChatRootView::chatModel() const
|
||||||
@ -69,9 +94,9 @@ ChatModel *ChatRootView::chatModel() const
|
|||||||
return m_chatModel;
|
return m_chatModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatRootView::sendMessage(const QString &message, bool sharingCurrentFile)
|
void ChatRootView::sendMessage(const QString &message)
|
||||||
{
|
{
|
||||||
if (m_chatModel->totalTokens() > m_chatModel->tokensThreshold()) {
|
if (m_inputTokensCount > m_chatModel->tokensThreshold()) {
|
||||||
QMessageBox::StandardButton reply = QMessageBox::question(
|
QMessageBox::StandardButton reply = QMessageBox::question(
|
||||||
Core::ICore::dialogParent(),
|
Core::ICore::dialogParent(),
|
||||||
tr("Token Limit Exceeded"),
|
tr("Token Limit Exceeded"),
|
||||||
@ -82,12 +107,12 @@ void ChatRootView::sendMessage(const QString &message, bool sharingCurrentFile)
|
|||||||
if (reply == QMessageBox::Yes) {
|
if (reply == QMessageBox::Yes) {
|
||||||
autosave();
|
autosave();
|
||||||
m_chatModel->clear();
|
m_chatModel->clear();
|
||||||
m_recentFilePath = QString{};
|
setRecentFilePath(QString{});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_clientInterface->sendMessage(message, m_attachmentFiles, sharingCurrentFile);
|
m_clientInterface->sendMessage(message, m_attachmentFiles, m_linkedFiles);
|
||||||
clearAttachmentFiles();
|
clearAttachmentFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,6 +134,14 @@ void ChatRootView::clearAttachmentFiles()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatRootView::clearLinkedFiles()
|
||||||
|
{
|
||||||
|
if (!m_linkedFiles.isEmpty()) {
|
||||||
|
m_linkedFiles.clear();
|
||||||
|
emit linkedFilesChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString ChatRootView::getChatsHistoryDir() const
|
QString ChatRootView::getChatsHistoryDir() const
|
||||||
{
|
{
|
||||||
QString path;
|
QString path;
|
||||||
@ -135,11 +168,6 @@ QString ChatRootView::currentTemplate() const
|
|||||||
return settings.caModel();
|
return settings.caModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChatRootView::isSharingCurrentFile() const
|
|
||||||
{
|
|
||||||
return Settings::chatAssistantSettings().sharingCurrentFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatRootView::saveHistory(const QString &filePath)
|
void ChatRootView::saveHistory(const QString &filePath)
|
||||||
{
|
{
|
||||||
auto result = ChatSerializer::saveToFile(m_chatModel, filePath);
|
auto result = ChatSerializer::saveToFile(m_chatModel, filePath);
|
||||||
@ -154,8 +182,9 @@ void ChatRootView::loadHistory(const QString &filePath)
|
|||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
LOG_MESSAGE(QString("Failed to load chat history: %1").arg(result.errorMessage));
|
LOG_MESSAGE(QString("Failed to load chat history: %1").arg(result.errorMessage));
|
||||||
} else {
|
} else {
|
||||||
m_recentFilePath = filePath;
|
setRecentFilePath(filePath);
|
||||||
}
|
}
|
||||||
|
updateInputTokensCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatRootView::showSaveDialog()
|
void ChatRootView::showSaveDialog()
|
||||||
@ -236,7 +265,7 @@ void ChatRootView::autosave()
|
|||||||
QString filePath = getAutosaveFilePath();
|
QString filePath = getAutosaveFilePath();
|
||||||
if (!filePath.isEmpty()) {
|
if (!filePath.isEmpty()) {
|
||||||
ChatSerializer::saveToFile(m_chatModel, filePath);
|
ChatSerializer::saveToFile(m_chatModel, filePath);
|
||||||
m_recentFilePath = filePath;
|
setRecentFilePath(filePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,6 +283,16 @@ QString ChatRootView::getAutosaveFilePath() const
|
|||||||
return QDir(dir).filePath(getSuggestedFileName() + ".json");
|
return QDir(dir).filePath(getSuggestedFileName() + ".json");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList ChatRootView::attachmentFiles() const
|
||||||
|
{
|
||||||
|
return m_attachmentFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ChatRootView::linkedFiles() const
|
||||||
|
{
|
||||||
|
return m_linkedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
void ChatRootView::showAttachFilesDialog()
|
void ChatRootView::showAttachFilesDialog()
|
||||||
{
|
{
|
||||||
QFileDialog dialog(nullptr, tr("Select Files to Attach"));
|
QFileDialog dialog(nullptr, tr("Select Files to Attach"));
|
||||||
@ -280,4 +319,145 @@ void ChatRootView::showAttachFilesDialog()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatRootView::removeFileFromAttachList(int index)
|
||||||
|
{
|
||||||
|
if (index >= 0 && index < m_attachmentFiles.size()) {
|
||||||
|
m_attachmentFiles.removeAt(index);
|
||||||
|
emit attachmentFilesChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::showLinkFilesDialog()
|
||||||
|
{
|
||||||
|
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 newFilePaths = dialog.selectedFiles();
|
||||||
|
if (!newFilePaths.isEmpty()) {
|
||||||
|
bool filesAdded = false;
|
||||||
|
for (const QString &filePath : newFilePaths) {
|
||||||
|
if (!m_linkedFiles.contains(filePath)) {
|
||||||
|
m_linkedFiles.append(filePath);
|
||||||
|
filesAdded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (filesAdded) {
|
||||||
|
emit linkedFilesChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::removeFileFromLinkList(int index)
|
||||||
|
{
|
||||||
|
if (index >= 0 && index < m_linkedFiles.size()) {
|
||||||
|
m_linkedFiles.removeAt(index);
|
||||||
|
emit linkedFilesChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::calculateMessageTokensCount(const QString &message)
|
||||||
|
{
|
||||||
|
m_messageTokensCount = Context::TokenUtils::estimateTokens(message);
|
||||||
|
updateInputTokensCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::setIsSyncOpenFiles(bool state)
|
||||||
|
{
|
||||||
|
if (m_isSyncOpenFiles != state) {
|
||||||
|
m_isSyncOpenFiles = state;
|
||||||
|
emit isSyncOpenFilesChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::updateInputTokensCount()
|
||||||
|
{
|
||||||
|
int inputTokens = m_messageTokensCount;
|
||||||
|
auto& settings = Settings::chatAssistantSettings();
|
||||||
|
|
||||||
|
if (settings.useSystemPrompt()) {
|
||||||
|
inputTokens += Context::TokenUtils::estimateTokens(settings.systemPrompt());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_attachmentFiles.isEmpty()) {
|
||||||
|
auto attachFiles = Context::ContextManager::instance().getContentFiles(m_attachmentFiles);
|
||||||
|
inputTokens += Context::TokenUtils::estimateFilesTokens(attachFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_linkedFiles.isEmpty()) {
|
||||||
|
auto linkFiles = Context::ContextManager::instance().getContentFiles(m_linkedFiles);
|
||||||
|
inputTokens += Context::TokenUtils::estimateFilesTokens(linkFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& history = m_chatModel->getChatHistory();
|
||||||
|
for (const auto& message : history) {
|
||||||
|
inputTokens += Context::TokenUtils::estimateTokens(message.content);
|
||||||
|
inputTokens += 4; // + role
|
||||||
|
}
|
||||||
|
|
||||||
|
m_inputTokensCount = inputTokens;
|
||||||
|
emit inputTokensCountChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ChatRootView::inputTokensCount() const
|
||||||
|
{
|
||||||
|
return m_inputTokensCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatRootView::isSyncOpenFiles() const
|
||||||
|
{
|
||||||
|
return m_isSyncOpenFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::onEditorOpened(Core::IEditor *editor)
|
||||||
|
{
|
||||||
|
if (auto document = editor->document(); document && isSyncOpenFiles()) {
|
||||||
|
QString filePath = document->filePath().toString();
|
||||||
|
if (!m_linkedFiles.contains(filePath)) {
|
||||||
|
m_linkedFiles.append(filePath);
|
||||||
|
emit linkedFilesChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::onEditorAboutToClose(Core::IEditor *editor)
|
||||||
|
{
|
||||||
|
if (auto document = editor->document(); document && isSyncOpenFiles()) {
|
||||||
|
QString filePath = document->filePath().toString();
|
||||||
|
m_linkedFiles.removeOne(filePath);
|
||||||
|
emit linkedFilesChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::onEditorsClosed(QList<Core::IEditor *> editors)
|
||||||
|
{
|
||||||
|
if (isSyncOpenFiles()) {
|
||||||
|
for (Core::IEditor *editor : editors) {
|
||||||
|
if (auto document = editor->document()) {
|
||||||
|
QString filePath = document->filePath().toString();
|
||||||
|
m_linkedFiles.removeOne(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit linkedFilesChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ChatRootView::chatFileName() const
|
||||||
|
{
|
||||||
|
return QFileInfo(m_recentFilePath).baseName();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::setRecentFilePath(const QString &filePath)
|
||||||
|
{
|
||||||
|
if (m_recentFilePath != filePath) {
|
||||||
|
m_recentFilePath = filePath;
|
||||||
|
emit chatFileNameChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
|||||||
@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
#include "ChatModel.hpp"
|
#include "ChatModel.hpp"
|
||||||
#include "ClientInterface.hpp"
|
#include "ClientInterface.hpp"
|
||||||
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
@ -31,9 +32,11 @@ class ChatRootView : public QQuickItem
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(QodeAssist::Chat::ChatModel *chatModel READ chatModel NOTIFY chatModelChanged FINAL)
|
Q_PROPERTY(QodeAssist::Chat::ChatModel *chatModel READ chatModel NOTIFY chatModelChanged FINAL)
|
||||||
Q_PROPERTY(QString currentTemplate READ currentTemplate NOTIFY currentTemplateChanged FINAL)
|
Q_PROPERTY(QString currentTemplate READ currentTemplate NOTIFY currentTemplateChanged FINAL)
|
||||||
Q_PROPERTY(bool isSharingCurrentFile READ isSharingCurrentFile NOTIFY
|
Q_PROPERTY(bool isSyncOpenFiles READ isSyncOpenFiles NOTIFY isSyncOpenFilesChanged FINAL)
|
||||||
isSharingCurrentFileChanged FINAL)
|
Q_PROPERTY(QStringList attachmentFiles READ attachmentFiles NOTIFY attachmentFilesChanged FINAL)
|
||||||
Q_PROPERTY(QStringList attachmentFiles MEMBER m_attachmentFiles NOTIFY attachmentFilesChanged)
|
Q_PROPERTY(QStringList linkedFiles READ linkedFiles NOTIFY linkedFilesChanged FINAL)
|
||||||
|
Q_PROPERTY(int inputTokensCount READ inputTokensCount NOTIFY inputTokensCountChanged FINAL)
|
||||||
|
Q_PROPERTY(QString chatFileName READ chatFileName NOTIFY chatFileNameChanged FINAL)
|
||||||
|
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
@ -43,8 +46,6 @@ public:
|
|||||||
ChatModel *chatModel() const;
|
ChatModel *chatModel() const;
|
||||||
QString currentTemplate() const;
|
QString currentTemplate() const;
|
||||||
|
|
||||||
bool isSharingCurrentFile() const;
|
|
||||||
|
|
||||||
void saveHistory(const QString &filePath);
|
void saveHistory(const QString &filePath);
|
||||||
void loadHistory(const QString &filePath);
|
void loadHistory(const QString &filePath);
|
||||||
|
|
||||||
@ -54,19 +55,43 @@ public:
|
|||||||
void autosave();
|
void autosave();
|
||||||
QString getAutosaveFilePath() const;
|
QString getAutosaveFilePath() const;
|
||||||
|
|
||||||
|
QStringList attachmentFiles() const;
|
||||||
|
QStringList linkedFiles() const;
|
||||||
|
|
||||||
Q_INVOKABLE void showAttachFilesDialog();
|
Q_INVOKABLE void showAttachFilesDialog();
|
||||||
|
Q_INVOKABLE void removeFileFromAttachList(int index);
|
||||||
|
Q_INVOKABLE void showLinkFilesDialog();
|
||||||
|
Q_INVOKABLE void removeFileFromLinkList(int index);
|
||||||
|
Q_INVOKABLE void calculateMessageTokensCount(const QString &message);
|
||||||
|
Q_INVOKABLE void setIsSyncOpenFiles(bool state);
|
||||||
|
|
||||||
|
Q_INVOKABLE void updateInputTokensCount();
|
||||||
|
int inputTokensCount() const;
|
||||||
|
|
||||||
|
bool isSyncOpenFiles() const;
|
||||||
|
|
||||||
|
void onEditorOpened(Core::IEditor *editor);
|
||||||
|
void onEditorAboutToClose(Core::IEditor *editor);
|
||||||
|
void onEditorsClosed(QList<Core::IEditor *> editors);
|
||||||
|
|
||||||
|
QString chatFileName() const;
|
||||||
|
void setRecentFilePath(const QString &filePath);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void sendMessage(const QString &message, bool sharingCurrentFile = false);
|
void sendMessage(const QString &message);
|
||||||
void copyToClipboard(const QString &text);
|
void copyToClipboard(const QString &text);
|
||||||
void cancelRequest();
|
void cancelRequest();
|
||||||
void clearAttachmentFiles();
|
void clearAttachmentFiles();
|
||||||
|
void clearLinkedFiles();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void chatModelChanged();
|
void chatModelChanged();
|
||||||
void currentTemplateChanged();
|
void currentTemplateChanged();
|
||||||
void isSharingCurrentFileChanged();
|
|
||||||
void attachmentFilesChanged();
|
void attachmentFilesChanged();
|
||||||
|
void linkedFilesChanged();
|
||||||
|
void inputTokensCountChanged();
|
||||||
|
void isSyncOpenFilesChanged();
|
||||||
|
void chatFileNameChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString getChatsHistoryDir() const;
|
QString getChatsHistoryDir() const;
|
||||||
@ -77,6 +102,10 @@ private:
|
|||||||
QString m_currentTemplate;
|
QString m_currentTemplate;
|
||||||
QString m_recentFilePath;
|
QString m_recentFilePath;
|
||||||
QStringList m_attachmentFiles;
|
QStringList m_attachmentFiles;
|
||||||
|
QStringList m_linkedFiles;
|
||||||
|
int m_messageTokensCount{0};
|
||||||
|
int m_inputTokensCount{0};
|
||||||
|
bool m_isSyncOpenFiles;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
|||||||
@ -82,7 +82,6 @@ QJsonObject ChatSerializer::serializeMessage(const ChatModel::Message &message)
|
|||||||
QJsonObject messageObj;
|
QJsonObject messageObj;
|
||||||
messageObj["role"] = static_cast<int>(message.role);
|
messageObj["role"] = static_cast<int>(message.role);
|
||||||
messageObj["content"] = message.content;
|
messageObj["content"] = message.content;
|
||||||
messageObj["tokenCount"] = message.tokenCount;
|
|
||||||
messageObj["id"] = message.id;
|
messageObj["id"] = message.id;
|
||||||
return messageObj;
|
return messageObj;
|
||||||
}
|
}
|
||||||
@ -92,7 +91,6 @@ ChatModel::Message ChatSerializer::deserializeMessage(const QJsonObject &json)
|
|||||||
ChatModel::Message message;
|
ChatModel::Message message;
|
||||||
message.role = static_cast<ChatModel::ChatRole>(json["role"].toInt());
|
message.role = static_cast<ChatModel::ChatRole>(json["role"].toInt());
|
||||||
message.content = json["content"].toString();
|
message.content = json["content"].toString();
|
||||||
message.tokenCount = json["tokenCount"].toInt();
|
|
||||||
message.id = json["id"].toString();
|
message.id = json["id"].toString();
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
@ -107,7 +105,6 @@ QJsonObject ChatSerializer::serializeChat(const ChatModel *model)
|
|||||||
QJsonObject root;
|
QJsonObject root;
|
||||||
root["version"] = VERSION;
|
root["version"] = VERSION;
|
||||||
root["messages"] = messagesArray;
|
root["messages"] = messagesArray;
|
||||||
root["totalTokens"] = model->totalTokens();
|
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,7 +66,7 @@ ClientInterface::ClientInterface(ChatModel *chatModel, QObject *parent)
|
|||||||
ClientInterface::~ClientInterface() = default;
|
ClientInterface::~ClientInterface() = default;
|
||||||
|
|
||||||
void ClientInterface::sendMessage(
|
void ClientInterface::sendMessage(
|
||||||
const QString &message, const QList<QString> &attachments, bool includeCurrentFile)
|
const QString &message, const QList<QString> &attachments, const QList<QString> &linkedFiles)
|
||||||
{
|
{
|
||||||
cancelRequest();
|
cancelRequest();
|
||||||
|
|
||||||
@ -100,11 +100,8 @@ void ClientInterface::sendMessage(
|
|||||||
if (chatAssistantSettings.useSystemPrompt())
|
if (chatAssistantSettings.useSystemPrompt())
|
||||||
systemPrompt = chatAssistantSettings.systemPrompt();
|
systemPrompt = chatAssistantSettings.systemPrompt();
|
||||||
|
|
||||||
if (includeCurrentFile) {
|
if (!linkedFiles.isEmpty()) {
|
||||||
QString fileContext = getCurrentFileContext();
|
systemPrompt = getSystemPromptWithLinkedFiles(systemPrompt, linkedFiles);
|
||||||
if (!fileContext.isEmpty()) {
|
|
||||||
systemPrompt = systemPrompt.append(fileContext);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject providerRequest;
|
QJsonObject providerRequest;
|
||||||
@ -198,4 +195,21 @@ QString ClientInterface::getCurrentFileContext() const
|
|||||||
return QString("Current file context:\n%1\nFile content:\n%2").arg(fileInfo, content);
|
return QString("Current file context:\n%1\nFile content:\n%2").arg(fileInfo, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ClientInterface::getSystemPromptWithLinkedFiles(const QString &basePrompt, const QList<QString> &linkedFiles) const
|
||||||
|
{
|
||||||
|
QString updatedPrompt = basePrompt;
|
||||||
|
|
||||||
|
if (!linkedFiles.isEmpty()) {
|
||||||
|
updatedPrompt += "\n\nLinked files for reference:\n";
|
||||||
|
|
||||||
|
auto contentFiles = Context::ContextManager::instance().getContentFiles(linkedFiles);
|
||||||
|
for (const auto &file : contentFiles) {
|
||||||
|
updatedPrompt += QString("\nFile: %1\nContent:\n%2\n")
|
||||||
|
.arg(file.filename, file.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedPrompt;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
|||||||
@ -39,7 +39,7 @@ public:
|
|||||||
void sendMessage(
|
void sendMessage(
|
||||||
const QString &message,
|
const QString &message,
|
||||||
const QList<QString> &attachments = {},
|
const QList<QString> &attachments = {},
|
||||||
bool includeCurrentFile = false);
|
const QList<QString> &linkedFiles = {});
|
||||||
void clearMessages();
|
void clearMessages();
|
||||||
void cancelRequest();
|
void cancelRequest();
|
||||||
|
|
||||||
@ -50,6 +50,9 @@ signals:
|
|||||||
private:
|
private:
|
||||||
void handleLLMResponse(const QString &response, const QJsonObject &request, bool isComplete);
|
void handleLLMResponse(const QString &response, const QJsonObject &request, bool isComplete);
|
||||||
QString getCurrentFileContext() const;
|
QString getCurrentFileContext() const;
|
||||||
|
QString getSystemPromptWithLinkedFiles(
|
||||||
|
const QString &basePrompt,
|
||||||
|
const QList<QString> &linkedFiles) const;
|
||||||
|
|
||||||
LLMCore::RequestHandler *m_requestHandler;
|
LLMCore::RequestHandler *m_requestHandler;
|
||||||
ChatModel *m_chatModel;
|
ChatModel *m_chatModel;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
<svg width="24" height="48" viewBox="0 0 24 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="48" viewBox="0 0 24 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g clip-path="url(#clip0_37_14)">
|
<g clip-path="url(#clip0_37_14)">
|
||||||
<path d="M22 10.1053V36.7368C22 41.8547 17.525 46 12 46C6.475 46 2 41.8547 2 36.7368V7.78947C2 4.59368 4.8 2 8.25 2C11.7 2 15.75 4.59368 15.75 7.78947V35.5789C15.75 36.8526 13.375 39.0526 12 39.0526C10.625 39.0526 8.25 36.8526 8.25 35.5789V21.6842V8.94737" stroke="black" stroke-width="3" stroke-linecap="round"/>
|
<path d="M22 10.1053V36.7368C22 41.8547 17.525 46 12 46C6.475 46 2 41.8547 2 36.7368V7.78947C2 4.59368 4.8 2 8.25 2C11.7 2 15.75 4.59368 15.75 7.78947V35.5789C15.75 36.8526 13.375 39.0526 12 39.0526C10.625 39.0526 8.25 36.8526 8.25 35.5789V21.6842V8.94737" stroke="black" stroke-width="3" stroke-linecap="round"/>
|
||||||
|
<path d="M22 10.1053V36.7368C22 41.8547 17.525 46 12 46C6.475 46 2 41.8547 2 36.7368V7.78947C2 4.59368 4.8 2 8.25 2C11.7 2 15.75 4.59368 15.75 7.78947V35.5789C15.75 36.8526 13.375 39.0526 12 39.0526C10.625 39.0526 8.25 36.8526 8.25 35.5789V21.6842V8.94737" stroke="black" stroke-width="3" stroke-linecap="round"/>
|
||||||
</g>
|
</g>
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="clip0_37_14">
|
<clipPath id="clip0_37_14">
|
||||||
|
Before Width: | Height: | Size: 555 B After Width: | Height: | Size: 869 B |
11
ChatView/icons/attach-file-light.svg
Normal file
11
ChatView/icons/attach-file-light.svg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<svg width="24" height="48" viewBox="0 0 24 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_51_20)">
|
||||||
|
<path d="M22 10.1053V36.7368C22 41.8547 17.525 46 12 46C6.475 46 2 41.8547 2 36.7368V7.78947C2 4.59368 4.8 2 8.25 2C11.7 2 15.75 4.59368 15.75 7.78947V35.5789C15.75 36.8526 13.375 39.0526 12 39.0526C10.625 39.0526 8.25 36.8526 8.25 35.5789V21.6842V8.94737" stroke="white" stroke-width="3" stroke-linecap="round"/>
|
||||||
|
<path d="M22 10.1053V36.7368C22 41.8547 17.525 46 12 46C6.475 46 2 41.8547 2 36.7368V7.78947C2 4.59368 4.8 2 8.25 2C11.7 2 15.75 4.59368 15.75 7.78947V35.5789C15.75 36.8526 13.375 39.0526 12 39.0526C10.625 39.0526 8.25 36.8526 8.25 35.5789V21.6842V8.94737" stroke="white" stroke-width="3" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_51_20">
|
||||||
|
<rect width="24" height="48" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 869 B |
12
ChatView/icons/link-file-dark.svg
Normal file
12
ChatView/icons/link-file-dark.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<svg width="20" height="44" viewBox="0 0 20 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_49_24)">
|
||||||
|
<path d="M10 12L10 32L10 12Z" fill="black"/>
|
||||||
|
<path d="M10 12L10 32" stroke="black" stroke-width="3"/>
|
||||||
|
<path d="M1.50001 12.484C1.50001 -1.99999 18.5 -1.99999 18.5 12.484M1.5 31.5334C1.50001 46 18.5 46 18.5 31.5334" stroke="black" stroke-width="3" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_49_24">
|
||||||
|
<rect width="20" height="44" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 513 B |
12
ChatView/icons/link-file-light.svg
Normal file
12
ChatView/icons/link-file-light.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<svg width="20" height="44" viewBox="0 0 20 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_51_24)">
|
||||||
|
<path d="M10 12L10 32Z" fill="white"/>
|
||||||
|
<path d="M10 12L10 32" stroke="white" stroke-width="3"/>
|
||||||
|
<path d="M1.50001 12.484C1.50001 -1.99999 18.5 -1.99999 18.5 12.484M1.5 31.5334C1.50001 46 18.5 46 18.5 31.5334" stroke="white" stroke-width="3" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_51_24">
|
||||||
|
<rect width="20" height="44" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 507 B |
@ -70,7 +70,10 @@ ChatRootView {
|
|||||||
loadButton.onClicked: root.showLoadDialog()
|
loadButton.onClicked: root.showLoadDialog()
|
||||||
clearButton.onClicked: root.clearChat()
|
clearButton.onClicked: root.clearChat()
|
||||||
tokensBadge {
|
tokensBadge {
|
||||||
text: qsTr("tokens:%1/%2").arg(root.chatModel.totalTokens).arg(root.chatModel.tokensThreshold)
|
text: qsTr("tokens:%1/%2").arg(root.inputTokensCount).arg(root.chatModel.tokensThreshold)
|
||||||
|
}
|
||||||
|
recentPath {
|
||||||
|
text: qsTr("Latest chat file name: %1").arg(root.chatFileName.length > 0 ? root.chatFileName : "Unsaved")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +104,7 @@ ChatRootView {
|
|||||||
height: 30
|
height: 30
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
ScrollBar.vertical: QQC.ScrollBar {
|
||||||
id: scroll
|
id: scroll
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,6 +150,8 @@ ChatRootView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onTextChanged: root.calculateMessageTokensCount(messageInput.text)
|
||||||
|
|
||||||
Keys.onPressed: function(event) {
|
Keys.onPressed: function(event) {
|
||||||
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && !(event.modifiers & Qt.ShiftModifier)) {
|
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && !(event.modifiers & Qt.ShiftModifier)) {
|
||||||
root.sendChatMessage()
|
root.sendChatMessage()
|
||||||
@ -161,6 +166,21 @@ ChatRootView {
|
|||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
attachedFilesModel: root.attachmentFiles
|
attachedFilesModel: root.attachmentFiles
|
||||||
|
iconPath: palette.window.hslLightness > 0.5 ? "qrc:/qt/qml/ChatView/icons/attach-file-dark.svg"
|
||||||
|
: "qrc:/qt/qml/ChatView/icons/attach-file-light.svg"
|
||||||
|
accentColor: Qt.tint(palette.mid, Qt.rgba(0, 0.8, 0.3, 0.4))
|
||||||
|
onRemoveFileFromListByIndex: (index) => root.removeFileFromAttachList(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachedFilesPlace {
|
||||||
|
id: linkedFilesPlace
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
attachedFilesModel: root.linkedFiles
|
||||||
|
iconPath: palette.window.hslLightness > 0.5 ? "qrc:/qt/qml/ChatView/icons/link-file-dark.svg"
|
||||||
|
: "qrc:/qt/qml/ChatView/icons/link-file-light.svg"
|
||||||
|
accentColor: Qt.tint(palette.mid, Qt.rgba(0, 0.3, 0.8, 0.4))
|
||||||
|
onRemoveFileFromListByIndex: (index) => root.removeFileFromLinkList(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
BottomBar {
|
BottomBar {
|
||||||
@ -171,13 +191,19 @@ ChatRootView {
|
|||||||
|
|
||||||
sendButton.onClicked: root.sendChatMessage()
|
sendButton.onClicked: root.sendChatMessage()
|
||||||
stopButton.onClicked: root.cancelRequest()
|
stopButton.onClicked: root.cancelRequest()
|
||||||
sharingCurrentFile.checked: root.isSharingCurrentFile
|
syncOpenFiles {
|
||||||
|
checked: root.isSyncOpenFiles
|
||||||
|
onCheckedChanged: root.setIsSyncOpenFiles(bottomBar.syncOpenFiles.checked)
|
||||||
|
}
|
||||||
attachFiles.onClicked: root.showAttachFilesDialog()
|
attachFiles.onClicked: root.showAttachFilesDialog()
|
||||||
|
linkFiles.onClicked: root.showLinkFilesDialog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearChat() {
|
function clearChat() {
|
||||||
root.chatModel.clear()
|
root.chatModel.clear()
|
||||||
|
root.clearAttachmentFiles()
|
||||||
|
root.updateInputTokensCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollToBottom() {
|
function scrollToBottom() {
|
||||||
@ -185,7 +211,7 @@ ChatRootView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sendChatMessage() {
|
function sendChatMessage() {
|
||||||
root.sendMessage(messageInput.text, bottomBar.sharingCurrentFile.checked)
|
root.sendMessage(messageInput.text)
|
||||||
messageInput.text = ""
|
messageInput.text = ""
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,9 +23,13 @@ import QtQuick.Layouts
|
|||||||
import ChatView
|
import ChatView
|
||||||
|
|
||||||
Flow {
|
Flow {
|
||||||
id: attachFilesPlace
|
id: root
|
||||||
|
|
||||||
property alias attachedFilesModel: attachRepeater.model
|
property alias attachedFilesModel: attachRepeater.model
|
||||||
|
property color accentColor: palette.mid
|
||||||
|
property string iconPath
|
||||||
|
|
||||||
|
signal removeFileFromListByIndex(index: int)
|
||||||
|
|
||||||
spacing: 5
|
spacing: 5
|
||||||
leftPadding: 5
|
leftPadding: 5
|
||||||
@ -41,18 +45,33 @@ Flow {
|
|||||||
required property string modelData
|
required property string modelData
|
||||||
|
|
||||||
height: 30
|
height: 30
|
||||||
width: fileNameText.width + closeButton.width + 20
|
width: contentRow.width + 10
|
||||||
radius: 4
|
radius: 4
|
||||||
color: palette.button
|
color: palette.button
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: palette.mid
|
border.color: mouse.hovered ? palette.highlight : root.accentColor
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
id: mouse
|
||||||
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
|
id: contentRow
|
||||||
|
|
||||||
spacing: 5
|
spacing: 5
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: 5
|
anchors.leftMargin: 5
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: icon
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
source: root.iconPath
|
||||||
|
sourceSize.width: 8
|
||||||
|
sourceSize.height: 15
|
||||||
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: fileNameText
|
id: fileNameText
|
||||||
|
|
||||||
@ -69,14 +88,10 @@ Flow {
|
|||||||
id: closeButton
|
id: closeButton
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: closeIcon.width
|
width: closeIcon.width + 5
|
||||||
height: closeButton.width
|
height: closeButton.width + 5
|
||||||
|
|
||||||
onClicked: {
|
onClicked: root.removeFileFromListByIndex(index)
|
||||||
const newList = [...root.attachmentFiles];
|
|
||||||
newList.splice(index, 1);
|
|
||||||
root.attachmentFiles = newList;
|
|
||||||
}
|
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: closeIcon
|
id: closeIcon
|
||||||
|
|||||||
@ -27,8 +27,9 @@ Rectangle {
|
|||||||
|
|
||||||
property alias sendButton: sendButtonId
|
property alias sendButton: sendButtonId
|
||||||
property alias stopButton: stopButtonId
|
property alias stopButton: stopButtonId
|
||||||
property alias sharingCurrentFile: sharingCurrentFileId
|
property alias syncOpenFiles: syncOpenFilesId
|
||||||
property alias attachFiles: attachFilesId
|
property alias attachFiles: attachFilesId
|
||||||
|
property alias linkFiles: linkFilesId
|
||||||
|
|
||||||
color: palette.window.hslLightness > 0.5 ?
|
color: palette.window.hslLightness > 0.5 ?
|
||||||
Qt.darker(palette.window, 1.1) :
|
Qt.darker(palette.window, 1.1) :
|
||||||
@ -59,23 +60,37 @@ Rectangle {
|
|||||||
text: qsTr("Stop")
|
text: qsTr("Stop")
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
|
||||||
id: sharingCurrentFileId
|
|
||||||
|
|
||||||
text: qsTr("Share current file with models")
|
|
||||||
}
|
|
||||||
|
|
||||||
QoAButton {
|
QoAButton {
|
||||||
id: attachFilesId
|
id: attachFilesId
|
||||||
|
|
||||||
icon {
|
icon {
|
||||||
source: "qrc:/qt/qml/ChatView/icons/attach-file.svg"
|
source: "qrc:/qt/qml/ChatView/icons/attach-file-dark.svg"
|
||||||
height: 15
|
height: 15
|
||||||
width: 8
|
width: 8
|
||||||
}
|
}
|
||||||
text: qsTr("Attach files")
|
text: qsTr("Attach files")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: linkFilesId
|
||||||
|
|
||||||
|
icon {
|
||||||
|
source: "qrc:/qt/qml/ChatView/icons/link-file-dark.svg"
|
||||||
|
height: 15
|
||||||
|
width: 8
|
||||||
|
}
|
||||||
|
text: qsTr("Link files")
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
id: syncOpenFilesId
|
||||||
|
|
||||||
|
text: qsTr("Sync open files")
|
||||||
|
|
||||||
|
ToolTip.visible: syncOpenFilesId.hovered
|
||||||
|
ToolTip.text: qsTr("Automatically synchronize currently opened files with the model context")
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,6 +28,7 @@ Rectangle {
|
|||||||
property alias loadButton: loadButtonId
|
property alias loadButton: loadButtonId
|
||||||
property alias clearButton: clearButtonId
|
property alias clearButton: clearButtonId
|
||||||
property alias tokensBadge: tokensBadgeId
|
property alias tokensBadge: tokensBadgeId
|
||||||
|
property alias recentPath: recentPathId
|
||||||
|
|
||||||
color: palette.window.hslLightness > 0.5 ?
|
color: palette.window.hslLightness > 0.5 ?
|
||||||
Qt.darker(palette.window, 1.1) :
|
Qt.darker(palette.window, 1.1) :
|
||||||
@ -62,6 +63,14 @@ Rectangle {
|
|||||||
text: qsTr("Clear")
|
text: qsTr("Clear")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: recentPathId
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
color: palette.text
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Id" : "qodeassist",
|
"Id" : "qodeassist",
|
||||||
"Name" : "QodeAssist",
|
"Name" : "QodeAssist",
|
||||||
"Version" : "0.4.6",
|
"Version" : "0.4.9",
|
||||||
"Vendor" : "Petr Mironychev",
|
"Vendor" : "Petr Mironychev",
|
||||||
"VendorId" : "petrmironychev",
|
"VendorId" : "petrmironychev",
|
||||||
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",
|
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",
|
||||||
|
|||||||
@ -3,6 +3,7 @@ add_library(Context STATIC
|
|||||||
ChangesManager.h ChangesManager.cpp
|
ChangesManager.h ChangesManager.cpp
|
||||||
ContextManager.hpp ContextManager.cpp
|
ContextManager.hpp ContextManager.cpp
|
||||||
ContentFile.hpp
|
ContentFile.hpp
|
||||||
|
TokenUtils.hpp TokenUtils.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(Context
|
target_link_libraries(Context
|
||||||
|
|||||||
54
context/TokenUtils.cpp
Normal file
54
context/TokenUtils.cpp
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
|
*
|
||||||
|
* This file is part of QodeAssist.
|
||||||
|
*
|
||||||
|
* QodeAssist is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* QodeAssist is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "TokenUtils.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
int TokenUtils::estimateTokens(const QString& text)
|
||||||
|
{
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: need to improve
|
||||||
|
return text.length() / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TokenUtils::estimateFileTokens(const Context::ContentFile& file)
|
||||||
|
{
|
||||||
|
int total = 0;
|
||||||
|
|
||||||
|
total += estimateTokens(file.filename);
|
||||||
|
total += estimateTokens(file.content);
|
||||||
|
total += 5;
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TokenUtils::estimateFilesTokens(const QList<Context::ContentFile>& files)
|
||||||
|
{
|
||||||
|
int total = 0;
|
||||||
|
for (const auto& file : files) {
|
||||||
|
total += estimateFileTokens(file);
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
36
context/TokenUtils.hpp
Normal file
36
context/TokenUtils.hpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
|
*
|
||||||
|
* This file is part of QodeAssist.
|
||||||
|
*
|
||||||
|
* QodeAssist is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* QodeAssist is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include "ContentFile.hpp"
|
||||||
|
#include <QList>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
class TokenUtils
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static int estimateTokens(const QString& text);
|
||||||
|
static int estimateFileTokens(const Context::ContentFile& file);
|
||||||
|
static int estimateFilesTokens(const QList<Context::ContentFile>& files);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@ -155,7 +155,9 @@ private:
|
|||||||
|
|
||||||
void handleUpdateCheckResult(const PluginUpdater::UpdateInfo &info)
|
void handleUpdateCheckResult(const PluginUpdater::UpdateInfo &info)
|
||||||
{
|
{
|
||||||
if (!info.isUpdateAvailable)
|
if (!info.isUpdateAvailable
|
||||||
|
|| QVersionNumber::fromString(info.currentIdeVersion)
|
||||||
|
> QVersionNumber::fromString(info.targetIdeVersion))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (m_statusWidget)
|
if (m_statusWidget)
|
||||||
|
|||||||
@ -44,15 +44,15 @@ ChatAssistantSettings::ChatAssistantSettings()
|
|||||||
|
|
||||||
// Chat Settings
|
// Chat Settings
|
||||||
chatTokensThreshold.setSettingsKey(Constants::CA_TOKENS_THRESHOLD);
|
chatTokensThreshold.setSettingsKey(Constants::CA_TOKENS_THRESHOLD);
|
||||||
chatTokensThreshold.setLabelText(Tr::tr("Chat History Token Limit:"));
|
chatTokensThreshold.setLabelText(Tr::tr("Chat history token limit:"));
|
||||||
chatTokensThreshold.setToolTip(Tr::tr("Maximum number of tokens in chat history. When "
|
chatTokensThreshold.setToolTip(Tr::tr("Maximum number of tokens in chat history. When "
|
||||||
"exceeded, oldest messages will be removed."));
|
"exceeded, oldest messages will be removed."));
|
||||||
chatTokensThreshold.setRange(1, 900000);
|
chatTokensThreshold.setRange(1, 900000);
|
||||||
chatTokensThreshold.setDefaultValue(8000);
|
chatTokensThreshold.setDefaultValue(8000);
|
||||||
|
|
||||||
sharingCurrentFile.setSettingsKey(Constants::CA_SHARING_CURRENT_FILE);
|
linkOpenFiles.setSettingsKey(Constants::CA_LINK_OPEN_FILES);
|
||||||
sharingCurrentFile.setLabelText(Tr::tr("Share Current File With Assistant by Default"));
|
linkOpenFiles.setLabelText(Tr::tr("Sync open files with assistant by default"));
|
||||||
sharingCurrentFile.setDefaultValue(true);
|
linkOpenFiles.setDefaultValue(false);
|
||||||
|
|
||||||
stream.setSettingsKey(Constants::CA_STREAM);
|
stream.setSettingsKey(Constants::CA_STREAM);
|
||||||
stream.setDefaultValue(true);
|
stream.setDefaultValue(true);
|
||||||
@ -171,7 +171,7 @@ ChatAssistantSettings::ChatAssistantSettings()
|
|||||||
Space{8},
|
Space{8},
|
||||||
Group{
|
Group{
|
||||||
title(Tr::tr("Chat Settings")),
|
title(Tr::tr("Chat Settings")),
|
||||||
Column{Row{chatTokensThreshold, Stretch{1}}, sharingCurrentFile, stream, autosave}},
|
Column{Row{chatTokensThreshold, Stretch{1}}, linkOpenFiles, stream, autosave}},
|
||||||
Space{8},
|
Space{8},
|
||||||
Group{
|
Group{
|
||||||
title(Tr::tr("General Parameters")),
|
title(Tr::tr("General Parameters")),
|
||||||
@ -227,6 +227,7 @@ void ChatAssistantSettings::resetSettingsToDefaults()
|
|||||||
resetAspect(systemPrompt);
|
resetAspect(systemPrompt);
|
||||||
resetAspect(ollamaLivetime);
|
resetAspect(ollamaLivetime);
|
||||||
resetAspect(contextWindow);
|
resetAspect(contextWindow);
|
||||||
|
resetAspect(linkOpenFiles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,7 @@ public:
|
|||||||
|
|
||||||
// Chat settings
|
// Chat settings
|
||||||
Utils::IntegerAspect chatTokensThreshold{this};
|
Utils::IntegerAspect chatTokensThreshold{this};
|
||||||
Utils::BoolAspect sharingCurrentFile{this};
|
Utils::BoolAspect linkOpenFiles{this};
|
||||||
Utils::BoolAspect stream{this};
|
Utils::BoolAspect stream{this};
|
||||||
Utils::BoolAspect autosave{this};
|
Utils::BoolAspect autosave{this};
|
||||||
|
|
||||||
|
|||||||
@ -153,16 +153,16 @@ CodeCompletionSettings::CodeCompletionSettings()
|
|||||||
systemPrompt.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
systemPrompt.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||||
systemPrompt.setDefaultValue(
|
systemPrompt.setDefaultValue(
|
||||||
"You are an expert in C++, Qt, and QML programming. Your task is to provide code "
|
"You are an expert in C++, Qt, and QML programming. Your task is to provide code "
|
||||||
"suggestions that seamlessly integrate with existing code. You will receive a code context "
|
"suggestions that seamlessly integrate with existing code. Do not repeat code from position "
|
||||||
"with specified insertion points. Your goal is to complete only one logic expression "
|
"before or after <cursor>. You will receive a code context with specified insertion points. "
|
||||||
"within these points."
|
"Your goal is to complete only one code block."
|
||||||
"Here is the code context with insertion points:<code_context>Before: {{variable}}After: "
|
"Here is the code context with insertion points:<code_context>Before: {{variable}}After: "
|
||||||
"{{variable}}</code_context> Instructions: 1. Carefully analyze the provided code context. "
|
"{{variable}}</code_context> Instructions: 1. Carefully analyze the provided code context. "
|
||||||
"2. Consider the existing code and the specified insertion points.3. Generate a code "
|
"2. Consider the existing code and the specified insertion points.3. Generate a code "
|
||||||
"suggestion that completes one logic expression between the 'Before' and 'After' points. "
|
"suggestion that completes one logic expression between the 'Before' and 'After' points. "
|
||||||
"4. Ensure your suggestion does not repeat any existing code. 5. Format your suggestion as "
|
"4. Ensure your suggestion does not repeat any existing code. 5. Format your suggestion as "
|
||||||
"a code block using triple backticks. 6. Do not include any comments or descriptions with "
|
"a code block using triple backticks. 6. Do not include any comments or descriptions with "
|
||||||
"your code suggestion. Remember to include only the new code to be inserted.");
|
"your code suggestion.");
|
||||||
|
|
||||||
useUserMessageTemplateForCC.setSettingsKey(Constants::CC_USE_USER_TEMPLATE);
|
useUserMessageTemplateForCC.setSettingsKey(Constants::CC_USE_USER_TEMPLATE);
|
||||||
useUserMessageTemplateForCC.setDefaultValue(true);
|
useUserMessageTemplateForCC.setDefaultValue(true);
|
||||||
@ -310,6 +310,7 @@ void CodeCompletionSettings::resetSettingsToDefaults()
|
|||||||
resetAspect(autoCompletion);
|
resetAspect(autoCompletion);
|
||||||
resetAspect(multiLineCompletion);
|
resetAspect(multiLineCompletion);
|
||||||
resetAspect(stream);
|
resetAspect(stream);
|
||||||
|
resetAspect(smartProcessInstuctText);
|
||||||
resetAspect(temperature);
|
resetAspect(temperature);
|
||||||
resetAspect(maxTokens);
|
resetAspect(maxTokens);
|
||||||
resetAspect(useTopP);
|
resetAspect(useTopP);
|
||||||
|
|||||||
@ -159,9 +159,7 @@ void PluginUpdater::handleDownloadFinished()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString downloadPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)
|
QString downloadPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
|
||||||
+ QDir::separator() + "qodeassisttemp";
|
|
||||||
QDir().mkpath(downloadPath);
|
|
||||||
|
|
||||||
QString filePath = downloadPath + "/" + m_lastUpdateInfo.fileName;
|
QString filePath = downloadPath + "/" + m_lastUpdateInfo.fileName;
|
||||||
QFile file(filePath);
|
QFile file(filePath);
|
||||||
@ -175,16 +173,7 @@ void PluginUpdater::handleDownloadFinished()
|
|||||||
file.write(reply->readAll());
|
file.write(reply->readAll());
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
if (!Core::executePluginInstallWizard(Utils::FilePath::fromString(filePath))) {
|
|
||||||
emit downloadError(tr("Failed to install the update"));
|
|
||||||
} else {
|
|
||||||
emit downloadFinished(filePath);
|
emit downloadFinished(filePath);
|
||||||
}
|
|
||||||
|
|
||||||
auto tempDir = QDir(downloadPath);
|
|
||||||
if (tempDir.exists()) {
|
|
||||||
tempDir.removeRecursively();
|
|
||||||
}
|
|
||||||
|
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,7 +62,7 @@ const char CC_STREAM[] = "QodeAssist.ccStream";
|
|||||||
const char CC_SMART_PROCESS_INSTRUCT_TEXT[] = "QodeAssist.ccSmartProcessInstructText";
|
const char CC_SMART_PROCESS_INSTRUCT_TEXT[] = "QodeAssist.ccSmartProcessInstructText";
|
||||||
const char CUSTOM_JSON_TEMPLATE[] = "QodeAssist.customJsonTemplate";
|
const char CUSTOM_JSON_TEMPLATE[] = "QodeAssist.customJsonTemplate";
|
||||||
const char CA_TOKENS_THRESHOLD[] = "QodeAssist.caTokensThreshold";
|
const char CA_TOKENS_THRESHOLD[] = "QodeAssist.caTokensThreshold";
|
||||||
const char CA_SHARING_CURRENT_FILE[] = "QodeAssist.caSharingCurrentFile";
|
const char CA_LINK_OPEN_FILES[] = "QodeAssist.caLinkOpenFiles";
|
||||||
const char CA_STREAM[] = "QodeAssist.caStream";
|
const char CA_STREAM[] = "QodeAssist.caStream";
|
||||||
const char CA_AUTOSAVE[] = "QodeAssist.caAutosave";
|
const char CA_AUTOSAVE[] = "QodeAssist.caAutosave";
|
||||||
|
|
||||||
|
|||||||
@ -60,6 +60,12 @@ UpdateDialog::UpdateDialog(QWidget *parent)
|
|||||||
m_versionLabel->setAlignment(Qt::AlignCenter);
|
m_versionLabel->setAlignment(Qt::AlignCenter);
|
||||||
m_layout->addWidget(m_versionLabel);
|
m_layout->addWidget(m_versionLabel);
|
||||||
|
|
||||||
|
m_releaseLink = new QLabel(this);
|
||||||
|
m_releaseLink->setOpenExternalLinks(true);
|
||||||
|
m_releaseLink->setTextFormat(Qt::RichText);
|
||||||
|
m_releaseLink->setAlignment(Qt::AlignCenter);
|
||||||
|
m_layout->addWidget(m_releaseLink);
|
||||||
|
|
||||||
if (!m_changelogLabel) {
|
if (!m_changelogLabel) {
|
||||||
m_changelogLabel = new QLabel(tr("Release Notes:"), this);
|
m_changelogLabel = new QLabel(tr("Release Notes:"), this);
|
||||||
m_layout->addWidget(m_changelogLabel);
|
m_layout->addWidget(m_changelogLabel);
|
||||||
@ -75,7 +81,7 @@ UpdateDialog::UpdateDialog(QWidget *parent)
|
|||||||
m_layout->addWidget(m_progress);
|
m_layout->addWidget(m_progress);
|
||||||
|
|
||||||
auto *buttonLayout = new QHBoxLayout;
|
auto *buttonLayout = new QHBoxLayout;
|
||||||
m_downloadButton = new QPushButton(tr("Download and Install"), this);
|
m_downloadButton = new QPushButton(tr("Download"), this);
|
||||||
m_downloadButton->setEnabled(false);
|
m_downloadButton->setEnabled(false);
|
||||||
buttonLayout->addWidget(m_downloadButton);
|
buttonLayout->addWidget(m_downloadButton);
|
||||||
|
|
||||||
@ -104,6 +110,10 @@ void UpdateDialog::checkForUpdatesAndShow(QWidget *parent)
|
|||||||
|
|
||||||
void UpdateDialog::handleUpdateInfo(const PluginUpdater::UpdateInfo &info)
|
void UpdateDialog::handleUpdateInfo(const PluginUpdater::UpdateInfo &info)
|
||||||
{
|
{
|
||||||
|
m_releaseLink->setText(
|
||||||
|
tr("<a href='https://github.com/Palm1r/QodeAssist/releases'>You can also download "
|
||||||
|
"from GitHub Releases</a>"));
|
||||||
|
|
||||||
if (info.incompatibleIdeVersion) {
|
if (info.incompatibleIdeVersion) {
|
||||||
m_titleLabel->setText(tr("Incompatible Qt Creator Version"));
|
m_titleLabel->setText(tr("Incompatible Qt Creator Version"));
|
||||||
m_versionLabel->setText(tr("This update requires Qt Creator %1, current is %2.\n"
|
m_versionLabel->setText(tr("This update requires Qt Creator %1, current is %2.\n"
|
||||||
@ -156,11 +166,7 @@ void UpdateDialog::updateProgress(qint64 received, qint64 total)
|
|||||||
void UpdateDialog::handleDownloadFinished(const QString &path)
|
void UpdateDialog::handleDownloadFinished(const QString &path)
|
||||||
{
|
{
|
||||||
m_progress->setVisible(false);
|
m_progress->setVisible(false);
|
||||||
QMessageBox::information(
|
QMessageBox::information(this, tr("Update Successful"), tr("Update has been downloaded."));
|
||||||
this,
|
|
||||||
tr("Update Successful"),
|
|
||||||
tr("Update has been downloaded and installed. "
|
|
||||||
"Please restart Qt Creator to apply changes."));
|
|
||||||
accept();
|
accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -52,6 +52,7 @@ private:
|
|||||||
QVBoxLayout *m_layout;
|
QVBoxLayout *m_layout;
|
||||||
QLabel *m_titleLabel;
|
QLabel *m_titleLabel;
|
||||||
QLabel *m_versionLabel;
|
QLabel *m_versionLabel;
|
||||||
|
QLabel *m_releaseLink;
|
||||||
QLabel *m_changelogLabel{nullptr};
|
QLabel *m_changelogLabel{nullptr};
|
||||||
QTextEdit *m_changelogText{nullptr};
|
QTextEdit *m_changelogText{nullptr};
|
||||||
QProgressBar *m_progress;
|
QProgressBar *m_progress;
|
||||||
|
|||||||
Reference in New Issue
Block a user