mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-06-13 17:59:15 -04:00
Compare commits
49 Commits
v0.4.4
...
dev-rag-ba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
142afa725f | ||
|
|
f36db033e6 | ||
|
|
5dfcf74128 | ||
|
|
02101665ca | ||
|
|
77a03d42ed | ||
|
|
09c38c8b0e | ||
|
|
7b73d7af7b | ||
|
|
5a426b4d9f | ||
|
|
1fa6a225a4 | ||
|
|
31133e3378 | ||
|
|
a2f15fc843 | ||
|
|
2a0beb6c4c | ||
|
|
e836b86569 | ||
|
|
288fefebe5 | ||
|
|
528badbf1e | ||
|
|
b789e42602 | ||
|
|
4bf955462f | ||
|
|
5b99e68e53 | ||
|
|
0f1b277ef7 | ||
|
|
56995c9edf | ||
|
|
45aba6b6be | ||
|
|
1dfb3feb96 | ||
|
|
2c49d45297 | ||
|
|
31145f191b | ||
|
|
9096adde6f | ||
|
|
b8e578d2d7 | ||
|
|
4e45774bce | ||
|
|
928490d31f | ||
|
|
97163cf6c9 | ||
|
|
f85c162692 | ||
|
|
258053d826 | ||
|
|
bf63ae5714 | ||
|
|
ae76850e78 | ||
|
|
bf3c0b3aa0 | ||
|
|
9add61c805 | ||
|
|
add86d2e67 | ||
|
|
a6c909d34d | ||
|
|
2814dec3e5 | ||
|
|
1b86b60de8 | ||
|
|
4b7f638731 | ||
|
|
de046f0529 | ||
|
|
e975e143b1 | ||
|
|
c97c0f62e8 | ||
|
|
61fded34ea | ||
|
|
289a19ac1a | ||
|
|
43ac662671 | ||
|
|
1d64d2afc9 | ||
|
|
9db61119aa | ||
|
|
70481b3116 |
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"
|
||||||
|
|||||||
@@ -53,13 +53,16 @@ add_qtc_plugin(QodeAssist
|
|||||||
templates/ChatML.hpp
|
templates/ChatML.hpp
|
||||||
templates/Alpaca.hpp
|
templates/Alpaca.hpp
|
||||||
templates/Llama2.hpp
|
templates/Llama2.hpp
|
||||||
providers/Providers.hpp
|
|
||||||
templates/Claude.hpp
|
templates/Claude.hpp
|
||||||
|
templates/OpenAI.hpp
|
||||||
|
templates/CodeLlamaQMLFim.hpp
|
||||||
|
providers/Providers.hpp
|
||||||
providers/OllamaProvider.hpp providers/OllamaProvider.cpp
|
providers/OllamaProvider.hpp providers/OllamaProvider.cpp
|
||||||
providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp
|
providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp
|
||||||
providers/OpenAICompatProvider.hpp providers/OpenAICompatProvider.cpp
|
providers/OpenAICompatProvider.hpp providers/OpenAICompatProvider.cpp
|
||||||
providers/OpenRouterAIProvider.hpp providers/OpenRouterAIProvider.cpp
|
providers/OpenRouterAIProvider.hpp providers/OpenRouterAIProvider.cpp
|
||||||
providers/ClaudeProvider.hpp providers/ClaudeProvider.cpp
|
providers/ClaudeProvider.hpp providers/ClaudeProvider.cpp
|
||||||
|
providers/OpenAIProvider.hpp providers/OpenAIProvider.cpp
|
||||||
QodeAssist.qrc
|
QodeAssist.qrc
|
||||||
LSPCompletion.hpp
|
LSPCompletion.hpp
|
||||||
LLMSuggestion.hpp LLMSuggestion.cpp
|
LLMSuggestion.hpp LLMSuggestion.cpp
|
||||||
@@ -68,4 +71,5 @@ add_qtc_plugin(QodeAssist
|
|||||||
chat/NavigationPanel.hpp chat/NavigationPanel.cpp
|
chat/NavigationPanel.hpp chat/NavigationPanel.cpp
|
||||||
ConfigurationManager.hpp ConfigurationManager.cpp
|
ConfigurationManager.hpp ConfigurationManager.cpp
|
||||||
CodeHandler.hpp CodeHandler.cpp
|
CodeHandler.hpp CodeHandler.cpp
|
||||||
|
UpdateStatusWidget.hpp UpdateStatusWidget.cpp
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -20,13 +20,16 @@
|
|||||||
#include "ChatRootView.hpp"
|
#include "ChatRootView.hpp"
|
||||||
|
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
|
#include <QDesktopServices>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
#include <projectexplorer/project.h>
|
#include <projectexplorer/project.h>
|
||||||
#include <projectexplorer/projectexplorer.h>
|
#include <projectexplorer/projectexplorer.h>
|
||||||
#include <projectexplorer/projectmanager.h>
|
#include <projectexplorer/projectmanager.h>
|
||||||
|
#include <projectexplorer/projecttree.h>
|
||||||
#include <utils/theme/theme.h>
|
#include <utils/theme/theme.h>
|
||||||
#include <utils/utilsicons.h>
|
#include <utils/utilsicons.h>
|
||||||
|
|
||||||
@@ -35,6 +38,10 @@
|
|||||||
#include "GeneralSettings.hpp"
|
#include "GeneralSettings.hpp"
|
||||||
#include "Logger.hpp"
|
#include "Logger.hpp"
|
||||||
#include "ProjectSettings.hpp"
|
#include "ProjectSettings.hpp"
|
||||||
|
#include "context/ContextManager.hpp"
|
||||||
|
#include "context/FileChunker.hpp"
|
||||||
|
#include "context/RAGManager.hpp"
|
||||||
|
#include "context/TokenUtils.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
@@ -43,6 +50,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,20 +64,44 @@ 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);
|
||||||
|
|
||||||
generateColors();
|
connect(m_chatModel, &ChatModel::modelReseted, this, [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::editorCreated, this, &ChatRootView::onEditorCreated);
|
||||||
|
connect(
|
||||||
|
editors,
|
||||||
|
&Core::EditorManager::editorAboutToClose,
|
||||||
|
this,
|
||||||
|
&ChatRootView::onEditorAboutToClose);
|
||||||
|
|
||||||
|
connect(editors, &Core::EditorManager::currentEditorAboutToChange, this, [this]() {
|
||||||
|
if (m_isSyncOpenFiles) {
|
||||||
|
for (auto editor : std::as_const(m_currentEditors)) {
|
||||||
|
onAppendLinkFileFromEditor(editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateInputTokensCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatModel *ChatRootView::chatModel() const
|
ChatModel *ChatRootView::chatModel() const
|
||||||
@@ -71,14 +109,9 @@ ChatModel *ChatRootView::chatModel() const
|
|||||||
return m_chatModel;
|
return m_chatModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
QColor ChatRootView::backgroundColor() const
|
void ChatRootView::sendMessage(const QString &message)
|
||||||
{
|
{
|
||||||
return Utils::creatorColor(Utils::Theme::BackgroundColorNormal);
|
if (m_inputTokensCount > m_chatModel->tokensThreshold()) {
|
||||||
}
|
|
||||||
|
|
||||||
void ChatRootView::sendMessage(const QString &message, bool sharingCurrentFile)
|
|
||||||
{
|
|
||||||
if (m_chatModel->totalTokens() > 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"),
|
||||||
@@ -89,12 +122,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,49 +149,14 @@ void ChatRootView::clearAttachmentFiles()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatRootView::generateColors()
|
void ChatRootView::clearLinkedFiles()
|
||||||
{
|
{
|
||||||
QColor baseColor = backgroundColor();
|
if (!m_linkedFiles.isEmpty()) {
|
||||||
bool isDarkTheme = baseColor.lightness() < 128;
|
m_linkedFiles.clear();
|
||||||
|
emit linkedFilesChanged();
|
||||||
if (isDarkTheme) {
|
|
||||||
m_primaryColor = generateColor(baseColor, 0.1, 1.2, 1.4);
|
|
||||||
m_secondaryColor = generateColor(baseColor, -0.1, 1.1, 1.2);
|
|
||||||
m_codeColor = generateColor(baseColor, 0.05, 0.8, 1.1);
|
|
||||||
} else {
|
|
||||||
m_primaryColor = generateColor(baseColor, 0.05, 1.05, 1.1);
|
|
||||||
m_secondaryColor = generateColor(baseColor, -0.05, 1.1, 1.2);
|
|
||||||
m_codeColor = generateColor(baseColor, 0.02, 0.95, 1.05);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QColor ChatRootView::generateColor(const QColor &baseColor,
|
|
||||||
float hueShift,
|
|
||||||
float saturationMod,
|
|
||||||
float lightnessMod)
|
|
||||||
{
|
|
||||||
float h, s, l, a;
|
|
||||||
baseColor.getHslF(&h, &s, &l, &a);
|
|
||||||
bool isDarkTheme = l < 0.5;
|
|
||||||
|
|
||||||
h = fmod(h + hueShift + 1.0, 1.0);
|
|
||||||
|
|
||||||
s = qBound(0.0f, s * saturationMod, 1.0f);
|
|
||||||
|
|
||||||
if (isDarkTheme) {
|
|
||||||
l = qBound(0.0f, l * lightnessMod, 1.0f);
|
|
||||||
} else {
|
|
||||||
l = qBound(0.0f, l / lightnessMod, 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
h = qBound(0.0f, h, 1.0f);
|
|
||||||
s = qBound(0.0f, s, 1.0f);
|
|
||||||
l = qBound(0.0f, l, 1.0f);
|
|
||||||
a = qBound(0.0f, a, 1.0f);
|
|
||||||
|
|
||||||
return QColor::fromHslF(h, s, l, a);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ChatRootView::getChatsHistoryDir() const
|
QString ChatRootView::getChatsHistoryDir() const
|
||||||
{
|
{
|
||||||
QString path;
|
QString path;
|
||||||
@@ -185,31 +183,13 @@ QString ChatRootView::currentTemplate() const
|
|||||||
return settings.caModel();
|
return settings.caModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
QColor ChatRootView::primaryColor() const
|
|
||||||
{
|
|
||||||
return m_primaryColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
QColor ChatRootView::secondaryColor() const
|
|
||||||
{
|
|
||||||
return m_secondaryColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
QColor ChatRootView::codeColor() const
|
|
||||||
{
|
|
||||||
return m_codeColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
LOG_MESSAGE(QString("Failed to save chat history: %1").arg(result.errorMessage));
|
LOG_MESSAGE(QString("Failed to save chat history: %1").arg(result.errorMessage));
|
||||||
|
} else {
|
||||||
|
setRecentFilePath(filePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,8 +199,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()
|
||||||
@@ -279,17 +260,50 @@ QString ChatRootView::getSuggestedFileName() const
|
|||||||
{
|
{
|
||||||
QStringList parts;
|
QStringList parts;
|
||||||
|
|
||||||
|
static const QRegularExpression saitizeSymbols = QRegularExpression("[\\/:*?\"<>|\\s]");
|
||||||
|
static const QRegularExpression underSymbols = QRegularExpression("_+");
|
||||||
|
|
||||||
if (m_chatModel->rowCount() > 0) {
|
if (m_chatModel->rowCount() > 0) {
|
||||||
QString firstMessage
|
QString firstMessage
|
||||||
= m_chatModel->data(m_chatModel->index(0), ChatModel::Content).toString();
|
= m_chatModel->data(m_chatModel->index(0), ChatModel::Content).toString();
|
||||||
QString shortMessage = firstMessage.split('\n').first().simplified().left(30);
|
QString shortMessage = firstMessage.split('\n').first().simplified().left(30);
|
||||||
shortMessage.replace(QRegularExpression("[^a-zA-Z0-9_-]"), "_");
|
|
||||||
parts << shortMessage;
|
QString sanitizedMessage = shortMessage;
|
||||||
|
sanitizedMessage.replace(saitizeSymbols, "_");
|
||||||
|
sanitizedMessage.replace(underSymbols, "_");
|
||||||
|
sanitizedMessage = sanitizedMessage.trimmed();
|
||||||
|
|
||||||
|
if (!sanitizedMessage.isEmpty()) {
|
||||||
|
if (sanitizedMessage.startsWith('_')) {
|
||||||
|
sanitizedMessage.remove(0, 1);
|
||||||
|
}
|
||||||
|
if (sanitizedMessage.endsWith('_')) {
|
||||||
|
sanitizedMessage.chop(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString targetDir = getChatsHistoryDir();
|
||||||
|
QString fullPath = QDir(targetDir).filePath(sanitizedMessage);
|
||||||
|
|
||||||
|
QFileInfo fileInfo(fullPath);
|
||||||
|
if (!fileInfo.exists() && QFileInfo(fileInfo.path()).isWritable()) {
|
||||||
|
parts << sanitizedMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parts << QDateTime::currentDateTime().toString("yyyy-MM-dd_HH-mm");
|
parts << QDateTime::currentDateTime().toString("yyyy-MM-dd_HH-mm");
|
||||||
|
|
||||||
return parts.join("_");
|
QString fileName = parts.join("_");
|
||||||
|
|
||||||
|
QString fullPath = QDir(getChatsHistoryDir()).filePath(fileName);
|
||||||
|
QFileInfo finalCheck(fullPath);
|
||||||
|
|
||||||
|
if (fileName.isEmpty() || finalCheck.exists() || !QFileInfo(finalCheck.path()).isWritable()) {
|
||||||
|
fileName = QString("chat_%1").arg(
|
||||||
|
QDateTime::currentDateTime().toString("yyyy-MM-dd_HH-mm"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatRootView::autosave()
|
void ChatRootView::autosave()
|
||||||
@@ -301,7 +315,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,6 +333,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"));
|
||||||
@@ -329,12 +353,247 @@ void ChatRootView::showAttachFilesDialog()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dialog.exec() == QDialog::Accepted) {
|
if (dialog.exec() == QDialog::Accepted) {
|
||||||
QStringList filePaths = dialog.selectedFiles();
|
QStringList newFilePaths = dialog.selectedFiles();
|
||||||
if (!filePaths.isEmpty()) {
|
if (!newFilePaths.isEmpty()) {
|
||||||
m_attachmentFiles = filePaths;
|
bool filesAdded = false;
|
||||||
emit attachmentFilesChanged();
|
for (const QString &filePath : std::as_const(newFilePaths)) {
|
||||||
|
if (!m_attachmentFiles.contains(filePath)) {
|
||||||
|
m_attachmentFiles.append(filePath);
|
||||||
|
filesAdded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (filesAdded) {
|
||||||
|
emit attachmentFilesChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 : std::as_const(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();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_isSyncOpenFiles) {
|
||||||
|
for (auto editor : std::as_const(m_currentEditors)) {
|
||||||
|
onAppendLinkFileFromEditor(editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::openChatHistoryFolder()
|
||||||
|
{
|
||||||
|
QString path;
|
||||||
|
if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
|
||||||
|
Settings::ProjectSettings projectSettings(project);
|
||||||
|
path = projectSettings.chatHistoryPath().toString();
|
||||||
|
} else {
|
||||||
|
path = QString("%1/qodeassist/chat_history").arg(Core::ICore::userResourcePath().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
QDir dir(path);
|
||||||
|
if (!dir.exists()) {
|
||||||
|
dir.mkpath(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl url = QUrl::fromLocalFile(dir.absolutePath());
|
||||||
|
QDesktopServices::openUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChatRootView.cpp
|
||||||
|
|
||||||
|
void ChatRootView::testRAG(const QString &message)
|
||||||
|
{
|
||||||
|
auto project = ProjectExplorer::ProjectTree::currentProject();
|
||||||
|
if (!project) {
|
||||||
|
qDebug() << "No active project found";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString TEST_QUERY = message;
|
||||||
|
|
||||||
|
qDebug() << "Starting RAG test with query:";
|
||||||
|
qDebug() << TEST_QUERY;
|
||||||
|
qDebug() << "\nFirst, processing project files...";
|
||||||
|
|
||||||
|
auto files = Context::ContextManager::instance().getProjectSourceFiles(project);
|
||||||
|
// Было: auto future = Context::RAGManager::instance().processFiles(project, files);
|
||||||
|
// Стало:
|
||||||
|
auto future = Context::RAGManager::instance().processProjectFiles(project, files);
|
||||||
|
|
||||||
|
connect(
|
||||||
|
&Context::RAGManager::instance(),
|
||||||
|
&Context::RAGManager::vectorizationProgress,
|
||||||
|
this,
|
||||||
|
[](int processed, int total) {
|
||||||
|
qDebug() << QString("Vectorization progress: %1 of %2 files").arg(processed).arg(total);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(
|
||||||
|
&Context::RAGManager::instance(),
|
||||||
|
&Context::RAGManager::vectorizationFinished,
|
||||||
|
this,
|
||||||
|
[this, project, TEST_QUERY]() {
|
||||||
|
qDebug() << "\nVectorization completed. Starting similarity search...\n";
|
||||||
|
// Было: Context::RAGManager::instance().searchSimilarDocuments(TEST_QUERY, project, 5);
|
||||||
|
// Стало:
|
||||||
|
auto future = Context::RAGManager::instance().findRelevantChunks(TEST_QUERY, project, 5);
|
||||||
|
future.then([](const QList<Context::RAGManager::ChunkSearchResult> &results) {
|
||||||
|
qDebug() << "Found" << results.size() << "relevant chunks:";
|
||||||
|
for (const auto &result : results) {
|
||||||
|
qDebug() << "File:" << result.filePath;
|
||||||
|
qDebug() << "Lines:" << result.startLine << "-" << result.endLine;
|
||||||
|
qDebug() << "Score:" << result.combinedScore;
|
||||||
|
qDebug() << "Content:" << result.content;
|
||||||
|
qDebug() << "---";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::testChunking()
|
||||||
|
{
|
||||||
|
auto project = ProjectExplorer::ProjectTree::currentProject();
|
||||||
|
if (!project) {
|
||||||
|
qDebug() << "No active project found";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Context::FileChunker::ChunkingConfig config;
|
||||||
|
Context::ContextManager::instance().testProjectChunks(project, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
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::onEditorAboutToClose(Core::IEditor *editor)
|
||||||
|
{
|
||||||
|
if (auto document = editor->document(); document && isSyncOpenFiles()) {
|
||||||
|
QString filePath = document->filePath().toString();
|
||||||
|
m_linkedFiles.removeOne(filePath);
|
||||||
|
emit linkedFilesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editor) {
|
||||||
|
m_currentEditors.removeOne(editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::onAppendLinkFileFromEditor(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::onEditorCreated(Core::IEditor *editor, const Utils::FilePath &filePath)
|
||||||
|
{
|
||||||
|
if (editor && editor->document()) {
|
||||||
|
m_currentEditors.append(editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,13 +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(QColor backgroundColor READ backgroundColor CONSTANT FINAL)
|
Q_PROPERTY(bool isSyncOpenFiles READ isSyncOpenFiles NOTIFY isSyncOpenFilesChanged FINAL)
|
||||||
Q_PROPERTY(QColor primaryColor READ primaryColor CONSTANT FINAL)
|
Q_PROPERTY(QStringList attachmentFiles READ attachmentFiles NOTIFY attachmentFilesChanged FINAL)
|
||||||
Q_PROPERTY(QColor secondaryColor READ secondaryColor CONSTANT FINAL)
|
Q_PROPERTY(QStringList linkedFiles READ linkedFiles NOTIFY linkedFilesChanged FINAL)
|
||||||
Q_PROPERTY(QColor codeColor READ codeColor CONSTANT FINAL)
|
Q_PROPERTY(int inputTokensCount READ inputTokensCount NOTIFY inputTokensCountChanged FINAL)
|
||||||
Q_PROPERTY(bool isSharingCurrentFile READ isSharingCurrentFile NOTIFY
|
Q_PROPERTY(QString chatFileName READ chatFileName NOTIFY chatFileNameChanged FINAL)
|
||||||
isSharingCurrentFileChanged FINAL)
|
|
||||||
Q_PROPERTY(QStringList attachmentFiles MEMBER m_attachmentFiles NOTIFY attachmentFilesChanged)
|
|
||||||
|
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
@@ -47,14 +46,6 @@ public:
|
|||||||
ChatModel *chatModel() const;
|
ChatModel *chatModel() const;
|
||||||
QString currentTemplate() const;
|
QString currentTemplate() const;
|
||||||
|
|
||||||
QColor backgroundColor() const;
|
|
||||||
QColor primaryColor() const;
|
|
||||||
QColor secondaryColor() const;
|
|
||||||
|
|
||||||
QColor codeColor() const;
|
|
||||||
|
|
||||||
bool isSharingCurrentFile() const;
|
|
||||||
|
|
||||||
void saveHistory(const QString &filePath);
|
void saveHistory(const QString &filePath);
|
||||||
void loadHistory(const QString &filePath);
|
void loadHistory(const QString &filePath);
|
||||||
|
|
||||||
@@ -64,38 +55,61 @@ 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 openChatHistoryFolder();
|
||||||
|
Q_INVOKABLE void testRAG(const QString &message);
|
||||||
|
Q_INVOKABLE void testChunking();
|
||||||
|
|
||||||
|
Q_INVOKABLE void updateInputTokensCount();
|
||||||
|
int inputTokensCount() const;
|
||||||
|
|
||||||
|
bool isSyncOpenFiles() const;
|
||||||
|
|
||||||
|
void onEditorAboutToClose(Core::IEditor *editor);
|
||||||
|
void onAppendLinkFileFromEditor(Core::IEditor *editor);
|
||||||
|
void onEditorCreated(Core::IEditor *editor, const Utils::FilePath &filePath);
|
||||||
|
|
||||||
|
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:
|
||||||
void generateColors();
|
|
||||||
QColor generateColor(const QColor &baseColor,
|
|
||||||
float hueShift,
|
|
||||||
float saturationMod,
|
|
||||||
float lightnessMod);
|
|
||||||
|
|
||||||
QString getChatsHistoryDir() const;
|
QString getChatsHistoryDir() const;
|
||||||
QString getSuggestedFileName() const;
|
QString getSuggestedFileName() const;
|
||||||
|
|
||||||
ChatModel *m_chatModel;
|
ChatModel *m_chatModel;
|
||||||
ClientInterface *m_clientInterface;
|
ClientInterface *m_clientInterface;
|
||||||
QString m_currentTemplate;
|
QString m_currentTemplate;
|
||||||
QColor m_primaryColor;
|
|
||||||
QColor m_secondaryColor;
|
|
||||||
QColor m_codeColor;
|
|
||||||
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;
|
||||||
|
QList<Core::IEditor *> m_currentEditors;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // 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 |
@@ -27,9 +27,6 @@ Rectangle {
|
|||||||
|
|
||||||
property alias msgModel: msgCreator.model
|
property alias msgModel: msgCreator.model
|
||||||
property alias messageAttachments: attachmentsModel.model
|
property alias messageAttachments: attachmentsModel.model
|
||||||
property color fontColor
|
|
||||||
property color codeBgColor
|
|
||||||
property color selectionColor
|
|
||||||
|
|
||||||
height: msgColumn.implicitHeight + 10
|
height: msgColumn.implicitHeight + 10
|
||||||
radius: 8
|
radius: 8
|
||||||
@@ -49,7 +46,7 @@ Rectangle {
|
|||||||
// why does `required property MessagePart modelData` not work?
|
// why does `required property MessagePart modelData` not work?
|
||||||
required property var modelData
|
required property var modelData
|
||||||
|
|
||||||
width: parent.width
|
Layout.preferredWidth: root.width
|
||||||
sourceComponent: {
|
sourceComponent: {
|
||||||
// If `required property MessagePart modelData` is used
|
// If `required property MessagePart modelData` is used
|
||||||
// and conversion to MessagePart fails, you're left
|
// and conversion to MessagePart fails, you're left
|
||||||
@@ -100,14 +97,16 @@ Rectangle {
|
|||||||
height: attachText.implicitHeight + 8
|
height: attachText.implicitHeight + 8
|
||||||
width: attachText.implicitWidth + 16
|
width: attachText.implicitWidth + 16
|
||||||
radius: 4
|
radius: 4
|
||||||
color: root.codeBgColor
|
color: palette.button
|
||||||
|
border.width: 1
|
||||||
|
border.color: palette.mid
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: attachText
|
id: attachText
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: modelData
|
text: modelData
|
||||||
color: root.fontColor
|
color: palette.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,8 +119,6 @@ Rectangle {
|
|||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
leftPadding: 10
|
leftPadding: 10
|
||||||
text: itemData.text
|
text: itemData.text
|
||||||
color: root.fontColor
|
|
||||||
selectionColor: root.selectionColor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -136,8 +133,5 @@ Rectangle {
|
|||||||
|
|
||||||
code: itemData.text
|
code: itemData.text
|
||||||
language: itemData.language
|
language: itemData.language
|
||||||
color: root.codeBgColor
|
|
||||||
selectionColor: root.selectionColor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ ChatRootView {
|
|||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
TopBar {
|
TopBar {
|
||||||
id: topBar
|
id: topBar
|
||||||
@@ -69,8 +70,12 @@ 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")
|
||||||
|
}
|
||||||
|
openChatHistory.onClicked: root.openChatHistoryFolder()
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
@@ -91,12 +96,8 @@ ChatRootView {
|
|||||||
width: ListView.view.width - scroll.width
|
width: ListView.view.width - scroll.width
|
||||||
msgModel: root.chatModel.processMessageContent(model.content)
|
msgModel: root.chatModel.processMessageContent(model.content)
|
||||||
messageAttachments: model.attachments
|
messageAttachments: model.attachments
|
||||||
color: model.roleType === ChatModel.User ? root.primaryColor : root.secondaryColor
|
color: model.roleType === ChatModel.User ? palette.alternateBase
|
||||||
fontColor: root.primaryColor.hslLightness > 0.5 ? "black" : "white"
|
: palette.base
|
||||||
codeBgColor: root.codeColor
|
|
||||||
selectionColor: root.primaryColor.hslLightness > 0.5 ? Qt.darker(root.primaryColor, 1.5)
|
|
||||||
: Qt.lighter(root.primaryColor, 1.5)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
header: Item {
|
header: Item {
|
||||||
@@ -104,7 +105,7 @@ ChatRootView {
|
|||||||
height: 30
|
height: 30
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
ScrollBar.vertical: QQC.ScrollBar {
|
||||||
id: scroll
|
id: scroll
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,15 +131,28 @@ ChatRootView {
|
|||||||
id: messageInput
|
id: messageInput
|
||||||
|
|
||||||
placeholderText: qsTr("Type your message here...")
|
placeholderText: qsTr("Type your message here...")
|
||||||
placeholderTextColor: "#888"
|
placeholderTextColor: palette.mid
|
||||||
color: root.primaryColor.hslLightness > 0.5 ? "black" : "white"
|
color: palette.text
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
radius: 2
|
radius: 2
|
||||||
color: root.primaryColor
|
color: palette.base
|
||||||
border.color: root.primaryColor.hslLightness > 0.5 ? Qt.lighter(root.primaryColor, 1.5)
|
border.color: messageInput.activeFocus ? palette.highlight : palette.button
|
||||||
: Qt.darker(root.primaryColor, 1.5)
|
|
||||||
border.width: 1
|
border.width: 1
|
||||||
|
|
||||||
|
Behavior on border.color {
|
||||||
|
ColorAnimation { duration: 150 }
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: palette.highlight
|
||||||
|
opacity: messageInput.hovered ? 0.1 : 0
|
||||||
|
radius: parent.radius
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
@@ -153,6 +167,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 {
|
||||||
@@ -163,13 +192,21 @@ 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()
|
||||||
|
testRag.onClicked: root.testRAG(messageInput.text)
|
||||||
|
testChunks.onClicked: root.testChunking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearChat() {
|
function clearChat() {
|
||||||
root.chatModel.clear()
|
root.chatModel.clear()
|
||||||
|
root.clearAttachmentFiles()
|
||||||
|
root.updateInputTokensCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollToBottom() {
|
function scrollToBottom() {
|
||||||
@@ -177,7 +214,7 @@ ChatRootView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sendChatMessage() {
|
function sendChatMessage() {
|
||||||
root.sendMessage(messageInput.text, bottomBar.sharingCurrentFile.checked)
|
root.sendMessage(messageInput.text)
|
||||||
messageInput.text = ""
|
messageInput.text = ""
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ Rectangle {
|
|||||||
|
|
||||||
property string code: ""
|
property string code: ""
|
||||||
property string language: ""
|
property string language: ""
|
||||||
property color selectionColor
|
|
||||||
|
|
||||||
readonly property string monospaceFont: {
|
readonly property string monospaceFont: {
|
||||||
switch (Qt.platform.os) {
|
switch (Qt.platform.os) {
|
||||||
@@ -41,6 +40,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
color: palette.alternateBase
|
||||||
border.color: root.color.hslLightness > 0.5 ? Qt.darker(root.color, 1.3)
|
border.color: root.color.hslLightness > 0.5 ? Qt.darker(root.color, 1.3)
|
||||||
: Qt.lighter(root.color, 1.3)
|
: Qt.lighter(root.color, 1.3)
|
||||||
border.width: 2
|
border.width: 2
|
||||||
@@ -62,10 +62,10 @@ Rectangle {
|
|||||||
readOnly: true
|
readOnly: true
|
||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
font.family: root.monospaceFont
|
font.family: root.monospaceFont
|
||||||
font.pointSize: 12
|
font.pointSize: Qt.application.font.pointSize
|
||||||
color: parent.color.hslLightness > 0.5 ? "black" : "white"
|
color: parent.color.hslLightness > 0.5 ? "black" : "white"
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
selectionColor: root.selectionColor
|
selectionColor: palette.highlight
|
||||||
}
|
}
|
||||||
|
|
||||||
TextEdit {
|
TextEdit {
|
||||||
@@ -80,7 +80,7 @@ Rectangle {
|
|||||||
font.pointSize: 8
|
font.pointSize: 8
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
QoAButton {
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.margins: 5
|
anchors.margins: 5
|
||||||
|
|||||||
@@ -26,4 +26,6 @@ TextEdit {
|
|||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
textFormat: Text.StyledText
|
textFormat: Text.StyledText
|
||||||
|
selectionColor: palette.highlight
|
||||||
|
color: palette.text
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,13 +23,19 @@ 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
|
||||||
rightPadding: 5
|
rightPadding: 5
|
||||||
|
topPadding: attachRepeater.model.length > 0 ? 2 : 0
|
||||||
|
bottomPadding: attachRepeater.model.length > 0 ? 2 : 0
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
id: attachRepeater
|
id: attachRepeater
|
||||||
@@ -39,15 +45,32 @@ 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.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
|
||||||
@@ -65,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,11 @@ 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
|
||||||
|
property alias testRag: testRagId
|
||||||
|
property alias testChunks: testChunksId
|
||||||
|
|
||||||
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 +62,49 @@ 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: testRagId
|
||||||
|
|
||||||
|
text: qsTr("Test RAG")
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: testChunksId
|
||||||
|
|
||||||
|
text: qsTr("Test Chunks")
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ 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
|
||||||
|
property alias openChatHistory: openChatHistoryId
|
||||||
|
|
||||||
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 +64,19 @@ Rectangle {
|
|||||||
text: qsTr("Clear")
|
text: qsTr("Clear")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: recentPathId
|
||||||
|
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
color: palette.text
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: openChatHistoryId
|
||||||
|
|
||||||
|
text: qsTr("Show in system")
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,13 @@ void ConfigurationManager::setupConnections()
|
|||||||
connect(&m_generalSettings.caSelectTemplate, &Button::clicked, this, &Config::selectTemplate);
|
connect(&m_generalSettings.caSelectTemplate, &Button::clicked, this, &Config::selectTemplate);
|
||||||
connect(&m_generalSettings.ccSetUrl, &Button::clicked, this, &Config::selectUrl);
|
connect(&m_generalSettings.ccSetUrl, &Button::clicked, this, &Config::selectUrl);
|
||||||
connect(&m_generalSettings.caSetUrl, &Button::clicked, this, &Config::selectUrl);
|
connect(&m_generalSettings.caSetUrl, &Button::clicked, this, &Config::selectUrl);
|
||||||
|
|
||||||
|
connect(
|
||||||
|
&m_generalSettings.ccPreset1SelectProvider, &Button::clicked, this, &Config::selectProvider);
|
||||||
|
connect(&m_generalSettings.ccPreset1SetUrl, &Button::clicked, this, &Config::selectUrl);
|
||||||
|
connect(&m_generalSettings.ccPreset1SelectModel, &Button::clicked, this, &Config::selectModel);
|
||||||
|
connect(
|
||||||
|
&m_generalSettings.ccPreset1SelectTemplate, &Button::clicked, this, &Config::selectTemplate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigurationManager::selectProvider()
|
void ConfigurationManager::selectProvider()
|
||||||
@@ -69,6 +76,8 @@ void ConfigurationManager::selectProvider()
|
|||||||
|
|
||||||
auto &targetSettings = (settingsButton == &m_generalSettings.ccSelectProvider)
|
auto &targetSettings = (settingsButton == &m_generalSettings.ccSelectProvider)
|
||||||
? m_generalSettings.ccProvider
|
? m_generalSettings.ccProvider
|
||||||
|
: settingsButton == &m_generalSettings.ccPreset1SelectProvider
|
||||||
|
? m_generalSettings.ccPreset1Provider
|
||||||
: m_generalSettings.caProvider;
|
: m_generalSettings.caProvider;
|
||||||
|
|
||||||
QTimer::singleShot(0, this, [this, providersList, &targetSettings] {
|
QTimer::singleShot(0, this, [this, providersList, &targetSettings] {
|
||||||
@@ -86,14 +95,19 @@ void ConfigurationManager::selectModel()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectModel);
|
const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectModel);
|
||||||
|
const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectModel);
|
||||||
|
|
||||||
const QString providerName = isCodeCompletion ? m_generalSettings.ccProvider.volatileValue()
|
const QString providerName = isCodeCompletion ? m_generalSettings.ccProvider.volatileValue()
|
||||||
: m_generalSettings.caProvider.volatileValue();
|
: isPreset1 ? m_generalSettings.ccPreset1Provider.volatileValue()
|
||||||
|
: m_generalSettings.caProvider.volatileValue();
|
||||||
|
|
||||||
const auto providerUrl = isCodeCompletion ? m_generalSettings.ccUrl.volatileValue()
|
const auto providerUrl = isCodeCompletion ? m_generalSettings.ccUrl.volatileValue()
|
||||||
|
: isPreset1 ? m_generalSettings.ccPreset1Url.volatileValue()
|
||||||
: m_generalSettings.caUrl.volatileValue();
|
: m_generalSettings.caUrl.volatileValue();
|
||||||
|
|
||||||
auto &targetSettings = isCodeCompletion ? m_generalSettings.ccModel : m_generalSettings.caModel;
|
auto &targetSettings = isCodeCompletion ? m_generalSettings.ccModel
|
||||||
|
: isPreset1 ? m_generalSettings.ccPreset1Model
|
||||||
|
: m_generalSettings.caModel;
|
||||||
|
|
||||||
if (auto provider = m_providersManager.getProviderByName(providerName)) {
|
if (auto provider = m_providersManager.getProviderByName(providerName)) {
|
||||||
if (!provider->supportsModelListing()) {
|
if (!provider->supportsModelListing()) {
|
||||||
@@ -122,11 +136,13 @@ void ConfigurationManager::selectTemplate()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectTemplate);
|
const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectTemplate);
|
||||||
|
const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectTemplate);
|
||||||
|
|
||||||
const auto templateList = isCodeCompletion ? m_templateManger.fimTemplatesNames()
|
const auto templateList = isCodeCompletion || isPreset1 ? m_templateManger.fimTemplatesNames()
|
||||||
: m_templateManger.chatTemplatesNames();
|
: m_templateManger.chatTemplatesNames();
|
||||||
|
|
||||||
auto &targetSettings = isCodeCompletion ? m_generalSettings.ccTemplate
|
auto &targetSettings = isCodeCompletion ? m_generalSettings.ccTemplate
|
||||||
|
: isPreset1 ? m_generalSettings.ccPreset1Template
|
||||||
: m_generalSettings.caTemplate;
|
: m_generalSettings.caTemplate;
|
||||||
|
|
||||||
QTimer::singleShot(0, &m_generalSettings, [this, templateList, &targetSettings]() {
|
QTimer::singleShot(0, &m_generalSettings, [this, templateList, &targetSettings]() {
|
||||||
@@ -150,8 +166,9 @@ void ConfigurationManager::selectUrl()
|
|||||||
urls.append(url);
|
urls.append(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &targetSettings = (settingsButton == &m_generalSettings.ccSetUrl)
|
auto &targetSettings = (settingsButton == &m_generalSettings.ccSetUrl) ? m_generalSettings.ccUrl
|
||||||
? m_generalSettings.ccUrl
|
: settingsButton == &m_generalSettings.ccPreset1SetUrl
|
||||||
|
? m_generalSettings.ccPreset1Url
|
||||||
: m_generalSettings.caUrl;
|
: m_generalSettings.caUrl;
|
||||||
|
|
||||||
QTimer::singleShot(0, &m_generalSettings, [this, urls, &targetSettings]() {
|
QTimer::singleShot(0, &m_generalSettings, [this, urls, &targetSettings]() {
|
||||||
|
|||||||
@@ -146,20 +146,41 @@ void LLMClientInterface::handleExit(const QJsonObject &request)
|
|||||||
emit finished();
|
emit finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QodeAssist::LLMClientInterface::isSpecifyCompletion(const QJsonObject &request)
|
||||||
|
{
|
||||||
|
auto &generalSettings = Settings::generalSettings();
|
||||||
|
|
||||||
|
Context::ProgrammingLanguage documentLanguage = getDocumentLanguage(request);
|
||||||
|
Context::ProgrammingLanguage preset1Language = Context::ProgrammingLanguageUtils::fromString(
|
||||||
|
generalSettings.preset1Language.displayForIndex(generalSettings.preset1Language()));
|
||||||
|
|
||||||
|
return generalSettings.specifyPreset1() && documentLanguage == preset1Language;
|
||||||
|
}
|
||||||
|
|
||||||
void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
||||||
{
|
{
|
||||||
auto updatedContext = prepareContext(request);
|
const auto updatedContext = prepareContext(request);
|
||||||
auto &completeSettings = Settings::codeCompletionSettings();
|
auto &completeSettings = Settings::codeCompletionSettings();
|
||||||
|
auto &generalSettings = Settings::generalSettings();
|
||||||
|
|
||||||
auto providerName = Settings::generalSettings().ccProvider();
|
bool isPreset1Active = isSpecifyCompletion(request);
|
||||||
auto provider = LLMCore::ProvidersManager::instance().getProviderByName(providerName);
|
|
||||||
|
const auto providerName = !isPreset1Active ? generalSettings.ccProvider()
|
||||||
|
: generalSettings.ccPreset1Provider();
|
||||||
|
const auto modelName = !isPreset1Active ? generalSettings.ccModel()
|
||||||
|
: generalSettings.ccPreset1Model();
|
||||||
|
const auto url = !isPreset1Active ? generalSettings.ccUrl() : generalSettings.ccPreset1Url();
|
||||||
|
|
||||||
|
const auto provider = LLMCore::ProvidersManager::instance().getProviderByName(providerName);
|
||||||
|
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
LOG_MESSAGE(QString("No provider found with name: %1").arg(providerName));
|
LOG_MESSAGE(QString("No provider found with name: %1").arg(providerName));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto templateName = Settings::generalSettings().ccTemplate();
|
auto templateName = !isPreset1Active ? generalSettings.ccTemplate()
|
||||||
|
: generalSettings.ccPreset1Template();
|
||||||
|
|
||||||
auto promptTemplate = LLMCore::PromptTemplateManager::instance().getFimTemplateByName(
|
auto promptTemplate = LLMCore::PromptTemplateManager::instance().getFimTemplateByName(
|
||||||
templateName);
|
templateName);
|
||||||
|
|
||||||
@@ -168,19 +189,18 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO refactor to dynamic presets system
|
||||||
LLMCore::LLMConfig config;
|
LLMCore::LLMConfig config;
|
||||||
config.requestType = LLMCore::RequestType::CodeCompletion;
|
config.requestType = LLMCore::RequestType::CodeCompletion;
|
||||||
config.provider = provider;
|
config.provider = provider;
|
||||||
config.promptTemplate = promptTemplate;
|
config.promptTemplate = promptTemplate;
|
||||||
config.url = QUrl(QString("%1%2").arg(
|
config.url = QUrl(QString("%1%2").arg(
|
||||||
Settings::generalSettings().ccUrl(),
|
url,
|
||||||
promptTemplate->type() == LLMCore::TemplateType::Fim ? provider->completionEndpoint()
|
promptTemplate->type() == LLMCore::TemplateType::Fim ? provider->completionEndpoint()
|
||||||
: provider->chatEndpoint()));
|
: provider->chatEndpoint()));
|
||||||
config.apiKey = provider->apiKey();
|
config.apiKey = provider->apiKey();
|
||||||
|
|
||||||
config.providerRequest
|
config.providerRequest = {{"model", modelName}, {"stream", completeSettings.stream()}};
|
||||||
= {{"model", Settings::generalSettings().ccModel()},
|
|
||||||
{"stream", Settings::codeCompletionSettings().stream()}};
|
|
||||||
|
|
||||||
config.multiLineCompletion = completeSettings.multiLineCompletion();
|
config.multiLineCompletion = completeSettings.multiLineCompletion();
|
||||||
|
|
||||||
@@ -246,11 +266,33 @@ LLMCore::ContextData LLMClientInterface::prepareContext(const QJsonObject &reque
|
|||||||
return reader.prepareContext(lineNumber, cursorPosition);
|
return reader.prepareContext(lineNumber, cursorPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Context::ProgrammingLanguage LLMClientInterface::getDocumentLanguage(const QJsonObject &request) const
|
||||||
|
{
|
||||||
|
QJsonObject params = request["params"].toObject();
|
||||||
|
QJsonObject doc = params["doc"].toObject();
|
||||||
|
QString uri = doc["uri"].toString();
|
||||||
|
|
||||||
|
Utils::FilePath filePath = Utils::FilePath::fromString(QUrl(uri).toLocalFile());
|
||||||
|
TextEditor::TextDocument *textDocument = TextEditor::TextDocument::textDocumentForFilePath(
|
||||||
|
filePath);
|
||||||
|
|
||||||
|
if (!textDocument) {
|
||||||
|
LOG_MESSAGE("Error: Document is not available for" + filePath.toString());
|
||||||
|
return Context::ProgrammingLanguage::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Context::ProgrammingLanguageUtils::fromMimeType(textDocument->mimeType());
|
||||||
|
}
|
||||||
|
|
||||||
void LLMClientInterface::sendCompletionToClient(const QString &completion,
|
void LLMClientInterface::sendCompletionToClient(const QString &completion,
|
||||||
const QJsonObject &request,
|
const QJsonObject &request,
|
||||||
bool isComplete)
|
bool isComplete)
|
||||||
{
|
{
|
||||||
auto templateName = Settings::generalSettings().ccTemplate();
|
bool isPreset1Active = isSpecifyCompletion(request);
|
||||||
|
|
||||||
|
auto templateName = !isPreset1Active ? Settings::generalSettings().ccTemplate()
|
||||||
|
: Settings::generalSettings().ccPreset1Template();
|
||||||
|
|
||||||
auto promptTemplate = LLMCore::PromptTemplateManager::instance().getFimTemplateByName(
|
auto promptTemplate = LLMCore::PromptTemplateManager::instance().getFimTemplateByName(
|
||||||
templateName);
|
templateName);
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include <languageclient/languageclientinterface.h>
|
#include <languageclient/languageclientinterface.h>
|
||||||
#include <texteditor/texteditor.h>
|
#include <texteditor/texteditor.h>
|
||||||
|
|
||||||
|
#include <context/ProgrammingLanguage.hpp>
|
||||||
#include <llmcore/ContextData.hpp>
|
#include <llmcore/ContextData.hpp>
|
||||||
#include <llmcore/RequestHandler.hpp>
|
#include <llmcore/RequestHandler.hpp>
|
||||||
|
|
||||||
@@ -58,8 +59,10 @@ private:
|
|||||||
void handleExit(const QJsonObject &request);
|
void handleExit(const QJsonObject &request);
|
||||||
void handleCancelRequest(const QJsonObject &request);
|
void handleCancelRequest(const QJsonObject &request);
|
||||||
|
|
||||||
LLMCore::ContextData prepareContext(const QJsonObject &request,
|
LLMCore::ContextData prepareContext(
|
||||||
const QStringView &accumulatedCompletion = QString{});
|
const QJsonObject &request, const QStringView &accumulatedCompletion = QString{});
|
||||||
|
Context::ProgrammingLanguage getDocumentLanguage(const QJsonObject &request) const;
|
||||||
|
bool isSpecifyCompletion(const QJsonObject &request);
|
||||||
|
|
||||||
LLMCore::RequestHandler m_requestHandler;
|
LLMCore::RequestHandler m_requestHandler;
|
||||||
QElapsedTimer m_completionTimer;
|
QElapsedTimer m_completionTimer;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Id" : "qodeassist",
|
"Id" : "qodeassist",
|
||||||
"Name" : "QodeAssist",
|
"Name" : "QodeAssist",
|
||||||
"Version" : "0.4.4",
|
"Version" : "0.4.13",
|
||||||
"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",
|
||||||
|
|||||||
206
README.md
206
README.md
@@ -1,10 +1,9 @@
|
|||||||
# QodeAssist - AI-powered coding assistant plugin for Qt Creator
|
# QodeAssist - AI-powered coding assistant plugin for Qt Creator
|
||||||
[](https://discord.gg/DGgMtTteAD)
|
|
||||||
[](https://github.com/Palm1r/QodeAssist/actions/workflows/build_cmake.yml)
|
[](https://github.com/Palm1r/QodeAssist/actions/workflows/build_cmake.yml)
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|
[](https://discord.gg/BGMkUsXUgf)
|
||||||
|
|
||||||
 QodeAssist is an AI-powered coding assistant plugin for Qt Creator. It provides intelligent code completion and suggestions for C++ and QML, leveraging large language models through local providers like Ollama. Enhance your coding productivity with context-aware AI assistance directly in your Qt development environment.
|
 QodeAssist is an AI-powered coding assistant plugin for Qt Creator. It provides intelligent code completion and suggestions for C++ and QML, leveraging large language models through local providers like Ollama. Enhance your coding productivity with context-aware AI assistance directly in your Qt development environment.
|
||||||
|
|
||||||
@@ -14,29 +13,45 @@
|
|||||||
> - The QodeAssist developer bears no responsibility for any charges incurred
|
> - The QodeAssist developer bears no responsibility for any charges incurred
|
||||||
> - Please carefully review the provider's pricing and your account settings before use
|
> - Please carefully review the provider's pricing and your account settings before use
|
||||||
|
|
||||||
|
⚠️ **Commercial Support and Custom Development**
|
||||||
|
> The QodeAssist developer offers commercial services for:
|
||||||
|
> - Adapting the plugin for specific Qt Creator versions
|
||||||
|
> - Custom development for particular operating systems
|
||||||
|
> - Integration with specific language models
|
||||||
|
> - Implementing custom features and modifications
|
||||||
|
>
|
||||||
|
> For commercial inquiries, please contact: qodeassist.dev@pm.me
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
1. [Overview](#overview)
|
1. [Overview](#overview)
|
||||||
2. [Installation for using Ollama](#installation-for-using-Ollama)
|
2. [Install plugin to QtCreator](#install-plugin-to-qtcreator)
|
||||||
3. [Installation for using Claude](#installation-for-using-Claude)
|
3. [Configure for Anthropic Claude](#configure-for-anthropic-claude)
|
||||||
3. [Configure Plugin](#configure-plugin)
|
4. [Configure for OpenAI](#configure-for-openai)
|
||||||
4. [Supported LLM Providers](#supported-llm-providers)
|
5. [Configure for using Ollama](#configure-for-using-ollama)
|
||||||
5. [Recommended Models](#recommended-models)
|
6. [System Prompt Configuration](#system-prompt-configuration)
|
||||||
- [Ollama](#ollama)
|
7. [File Context Features](#file-context-features)
|
||||||
6. [QtCreator Version Compatibility](#qtcreator-version-compatibility)
|
8. [Template-Model Compatibility](#template-model-compatibility)
|
||||||
7. [Development Progress](#development-progress)
|
9. [QtCreator Version Compatibility](#qtcreator-version-compatibility)
|
||||||
8. [Hotkeys](#hotkeys)
|
10. [Development Progress](#development-progress)
|
||||||
9. [Troubleshooting](#troubleshooting)
|
11. [Hotkeys](#hotkeys)
|
||||||
10. [Support the Development](#support-the-development-of-qodeassist)
|
12. [Troubleshooting](#troubleshooting)
|
||||||
11. [How to Build](#how-to-build)
|
13. [Support the Development](#support-the-development-of-qodeassist)
|
||||||
|
14. [How to Build](#how-to-build)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
- AI-powered code completion
|
- AI-powered code completion
|
||||||
- Chat functionality:
|
- Chat functionality:
|
||||||
- Side and Bottom panels
|
- Side and Bottom panels
|
||||||
|
- Chat history autosave and restore
|
||||||
|
- Token usage monitoring and management
|
||||||
|
- Attach files for one-time code analysis
|
||||||
|
- Link files for persistent context with auto update in conversations
|
||||||
|
- Automatic syncing with open editor files (optional)
|
||||||
- Support for multiple LLM providers:
|
- Support for multiple LLM providers:
|
||||||
- Ollama
|
- Ollama
|
||||||
- Claude
|
- OpenAI
|
||||||
|
- Anthropic Claude
|
||||||
- LM Studio
|
- LM Studio
|
||||||
- OpenAI-compatible providers(eg. https://openrouter.ai)
|
- OpenAI-compatible providers(eg. https://openrouter.ai)
|
||||||
- Extensive library of model-specific templates
|
- Extensive library of model-specific templates
|
||||||
@@ -63,11 +78,52 @@
|
|||||||
<img width="326" alt="QodeAssistBottomPanel" src="https://github.com/user-attachments/assets/4cc64c23-a294-4df8-9153-39ad6fdab34b">
|
<img width="326" alt="QodeAssistBottomPanel" src="https://github.com/user-attachments/assets/4cc64c23-a294-4df8-9153-39ad6fdab34b">
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Installation for using Ollama
|
<details>
|
||||||
|
<summary>Automatic syncing with open editor files: (click to expand)</summary>
|
||||||
|
<img width="600" alt="OpenedDocumentsSync" src="https://github.com/user-attachments/assets/08efda2f-dc4d-44c3-927c-e6a975090d2f">
|
||||||
|
</details>
|
||||||
|
|
||||||
1. Install Latest QtCreator
|
## Install plugin to QtCreator
|
||||||
2. Install [Ollama](https://ollama.com). Make sure to review the system requirements before installation.
|
1. Install Latest Qt Creator
|
||||||
3. Install a language models in Ollama via terminal. For example, you can run:
|
2. Download the QodeAssist plugin for your Qt Creator
|
||||||
|
- Remove old version plugin if already was installed
|
||||||
|
3. Launch Qt Creator and install the plugin:
|
||||||
|
- Go to:
|
||||||
|
- MacOS: Qt Creator -> About Plugins...
|
||||||
|
- Windows\Linux: Help -> About Plugins...
|
||||||
|
- Click on "Install Plugin..."
|
||||||
|
- Select the downloaded QodeAssist plugin archive file
|
||||||
|
|
||||||
|
## Configure for Anthropic Claude
|
||||||
|
1. Open Qt Creator settings and navigate to the QodeAssist section
|
||||||
|
2. Go to Provider Settings tab and configure Claude api key
|
||||||
|
3. Return to General tab and configure:
|
||||||
|
- Set "Claude" as the provider for code completion or/and chat assistant
|
||||||
|
- Set the Claude URL (https://api.anthropic.com)
|
||||||
|
- Select your preferred model (e.g., claude-3-5-sonnet-20241022)
|
||||||
|
- Choose the Claude template for code completion or/and chat
|
||||||
|
<details>
|
||||||
|
<summary>Example of Claude settings: (click to expand)</summary>
|
||||||
|
<img width="823" alt="Claude Settings" src="https://github.com/user-attachments/assets/828e09ea-e271-4a7a-8271-d3d5dd5c13fd" />
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Configure for OpenAI
|
||||||
|
1. Open Qt Creator settings and navigate to the QodeAssist section
|
||||||
|
2. Go to Provider Settings tab and configure OpenAI api key
|
||||||
|
3. Return to General tab and configure:
|
||||||
|
- Set "OpenAI" as the provider for code completion or/and chat assistant
|
||||||
|
- Set the OpenAI URL (https://api.openai.com)
|
||||||
|
- Select your preferred model (e.g., gpt-4o)
|
||||||
|
- Choose the OpenAI template for code completion or/and chat
|
||||||
|
<details>
|
||||||
|
<summary>Example of OpenAI settings: (click to expand)</summary>
|
||||||
|
<img width="829" alt="OpenAI Settings" src="https://github.com/user-attachments/assets/4716f790-6159-44d0-a8f4-565ccb6eb713" />
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Configure for using Ollama
|
||||||
|
|
||||||
|
1. Install [Ollama](https://ollama.com). Make sure to review the system requirements before installation.
|
||||||
|
2. Install a language models in Ollama via terminal. For example, you can run:
|
||||||
|
|
||||||
For standard computers (minimum 8GB RAM):
|
For standard computers (minimum 8GB RAM):
|
||||||
```
|
```
|
||||||
@@ -81,31 +137,6 @@ For high-end systems (32GB+ RAM):
|
|||||||
```
|
```
|
||||||
ollama run qwen2.5-coder:32b
|
ollama run qwen2.5-coder:32b
|
||||||
```
|
```
|
||||||
4. Download the QodeAssist plugin for your QtCreator.
|
|
||||||
5. Launch Qt Creator and install the plugin:
|
|
||||||
- Go to MacOS: Qt Creator -> About Plugins...
|
|
||||||
Windows\Linux: Help -> About Plugins...
|
|
||||||
- Click on "Install Plugin..."
|
|
||||||
- Select the downloaded QodeAssist plugin archive file
|
|
||||||
|
|
||||||
## Installation for using Claude
|
|
||||||
|
|
||||||
1. Install Latest QtCreator
|
|
||||||
2. Download the QodeAssist plugin for your QtCreator.
|
|
||||||
3. Launch Qt Creator and install the plugin:
|
|
||||||
- Go to MacOS: Qt Creator -> About Plugins...
|
|
||||||
Windows\Linux: Help -> About Plugins...
|
|
||||||
- Click on "Install Plugin..."
|
|
||||||
- Select the downloaded QodeAssist plugin archive file
|
|
||||||
4. Select Claude provider
|
|
||||||
5. Select Claude api
|
|
||||||
6. Fill in api key for Claude
|
|
||||||
5. Select Claude templates for code completion and chat
|
|
||||||
6. Enjoy!
|
|
||||||
|
|
||||||
## Configure Plugin
|
|
||||||
|
|
||||||
QodeAssist comes with default settings that should work immediately after installing a language model. The plugin is pre-configured to use Ollama with standard templates, so you may only need to verify the settings.
|
|
||||||
|
|
||||||
1. Open Qt Creator settings (Edit > Preferences on Linux/Windows, Qt Creator > Preferences on macOS)
|
1. Open Qt Creator settings (Edit > Preferences on Linux/Windows, Qt Creator > Preferences on macOS)
|
||||||
2. Navigate to the "Qode Assist" tab
|
2. Navigate to the "Qode Assist" tab
|
||||||
@@ -113,75 +144,72 @@ QodeAssist comes with default settings that should work immediately after instal
|
|||||||
- Ollama is selected as your LLM provider
|
- Ollama is selected as your LLM provider
|
||||||
- The URL is set to http://localhost:11434
|
- The URL is set to http://localhost:11434
|
||||||
- Your installed model appears in the model selection
|
- Your installed model appears in the model selection
|
||||||
- The prompt template is Ollama Auto FIM
|
- The prompt template is Ollama Auto FIM or Ollama Auto Chat for chat assistance. You can specify template if it is not work correct
|
||||||
4. Click Apply if you made any changes
|
4. Click Apply if you made any changes
|
||||||
|
|
||||||
You're all set! QodeAssist is now ready to use in Qt Creator.
|
You're all set! QodeAssist is now ready to use in Qt Creator.
|
||||||
|
<details>
|
||||||
|
<summary>Example of Ollama settings: (click to expand)</summary>
|
||||||
|
<img width="824" alt="Ollama Settings" src="https://github.com/user-attachments/assets/ed64e03a-a923-467a-aa44-4f790e315b53" />
|
||||||
|
</details>
|
||||||
|
|
||||||
## Supported LLM Providers
|
## System Prompt Configuration
|
||||||
QodeAssist currently supports the following LLM (Large Language Model) providers:
|
|
||||||
- [Ollama](https://ollama.com)
|
|
||||||
- [LM Studio](https://lmstudio.ai)
|
|
||||||
- [OpenRouter](https://openrouter.ai)
|
|
||||||
- OpenAI compatible providers
|
|
||||||
|
|
||||||
## Recommended Models:
|
The plugin comes with default system prompts optimized for chat and instruct models, as these currently provide better results for code assistance. If you prefer using FIM (Fill-in-Middle) models, you can easily customize the system prompt in the settings.
|
||||||
QodeAssist has been thoroughly tested and optimized for use with the following language models:
|
|
||||||
|
|
||||||
- Qwen2.5-coder
|
## File Context Features
|
||||||
- CodeLlama
|
|
||||||
- StarCoder2
|
|
||||||
- DeepSeek-Coder-V2
|
|
||||||
|
|
||||||
### Model Types
|
QodeAssist provides two powerful ways to include source code files in your chat conversations: Attachments and Linked Files. Each serves a distinct purpose and helps provide better context for the AI assistant.
|
||||||
|
|
||||||
FIM models (codellama:7b-code, starcoder2:7b, etc.) - Optimized for code completion and suggestions
|
### Attached Files
|
||||||
|
|
||||||
Instruct models (codellama:7b-instruct, starcoder2:instruct, etc.) - Better for chat assistance, explanations, and code review
|
Attachments are designed for one-time code analysis and specific queries:
|
||||||
|
- Files are included only in the current message
|
||||||
|
- Content is discarded after the message is processed
|
||||||
|
- Ideal for:
|
||||||
|
- Getting specific feedback on code changes
|
||||||
|
- Code review requests
|
||||||
|
- Analyzing isolated code segments
|
||||||
|
- Quick implementation questions
|
||||||
|
- Files can be attached using the paperclip icon in the chat interface
|
||||||
|
- Multiple files can be attached to a single message
|
||||||
|
|
||||||
For best results, use FIM models with code completion and Instruct models with chat features.
|
### Linked Files
|
||||||
|
|
||||||
### Ollama:
|
Linked files provide persistent context throughout the conversation:
|
||||||
### For autocomplete(FIM)
|
|
||||||
```
|
|
||||||
ollama run codellama:7b-code
|
|
||||||
ollama run starcoder2:7b
|
|
||||||
ollama run qwen2.5-coder:7b-base
|
|
||||||
ollama run deepseek-coder-v2:16b-lite-base-q3_K_M
|
|
||||||
```
|
|
||||||
### For chat and instruct
|
|
||||||
```
|
|
||||||
ollama run codellama:7b-instruct
|
|
||||||
ollama run starcoder2:instruct
|
|
||||||
ollama run qwen2.5-coder:7b-instruct
|
|
||||||
ollama run deepseek-coder-v2
|
|
||||||
```
|
|
||||||
|
|
||||||
### Template-Model Compatibility
|
- Files remain accessible for the entire chat session
|
||||||
|
- Content is included in every message exchange
|
||||||
|
- Files are automatically refreshed - always using latest content from disk
|
||||||
|
- Perfect for:
|
||||||
|
- Long-term refactoring discussions
|
||||||
|
- Complex architectural changes
|
||||||
|
- Multi-file implementations
|
||||||
|
- Maintaining context across related questions
|
||||||
|
- Can be managed using the link icon in the chat interface
|
||||||
|
- Supports automatic syncing with open editor files (can be enabled in settings)
|
||||||
|
- Files can be added/removed at any time during the conversation
|
||||||
|
|
||||||
|
## Template-Model Compatibility
|
||||||
|
|
||||||
| Template | Compatible Models | Purpose |
|
| Template | Compatible Models | Purpose |
|
||||||
|----------|------------------|----------|
|
|----------|------------------|----------|
|
||||||
| CodeLlama FIM | `codellama:code` | Code completion |
|
| CodeLlama FIM | `codellama:code` | Code completion |
|
||||||
| DeepSeekCoder FIM | `deepseek-coder-v2`, `deepseek-v2.5` | Code completion |
|
| DeepSeekCoder FIM | `deepseek-coder-v2`, `deepseek-v2.5` | Code completion |
|
||||||
| Ollama Auto FIM | `Any Ollama base model` | Code completion |
|
| Ollama Auto FIM | `Any Ollama base/fim models` | Code completion |
|
||||||
| Qwen FIM | `Qwen 2.5 models` | Code completion |
|
| Qwen FIM | `Qwen 2.5 models(exclude instruct)` | Code completion |
|
||||||
| StarCoder2 FIM | `starcoder2 base model` | Code completion |
|
| StarCoder2 FIM | `starcoder2 base model` | Code completion |
|
||||||
| Alpaca | `starcoder2:instruct` | Chat assistance |
|
| Alpaca | `starcoder2:instruct` | Chat assistance |
|
||||||
| Basic Chat| `Messages without tokens` | Chat assistance |
|
| Basic Chat| `Messages without tokens` | Chat assistance |
|
||||||
| ChatML | `Qwen 2.5 models` | Chat assistance |
|
| ChatML | `Qwen 2.5 models(exclude base models)` | Chat assistance |
|
||||||
| Llama2 | `llama2 model family`, `codellama:instruct` | Chat assistance |
|
| Llama2 | `llama2 model family`, `codellama:instruct` | Chat assistance |
|
||||||
| Llama3 | `llama3 model family` | Chat assistance |
|
| Llama3 | `llama3 model family` | Chat assistance |
|
||||||
| Ollama Auto Chat | `Any Ollama chat model` | Chat assistance |
|
| Ollama Auto Chat | `Any Ollama chat/instruct models` | Chat assistance |
|
||||||
|
|
||||||
> Note:
|
|
||||||
> - FIM (Fill-in-Middle) templates are optimized for code completion
|
|
||||||
> - Chat templates are designed for interactive dialogue
|
|
||||||
> - The Ollama Auto templates automatically adapt to most Ollama models
|
|
||||||
> - Custom Template allows you to define your own prompt format
|
|
||||||
|
|
||||||
## QtCreator Version Compatibility
|
## QtCreator Version Compatibility
|
||||||
|
|
||||||
- QtCreator 15.0.0 - 0.4.x
|
- QtCreator 15.0.1 - 0.4.8 - 0.4.x
|
||||||
|
- QtCreator 15.0.0 - 0.4.0 - 0.4.7
|
||||||
- QtCreator 14.0.2 - 0.2.3 - 0.3.x
|
- QtCreator 14.0.2 - 0.2.3 - 0.3.x
|
||||||
- QtCreator 14.0.1 - 0.2.2 plugin version and below
|
- QtCreator 14.0.1 - 0.2.2 plugin version and below
|
||||||
|
|
||||||
|
|||||||
72
UpdateStatusWidget.cpp
Normal file
72
UpdateStatusWidget.cpp
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* 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 "UpdateStatusWidget.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist {
|
||||||
|
|
||||||
|
UpdateStatusWidget::UpdateStatusWidget(QWidget *parent)
|
||||||
|
: QFrame(parent)
|
||||||
|
{
|
||||||
|
setFrameStyle(QFrame::NoFrame);
|
||||||
|
|
||||||
|
auto layout = new QHBoxLayout(this);
|
||||||
|
layout->setContentsMargins(4, 0, 4, 0);
|
||||||
|
layout->setSpacing(4);
|
||||||
|
|
||||||
|
m_actionButton = new QToolButton(this);
|
||||||
|
m_actionButton->setToolButtonStyle(Qt::ToolButtonIconOnly);
|
||||||
|
|
||||||
|
m_versionLabel = new QLabel(this);
|
||||||
|
m_versionLabel->setVisible(false);
|
||||||
|
|
||||||
|
m_updateButton = new QPushButton(tr("Update"), this);
|
||||||
|
m_updateButton->setVisible(false);
|
||||||
|
m_updateButton->setStyleSheet("QPushButton { padding: 2px 8px; }");
|
||||||
|
|
||||||
|
layout->addWidget(m_actionButton);
|
||||||
|
layout->addWidget(m_versionLabel);
|
||||||
|
layout->addWidget(m_updateButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateStatusWidget::setDefaultAction(QAction *action)
|
||||||
|
{
|
||||||
|
m_actionButton->setDefaultAction(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateStatusWidget::showUpdateAvailable(const QString &version)
|
||||||
|
{
|
||||||
|
m_versionLabel->setText(tr("new version: v%1").arg(version));
|
||||||
|
m_versionLabel->setVisible(true);
|
||||||
|
m_updateButton->setVisible(true);
|
||||||
|
m_updateButton->setToolTip(tr("Update QodeAssist to version %1").arg(version));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateStatusWidget::hideUpdateInfo()
|
||||||
|
{
|
||||||
|
m_versionLabel->setVisible(false);
|
||||||
|
m_updateButton->setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton *UpdateStatusWidget::updateButton() const
|
||||||
|
{
|
||||||
|
return m_updateButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist
|
||||||
47
UpdateStatusWidget.hpp
Normal file
47
UpdateStatusWidget.hpp
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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 <QFrame>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLayout>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QToolButton>
|
||||||
|
|
||||||
|
namespace QodeAssist {
|
||||||
|
|
||||||
|
class UpdateStatusWidget : public QFrame
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit UpdateStatusWidget(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
void setDefaultAction(QAction *action);
|
||||||
|
void showUpdateAvailable(const QString &version);
|
||||||
|
void hideUpdateInfo();
|
||||||
|
|
||||||
|
QPushButton *updateButton() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QToolButton *m_actionButton;
|
||||||
|
QLabel *m_versionLabel;
|
||||||
|
QPushButton *m_updateButton;
|
||||||
|
};
|
||||||
|
} // namespace QodeAssist
|
||||||
@@ -3,11 +3,22 @@ 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
|
||||||
|
ProgrammingLanguage.hpp ProgrammingLanguage.cpp
|
||||||
|
RAGManager.hpp RAGManager.cpp
|
||||||
|
RAGStorage.hpp RAGStorage.cpp
|
||||||
|
RAGData.hpp
|
||||||
|
RAGVectorizer.hpp RAGVectorizer.cpp
|
||||||
|
RAGSimilaritySearch.hpp RAGSimilaritySearch.cpp
|
||||||
|
RAGPreprocessor.hpp RAGPreprocessor.cpp
|
||||||
|
EnhancedRAGSimilaritySearch.hpp EnhancedRAGSimilaritySearch.cpp
|
||||||
|
FileChunker.hpp FileChunker.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(Context
|
target_link_libraries(Context
|
||||||
PUBLIC
|
PUBLIC
|
||||||
Qt::Core
|
Qt::Core
|
||||||
|
Qt::Sql
|
||||||
QtCreator::Core
|
QtCreator::Core
|
||||||
QtCreator::TextEditor
|
QtCreator::TextEditor
|
||||||
QtCreator::Utils
|
QtCreator::Utils
|
||||||
|
|||||||
@@ -23,6 +23,11 @@
|
|||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
|
||||||
|
#include <projectexplorer/project.h>
|
||||||
|
#include <projectexplorer/projectnodes.h>
|
||||||
|
|
||||||
|
#include "FileChunker.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::Context {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
ContextManager &ContextManager::instance()
|
ContextManager &ContextManager::instance()
|
||||||
@@ -64,4 +69,108 @@ ContentFile ContextManager::createContentFile(const QString &filePath) const
|
|||||||
return contentFile;
|
return contentFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ContextManager::isInBuildDirectory(const QString &filePath) const
|
||||||
|
{
|
||||||
|
static const QStringList buildDirPatterns
|
||||||
|
= {QString(QDir::separator()) + QLatin1String("build") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("Build") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("BUILD") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("debug") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("Debug") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("DEBUG") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("release") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("Release") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("RELEASE") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("builds") + QDir::separator()};
|
||||||
|
|
||||||
|
// Нормализуем путь
|
||||||
|
QString normalizedPath = QDir::fromNativeSeparators(filePath);
|
||||||
|
|
||||||
|
// Проверяем, содержит ли путь паттерны build-директории
|
||||||
|
for (const QString &pattern : buildDirPatterns) {
|
||||||
|
// Сравниваем с нормализованным паттерном
|
||||||
|
QString normalizedPattern = QDir::fromNativeSeparators(pattern);
|
||||||
|
if (normalizedPath.contains(normalizedPattern)) {
|
||||||
|
qDebug() << "Skipping build file:" << filePath;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ContextManager::getProjectSourceFiles(ProjectExplorer::Project *project) const
|
||||||
|
{
|
||||||
|
QStringList sourceFiles;
|
||||||
|
if (!project)
|
||||||
|
return sourceFiles;
|
||||||
|
|
||||||
|
auto projectNode = project->rootProjectNode();
|
||||||
|
if (!projectNode)
|
||||||
|
return sourceFiles;
|
||||||
|
|
||||||
|
projectNode->forEachNode(
|
||||||
|
[&sourceFiles, this](ProjectExplorer::FileNode *fileNode) {
|
||||||
|
if (fileNode) {
|
||||||
|
QString filePath = fileNode->filePath().toString();
|
||||||
|
if (shouldProcessFile(filePath) && !isInBuildDirectory(filePath)) {
|
||||||
|
sourceFiles.append(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
return sourceFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContextManager::shouldProcessFile(const QString &filePath) const
|
||||||
|
{
|
||||||
|
static const QStringList supportedExtensions
|
||||||
|
= {"cpp", "hpp", "c", "h", "cc", "hh", "cxx", "hxx", "qml", "js", "py"};
|
||||||
|
|
||||||
|
QFileInfo fileInfo(filePath);
|
||||||
|
return supportedExtensions.contains(fileInfo.suffix().toLower());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextManager::testProjectChunks(
|
||||||
|
ProjectExplorer::Project *project, const FileChunker::ChunkingConfig &config)
|
||||||
|
{
|
||||||
|
if (!project) {
|
||||||
|
qDebug() << "No project provided";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "\nStarting test chunking for project:" << project->displayName();
|
||||||
|
|
||||||
|
// Get source files
|
||||||
|
QStringList sourceFiles = getProjectSourceFiles(project);
|
||||||
|
qDebug() << "Found" << sourceFiles.size() << "source files";
|
||||||
|
|
||||||
|
// Create chunker
|
||||||
|
auto chunker = new FileChunker(config, this);
|
||||||
|
|
||||||
|
// Connect progress and error signals
|
||||||
|
connect(chunker, &FileChunker::progressUpdated, this, [](int processed, int total) {
|
||||||
|
qDebug() << "Progress:" << processed << "/" << total << "files";
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(chunker, &FileChunker::error, this, [](const QString &error) {
|
||||||
|
qDebug() << "Error:" << error;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start chunking and handle results
|
||||||
|
auto future = chunker->chunkFiles(sourceFiles);
|
||||||
|
|
||||||
|
// Используем QFutureWatcher для обработки результатов
|
||||||
|
auto watcher = new QFutureWatcher<QList<FileChunk>>(this);
|
||||||
|
|
||||||
|
connect(watcher, &QFutureWatcher<QList<FileChunk>>::finished, this, [watcher, chunker]() {
|
||||||
|
// Очистка
|
||||||
|
watcher->deleteLater();
|
||||||
|
chunker->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
watcher->setFuture(future);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Context
|
} // namespace QodeAssist::Context
|
||||||
|
|||||||
@@ -19,10 +19,16 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "ContentFile.hpp"
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include "ContentFile.hpp"
|
#include "FileChunker.hpp"
|
||||||
|
|
||||||
|
namespace ProjectExplorer {
|
||||||
|
class Project;
|
||||||
|
}
|
||||||
|
|
||||||
namespace QodeAssist::Context {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
@@ -32,15 +38,23 @@ class ContextManager : public QObject
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
static ContextManager &instance();
|
static ContextManager &instance();
|
||||||
|
|
||||||
QString readFile(const QString &filePath) const;
|
QString readFile(const QString &filePath) const;
|
||||||
QList<ContentFile> getContentFiles(const QStringList &filePaths) const;
|
QList<ContentFile> getContentFiles(const QStringList &filePaths) const;
|
||||||
|
QStringList getProjectSourceFiles(ProjectExplorer::Project *project) const;
|
||||||
|
|
||||||
|
void testProjectChunks(
|
||||||
|
ProjectExplorer::Project *project, const FileChunker::ChunkingConfig &config);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit ContextManager(QObject *parent = nullptr);
|
explicit ContextManager(QObject *parent = nullptr);
|
||||||
~ContextManager() = default;
|
~ContextManager() = default;
|
||||||
ContextManager(const ContextManager &) = delete;
|
ContextManager(const ContextManager &) = delete;
|
||||||
ContextManager &operator=(const ContextManager &) = delete;
|
ContextManager &operator=(const ContextManager &) = delete;
|
||||||
|
|
||||||
ContentFile createContentFile(const QString &filePath) const;
|
ContentFile createContentFile(const QString &filePath) const;
|
||||||
|
bool shouldProcessFile(const QString &filePath) const;
|
||||||
|
bool isInBuildDirectory(const QString &filePath) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Context
|
} // namespace QodeAssist::Context
|
||||||
|
|||||||
265
context/EnhancedRAGSimilaritySearch.cpp
Normal file
265
context/EnhancedRAGSimilaritySearch.cpp
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
#include "EnhancedRAGSimilaritySearch.hpp"
|
||||||
|
|
||||||
|
#include <QSet>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
// Static regex getters
|
||||||
|
const QRegularExpression &EnhancedRAGSimilaritySearch::getNamespaceRegex()
|
||||||
|
{
|
||||||
|
static const QRegularExpression regex(R"(namespace\s+(?:\w+\s*::\s*)*\w+\s*\{)");
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QRegularExpression &EnhancedRAGSimilaritySearch::getClassRegex()
|
||||||
|
{
|
||||||
|
static const QRegularExpression regex(
|
||||||
|
R"((?:template\s*<[^>]*>\s*)?(?:class|struct)\s+(\w+)\s*(?:final\s*)?(?::\s*(?:public|protected|private)\s+\w+(?:\s*,\s*(?:public|protected|private)\s+\w+)*\s*)?{)");
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QRegularExpression &EnhancedRAGSimilaritySearch::getFunctionRegex()
|
||||||
|
{
|
||||||
|
static const QRegularExpression regex(
|
||||||
|
R"((?:virtual\s+)?(?:static\s+)?(?:inline\s+)?(?:explicit\s+)?(?:constexpr\s+)?(?:[\w:]+\s+)?(?:\w+\s*::\s*)*\w+\s*\([^)]*\)\s*(?:const\s*)?(?:noexcept\s*)?(?:override\s*)?(?:final\s*)?(?:=\s*0\s*)?(?:=\s*default\s*)?(?:=\s*delete\s*)?(?:\s*->.*?)?\s*{)");
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QRegularExpression &EnhancedRAGSimilaritySearch::getTemplateRegex()
|
||||||
|
{
|
||||||
|
static const QRegularExpression regex(R"(template\s*<[^>]*>\s*(?:class|struct|typename)\s+\w+)");
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache getters
|
||||||
|
QCache<QString, EnhancedRAGSimilaritySearch::SimilarityScore> &
|
||||||
|
EnhancedRAGSimilaritySearch::getScoreCache()
|
||||||
|
{
|
||||||
|
static QCache<QString, SimilarityScore> cache(1000); // Cache size of 1000 entries
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
QCache<QString, QStringList> &EnhancedRAGSimilaritySearch::getStructureCache()
|
||||||
|
{
|
||||||
|
static QCache<QString, QStringList> cache(500); // Cache size of 500 entries
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main public interface
|
||||||
|
EnhancedRAGSimilaritySearch::SimilarityScore EnhancedRAGSimilaritySearch::calculateSimilarity(
|
||||||
|
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2)
|
||||||
|
{
|
||||||
|
// Generate cache key based on content hashes
|
||||||
|
QString cacheKey = QString("%1_%2").arg(qHash(code1)).arg(qHash(code2));
|
||||||
|
|
||||||
|
// Check cache first
|
||||||
|
auto &scoreCache = getScoreCache();
|
||||||
|
if (auto *cached = scoreCache.object(cacheKey)) {
|
||||||
|
return *cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate new similarity score
|
||||||
|
SimilarityScore score = calculateSimilarityInternal(v1, v2, code1, code2);
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
scoreCache.insert(cacheKey, new SimilarityScore(score));
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal implementation
|
||||||
|
EnhancedRAGSimilaritySearch::SimilarityScore EnhancedRAGSimilaritySearch::calculateSimilarityInternal(
|
||||||
|
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2)
|
||||||
|
{
|
||||||
|
if (v1.empty() || v2.empty()) {
|
||||||
|
LOG_MESSAGE("Warning: Empty vectors in similarity calculation");
|
||||||
|
return SimilarityScore(0.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v1.size() != v2.size()) {
|
||||||
|
LOG_MESSAGE(QString("Vector size mismatch: %1 vs %2").arg(v1.size()).arg(v2.size()));
|
||||||
|
return SimilarityScore(0.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate semantic similarity using vector embeddings
|
||||||
|
float semantic_similarity = 0.0f;
|
||||||
|
|
||||||
|
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||||
|
if (v1.size() >= 4) { // Use SSE for vectors of 4 or more elements
|
||||||
|
semantic_similarity = calculateCosineSimilaritySSE(v1, v2);
|
||||||
|
} else {
|
||||||
|
semantic_similarity = calculateCosineSimilarity(v1, v2);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
semantic_similarity = calculateCosineSimilarity(v1, v2);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// If semantic similarity is very low, skip structural analysis
|
||||||
|
if (semantic_similarity < 0.0001f) {
|
||||||
|
return SimilarityScore(0.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate structural similarity
|
||||||
|
float structural_similarity = calculateStructuralSimilarity(code1, code2);
|
||||||
|
|
||||||
|
// Calculate combined score with dynamic weights
|
||||||
|
float semantic_weight = 0.7f;
|
||||||
|
const int large_file_threshold = 10000;
|
||||||
|
|
||||||
|
if (code1.size() > large_file_threshold || code2.size() > large_file_threshold) {
|
||||||
|
semantic_weight = 0.8f; // Increase semantic weight for large files
|
||||||
|
}
|
||||||
|
|
||||||
|
float combined_score = semantic_weight * semantic_similarity
|
||||||
|
+ (1.0f - semantic_weight) * structural_similarity;
|
||||||
|
|
||||||
|
return SimilarityScore(semantic_similarity, structural_similarity, combined_score);
|
||||||
|
}
|
||||||
|
|
||||||
|
float EnhancedRAGSimilaritySearch::calculateCosineSimilarity(const RAGVector &v1, const RAGVector &v2)
|
||||||
|
{
|
||||||
|
float dotProduct = 0.0f;
|
||||||
|
float norm1 = 0.0f;
|
||||||
|
float norm2 = 0.0f;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < v1.size(); ++i) {
|
||||||
|
dotProduct += v1[i] * v2[i];
|
||||||
|
norm1 += v1[i] * v1[i];
|
||||||
|
norm2 += v2[i] * v2[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
norm1 = std::sqrt(norm1);
|
||||||
|
norm2 = std::sqrt(norm2);
|
||||||
|
|
||||||
|
if (norm1 == 0.0f || norm2 == 0.0f) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dotProduct / (norm1 * norm2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||||
|
float EnhancedRAGSimilaritySearch::calculateCosineSimilaritySSE(
|
||||||
|
const RAGVector &v1, const RAGVector &v2)
|
||||||
|
{
|
||||||
|
const float *p1 = v1.data();
|
||||||
|
const float *p2 = v2.data();
|
||||||
|
const size_t size = v1.size();
|
||||||
|
const size_t alignedSize = size & ~3ULL; // Round down to multiple of 4
|
||||||
|
|
||||||
|
__m128 sum = _mm_setzero_ps();
|
||||||
|
__m128 norm1 = _mm_setzero_ps();
|
||||||
|
__m128 norm2 = _mm_setzero_ps();
|
||||||
|
|
||||||
|
// Process 4 elements at a time using SSE
|
||||||
|
for (size_t i = 0; i < alignedSize; i += 4) {
|
||||||
|
__m128 v1_vec = _mm_loadu_ps(p1 + i); // Use unaligned load for safety
|
||||||
|
__m128 v2_vec = _mm_loadu_ps(p2 + i);
|
||||||
|
|
||||||
|
sum = _mm_add_ps(sum, _mm_mul_ps(v1_vec, v2_vec));
|
||||||
|
norm1 = _mm_add_ps(norm1, _mm_mul_ps(v1_vec, v1_vec));
|
||||||
|
norm2 = _mm_add_ps(norm2, _mm_mul_ps(v2_vec, v2_vec));
|
||||||
|
}
|
||||||
|
|
||||||
|
float dotProduct = horizontalSum(sum);
|
||||||
|
float n1 = std::sqrt(horizontalSum(norm1));
|
||||||
|
float n2 = std::sqrt(horizontalSum(norm2));
|
||||||
|
|
||||||
|
// Process remaining elements
|
||||||
|
for (size_t i = alignedSize; i < size; ++i) {
|
||||||
|
dotProduct += v1[i] * v2[i];
|
||||||
|
n1 += v1[i] * v1[i];
|
||||||
|
n2 += v2[i] * v2[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n1 == 0.0f || n2 == 0.0f) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dotProduct / (std::sqrt(n1) * std::sqrt(n2));
|
||||||
|
}
|
||||||
|
|
||||||
|
float EnhancedRAGSimilaritySearch::horizontalSum(__m128 x)
|
||||||
|
{
|
||||||
|
__m128 shuf = _mm_shuffle_ps(x, x, _MM_SHUFFLE(2, 3, 0, 1));
|
||||||
|
__m128 sums = _mm_add_ps(x, shuf);
|
||||||
|
shuf = _mm_movehl_ps(shuf, sums);
|
||||||
|
sums = _mm_add_ss(sums, shuf);
|
||||||
|
return _mm_cvtss_f32(sums);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
float EnhancedRAGSimilaritySearch::calculateStructuralSimilarity(
|
||||||
|
const QString &code1, const QString &code2)
|
||||||
|
{
|
||||||
|
QStringList structures1 = extractStructures(code1);
|
||||||
|
QStringList structures2 = extractStructures(code2);
|
||||||
|
|
||||||
|
return calculateJaccardSimilarity(structures1, structures2);
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList EnhancedRAGSimilaritySearch::extractStructures(const QString &code)
|
||||||
|
{
|
||||||
|
// Check cache first
|
||||||
|
auto &structureCache = getStructureCache();
|
||||||
|
QString cacheKey = QString::number(qHash(code));
|
||||||
|
|
||||||
|
if (auto *cached = structureCache.object(cacheKey)) {
|
||||||
|
return *cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList structures;
|
||||||
|
structures.reserve(100); // Reserve space for typical file
|
||||||
|
|
||||||
|
// Extract namespaces
|
||||||
|
auto namespaceMatches = getNamespaceRegex().globalMatch(code);
|
||||||
|
while (namespaceMatches.hasNext()) {
|
||||||
|
structures.append(namespaceMatches.next().captured().trimmed());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract classes
|
||||||
|
auto classMatches = getClassRegex().globalMatch(code);
|
||||||
|
while (classMatches.hasNext()) {
|
||||||
|
structures.append(classMatches.next().captured().trimmed());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract functions
|
||||||
|
auto functionMatches = getFunctionRegex().globalMatch(code);
|
||||||
|
while (functionMatches.hasNext()) {
|
||||||
|
structures.append(functionMatches.next().captured().trimmed());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract templates
|
||||||
|
auto templateMatches = getTemplateRegex().globalMatch(code);
|
||||||
|
while (templateMatches.hasNext()) {
|
||||||
|
structures.append(templateMatches.next().captured().trimmed());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
structureCache.insert(cacheKey, new QStringList(structures));
|
||||||
|
|
||||||
|
return structures;
|
||||||
|
}
|
||||||
|
|
||||||
|
float EnhancedRAGSimilaritySearch::calculateJaccardSimilarity(
|
||||||
|
const QStringList &set1, const QStringList &set2)
|
||||||
|
{
|
||||||
|
if (set1.isEmpty() && set2.isEmpty()) {
|
||||||
|
return 1.0f; // Пустые множества считаем идентичными
|
||||||
|
}
|
||||||
|
if (set1.isEmpty() || set2.isEmpty()) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSet<QString> set1Unique = QSet<QString>(set1.begin(), set1.end());
|
||||||
|
QSet<QString> set2Unique = QSet<QString>(set2.begin(), set2.end());
|
||||||
|
|
||||||
|
QSet<QString> intersection = set1Unique;
|
||||||
|
intersection.intersect(set2Unique);
|
||||||
|
|
||||||
|
QSet<QString> union_set = set1Unique;
|
||||||
|
union_set.unite(set2Unique);
|
||||||
|
|
||||||
|
return static_cast<float>(intersection.size()) / union_set.size();
|
||||||
|
}
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
74
context/EnhancedRAGSimilaritySearch.hpp
Normal file
74
context/EnhancedRAGSimilaritySearch.hpp
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QCache>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||||
|
#include <emmintrin.h>
|
||||||
|
#include <xmmintrin.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "RAGData.hpp"
|
||||||
|
#include "logger/Logger.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
class EnhancedRAGSimilaritySearch
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct SimilarityScore
|
||||||
|
{
|
||||||
|
float semantic_similarity{0.0f};
|
||||||
|
float structural_similarity{0.0f};
|
||||||
|
float combined_score{0.0f};
|
||||||
|
|
||||||
|
SimilarityScore() = default;
|
||||||
|
SimilarityScore(float sem, float str, float comb)
|
||||||
|
: semantic_similarity(sem)
|
||||||
|
, structural_similarity(str)
|
||||||
|
, combined_score(comb)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
static SimilarityScore calculateSimilarity(
|
||||||
|
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static SimilarityScore calculateSimilarityInternal(
|
||||||
|
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2);
|
||||||
|
|
||||||
|
static float calculateCosineSimilarity(const RAGVector &v1, const RAGVector &v2);
|
||||||
|
|
||||||
|
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||||
|
static float calculateCosineSimilaritySSE(const RAGVector &v1, const RAGVector &v2);
|
||||||
|
static float horizontalSum(__m128 x);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static float calculateStructuralSimilarity(const QString &code1, const QString &code2);
|
||||||
|
static QStringList extractStructures(const QString &code);
|
||||||
|
static float calculateJaccardSimilarity(const QStringList &set1, const QStringList &set2);
|
||||||
|
|
||||||
|
static const QRegularExpression &getNamespaceRegex();
|
||||||
|
static const QRegularExpression &getClassRegex();
|
||||||
|
static const QRegularExpression &getFunctionRegex();
|
||||||
|
static const QRegularExpression &getTemplateRegex();
|
||||||
|
|
||||||
|
// Cache for similarity scores
|
||||||
|
static QCache<QString, SimilarityScore> &getScoreCache();
|
||||||
|
|
||||||
|
// Cache for extracted structures
|
||||||
|
static QCache<QString, QStringList> &getStructureCache();
|
||||||
|
|
||||||
|
EnhancedRAGSimilaritySearch() = delete; // Prevent instantiation
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
198
context/FileChunker.cpp
Normal file
198
context/FileChunker.cpp
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
// FileChunker.cpp
|
||||||
|
#include "FileChunker.hpp"
|
||||||
|
|
||||||
|
#include <coreplugin/idocument.h>
|
||||||
|
#include <texteditor/syntaxhighlighter.h>
|
||||||
|
#include <texteditor/textdocument.h>
|
||||||
|
#include <texteditor/texteditorconstants.h>
|
||||||
|
|
||||||
|
#include <QFutureWatcher>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
FileChunker::FileChunker(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{}
|
||||||
|
|
||||||
|
FileChunker::FileChunker(const ChunkingConfig &config, QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_config(config)
|
||||||
|
{}
|
||||||
|
|
||||||
|
QFuture<QList<FileChunk>> FileChunker::chunkFiles(const QStringList &filePaths)
|
||||||
|
{
|
||||||
|
qDebug() << "\nStarting chunking process for" << filePaths.size() << "files";
|
||||||
|
qDebug() << "Configuration:"
|
||||||
|
<< "\n Max lines per chunk:" << m_config.maxLinesPerChunk
|
||||||
|
<< "\n Overlap lines:" << m_config.overlapLines
|
||||||
|
<< "\n Skip empty lines:" << m_config.skipEmptyLines
|
||||||
|
<< "\n Preserve functions:" << m_config.preserveFunctions
|
||||||
|
<< "\n Preserve classes:" << m_config.preserveClasses
|
||||||
|
<< "\n Batch size:" << m_config.batchSize;
|
||||||
|
|
||||||
|
auto promise = std::make_shared<QPromise<QList<FileChunk>>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
if (filePaths.isEmpty()) {
|
||||||
|
qDebug() << "No files to process";
|
||||||
|
promise->addResult({});
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
processNextBatch(promise, filePaths, 0);
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileChunker::processNextBatch(
|
||||||
|
std::shared_ptr<QPromise<QList<FileChunk>>> promise, const QStringList &files, int startIndex)
|
||||||
|
{
|
||||||
|
if (startIndex >= files.size()) {
|
||||||
|
emit chunkingComplete();
|
||||||
|
promise->finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int endIndex = qMin(startIndex + m_config.batchSize, files.size());
|
||||||
|
QList<FileChunk> batchChunks;
|
||||||
|
|
||||||
|
for (int i = startIndex; i < endIndex; ++i) {
|
||||||
|
try {
|
||||||
|
auto chunks = processFile(files[i]);
|
||||||
|
batchChunks.append(chunks);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
emit error(QString("Error processing file %1: %2").arg(files[i], e.what()));
|
||||||
|
}
|
||||||
|
emit progressUpdated(i + 1, files.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
promise->addResult(batchChunks);
|
||||||
|
|
||||||
|
// Планируем обработку следующего батча
|
||||||
|
QTimer::singleShot(0, this, [this, promise, files, endIndex]() {
|
||||||
|
processNextBatch(promise, files, endIndex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<FileChunk> FileChunker::processFile(const QString &filePath)
|
||||||
|
{
|
||||||
|
qDebug() << "\nProcessing file:" << filePath;
|
||||||
|
|
||||||
|
auto document = new TextEditor::TextDocument;
|
||||||
|
auto filePathObj = Utils::FilePath::fromString(filePath);
|
||||||
|
auto result = document->open(&m_error, filePathObj, filePathObj);
|
||||||
|
if (result != Core::IDocument::OpenResult::Success) {
|
||||||
|
qDebug() << "Failed to open document:" << filePath << "-" << m_error;
|
||||||
|
emit error(QString("Failed to open document: %1 - %2").arg(filePath, m_error));
|
||||||
|
delete document;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Document opened successfully. Line count:" << document->document()->blockCount();
|
||||||
|
|
||||||
|
auto chunks = createChunksForDocument(document);
|
||||||
|
qDebug() << "Created" << chunks.size() << "chunks for file";
|
||||||
|
|
||||||
|
delete document;
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<FileChunk> FileChunker::createChunksForDocument(TextEditor::TextDocument *document)
|
||||||
|
{
|
||||||
|
QList<FileChunk> chunks;
|
||||||
|
QString filePath = document->filePath().toString();
|
||||||
|
qDebug() << "\nCreating chunks for document:" << filePath << "\nConfiguration:"
|
||||||
|
<< "\n Max lines per chunk:" << m_config.maxLinesPerChunk
|
||||||
|
<< "\n Min lines per chunk:" << m_config.minLinesPerChunk
|
||||||
|
<< "\n Overlap lines:" << m_config.overlapLines;
|
||||||
|
// Если файл меньше минимального размера чанка, создаем один чанк
|
||||||
|
if (document->document()->blockCount() <= m_config.minLinesPerChunk) {
|
||||||
|
FileChunk chunk;
|
||||||
|
chunk.filePath = filePath;
|
||||||
|
chunk.startLine = 0;
|
||||||
|
chunk.endLine = document->document()->blockCount() - 1;
|
||||||
|
chunk.createdAt = QDateTime::currentDateTime();
|
||||||
|
chunk.updatedAt = chunk.createdAt;
|
||||||
|
|
||||||
|
QString content;
|
||||||
|
QTextBlock block = document->document()->firstBlock();
|
||||||
|
while (block.isValid()) {
|
||||||
|
content += block.text() + "\n";
|
||||||
|
block = block.next();
|
||||||
|
}
|
||||||
|
chunk.content = content;
|
||||||
|
|
||||||
|
qDebug() << "File is smaller than minimum chunk size. Creating single chunk:"
|
||||||
|
<< "\n Lines:" << chunk.lineCount() << "\n Content size:" << chunk.content.size()
|
||||||
|
<< "bytes";
|
||||||
|
|
||||||
|
chunks.append(chunk);
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Для больших файлов создаем чанки фиксированного размера с перекрытием
|
||||||
|
int currentStartLine = 0;
|
||||||
|
int lineCount = 0;
|
||||||
|
QString content;
|
||||||
|
QTextBlock block = document->document()->firstBlock();
|
||||||
|
|
||||||
|
while (block.isValid()) {
|
||||||
|
content += block.text() + "\n";
|
||||||
|
lineCount++;
|
||||||
|
|
||||||
|
// Если достигли размера чанка или это последний блок
|
||||||
|
if (lineCount >= m_config.maxLinesPerChunk || !block.next().isValid()) {
|
||||||
|
FileChunk chunk;
|
||||||
|
chunk.filePath = filePath;
|
||||||
|
chunk.startLine = currentStartLine;
|
||||||
|
chunk.endLine = currentStartLine + lineCount - 1;
|
||||||
|
chunk.content = content;
|
||||||
|
chunk.createdAt = QDateTime::currentDateTime();
|
||||||
|
chunk.updatedAt = chunk.createdAt;
|
||||||
|
|
||||||
|
qDebug() << "Creating chunk:"
|
||||||
|
<< "\n Start line:" << chunk.startLine << "\n End line:" << chunk.endLine
|
||||||
|
<< "\n Lines:" << chunk.lineCount()
|
||||||
|
<< "\n Content size:" << chunk.content.size() << "bytes";
|
||||||
|
|
||||||
|
chunks.append(chunk);
|
||||||
|
|
||||||
|
// Начинаем новый чанк с учетом перекрытия
|
||||||
|
if (block.next().isValid()) {
|
||||||
|
// Отступаем назад на размер перекрытия
|
||||||
|
int overlapLines = qMin(m_config.overlapLines, lineCount);
|
||||||
|
currentStartLine = chunk.endLine - overlapLines + 1;
|
||||||
|
|
||||||
|
// Сбрасываем контент, но добавляем перекрывающиеся строки
|
||||||
|
content.clear();
|
||||||
|
QTextBlock overlapBlock = document->document()->findBlockByLineNumber(
|
||||||
|
currentStartLine);
|
||||||
|
while (overlapBlock.isValid() && overlapBlock.blockNumber() <= chunk.endLine) {
|
||||||
|
content += overlapBlock.text() + "\n";
|
||||||
|
overlapBlock = overlapBlock.next();
|
||||||
|
}
|
||||||
|
lineCount = overlapLines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
block = block.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Finished creating chunks for file:" << filePath
|
||||||
|
<< "\nTotal chunks:" << chunks.size();
|
||||||
|
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileChunker::setConfig(const ChunkingConfig &config)
|
||||||
|
{
|
||||||
|
m_config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileChunker::ChunkingConfig FileChunker::config() const
|
||||||
|
{
|
||||||
|
return m_config;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
68
context/FileChunker.hpp
Normal file
68
context/FileChunker.hpp
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// FileChunker.hpp
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <texteditor/textdocument.h>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QFuture>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
struct FileChunk
|
||||||
|
{
|
||||||
|
QString filePath; // Path to the source file
|
||||||
|
int startLine; // Starting line of the chunk
|
||||||
|
int endLine; // Ending line of the chunk
|
||||||
|
QDateTime createdAt; // When the chunk was created
|
||||||
|
QDateTime updatedAt; // When the chunk was last updated
|
||||||
|
QString content; // Content of the chunk
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
int lineCount() const { return endLine - startLine + 1; }
|
||||||
|
bool isValid() const { return !filePath.isEmpty() && startLine >= 0 && endLine >= startLine; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileChunker : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct ChunkingConfig
|
||||||
|
{
|
||||||
|
int maxLinesPerChunk = 80;
|
||||||
|
int minLinesPerChunk = 40;
|
||||||
|
int overlapLines = 20;
|
||||||
|
bool skipEmptyLines = true;
|
||||||
|
bool preserveFunctions = true;
|
||||||
|
bool preserveClasses = true;
|
||||||
|
int batchSize = 10;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit FileChunker(QObject *parent = nullptr);
|
||||||
|
explicit FileChunker(const ChunkingConfig &config, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
// Main chunking method
|
||||||
|
QFuture<QList<FileChunk>> chunkFiles(const QStringList &filePaths);
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
void setConfig(const ChunkingConfig &config);
|
||||||
|
ChunkingConfig config() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void progressUpdated(int processedFiles, int totalFiles);
|
||||||
|
void chunkingComplete();
|
||||||
|
void error(const QString &errorMessage);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<FileChunk> processFile(const QString &filePath);
|
||||||
|
QList<FileChunk> createChunksForDocument(TextEditor::TextDocument *document);
|
||||||
|
void processNextBatch(
|
||||||
|
std::shared_ptr<QPromise<QList<FileChunk>>> promise,
|
||||||
|
const QStringList &files,
|
||||||
|
int startIndex);
|
||||||
|
|
||||||
|
ChunkingConfig m_config;
|
||||||
|
QString m_error;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
70
context/ProgrammingLanguage.cpp
Normal file
70
context/ProgrammingLanguage.cpp
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* 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 "ProgrammingLanguage.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
ProgrammingLanguage ProgrammingLanguageUtils::fromMimeType(const QString &mimeType)
|
||||||
|
{
|
||||||
|
if (mimeType == "text/x-qml" || mimeType == "application/javascript"
|
||||||
|
|| mimeType == "text/javascript" || mimeType == "text/x-javascript") {
|
||||||
|
return ProgrammingLanguage::QML;
|
||||||
|
}
|
||||||
|
if (mimeType == "text/x-c++src" || mimeType == "text/x-c++hdr" || mimeType == "text/x-csrc"
|
||||||
|
|| mimeType == "text/x-chdr") {
|
||||||
|
return ProgrammingLanguage::Cpp;
|
||||||
|
}
|
||||||
|
if (mimeType == "text/x-python") {
|
||||||
|
return ProgrammingLanguage::Python;
|
||||||
|
}
|
||||||
|
return ProgrammingLanguage::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProgrammingLanguageUtils::toString(ProgrammingLanguage language)
|
||||||
|
{
|
||||||
|
switch (language) {
|
||||||
|
case ProgrammingLanguage::Cpp:
|
||||||
|
return "c/c++";
|
||||||
|
case ProgrammingLanguage::QML:
|
||||||
|
return "qml";
|
||||||
|
case ProgrammingLanguage::Python:
|
||||||
|
return "python";
|
||||||
|
case ProgrammingLanguage::Unknown:
|
||||||
|
default:
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgrammingLanguage ProgrammingLanguageUtils::fromString(const QString &str)
|
||||||
|
{
|
||||||
|
QString lower = str.toLower();
|
||||||
|
if (lower == "c/c++") {
|
||||||
|
return ProgrammingLanguage::Cpp;
|
||||||
|
}
|
||||||
|
if (lower == "qml") {
|
||||||
|
return ProgrammingLanguage::QML;
|
||||||
|
}
|
||||||
|
if (lower == "python") {
|
||||||
|
return ProgrammingLanguage::Python;
|
||||||
|
}
|
||||||
|
return ProgrammingLanguage::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
43
context/ProgrammingLanguage.hpp
Normal file
43
context/ProgrammingLanguage.hpp
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
enum class ProgrammingLanguage {
|
||||||
|
QML,
|
||||||
|
Cpp,
|
||||||
|
Python,
|
||||||
|
Unknown,
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace ProgrammingLanguageUtils {
|
||||||
|
|
||||||
|
ProgrammingLanguage fromMimeType(const QString &mimeType);
|
||||||
|
|
||||||
|
QString toString(ProgrammingLanguage language);
|
||||||
|
|
||||||
|
ProgrammingLanguage fromString(const QString &str);
|
||||||
|
|
||||||
|
} // namespace ProgrammingLanguageUtils
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
7
context/RAGData.hpp
Normal file
7
context/RAGData.hpp
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
using RAGVector = std::vector<float>;
|
||||||
|
}
|
||||||
443
context/RAGManager.cpp
Normal file
443
context/RAGManager.cpp
Normal file
@@ -0,0 +1,443 @@
|
|||||||
|
/*
|
||||||
|
* 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 "RAGManager.hpp"
|
||||||
|
#include "EnhancedRAGSimilaritySearch.hpp"
|
||||||
|
#include "RAGPreprocessor.hpp"
|
||||||
|
#include "RAGSimilaritySearch.hpp"
|
||||||
|
#include "logger/Logger.hpp"
|
||||||
|
|
||||||
|
#include <coreplugin/icore.h>
|
||||||
|
#include <projectexplorer/project.h>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
RAGManager &RAGManager::instance()
|
||||||
|
{
|
||||||
|
static RAGManager manager;
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
RAGManager::RAGManager(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_vectorizer(std::make_unique<RAGVectorizer>())
|
||||||
|
{}
|
||||||
|
|
||||||
|
RAGManager::~RAGManager() {}
|
||||||
|
|
||||||
|
QString RAGManager::getStoragePath(ProjectExplorer::Project *project) const
|
||||||
|
{
|
||||||
|
return QString("%1/qodeassist/%2/rag/vectors.db")
|
||||||
|
.arg(Core::ICore::userResourcePath().toString(), project->displayName());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<QString> RAGManager::loadFileContent(const QString &filePath)
|
||||||
|
{
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
qDebug() << "ERROR: Failed to open file for reading:" << filePath
|
||||||
|
<< "Error:" << file.errorString();
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo fileInfo(filePath);
|
||||||
|
qDebug() << "Loading content from file:" << fileInfo.fileName() << "Size:" << fileInfo.size()
|
||||||
|
<< "bytes";
|
||||||
|
|
||||||
|
QString content = QString::fromUtf8(file.readAll());
|
||||||
|
if (content.isEmpty()) {
|
||||||
|
qDebug() << "WARNING: Empty content read from file:" << filePath;
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAGManager::ensureStorageForProject(ProjectExplorer::Project *project) const
|
||||||
|
{
|
||||||
|
qDebug() << "Ensuring storage for project:" << project->displayName();
|
||||||
|
|
||||||
|
if (m_currentProject == project && m_currentStorage) {
|
||||||
|
qDebug() << "Using existing storage";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Creating new storage";
|
||||||
|
m_currentStorage.reset();
|
||||||
|
m_currentProject = project;
|
||||||
|
|
||||||
|
if (project) {
|
||||||
|
QString storagePath = getStoragePath(project);
|
||||||
|
qDebug() << "Storage path:" << storagePath;
|
||||||
|
|
||||||
|
StorageOptions options;
|
||||||
|
m_currentStorage = std::make_unique<RAGStorage>(storagePath, options);
|
||||||
|
|
||||||
|
qDebug() << "Initializing storage...";
|
||||||
|
if (!m_currentStorage->init()) {
|
||||||
|
qDebug() << "Failed to initialize storage";
|
||||||
|
m_currentStorage.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qDebug() << "Storage initialized successfully";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<void> RAGManager::processProjectFiles(
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
const QStringList &filePaths,
|
||||||
|
const FileChunker::ChunkingConfig &config)
|
||||||
|
{
|
||||||
|
qDebug() << "\nStarting batch processing of" << filePaths.size()
|
||||||
|
<< "files for project:" << project->displayName();
|
||||||
|
|
||||||
|
auto promise = std::make_shared<QPromise<void>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
qDebug() << "Initializing storage...";
|
||||||
|
ensureStorageForProject(project);
|
||||||
|
|
||||||
|
if (!m_currentStorage) {
|
||||||
|
qDebug() << "Failed to initialize storage for project:" << project->displayName();
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
qDebug() << "Storage initialized successfully";
|
||||||
|
|
||||||
|
qDebug() << "Checking files for processing...";
|
||||||
|
QSet<QString> uniqueFiles;
|
||||||
|
for (const QString &filePath : filePaths) {
|
||||||
|
qDebug() << "Checking file:" << filePath;
|
||||||
|
if (isFileStorageOutdated(project, filePath)) {
|
||||||
|
qDebug() << "File needs processing:" << filePath;
|
||||||
|
uniqueFiles.insert(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList filesToProcess = uniqueFiles.values();
|
||||||
|
|
||||||
|
if (filesToProcess.isEmpty()) {
|
||||||
|
qDebug() << "No files need processing";
|
||||||
|
emit vectorizationFinished();
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Starting to process" << filesToProcess.size() << "files";
|
||||||
|
const int batchSize = 10;
|
||||||
|
processNextFileBatch(promise, project, filesToProcess, config, 0, batchSize);
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAGManager::processNextFileBatch(
|
||||||
|
std::shared_ptr<QPromise<void>> promise,
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
const QStringList &files,
|
||||||
|
const FileChunker::ChunkingConfig &config,
|
||||||
|
int startIndex,
|
||||||
|
int batchSize)
|
||||||
|
{
|
||||||
|
if (startIndex >= files.size()) {
|
||||||
|
qDebug() << "All batches processed successfully";
|
||||||
|
emit vectorizationFinished();
|
||||||
|
promise->finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int endIndex = qMin(startIndex + batchSize, files.size());
|
||||||
|
auto currentBatch = files.mid(startIndex, endIndex - startIndex);
|
||||||
|
|
||||||
|
qDebug() << "\nProcessing batch" << (startIndex / batchSize + 1) << "(" << currentBatch.size()
|
||||||
|
<< "files)"
|
||||||
|
<< "\nProgress:" << startIndex << "to" << endIndex << "of" << files.size();
|
||||||
|
|
||||||
|
for (const QString &filePath : currentBatch) {
|
||||||
|
qDebug() << "Starting processing file:" << filePath;
|
||||||
|
auto future = processFileWithChunks(project, filePath, config);
|
||||||
|
auto watcher = new QFutureWatcher<bool>;
|
||||||
|
watcher->setFuture(future);
|
||||||
|
|
||||||
|
connect(
|
||||||
|
watcher,
|
||||||
|
&QFutureWatcher<bool>::finished,
|
||||||
|
this,
|
||||||
|
[this,
|
||||||
|
watcher,
|
||||||
|
promise,
|
||||||
|
project,
|
||||||
|
files,
|
||||||
|
startIndex,
|
||||||
|
endIndex,
|
||||||
|
batchSize,
|
||||||
|
config,
|
||||||
|
filePath]() {
|
||||||
|
bool success = watcher->result();
|
||||||
|
qDebug() << "File processed:" << filePath << "success:" << success;
|
||||||
|
|
||||||
|
bool isLastFileInBatch = (filePath == files[endIndex - 1]);
|
||||||
|
if (isLastFileInBatch) {
|
||||||
|
qDebug() << "Batch completed, moving to next batch";
|
||||||
|
emit vectorizationProgress(endIndex, files.size());
|
||||||
|
processNextFileBatch(promise, project, files, config, endIndex, batchSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
watcher->deleteLater();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<bool> RAGManager::processFileWithChunks(
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
const QString &filePath,
|
||||||
|
const FileChunker::ChunkingConfig &config)
|
||||||
|
{
|
||||||
|
auto promise = std::make_shared<QPromise<bool>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
ensureStorageForProject(project);
|
||||||
|
if (!m_currentStorage) {
|
||||||
|
qDebug() << "Storage not initialized for file:" << filePath;
|
||||||
|
promise->addResult(false);
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto fileContent = loadFileContent(filePath);
|
||||||
|
if (!fileContent) {
|
||||||
|
qDebug() << "Failed to load content for file:" << filePath;
|
||||||
|
promise->addResult(false);
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Creating chunks for file:" << filePath;
|
||||||
|
auto chunksFuture = m_chunker.chunkFiles({filePath});
|
||||||
|
auto chunks = chunksFuture.result();
|
||||||
|
|
||||||
|
if (chunks.isEmpty()) {
|
||||||
|
qDebug() << "No chunks created for file:" << filePath;
|
||||||
|
promise->addResult(false);
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Created" << chunks.size() << "chunks for file:" << filePath;
|
||||||
|
|
||||||
|
// Преобразуем FileChunk в FileChunkData
|
||||||
|
QList<FileChunkData> chunkData;
|
||||||
|
for (const auto &chunk : chunks) {
|
||||||
|
FileChunkData data;
|
||||||
|
data.filePath = chunk.filePath;
|
||||||
|
data.startLine = chunk.startLine;
|
||||||
|
data.endLine = chunk.endLine;
|
||||||
|
data.content = chunk.content;
|
||||||
|
chunkData.append(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Deleting old chunks for file:" << filePath;
|
||||||
|
m_currentStorage->deleteChunksForFile(filePath);
|
||||||
|
|
||||||
|
auto vectorizeFuture = vectorizeAndStoreChunks(filePath, chunkData);
|
||||||
|
auto watcher = new QFutureWatcher<void>;
|
||||||
|
watcher->setFuture(vectorizeFuture);
|
||||||
|
|
||||||
|
connect(watcher, &QFutureWatcher<void>::finished, this, [promise, watcher, filePath]() {
|
||||||
|
qDebug() << "Completed processing file:" << filePath;
|
||||||
|
promise->addResult(true);
|
||||||
|
promise->finish();
|
||||||
|
watcher->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<void> RAGManager::vectorizeAndStoreChunks(
|
||||||
|
const QString &filePath, const QList<FileChunkData> &chunks)
|
||||||
|
{
|
||||||
|
qDebug() << "Vectorizing and storing" << chunks.size() << "chunks for file:" << filePath;
|
||||||
|
|
||||||
|
auto promise = std::make_shared<QPromise<void>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
// Обрабатываем чанки последовательно
|
||||||
|
processNextChunk(promise, chunks, 0);
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAGManager::processNextChunk(
|
||||||
|
std::shared_ptr<QPromise<void>> promise, const QList<FileChunkData> &chunks, int currentIndex)
|
||||||
|
{
|
||||||
|
if (currentIndex >= chunks.size()) {
|
||||||
|
promise->finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &chunk = chunks[currentIndex];
|
||||||
|
QString processedContent = RAGPreprocessor::preprocessCode(chunk.content);
|
||||||
|
qDebug() << "Processing chunk" << currentIndex + 1 << "of" << chunks.size();
|
||||||
|
|
||||||
|
auto vectorFuture = m_vectorizer->vectorizeText(processedContent);
|
||||||
|
auto watcher = new QFutureWatcher<RAGVector>;
|
||||||
|
watcher->setFuture(vectorFuture);
|
||||||
|
|
||||||
|
connect(
|
||||||
|
watcher,
|
||||||
|
&QFutureWatcher<RAGVector>::finished,
|
||||||
|
this,
|
||||||
|
[this, watcher, promise, chunks, currentIndex, chunk]() {
|
||||||
|
auto vector = watcher->result();
|
||||||
|
|
||||||
|
if (!vector.empty()) {
|
||||||
|
qDebug() << "Storing vector and chunk for file:" << chunk.filePath;
|
||||||
|
bool vectorStored = m_currentStorage->storeVector(chunk.filePath, vector);
|
||||||
|
bool chunkStored = m_currentStorage->storeChunk(chunk);
|
||||||
|
qDebug() << "Storage results - Vector:" << vectorStored << "Chunk:" << chunkStored;
|
||||||
|
} else {
|
||||||
|
qDebug() << "Failed to vectorize chunk content";
|
||||||
|
}
|
||||||
|
|
||||||
|
processNextChunk(promise, chunks, currentIndex + 1);
|
||||||
|
|
||||||
|
watcher->deleteLater();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<QList<RAGManager::ChunkSearchResult>> RAGManager::findRelevantChunks(
|
||||||
|
const QString &query, ProjectExplorer::Project *project, int topK)
|
||||||
|
{
|
||||||
|
auto promise = std::make_shared<QPromise<QList<ChunkSearchResult>>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
ensureStorageForProject(project);
|
||||||
|
if (!m_currentStorage) {
|
||||||
|
qDebug() << "Storage not initialized for project:" << project->displayName();
|
||||||
|
promise->addResult({});
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString processedQuery = RAGPreprocessor::preprocessCode(query);
|
||||||
|
|
||||||
|
auto vectorFuture = m_vectorizer->vectorizeText(processedQuery);
|
||||||
|
vectorFuture.then([this, promise, project, processedQuery, topK](const RAGVector &queryVector) {
|
||||||
|
if (queryVector.empty()) {
|
||||||
|
qDebug() << "Failed to vectorize query";
|
||||||
|
promise->addResult({});
|
||||||
|
promise->finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto files = m_currentStorage->getFilesWithChunks();
|
||||||
|
QList<FileChunkData> allChunks;
|
||||||
|
|
||||||
|
for (const auto &filePath : files) {
|
||||||
|
auto fileChunks = m_currentStorage->getChunksForFile(filePath);
|
||||||
|
allChunks.append(fileChunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto results = rankChunks(queryVector, processedQuery, allChunks);
|
||||||
|
|
||||||
|
if (results.size() > topK) {
|
||||||
|
results = results.mid(0, topK);
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Found" << results.size() << "relevant chunks";
|
||||||
|
promise->addResult(results);
|
||||||
|
promise->finish();
|
||||||
|
|
||||||
|
closeStorage();
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<RAGManager::ChunkSearchResult> RAGManager::rankChunks(
|
||||||
|
const RAGVector &queryVector, const QString &queryText, const QList<FileChunkData> &chunks)
|
||||||
|
{
|
||||||
|
QList<ChunkSearchResult> results;
|
||||||
|
results.reserve(chunks.size());
|
||||||
|
|
||||||
|
for (const auto &chunk : chunks) {
|
||||||
|
auto chunkVector = m_currentStorage->getVector(chunk.filePath);
|
||||||
|
if (!chunkVector.has_value()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString processedChunk = RAGPreprocessor::preprocessCode(chunk.content);
|
||||||
|
|
||||||
|
auto similarity = EnhancedRAGSimilaritySearch::calculateSimilarity(
|
||||||
|
queryVector, chunkVector.value(), queryText, processedChunk);
|
||||||
|
|
||||||
|
results.append(ChunkSearchResult{
|
||||||
|
chunk.filePath,
|
||||||
|
chunk.startLine,
|
||||||
|
chunk.endLine,
|
||||||
|
chunk.content,
|
||||||
|
similarity.semantic_similarity,
|
||||||
|
similarity.structural_similarity,
|
||||||
|
similarity.combined_score});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(results.begin(), results.end());
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList RAGManager::getStoredFiles(ProjectExplorer::Project *project) const
|
||||||
|
{
|
||||||
|
ensureStorageForProject(project);
|
||||||
|
if (!m_currentStorage) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return m_currentStorage->getAllFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RAGManager::isFileStorageOutdated(
|
||||||
|
ProjectExplorer::Project *project, const QString &filePath) const
|
||||||
|
{
|
||||||
|
ensureStorageForProject(project);
|
||||||
|
if (!m_currentStorage) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return m_currentStorage->needsUpdate(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<RAGVector> RAGManager::loadVectorFromStorage(
|
||||||
|
ProjectExplorer::Project *project, const QString &filePath)
|
||||||
|
{
|
||||||
|
ensureStorageForProject(project);
|
||||||
|
if (!m_currentStorage) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return m_currentStorage->getVector(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAGManager::closeStorage()
|
||||||
|
{
|
||||||
|
qDebug() << "Closing storage...";
|
||||||
|
if (m_currentStorage) {
|
||||||
|
m_currentStorage.reset();
|
||||||
|
m_currentProject = nullptr;
|
||||||
|
qDebug() << "Storage closed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
119
context/RAGManager.hpp
Normal file
119
context/RAGManager.hpp
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* 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 <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <QFuture>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "FileChunker.hpp"
|
||||||
|
#include "RAGData.hpp"
|
||||||
|
#include "RAGStorage.hpp"
|
||||||
|
#include "RAGVectorizer.hpp"
|
||||||
|
|
||||||
|
namespace ProjectExplorer {
|
||||||
|
class Project;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
class RAGManager : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct ChunkSearchResult
|
||||||
|
{
|
||||||
|
QString filePath;
|
||||||
|
int startLine;
|
||||||
|
int endLine;
|
||||||
|
QString content;
|
||||||
|
float semanticScore;
|
||||||
|
float structuralScore;
|
||||||
|
float combinedScore;
|
||||||
|
|
||||||
|
bool operator<(const ChunkSearchResult &other) const
|
||||||
|
{
|
||||||
|
return combinedScore > other.combinedScore;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static RAGManager &instance();
|
||||||
|
|
||||||
|
QFuture<void> processProjectFiles(
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
const QStringList &filePaths,
|
||||||
|
const FileChunker::ChunkingConfig &config = FileChunker::ChunkingConfig());
|
||||||
|
|
||||||
|
QFuture<QList<ChunkSearchResult>> findRelevantChunks(
|
||||||
|
const QString &query, ProjectExplorer::Project *project, int topK = 5);
|
||||||
|
|
||||||
|
QStringList getStoredFiles(ProjectExplorer::Project *project) const;
|
||||||
|
bool isFileStorageOutdated(ProjectExplorer::Project *project, const QString &filePath) const;
|
||||||
|
|
||||||
|
void processNextChunk(
|
||||||
|
std::shared_ptr<QPromise<void>> promise,
|
||||||
|
const QList<FileChunkData> &chunks,
|
||||||
|
int currentIndex);
|
||||||
|
void closeStorage();
|
||||||
|
signals:
|
||||||
|
void vectorizationProgress(int processed, int total);
|
||||||
|
void vectorizationFinished();
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit RAGManager(QObject *parent = nullptr);
|
||||||
|
~RAGManager();
|
||||||
|
RAGManager(const RAGManager &) = delete;
|
||||||
|
RAGManager &operator=(const RAGManager &) = delete;
|
||||||
|
|
||||||
|
QString getStoragePath(ProjectExplorer::Project *project) const;
|
||||||
|
void ensureStorageForProject(ProjectExplorer::Project *project) const;
|
||||||
|
std::optional<QString> loadFileContent(const QString &filePath);
|
||||||
|
std::optional<RAGVector> loadVectorFromStorage(
|
||||||
|
ProjectExplorer::Project *project, const QString &filePath);
|
||||||
|
|
||||||
|
void processNextFileBatch(
|
||||||
|
std::shared_ptr<QPromise<void>> promise,
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
const QStringList &files,
|
||||||
|
const FileChunker::ChunkingConfig &config,
|
||||||
|
int startIndex,
|
||||||
|
int batchSize);
|
||||||
|
|
||||||
|
QFuture<bool> processFileWithChunks(
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
const QString &filePath,
|
||||||
|
const FileChunker::ChunkingConfig &config);
|
||||||
|
|
||||||
|
QFuture<void> vectorizeAndStoreChunks(
|
||||||
|
const QString &filePath, const QList<FileChunkData> &chunks);
|
||||||
|
|
||||||
|
QList<ChunkSearchResult> rankChunks(
|
||||||
|
const RAGVector &queryVector, const QString &queryText, const QList<FileChunkData> &chunks);
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable std::unique_ptr<RAGVectorizer> m_vectorizer;
|
||||||
|
mutable std::unique_ptr<RAGStorage> m_currentStorage;
|
||||||
|
mutable ProjectExplorer::Project *m_currentProject{nullptr};
|
||||||
|
FileChunker m_chunker;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
2
context/RAGPreprocessor.cpp
Normal file
2
context/RAGPreprocessor.cpp
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#include "RAGPreprocessor.hpp"
|
||||||
|
|
||||||
64
context/RAGPreprocessor.hpp
Normal file
64
context/RAGPreprocessor.hpp
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "Logger.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
class RAGPreprocessor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const QRegularExpression &getLicenseRegex()
|
||||||
|
{
|
||||||
|
static const QRegularExpression regex(
|
||||||
|
R"((/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)|//[^\n]*(?:\n|$))",
|
||||||
|
QRegularExpression::MultilineOption);
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const QRegularExpression &getClassRegex()
|
||||||
|
{
|
||||||
|
static const QRegularExpression regex(
|
||||||
|
R"((?:template\s*<[^>]*>\s*)?(?:class|struct)\s+(\w+)\s*(?:final\s*)?(?::\s*(?:public|protected|private)\s+\w+(?:\s*,\s*(?:public|protected|private)\s+\w+)*\s*)?{)");
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString preprocessCode(const QString &code)
|
||||||
|
{
|
||||||
|
if (code.isEmpty()) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
QStringList lines = code.split('\n', Qt::SkipEmptyParts);
|
||||||
|
return processLines(lines);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
LOG_MESSAGE(QString("Error preprocessing code: %1").arg(e.what()));
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static QString processLines(const QStringList &lines)
|
||||||
|
{
|
||||||
|
const int estimatedAvgLength = 80;
|
||||||
|
QString result;
|
||||||
|
result.reserve(lines.size() * estimatedAvgLength);
|
||||||
|
|
||||||
|
for (const QString &line : lines) {
|
||||||
|
const QString trimmed = line.trimmed();
|
||||||
|
if (!trimmed.isEmpty()) {
|
||||||
|
result += trimmed;
|
||||||
|
result += QLatin1Char('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.endsWith('\n')) {
|
||||||
|
result.chop(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
67
context/RAGSimilaritySearch.cpp
Normal file
67
context/RAGSimilaritySearch.cpp
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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 "RAGSimilaritySearch.hpp"
|
||||||
|
#include "logger/Logger.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
float RAGSimilaritySearch::l2Distance(const RAGVector &v1, const RAGVector &v2)
|
||||||
|
{
|
||||||
|
if (v1.size() != v2.size()) {
|
||||||
|
LOG_MESSAGE(QString("Vector size mismatch: %1 vs %2").arg(v1.size()).arg(v2.size()));
|
||||||
|
return std::numeric_limits<float>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
float sum = 0.0f;
|
||||||
|
for (size_t i = 0; i < v1.size(); ++i) {
|
||||||
|
float diff = v1[i] - v2[i];
|
||||||
|
sum += diff * diff;
|
||||||
|
}
|
||||||
|
return std::sqrt(sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
float RAGSimilaritySearch::cosineSimilarity(const RAGVector &v1, const RAGVector &v2)
|
||||||
|
{
|
||||||
|
if (v1.size() != v2.size()) {
|
||||||
|
LOG_MESSAGE(QString("Vector size mismatch: %1 vs %2").arg(v1.size()).arg(v2.size()));
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float dotProduct = 0.0f;
|
||||||
|
float norm1 = 0.0f;
|
||||||
|
float norm2 = 0.0f;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < v1.size(); ++i) {
|
||||||
|
dotProduct += v1[i] * v2[i];
|
||||||
|
norm1 += v1[i] * v1[i];
|
||||||
|
norm2 += v2[i] * v2[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
norm1 = std::sqrt(norm1);
|
||||||
|
norm2 = std::sqrt(norm2);
|
||||||
|
|
||||||
|
if (norm1 == 0.0f || norm2 == 0.0f)
|
||||||
|
return 0.0f;
|
||||||
|
return dotProduct / (norm1 * norm2);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
37
context/RAGSimilaritySearch.hpp
Normal file
37
context/RAGSimilaritySearch.hpp
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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 "RAGData.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
class RAGSimilaritySearch
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static float l2Distance(const RAGVector &v1, const RAGVector &v2);
|
||||||
|
|
||||||
|
static float cosineSimilarity(const RAGVector &v1, const RAGVector &v2);
|
||||||
|
|
||||||
|
private:
|
||||||
|
RAGSimilaritySearch() = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
1047
context/RAGStorage.cpp
Normal file
1047
context/RAGStorage.cpp
Normal file
File diff suppressed because it is too large
Load Diff
174
context/RAGStorage.hpp
Normal file
174
context/RAGStorage.hpp
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// RAGStorage.hpp
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QSqlDatabase>
|
||||||
|
#include <QString>
|
||||||
|
#include <qsqlquery.h>
|
||||||
|
|
||||||
|
#include <RAGData.hpp>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
struct FileChunkData
|
||||||
|
{
|
||||||
|
QString filePath;
|
||||||
|
int startLine;
|
||||||
|
int endLine;
|
||||||
|
QString content;
|
||||||
|
QDateTime createdAt;
|
||||||
|
QDateTime updatedAt;
|
||||||
|
|
||||||
|
bool isValid() const
|
||||||
|
{
|
||||||
|
return !filePath.isEmpty() && startLine >= 0 && endLine >= startLine && !content.isEmpty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StorageOptions
|
||||||
|
{
|
||||||
|
int maxChunkSize = 1024 * 1024;
|
||||||
|
int maxVectorSize = 1024;
|
||||||
|
bool useCompression = false;
|
||||||
|
bool enableLogging = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StorageStatistics
|
||||||
|
{
|
||||||
|
int totalChunks;
|
||||||
|
int totalVectors;
|
||||||
|
int totalFiles;
|
||||||
|
qint64 totalSize;
|
||||||
|
QDateTime lastUpdate;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RAGStorage : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr int CURRENT_VERSION = 1;
|
||||||
|
|
||||||
|
enum class Status { Ok, DatabaseError, ValidationError, VersionError, ConnectionError };
|
||||||
|
|
||||||
|
struct ValidationResult
|
||||||
|
{
|
||||||
|
bool isValid;
|
||||||
|
QString errorMessage;
|
||||||
|
Status errorStatus;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Error
|
||||||
|
{
|
||||||
|
QString message;
|
||||||
|
QString sqlError;
|
||||||
|
QString query;
|
||||||
|
Status status;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit RAGStorage(
|
||||||
|
const QString &dbPath,
|
||||||
|
const StorageOptions &options = StorageOptions(),
|
||||||
|
QObject *parent = nullptr);
|
||||||
|
~RAGStorage();
|
||||||
|
|
||||||
|
bool init();
|
||||||
|
Status status() const;
|
||||||
|
Error lastError() const;
|
||||||
|
bool isReady() const;
|
||||||
|
QString dbPath() const;
|
||||||
|
|
||||||
|
bool beginTransaction();
|
||||||
|
bool commitTransaction();
|
||||||
|
bool rollbackTransaction();
|
||||||
|
|
||||||
|
bool storeVector(const QString &filePath, const RAGVector &vector);
|
||||||
|
bool updateVector(const QString &filePath, const RAGVector &vector);
|
||||||
|
std::optional<RAGVector> getVector(const QString &filePath);
|
||||||
|
bool needsUpdate(const QString &filePath);
|
||||||
|
QStringList getAllFiles();
|
||||||
|
|
||||||
|
bool storeChunk(const FileChunkData &chunk);
|
||||||
|
bool storeChunks(const QList<FileChunkData> &chunks);
|
||||||
|
bool updateChunk(const FileChunkData &chunk);
|
||||||
|
bool updateChunks(const QList<FileChunkData> &chunks);
|
||||||
|
bool deleteChunksForFile(const QString &filePath);
|
||||||
|
std::optional<FileChunkData> getChunk(const QString &filePath, int startLine, int endLine);
|
||||||
|
QList<FileChunkData> getChunksForFile(const QString &filePath);
|
||||||
|
bool chunkExists(const QString &filePath, int startLine, int endLine);
|
||||||
|
|
||||||
|
int getChunkCount(const QString &filePath);
|
||||||
|
bool deleteOldChunks(const QString &filePath, const QDateTime &olderThan);
|
||||||
|
bool deleteAllChunks();
|
||||||
|
QStringList getFilesWithChunks();
|
||||||
|
bool vacuum();
|
||||||
|
bool backup(const QString &backupPath);
|
||||||
|
bool restore(const QString &backupPath);
|
||||||
|
StorageStatistics getStatistics() const;
|
||||||
|
|
||||||
|
int getStorageVersion() const;
|
||||||
|
bool isVersionCompatible() const;
|
||||||
|
|
||||||
|
bool applyMigration(int version);
|
||||||
|
signals:
|
||||||
|
void errorOccurred(const Error &error);
|
||||||
|
void operationCompleted(const QString &operation);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool createTables();
|
||||||
|
bool createIndices();
|
||||||
|
bool createVersionTable();
|
||||||
|
bool createChunksTable();
|
||||||
|
bool createVectorsTable();
|
||||||
|
bool openDatabase();
|
||||||
|
bool initializeNewStorage();
|
||||||
|
bool upgradeStorage(int fromVersion);
|
||||||
|
bool validateSchema() const;
|
||||||
|
|
||||||
|
QDateTime getFileLastModified(const QString &filePath);
|
||||||
|
RAGVector blobToVector(const QByteArray &blob);
|
||||||
|
QByteArray vectorToBlob(const RAGVector &vector);
|
||||||
|
|
||||||
|
void setError(const QString &message, Status status = Status::DatabaseError);
|
||||||
|
void clearError();
|
||||||
|
bool prepareStatements();
|
||||||
|
ValidationResult validateChunk(const FileChunkData &chunk) const;
|
||||||
|
ValidationResult validateVector(const RAGVector &vector) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSqlDatabase m_db;
|
||||||
|
QString m_dbPath;
|
||||||
|
StorageOptions m_options;
|
||||||
|
mutable QMutex m_mutex;
|
||||||
|
Error m_lastError;
|
||||||
|
Status m_status;
|
||||||
|
|
||||||
|
QSqlQuery m_insertChunkQuery;
|
||||||
|
QSqlQuery m_updateChunkQuery;
|
||||||
|
QSqlQuery m_insertVectorQuery;
|
||||||
|
QSqlQuery m_updateVectorQuery;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
116
context/RAGVectorizer.cpp
Normal file
116
context/RAGVectorizer.cpp
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* 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 "RAGVectorizer.hpp"
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
RAGVectorizer::RAGVectorizer(const QString &providerUrl,
|
||||||
|
const QString &modelName,
|
||||||
|
QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_network(new QNetworkAccessManager(this))
|
||||||
|
, m_embedProviderUrl(providerUrl)
|
||||||
|
, m_model(modelName)
|
||||||
|
{}
|
||||||
|
|
||||||
|
RAGVectorizer::~RAGVectorizer() {}
|
||||||
|
|
||||||
|
QJsonObject RAGVectorizer::prepareEmbeddingRequest(const QString &text) const
|
||||||
|
{
|
||||||
|
return QJsonObject{{"model", m_model}, {"prompt", text}};
|
||||||
|
}
|
||||||
|
|
||||||
|
RAGVector RAGVectorizer::parseEmbeddingResponse(const QByteArray &response) const
|
||||||
|
{
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(response);
|
||||||
|
if (doc.isNull()) {
|
||||||
|
qDebug() << "Failed to parse JSON response";
|
||||||
|
return RAGVector();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject obj = doc.object();
|
||||||
|
if (!obj.contains("embedding")) {
|
||||||
|
qDebug() << "Response does not contain 'embedding' field";
|
||||||
|
// qDebug() << "Response content:" << response;
|
||||||
|
return RAGVector();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray array = obj["embedding"].toArray();
|
||||||
|
if (array.isEmpty()) {
|
||||||
|
qDebug() << "Embedding array is empty";
|
||||||
|
return RAGVector();
|
||||||
|
}
|
||||||
|
|
||||||
|
RAGVector result;
|
||||||
|
result.reserve(array.size());
|
||||||
|
for (const auto &value : array) {
|
||||||
|
result.push_back(value.toDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Successfully parsed vector with size:" << result.size();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<RAGVector> RAGVectorizer::vectorizeText(const QString &text)
|
||||||
|
{
|
||||||
|
qDebug() << "Vectorizing text, length:" << text.length();
|
||||||
|
qDebug() << "Using embedding provider:" << m_embedProviderUrl;
|
||||||
|
|
||||||
|
auto promise = std::make_shared<QPromise<RAGVector>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
QNetworkRequest request(QUrl(m_embedProviderUrl + "/api/embeddings"));
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
QJsonObject requestData = prepareEmbeddingRequest(text);
|
||||||
|
QByteArray jsonData = QJsonDocument(requestData).toJson();
|
||||||
|
qDebug() << "Sending request to embeddings API:" << jsonData;
|
||||||
|
|
||||||
|
auto reply = m_network->post(request, jsonData);
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [promise, reply, this]() {
|
||||||
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
QByteArray response = reply->readAll();
|
||||||
|
// qDebug() << "Received response from embeddings API:" << response;
|
||||||
|
|
||||||
|
auto vector = parseEmbeddingResponse(response);
|
||||||
|
qDebug() << "Parsed vector size:" << vector.size();
|
||||||
|
promise->addResult(vector);
|
||||||
|
} else {
|
||||||
|
qDebug() << "Network error:" << reply->errorString();
|
||||||
|
qDebug() << "HTTP status code:"
|
||||||
|
<< reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
qDebug() << "Response:" << reply->readAll();
|
||||||
|
|
||||||
|
promise->addResult(RAGVector());
|
||||||
|
}
|
||||||
|
promise->finish();
|
||||||
|
reply->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
51
context/RAGVectorizer.hpp
Normal file
51
context/RAGVectorizer.hpp
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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 <QFuture>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include <RAGData.hpp>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
class RAGVectorizer : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit RAGVectorizer(
|
||||||
|
const QString &providerUrl = "http://localhost:11434",
|
||||||
|
const QString &modelName = "all-minilm:33m-l12-v2-fp16",
|
||||||
|
QObject *parent = nullptr);
|
||||||
|
~RAGVectorizer();
|
||||||
|
|
||||||
|
QFuture<RAGVector> vectorizeText(const QString &text);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QJsonObject prepareEmbeddingRequest(const QString &text) const;
|
||||||
|
RAGVector parseEmbeddingResponse(const QByteArray &response) const;
|
||||||
|
|
||||||
|
QNetworkAccessManager *m_network;
|
||||||
|
QString m_embedProviderUrl;
|
||||||
|
QString m_model;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::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);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -108,15 +108,22 @@ bool LMStudioProvider::handleResponse(QNetworkReply *reply, QString &accumulated
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArrayList chunks = data.split('\n');
|
bool isDone = false;
|
||||||
for (const QByteArray &chunk : chunks) {
|
QByteArrayList lines = data.split('\n');
|
||||||
if (chunk.trimmed().isEmpty() || chunk == "data: [DONE]") {
|
|
||||||
|
for (const QByteArray &line : lines) {
|
||||||
|
if (line.trimmed().isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray jsonData = chunk;
|
if (line == "data: [DONE]") {
|
||||||
if (chunk.startsWith("data: ")) {
|
isDone = true;
|
||||||
jsonData = chunk.mid(6);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray jsonData = line;
|
||||||
|
if (line.startsWith("data: ")) {
|
||||||
|
jsonData = line.mid(6);
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
@@ -128,15 +135,21 @@ bool LMStudioProvider::handleResponse(QNetworkReply *reply, QString &accumulated
|
|||||||
|
|
||||||
auto message = LLMCore::OpenAIMessage::fromJson(doc.object());
|
auto message = LLMCore::OpenAIMessage::fromJson(doc.object());
|
||||||
if (message.hasError()) {
|
if (message.hasError()) {
|
||||||
LOG_MESSAGE("Error in LMStudioProvider response: " + message.error);
|
LOG_MESSAGE("Error in OpenAI response: " + message.error);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
accumulatedResponse += message.getContent();
|
QString content = message.getContent();
|
||||||
return message.isDone();
|
if (!content.isEmpty()) {
|
||||||
|
accumulatedResponse += content;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.isDone()) {
|
||||||
|
isDone = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return isDone;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> LMStudioProvider::getInstalledModels(const QString &url)
|
QList<QString> LMStudioProvider::getInstalledModels(const QString &url)
|
||||||
|
|||||||
@@ -95,18 +95,36 @@ bool OllamaProvider::handleResponse(QNetworkReply *reply, QString &accumulatedRe
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString endpoint = reply->url().path();
|
QByteArrayList lines = data.split('\n');
|
||||||
auto messageType = endpoint == completionEndpoint() ? LLMCore::OllamaMessage::Type::Generate
|
bool isDone = false;
|
||||||
: LLMCore::OllamaMessage::Type::Chat;
|
|
||||||
|
|
||||||
auto message = LLMCore::OllamaMessage::fromJson(data, messageType);
|
for (const QByteArray &line : lines) {
|
||||||
if (message.hasError()) {
|
if (line.trimmed().isEmpty()) {
|
||||||
LOG_MESSAGE("Error in Ollama response: " + message.error);
|
continue;
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
const QString endpoint = reply->url().path();
|
||||||
|
auto messageType = endpoint == completionEndpoint()
|
||||||
|
? LLMCore::OllamaMessage::Type::Generate
|
||||||
|
: LLMCore::OllamaMessage::Type::Chat;
|
||||||
|
|
||||||
|
auto message = LLMCore::OllamaMessage::fromJson(line, messageType);
|
||||||
|
if (message.hasError()) {
|
||||||
|
LOG_MESSAGE("Error in Ollama response: " + message.error);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString content = message.getContent();
|
||||||
|
if (!content.isEmpty()) {
|
||||||
|
accumulatedResponse += content;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.done) {
|
||||||
|
isDone = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
accumulatedResponse += message.getContent();
|
return isDone;
|
||||||
return message.done;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> OllamaProvider::getInstalledModels(const QString &url)
|
QList<QString> OllamaProvider::getInstalledModels(const QString &url)
|
||||||
|
|||||||
@@ -109,15 +109,22 @@ bool OpenAICompatProvider::handleResponse(QNetworkReply *reply, QString &accumul
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArrayList chunks = data.split('\n');
|
bool isDone = false;
|
||||||
for (const QByteArray &chunk : chunks) {
|
QByteArrayList lines = data.split('\n');
|
||||||
if (chunk.trimmed().isEmpty() || chunk == "data: [DONE]") {
|
|
||||||
|
for (const QByteArray &line : lines) {
|
||||||
|
if (line.trimmed().isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray jsonData = chunk;
|
if (line == "data: [DONE]") {
|
||||||
if (chunk.startsWith("data: ")) {
|
isDone = true;
|
||||||
jsonData = chunk.mid(6);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray jsonData = line;
|
||||||
|
if (line.startsWith("data: ")) {
|
||||||
|
jsonData = line.mid(6);
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
@@ -133,11 +140,17 @@ bool OpenAICompatProvider::handleResponse(QNetworkReply *reply, QString &accumul
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
accumulatedResponse += message.getContent();
|
QString content = message.getContent();
|
||||||
return message.isDone();
|
if (!content.isEmpty()) {
|
||||||
|
accumulatedResponse += content;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.isDone()) {
|
||||||
|
isDone = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return isDone;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> OpenAICompatProvider::getInstalledModels(const QString &url)
|
QList<QString> OpenAICompatProvider::getInstalledModels(const QString &url)
|
||||||
|
|||||||
229
providers/OpenAIProvider.cpp
Normal file
229
providers/OpenAIProvider.cpp
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
/*
|
||||||
|
* 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 "OpenAIProvider.hpp"
|
||||||
|
|
||||||
|
#include "settings/ChatAssistantSettings.hpp"
|
||||||
|
#include "settings/CodeCompletionSettings.hpp"
|
||||||
|
#include "settings/ProviderSettings.hpp"
|
||||||
|
|
||||||
|
#include <QEventLoop>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
|
||||||
|
#include "llmcore/OpenAIMessage.hpp"
|
||||||
|
#include "llmcore/ValidationUtils.hpp"
|
||||||
|
#include "logger/Logger.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Providers {
|
||||||
|
|
||||||
|
OpenAIProvider::OpenAIProvider() {}
|
||||||
|
|
||||||
|
QString OpenAIProvider::name() const
|
||||||
|
{
|
||||||
|
return "OpenAI";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString OpenAIProvider::url() const
|
||||||
|
{
|
||||||
|
return "https://api.openai.com";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString OpenAIProvider::completionEndpoint() const
|
||||||
|
{
|
||||||
|
return "/v1/chat/completions";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString OpenAIProvider::chatEndpoint() const
|
||||||
|
{
|
||||||
|
return "/v1/chat/completions";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenAIProvider::supportsModelListing() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenAIProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||||
|
{
|
||||||
|
auto prepareMessages = [](QJsonObject &req) -> QJsonArray {
|
||||||
|
QJsonArray messages;
|
||||||
|
if (req.contains("system")) {
|
||||||
|
messages.append(
|
||||||
|
QJsonObject{{"role", "system"}, {"content", req.take("system").toString()}});
|
||||||
|
}
|
||||||
|
if (req.contains("prompt")) {
|
||||||
|
messages.append(
|
||||||
|
QJsonObject{{"role", "user"}, {"content", req.take("prompt").toString()}});
|
||||||
|
}
|
||||||
|
return messages;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto applyModelParams = [&request](const auto &settings) {
|
||||||
|
request["max_tokens"] = settings.maxTokens();
|
||||||
|
request["temperature"] = settings.temperature();
|
||||||
|
|
||||||
|
if (settings.useTopP())
|
||||||
|
request["top_p"] = settings.topP();
|
||||||
|
if (settings.useTopK())
|
||||||
|
request["top_k"] = settings.topK();
|
||||||
|
if (settings.useFrequencyPenalty())
|
||||||
|
request["frequency_penalty"] = settings.frequencyPenalty();
|
||||||
|
if (settings.usePresencePenalty())
|
||||||
|
request["presence_penalty"] = settings.presencePenalty();
|
||||||
|
};
|
||||||
|
|
||||||
|
QJsonArray messages = prepareMessages(request);
|
||||||
|
if (!messages.isEmpty()) {
|
||||||
|
request["messages"] = std::move(messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||||
|
applyModelParams(Settings::codeCompletionSettings());
|
||||||
|
} else {
|
||||||
|
applyModelParams(Settings::chatAssistantSettings());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenAIProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
|
||||||
|
{
|
||||||
|
QByteArray data = reply->readAll();
|
||||||
|
if (data.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDone = false;
|
||||||
|
QByteArrayList lines = data.split('\n');
|
||||||
|
|
||||||
|
for (const QByteArray &line : lines) {
|
||||||
|
if (line.trimmed().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line == "data: [DONE]") {
|
||||||
|
isDone = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray jsonData = line;
|
||||||
|
if (line.startsWith("data: ")) {
|
||||||
|
jsonData = line.mid(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonParseError error;
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error);
|
||||||
|
|
||||||
|
if (doc.isNull()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto message = LLMCore::OpenAIMessage::fromJson(doc.object());
|
||||||
|
if (message.hasError()) {
|
||||||
|
LOG_MESSAGE("Error in OpenAI response: " + message.error);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString content = message.getContent();
|
||||||
|
if (!content.isEmpty()) {
|
||||||
|
accumulatedResponse += content;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.isDone()) {
|
||||||
|
isDone = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QString> OpenAIProvider::getInstalledModels(const QString &url)
|
||||||
|
{
|
||||||
|
QList<QString> models;
|
||||||
|
QNetworkAccessManager manager;
|
||||||
|
QNetworkRequest request(QString("%1/v1/models").arg(url));
|
||||||
|
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
if (!apiKey().isEmpty()) {
|
||||||
|
request.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey()).toUtf8());
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply *reply = manager.get(request);
|
||||||
|
QEventLoop loop;
|
||||||
|
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
QByteArray responseData = reply->readAll();
|
||||||
|
QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
|
||||||
|
QJsonObject jsonObject = jsonResponse.object();
|
||||||
|
|
||||||
|
if (jsonObject.contains("data")) {
|
||||||
|
QJsonArray modelArray = jsonObject["data"].toArray();
|
||||||
|
for (const QJsonValue &value : modelArray) {
|
||||||
|
QJsonObject modelObject = value.toObject();
|
||||||
|
if (modelObject.contains("id")) {
|
||||||
|
QString modelId = modelObject["id"].toString();
|
||||||
|
if (modelId.startsWith("gpt")) {
|
||||||
|
models.append(modelId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG_MESSAGE(QString("Error fetching ChatGPT models: %1").arg(reply->errorString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
reply->deleteLater();
|
||||||
|
return models;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QString> OpenAIProvider::validateRequest(const QJsonObject &request, LLMCore::TemplateType type)
|
||||||
|
{
|
||||||
|
const auto templateReq = QJsonObject{
|
||||||
|
{"model", {}},
|
||||||
|
{"messages", QJsonArray{{QJsonObject{{"role", {}}, {"content", {}}}}}},
|
||||||
|
{"temperature", {}},
|
||||||
|
{"max_tokens", {}},
|
||||||
|
{"top_p", {}},
|
||||||
|
{"top_k", {}},
|
||||||
|
{"frequency_penalty", {}},
|
||||||
|
{"presence_penalty", {}},
|
||||||
|
{"stop", QJsonArray{}},
|
||||||
|
{"stream", {}}};
|
||||||
|
|
||||||
|
return LLMCore::ValidationUtils::validateRequestFields(request, templateReq);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString OpenAIProvider::apiKey() const
|
||||||
|
{
|
||||||
|
return Settings::providerSettings().openAiApiKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenAIProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) const
|
||||||
|
{
|
||||||
|
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
if (!apiKey().isEmpty()) {
|
||||||
|
networkRequest.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey()).toUtf8());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Providers
|
||||||
44
providers/OpenAIProvider.hpp
Normal file
44
providers/OpenAIProvider.hpp
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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 "llmcore/Provider.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Providers {
|
||||||
|
|
||||||
|
class OpenAIProvider : public LLMCore::Provider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OpenAIProvider();
|
||||||
|
|
||||||
|
QString name() const override;
|
||||||
|
QString url() const override;
|
||||||
|
QString completionEndpoint() const override;
|
||||||
|
QString chatEndpoint() const override;
|
||||||
|
bool supportsModelListing() const override;
|
||||||
|
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
|
||||||
|
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||||
|
QList<QString> getInstalledModels(const QString &url) override;
|
||||||
|
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||||
|
QString apiKey() const override;
|
||||||
|
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Providers
|
||||||
@@ -93,16 +93,22 @@ bool OpenRouterProvider::handleResponse(QNetworkReply *reply, QString &accumulat
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArrayList chunks = data.split('\n');
|
bool isDone = false;
|
||||||
for (const QByteArray &chunk : chunks) {
|
QByteArrayList lines = data.split('\n');
|
||||||
if (chunk.trimmed().isEmpty() || chunk.contains("OPENROUTER PROCESSING")
|
|
||||||
|| chunk == "data: [DONE]") {
|
for (const QByteArray &line : lines) {
|
||||||
|
if (line.trimmed().isEmpty() || line.contains("OPENROUTER PROCESSING")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray jsonData = chunk;
|
if (line == "data: [DONE]") {
|
||||||
if (chunk.startsWith("data: ")) {
|
isDone = true;
|
||||||
jsonData = chunk.mid(6);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray jsonData = line;
|
||||||
|
if (line.startsWith("data: ")) {
|
||||||
|
jsonData = line.mid(6);
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
@@ -114,15 +120,21 @@ bool OpenRouterProvider::handleResponse(QNetworkReply *reply, QString &accumulat
|
|||||||
|
|
||||||
auto message = LLMCore::OpenAIMessage::fromJson(doc.object());
|
auto message = LLMCore::OpenAIMessage::fromJson(doc.object());
|
||||||
if (message.hasError()) {
|
if (message.hasError()) {
|
||||||
LOG_MESSAGE("Error in OpenRouter response: " + message.error);
|
LOG_MESSAGE("Error in OpenAI response: " + message.error);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
accumulatedResponse += message.getContent();
|
QString content = message.getContent();
|
||||||
return message.isDone();
|
if (!content.isEmpty()) {
|
||||||
|
accumulatedResponse += content;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.isDone()) {
|
||||||
|
isDone = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return isDone;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString OpenRouterProvider::apiKey() const
|
QString OpenRouterProvider::apiKey() const
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include "providers/LMStudioProvider.hpp"
|
#include "providers/LMStudioProvider.hpp"
|
||||||
#include "providers/OllamaProvider.hpp"
|
#include "providers/OllamaProvider.hpp"
|
||||||
#include "providers/OpenAICompatProvider.hpp"
|
#include "providers/OpenAICompatProvider.hpp"
|
||||||
|
#include "providers/OpenAIProvider.hpp"
|
||||||
#include "providers/OpenRouterAIProvider.hpp"
|
#include "providers/OpenRouterAIProvider.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::Providers {
|
namespace QodeAssist::Providers {
|
||||||
@@ -36,6 +37,7 @@ inline void registerProviders()
|
|||||||
providerManager.registerProvider<OpenAICompatProvider>();
|
providerManager.registerProvider<OpenAICompatProvider>();
|
||||||
providerManager.registerProvider<OpenRouterProvider>();
|
providerManager.registerProvider<OpenRouterProvider>();
|
||||||
providerManager.registerProvider<ClaudeProvider>();
|
providerManager.registerProvider<ClaudeProvider>();
|
||||||
|
providerManager.registerProvider<OpenAIProvider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
#include "QodeAssistConstants.hpp"
|
#include "QodeAssistConstants.hpp"
|
||||||
#include "QodeAssisttr.h"
|
#include "QodeAssisttr.h"
|
||||||
|
#include "settings/PluginUpdater.hpp"
|
||||||
|
#include "settings/UpdateDialog.hpp"
|
||||||
|
|
||||||
#include <coreplugin/actionmanager/actioncontainer.h>
|
#include <coreplugin/actionmanager/actioncontainer.h>
|
||||||
#include <coreplugin/actionmanager/actionmanager.h>
|
#include <coreplugin/actionmanager/actionmanager.h>
|
||||||
@@ -32,19 +34,21 @@
|
|||||||
#include <extensionsystem/iplugin.h>
|
#include <extensionsystem/iplugin.h>
|
||||||
#include <languageclient/languageclientmanager.h>
|
#include <languageclient/languageclientmanager.h>
|
||||||
|
|
||||||
|
#include <texteditor/texteditor.h>
|
||||||
|
#include <utils/icon.h>
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <texteditor/texteditor.h>
|
|
||||||
#include <utils/icon.h>
|
|
||||||
|
|
||||||
#include "ConfigurationManager.hpp"
|
#include "ConfigurationManager.hpp"
|
||||||
#include "QodeAssistClient.hpp"
|
#include "QodeAssistClient.hpp"
|
||||||
#include "chat/ChatOutputPane.h"
|
#include "chat/ChatOutputPane.h"
|
||||||
#include "chat/NavigationPanel.hpp"
|
#include "chat/NavigationPanel.hpp"
|
||||||
|
#include "settings/GeneralSettings.hpp"
|
||||||
#include "settings/ProjectSettingsPanel.hpp"
|
#include "settings/ProjectSettingsPanel.hpp"
|
||||||
|
|
||||||
|
#include "UpdateStatusWidget.hpp"
|
||||||
#include "providers/Providers.hpp"
|
#include "providers/Providers.hpp"
|
||||||
#include "templates/Templates.hpp"
|
#include "templates/Templates.hpp"
|
||||||
|
|
||||||
@@ -61,8 +65,8 @@ class QodeAssistPlugin final : public ExtensionSystem::IPlugin
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
QodeAssistPlugin()
|
QodeAssistPlugin()
|
||||||
{
|
: m_updater(new PluginUpdater(this))
|
||||||
}
|
{}
|
||||||
|
|
||||||
~QodeAssistPlugin() final
|
~QodeAssistPlugin() final
|
||||||
{
|
{
|
||||||
@@ -96,33 +100,36 @@ public:
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
auto toggleButton = new QToolButton;
|
m_statusWidget = new UpdateStatusWidget;
|
||||||
toggleButton->setDefaultAction(requestAction.contextAction());
|
m_statusWidget->setDefaultAction(requestAction.contextAction());
|
||||||
StatusBarManager::addStatusBarWidget(toggleButton, StatusBarManager::RightCorner);
|
StatusBarManager::addStatusBarWidget(m_statusWidget, StatusBarManager::RightCorner);
|
||||||
|
|
||||||
|
connect(m_statusWidget->updateButton(), &QPushButton::clicked, this, [this]() {
|
||||||
|
UpdateDialog::checkForUpdatesAndShow(Core::ICore::mainWindow());
|
||||||
|
});
|
||||||
|
|
||||||
m_chatOutputPane = new Chat::ChatOutputPane(this);
|
m_chatOutputPane = new Chat::ChatOutputPane(this);
|
||||||
m_navigationPanel = new Chat::NavigationPanel();
|
m_navigationPanel = new Chat::NavigationPanel();
|
||||||
|
|
||||||
Settings::setupProjectPanel();
|
Settings::setupProjectPanel();
|
||||||
|
|
||||||
ConfigurationManager::instance().init();
|
ConfigurationManager::instance().init();
|
||||||
|
|
||||||
|
if (Settings::generalSettings().enableCheckUpdate()) {
|
||||||
|
QTimer::singleShot(3000, this, &QodeAssistPlugin::checkForUpdates);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void extensionsInitialized() final
|
void extensionsInitialized() final {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void restartClient()
|
void restartClient()
|
||||||
{
|
{
|
||||||
LanguageClient::LanguageClientManager::shutdownClient(m_qodeAssistClient);
|
LanguageClient::LanguageClientManager::shutdownClient(m_qodeAssistClient);
|
||||||
|
|
||||||
m_qodeAssistClient = new QodeAssistClient();
|
m_qodeAssistClient = new QodeAssistClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool delayedInitialize() final
|
bool delayedInitialize() final
|
||||||
{
|
{
|
||||||
restartClient();
|
restartClient();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,17 +137,38 @@ public:
|
|||||||
{
|
{
|
||||||
if (!m_qodeAssistClient)
|
if (!m_qodeAssistClient)
|
||||||
return SynchronousShutdown;
|
return SynchronousShutdown;
|
||||||
connect(m_qodeAssistClient,
|
connect(m_qodeAssistClient, &QObject::destroyed, this, &IPlugin::asynchronousShutdownFinished);
|
||||||
&QObject::destroyed,
|
|
||||||
this,
|
|
||||||
&IPlugin::asynchronousShutdownFinished);
|
|
||||||
return AsynchronousShutdown;
|
return AsynchronousShutdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void checkForUpdates()
|
||||||
|
{
|
||||||
|
connect(
|
||||||
|
m_updater,
|
||||||
|
&PluginUpdater::updateCheckFinished,
|
||||||
|
this,
|
||||||
|
&QodeAssistPlugin::handleUpdateCheckResult,
|
||||||
|
Qt::UniqueConnection);
|
||||||
|
m_updater->checkForUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleUpdateCheckResult(const PluginUpdater::UpdateInfo &info)
|
||||||
|
{
|
||||||
|
if (!info.isUpdateAvailable
|
||||||
|
|| QVersionNumber::fromString(info.currentIdeVersion)
|
||||||
|
> QVersionNumber::fromString(info.targetIdeVersion))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_statusWidget)
|
||||||
|
m_statusWidget->showUpdateAvailable(info.version);
|
||||||
|
}
|
||||||
|
|
||||||
QPointer<QodeAssistClient> m_qodeAssistClient;
|
QPointer<QodeAssistClient> m_qodeAssistClient;
|
||||||
QPointer<Chat::ChatOutputPane> m_chatOutputPane;
|
QPointer<Chat::ChatOutputPane> m_chatOutputPane;
|
||||||
QPointer<Chat::NavigationPanel> m_navigationPanel;
|
QPointer<Chat::NavigationPanel> m_navigationPanel;
|
||||||
|
QPointer<PluginUpdater> m_updater;
|
||||||
|
UpdateStatusWidget *m_statusWidget{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Internal
|
} // namespace QodeAssist::Internal
|
||||||
|
|||||||
@@ -35,11 +35,26 @@ public:
|
|||||||
void addToLayoutImpl(Layouting::Layout &parent) override
|
void addToLayoutImpl(Layouting::Layout &parent) override
|
||||||
{
|
{
|
||||||
auto button = new QPushButton(m_buttonText);
|
auto button = new QPushButton(m_buttonText);
|
||||||
|
button->setVisible(m_visible);
|
||||||
connect(button, &QPushButton::clicked, this, &ButtonAspect::clicked);
|
connect(button, &QPushButton::clicked, this, &ButtonAspect::clicked);
|
||||||
|
connect(this, &ButtonAspect::visibleChanged, button, &QPushButton::setVisible);
|
||||||
parent.addItem(button);
|
parent.addItem(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateVisibility(bool visible)
|
||||||
|
{
|
||||||
|
if (m_visible == visible)
|
||||||
|
return;
|
||||||
|
m_visible = visible;
|
||||||
|
emit visibleChanged(visible);
|
||||||
|
}
|
||||||
|
|
||||||
QString m_buttonText;
|
QString m_buttonText;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void clicked();
|
void clicked();
|
||||||
|
void visibleChanged(bool state);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_visible = true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ add_library(QodeAssistSettings STATIC
|
|||||||
ProjectSettings.hpp ProjectSettings.cpp
|
ProjectSettings.hpp ProjectSettings.cpp
|
||||||
ProjectSettingsPanel.hpp ProjectSettingsPanel.cpp
|
ProjectSettingsPanel.hpp ProjectSettingsPanel.cpp
|
||||||
ProviderSettings.hpp ProviderSettings.cpp
|
ProviderSettings.hpp ProviderSettings.cpp
|
||||||
|
PluginUpdater.hpp PluginUpdater.cpp
|
||||||
|
UpdateDialog.hpp UpdateDialog.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(QodeAssistSettings
|
target_link_libraries(QodeAssistSettings
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
#include "SettingsDialog.hpp"
|
#include "SettingsDialog.hpp"
|
||||||
#include "SettingsTr.hpp"
|
#include "SettingsTr.hpp"
|
||||||
#include "SettingsUtils.hpp"
|
#include "SettingsUtils.hpp"
|
||||||
|
#include "UpdateDialog.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::Settings {
|
namespace QodeAssist::Settings {
|
||||||
|
|
||||||
@@ -60,7 +61,12 @@ GeneralSettings::GeneralSettings()
|
|||||||
enableLogging.setLabelText(TrConstants::ENABLE_LOG);
|
enableLogging.setLabelText(TrConstants::ENABLE_LOG);
|
||||||
enableLogging.setDefaultValue(false);
|
enableLogging.setDefaultValue(false);
|
||||||
|
|
||||||
|
enableCheckUpdate.setSettingsKey(Constants::ENABLE_CHECK_UPDATE);
|
||||||
|
enableCheckUpdate.setLabelText(TrConstants::ENABLE_CHECK_UPDATE_ON_START);
|
||||||
|
enableCheckUpdate.setDefaultValue(true);
|
||||||
|
|
||||||
resetToDefaults.m_buttonText = TrConstants::RESET_TO_DEFAULTS;
|
resetToDefaults.m_buttonText = TrConstants::RESET_TO_DEFAULTS;
|
||||||
|
checkUpdate.m_buttonText = TrConstants::CHECK_UPDATE;
|
||||||
|
|
||||||
initStringAspect(ccProvider, Constants::CC_PROVIDER, TrConstants::PROVIDER, "Ollama");
|
initStringAspect(ccProvider, Constants::CC_PROVIDER, TrConstants::PROVIDER, "Ollama");
|
||||||
ccProvider.setReadOnly(true);
|
ccProvider.setReadOnly(true);
|
||||||
@@ -83,6 +89,39 @@ GeneralSettings::GeneralSettings()
|
|||||||
ccStatus.setDefaultValue("");
|
ccStatus.setDefaultValue("");
|
||||||
ccTest.m_buttonText = TrConstants::TEST;
|
ccTest.m_buttonText = TrConstants::TEST;
|
||||||
|
|
||||||
|
// preset1
|
||||||
|
specifyPreset1.setSettingsKey(Constants::CC_SPECIFY_PRESET1);
|
||||||
|
specifyPreset1.setLabelText(TrConstants::ADD_NEW_PRESET);
|
||||||
|
specifyPreset1.setDefaultValue(false);
|
||||||
|
|
||||||
|
preset1Language.setSettingsKey(Constants::CC_PRESET1_LANGUAGE);
|
||||||
|
preset1Language.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
|
||||||
|
// see ProgrammingLanguageUtils
|
||||||
|
preset1Language.addOption("qml");
|
||||||
|
preset1Language.addOption("c/c++");
|
||||||
|
preset1Language.addOption("python");
|
||||||
|
|
||||||
|
initStringAspect(
|
||||||
|
ccPreset1Provider, Constants::CC_PRESET1_PROVIDER, TrConstants::PROVIDER, "Ollama");
|
||||||
|
ccPreset1Provider.setReadOnly(true);
|
||||||
|
ccPreset1SelectProvider.m_buttonText = TrConstants::SELECT;
|
||||||
|
|
||||||
|
initStringAspect(
|
||||||
|
ccPreset1Url, Constants::CC_PRESET1_URL, TrConstants::URL, "http://localhost:11434");
|
||||||
|
ccPreset1Url.setHistoryCompleter(Constants::CC_PRESET1_URL_HISTORY);
|
||||||
|
ccPreset1SetUrl.m_buttonText = TrConstants::SELECT;
|
||||||
|
|
||||||
|
initStringAspect(
|
||||||
|
ccPreset1Model, Constants::CC_PRESET1_MODEL, TrConstants::MODEL, "qwen2.5-coder:7b");
|
||||||
|
ccPreset1Model.setHistoryCompleter(Constants::CC_PRESET1_MODEL_HISTORY);
|
||||||
|
ccPreset1SelectModel.m_buttonText = TrConstants::SELECT;
|
||||||
|
|
||||||
|
initStringAspect(
|
||||||
|
ccPreset1Template, Constants::CC_PRESET1_TEMPLATE, TrConstants::TEMPLATE, "Ollama Auto FIM");
|
||||||
|
ccPreset1Template.setReadOnly(true);
|
||||||
|
ccPreset1SelectTemplate.m_buttonText = TrConstants::SELECT;
|
||||||
|
|
||||||
|
// chat assistance
|
||||||
initStringAspect(caProvider, Constants::CA_PROVIDER, TrConstants::PROVIDER, "Ollama");
|
initStringAspect(caProvider, Constants::CA_PROVIDER, TrConstants::PROVIDER, "Ollama");
|
||||||
caProvider.setReadOnly(true);
|
caProvider.setReadOnly(true);
|
||||||
caSelectProvider.m_buttonText = TrConstants::SELECT;
|
caSelectProvider.m_buttonText = TrConstants::SELECT;
|
||||||
@@ -111,6 +150,8 @@ GeneralSettings::GeneralSettings()
|
|||||||
|
|
||||||
setupConnections();
|
setupConnections();
|
||||||
|
|
||||||
|
updatePreset1Visiblity(specifyPreset1.value());
|
||||||
|
|
||||||
setLayouter([this]() {
|
setLayouter([this]() {
|
||||||
using namespace Layouting;
|
using namespace Layouting;
|
||||||
|
|
||||||
@@ -120,22 +161,32 @@ GeneralSettings::GeneralSettings()
|
|||||||
ccGrid.addRow({ccModel, ccSelectModel});
|
ccGrid.addRow({ccModel, ccSelectModel});
|
||||||
ccGrid.addRow({ccTemplate, ccSelectTemplate});
|
ccGrid.addRow({ccTemplate, ccSelectTemplate});
|
||||||
|
|
||||||
|
auto ccPreset1Grid = Grid{};
|
||||||
|
ccPreset1Grid.addRow({ccPreset1Provider, ccPreset1SelectProvider});
|
||||||
|
ccPreset1Grid.addRow({ccPreset1Url, ccPreset1SetUrl});
|
||||||
|
ccPreset1Grid.addRow({ccPreset1Model, ccPreset1SelectModel});
|
||||||
|
ccPreset1Grid.addRow({ccPreset1Template, ccPreset1SelectTemplate});
|
||||||
|
|
||||||
auto caGrid = Grid{};
|
auto caGrid = Grid{};
|
||||||
caGrid.addRow({caProvider, caSelectProvider});
|
caGrid.addRow({caProvider, caSelectProvider});
|
||||||
caGrid.addRow({caUrl, caSetUrl});
|
caGrid.addRow({caUrl, caSetUrl});
|
||||||
caGrid.addRow({caModel, caSelectModel});
|
caGrid.addRow({caModel, caSelectModel});
|
||||||
caGrid.addRow({caTemplate, caSelectTemplate});
|
caGrid.addRow({caTemplate, caSelectTemplate});
|
||||||
|
|
||||||
auto ccGroup = Group{title(TrConstants::CODE_COMPLETION), ccGrid};
|
auto ccGroup = Group{
|
||||||
|
title(TrConstants::CODE_COMPLETION),
|
||||||
|
Column{ccGrid, Row{specifyPreset1, preset1Language, Stretch{1}}, ccPreset1Grid}};
|
||||||
auto caGroup = Group{title(TrConstants::CHAT_ASSISTANT), caGrid};
|
auto caGroup = Group{title(TrConstants::CHAT_ASSISTANT), caGrid};
|
||||||
|
|
||||||
auto rootLayout = Column{Row{enableQodeAssist, Stretch{1}, resetToDefaults},
|
auto rootLayout = Column{
|
||||||
Row{enableLogging, Stretch{1}},
|
Row{enableQodeAssist, Stretch{1}, Row{checkUpdate, resetToDefaults}},
|
||||||
Space{8},
|
Row{enableLogging, Stretch{1}},
|
||||||
ccGroup,
|
Row{enableCheckUpdate, Stretch{1}},
|
||||||
Space{8},
|
Space{8},
|
||||||
caGroup,
|
ccGroup,
|
||||||
Stretch{1}};
|
Space{8},
|
||||||
|
caGroup,
|
||||||
|
Stretch{1}};
|
||||||
|
|
||||||
return rootLayout;
|
return rootLayout;
|
||||||
});
|
});
|
||||||
@@ -259,9 +310,11 @@ void GeneralSettings::showUrlSelectionDialog(
|
|||||||
dialog.addSpacing();
|
dialog.addSpacing();
|
||||||
|
|
||||||
QStringList allUrls = predefinedUrls;
|
QStringList allUrls = predefinedUrls;
|
||||||
QString key
|
QString key = QString("CompleterHistory/")
|
||||||
= QString("CompleterHistory/")
|
.append(
|
||||||
.append((&aspect == &ccUrl) ? Constants::CC_URL_HISTORY : Constants::CA_URL_HISTORY);
|
(&aspect == &ccUrl) ? Constants::CC_URL_HISTORY
|
||||||
|
: (&aspect == &ccPreset1Url) ? Constants::CC_PRESET1_URL_HISTORY
|
||||||
|
: Constants::CA_URL_HISTORY);
|
||||||
QStringList historyList = qtcSettings()->value(Utils::Key(key.toLocal8Bit())).toStringList();
|
QStringList historyList = qtcSettings()->value(Utils::Key(key.toLocal8Bit())).toStringList();
|
||||||
allUrls.append(historyList);
|
allUrls.append(historyList);
|
||||||
allUrls.removeDuplicates();
|
allUrls.removeDuplicates();
|
||||||
@@ -289,12 +342,31 @@ void GeneralSettings::showUrlSelectionDialog(
|
|||||||
dialog.exec();
|
dialog.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GeneralSettings::updatePreset1Visiblity(bool state)
|
||||||
|
{
|
||||||
|
ccPreset1Provider.setVisible(specifyPreset1.volatileValue());
|
||||||
|
ccPreset1SelectProvider.updateVisibility(specifyPreset1.volatileValue());
|
||||||
|
ccPreset1Url.setVisible(specifyPreset1.volatileValue());
|
||||||
|
ccPreset1SetUrl.updateVisibility(specifyPreset1.volatileValue());
|
||||||
|
ccPreset1Model.setVisible(specifyPreset1.volatileValue());
|
||||||
|
ccPreset1SelectModel.updateVisibility(specifyPreset1.volatileValue());
|
||||||
|
ccPreset1Template.setVisible(specifyPreset1.volatileValue());
|
||||||
|
ccPreset1SelectTemplate.updateVisibility(specifyPreset1.volatileValue());
|
||||||
|
}
|
||||||
|
|
||||||
void GeneralSettings::setupConnections()
|
void GeneralSettings::setupConnections()
|
||||||
{
|
{
|
||||||
connect(&enableLogging, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
connect(&enableLogging, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||||
Logger::instance().setLoggingEnabled(enableLogging.volatileValue());
|
Logger::instance().setLoggingEnabled(enableLogging.volatileValue());
|
||||||
});
|
});
|
||||||
connect(&resetToDefaults, &ButtonAspect::clicked, this, &GeneralSettings::resetPageToDefaults);
|
connect(&resetToDefaults, &ButtonAspect::clicked, this, &GeneralSettings::resetPageToDefaults);
|
||||||
|
connect(&checkUpdate, &ButtonAspect::clicked, this, [this]() {
|
||||||
|
QodeAssist::UpdateDialog::checkForUpdatesAndShow(Core::ICore::dialogParent());
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(&specifyPreset1, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||||
|
updatePreset1Visiblity(specifyPreset1.volatileValue());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void GeneralSettings::resetPageToDefaults()
|
void GeneralSettings::resetPageToDefaults()
|
||||||
@@ -316,6 +388,13 @@ void GeneralSettings::resetPageToDefaults()
|
|||||||
resetAspect(caModel);
|
resetAspect(caModel);
|
||||||
resetAspect(caTemplate);
|
resetAspect(caTemplate);
|
||||||
resetAspect(caUrl);
|
resetAspect(caUrl);
|
||||||
|
resetAspect(enableCheckUpdate);
|
||||||
|
resetAspect(specifyPreset1);
|
||||||
|
resetAspect(preset1Language);
|
||||||
|
resetAspect(ccPreset1Provider);
|
||||||
|
resetAspect(ccPreset1Model);
|
||||||
|
resetAspect(ccPreset1Template);
|
||||||
|
resetAspect(ccPreset1Url);
|
||||||
writeSettings();
|
writeSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ public:
|
|||||||
|
|
||||||
Utils::BoolAspect enableQodeAssist{this};
|
Utils::BoolAspect enableQodeAssist{this};
|
||||||
Utils::BoolAspect enableLogging{this};
|
Utils::BoolAspect enableLogging{this};
|
||||||
|
Utils::BoolAspect enableCheckUpdate{this};
|
||||||
|
ButtonAspect checkUpdate{this};
|
||||||
ButtonAspect resetToDefaults{this};
|
ButtonAspect resetToDefaults{this};
|
||||||
|
|
||||||
// code completion setttings
|
// code completion setttings
|
||||||
@@ -53,6 +55,23 @@ public:
|
|||||||
Utils::StringAspect ccStatus{this};
|
Utils::StringAspect ccStatus{this};
|
||||||
ButtonAspect ccTest{this};
|
ButtonAspect ccTest{this};
|
||||||
|
|
||||||
|
// TODO create dynamic presets system
|
||||||
|
// preset1 for code completion settings
|
||||||
|
Utils::BoolAspect specifyPreset1{this};
|
||||||
|
Utils::SelectionAspect preset1Language{this};
|
||||||
|
|
||||||
|
Utils::StringAspect ccPreset1Provider{this};
|
||||||
|
ButtonAspect ccPreset1SelectProvider{this};
|
||||||
|
|
||||||
|
Utils::StringAspect ccPreset1Url{this};
|
||||||
|
ButtonAspect ccPreset1SetUrl{this};
|
||||||
|
|
||||||
|
Utils::StringAspect ccPreset1Model{this};
|
||||||
|
ButtonAspect ccPreset1SelectModel{this};
|
||||||
|
|
||||||
|
Utils::StringAspect ccPreset1Template{this};
|
||||||
|
ButtonAspect ccPreset1SelectTemplate{this};
|
||||||
|
|
||||||
// chat assistant settings
|
// chat assistant settings
|
||||||
Utils::StringAspect caProvider{this};
|
Utils::StringAspect caProvider{this};
|
||||||
ButtonAspect caSelectProvider{this};
|
ButtonAspect caSelectProvider{this};
|
||||||
@@ -80,6 +99,8 @@ public:
|
|||||||
|
|
||||||
void showUrlSelectionDialog(Utils::StringAspect &aspect, const QStringList &predefinedUrls);
|
void showUrlSelectionDialog(Utils::StringAspect &aspect, const QStringList &predefinedUrls);
|
||||||
|
|
||||||
|
void updatePreset1Visiblity(bool state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupConnections();
|
void setupConnections();
|
||||||
void resetPageToDefaults();
|
void resetPageToDefaults();
|
||||||
|
|||||||
188
settings/PluginUpdater.cpp
Normal file
188
settings/PluginUpdater.cpp
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* 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 "PluginUpdater.hpp"
|
||||||
|
|
||||||
|
#include <coreplugin/coreconstants.h>
|
||||||
|
#include <coreplugin/coreplugin.h>
|
||||||
|
#include <extensionsystem/pluginmanager.h>
|
||||||
|
#include <extensionsystem/pluginspec.h>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
|
||||||
|
namespace QodeAssist {
|
||||||
|
|
||||||
|
PluginUpdater::PluginUpdater(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_networkManager(new QNetworkAccessManager(this))
|
||||||
|
{}
|
||||||
|
|
||||||
|
void PluginUpdater::checkForUpdates()
|
||||||
|
{
|
||||||
|
if (m_isCheckingUpdate)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_isCheckingUpdate = true;
|
||||||
|
QNetworkRequest request((QUrl(getUpdateUrl())));
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
QNetworkReply *reply = m_networkManager->get(request);
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||||
|
handleUpdateResponse(reply);
|
||||||
|
m_isCheckingUpdate = false;
|
||||||
|
reply->deleteLater();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginUpdater::handleUpdateResponse(QNetworkReply *reply)
|
||||||
|
{
|
||||||
|
UpdateInfo info;
|
||||||
|
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
emit downloadError(reply->errorString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());
|
||||||
|
QJsonObject obj = doc.object();
|
||||||
|
|
||||||
|
info.version = obj["tag_name"].toString();
|
||||||
|
if (info.version.startsWith('v'))
|
||||||
|
info.version.remove(0, 1);
|
||||||
|
|
||||||
|
QString qtcVersionStr = Core::ICore::versionString().split(' ').last();
|
||||||
|
QVersionNumber qtcVersion = QVersionNumber::fromString(qtcVersionStr);
|
||||||
|
info.currentIdeVersion = qtcVersionStr;
|
||||||
|
|
||||||
|
auto assets = obj["assets"].toArray();
|
||||||
|
for (const auto &asset : assets) {
|
||||||
|
QString name = asset.toObject()["name"].toString();
|
||||||
|
|
||||||
|
if (name.startsWith("QodeAssist-")) {
|
||||||
|
QString assetVersionStr = name.section('-', 1, 1);
|
||||||
|
QVersionNumber assetVersion = QVersionNumber::fromString(assetVersionStr);
|
||||||
|
info.targetIdeVersion = assetVersionStr;
|
||||||
|
|
||||||
|
if (assetVersion != qtcVersion) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
if (name.contains("Windows"))
|
||||||
|
#elif defined(Q_OS_MACOS)
|
||||||
|
if (name.contains("macOS"))
|
||||||
|
#else
|
||||||
|
if (name.contains("Linux") && !name.contains("experimental"))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
info.downloadUrl = asset.toObject()["browser_download_url"].toString();
|
||||||
|
info.fileName = name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.downloadUrl.isEmpty()) {
|
||||||
|
info.incompatibleIdeVersion = true;
|
||||||
|
emit updateCheckFinished(info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
info.changeLog = obj["body"].toString();
|
||||||
|
info.isUpdateAvailable = QVersionNumber::fromString(info.version)
|
||||||
|
> QVersionNumber::fromString(currentVersion());
|
||||||
|
|
||||||
|
m_lastUpdateInfo = info;
|
||||||
|
emit updateCheckFinished(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginUpdater::downloadUpdate(const QString &url)
|
||||||
|
{
|
||||||
|
QNetworkRequest request(url);
|
||||||
|
QNetworkReply *reply = m_networkManager->get(request);
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::downloadProgress, this, &PluginUpdater::handleDownloadProgress);
|
||||||
|
connect(reply, &QNetworkReply::finished, this, &PluginUpdater::handleDownloadFinished);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PluginUpdater::currentVersion() const
|
||||||
|
{
|
||||||
|
const auto pluginSpecs = ExtensionSystem::PluginManager::plugins();
|
||||||
|
for (const ExtensionSystem::PluginSpec *spec : pluginSpecs) {
|
||||||
|
if (spec->name() == QLatin1String("QodeAssist"))
|
||||||
|
return spec->version();
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PluginUpdater::isUpdateAvailable() const
|
||||||
|
{
|
||||||
|
return m_lastUpdateInfo.isUpdateAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PluginUpdater::getUpdateUrl() const
|
||||||
|
{
|
||||||
|
return "https://api.github.com/repos/Palm1r/qodeassist/releases/latest";
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginUpdater::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
||||||
|
{
|
||||||
|
emit downloadProgress(bytesReceived, bytesTotal);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginUpdater::handleDownloadFinished()
|
||||||
|
{
|
||||||
|
auto reply = qobject_cast<QNetworkReply *>(sender());
|
||||||
|
QTC_ASSERT(reply, return);
|
||||||
|
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
emit downloadError(reply->errorString());
|
||||||
|
reply->deleteLater();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString downloadPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)
|
||||||
|
+ QDir::separator() + "QodeAssist_v" + m_lastUpdateInfo.version;
|
||||||
|
QDir().mkpath(downloadPath);
|
||||||
|
|
||||||
|
QString filePath = downloadPath + QDir::separator() + m_lastUpdateInfo.fileName;
|
||||||
|
|
||||||
|
if (QFile::exists(filePath)) {
|
||||||
|
emit downloadError(tr("Update file already exists: %1").arg(filePath));
|
||||||
|
reply->deleteLater();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::WriteOnly)) {
|
||||||
|
emit downloadError(tr("Could not save the update file"));
|
||||||
|
reply->deleteLater();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.write(reply->readAll());
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
emit downloadFinished(filePath);
|
||||||
|
reply->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist
|
||||||
72
settings/PluginUpdater.hpp
Normal file
72
settings/PluginUpdater.hpp
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* 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 <coreplugin/icore.h>
|
||||||
|
#include <coreplugin/plugininstallwizard.h>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QVersionNumber>
|
||||||
|
|
||||||
|
namespace QodeAssist {
|
||||||
|
|
||||||
|
class PluginUpdater : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
struct UpdateInfo
|
||||||
|
{
|
||||||
|
QString version;
|
||||||
|
QString downloadUrl;
|
||||||
|
QString changeLog;
|
||||||
|
QString fileName;
|
||||||
|
bool isUpdateAvailable;
|
||||||
|
bool incompatibleIdeVersion{false};
|
||||||
|
QString targetIdeVersion;
|
||||||
|
QString currentIdeVersion;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit PluginUpdater(QObject *parent = nullptr);
|
||||||
|
~PluginUpdater() = default;
|
||||||
|
|
||||||
|
void checkForUpdates();
|
||||||
|
void downloadUpdate(const QString &url);
|
||||||
|
QString currentVersion() const;
|
||||||
|
bool isUpdateAvailable() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void updateCheckFinished(const UpdateInfo &info);
|
||||||
|
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||||
|
void downloadFinished(const QString &filePath);
|
||||||
|
void downloadError(const QString &error);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void handleUpdateResponse(QNetworkReply *reply);
|
||||||
|
void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||||
|
void handleDownloadFinished();
|
||||||
|
QString getUpdateUrl() const;
|
||||||
|
|
||||||
|
QNetworkAccessManager *m_networkManager;
|
||||||
|
UpdateInfo m_lastUpdateInfo;
|
||||||
|
bool m_isCheckingUpdate{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist
|
||||||
@@ -69,6 +69,14 @@ ProviderSettings::ProviderSettings()
|
|||||||
claudeApiKey.setDefaultValue("");
|
claudeApiKey.setDefaultValue("");
|
||||||
claudeApiKey.setAutoApply(true);
|
claudeApiKey.setAutoApply(true);
|
||||||
|
|
||||||
|
openAiApiKey.setSettingsKey(Constants::OPEN_AI_API_KEY);
|
||||||
|
openAiApiKey.setLabelText(Tr::tr("OpenAI API Key:"));
|
||||||
|
openAiApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||||
|
openAiApiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
|
||||||
|
openAiApiKey.setHistoryCompleter(Constants::OPEN_AI_API_KEY_HISTORY);
|
||||||
|
openAiApiKey.setDefaultValue("");
|
||||||
|
openAiApiKey.setAutoApply(true);
|
||||||
|
|
||||||
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
|
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
|
||||||
|
|
||||||
readSettings();
|
readSettings();
|
||||||
@@ -83,6 +91,8 @@ ProviderSettings::ProviderSettings()
|
|||||||
Space{8},
|
Space{8},
|
||||||
Group{title(Tr::tr("OpenRouter Settings")), Column{openRouterApiKey}},
|
Group{title(Tr::tr("OpenRouter Settings")), Column{openRouterApiKey}},
|
||||||
Space{8},
|
Space{8},
|
||||||
|
Group{title(Tr::tr("OpenAI Settings")), Column{openAiApiKey}},
|
||||||
|
Space{8},
|
||||||
Group{title(Tr::tr("OpenAI Compatible Settings")), Column{openAiCompatApiKey}},
|
Group{title(Tr::tr("OpenAI Compatible Settings")), Column{openAiCompatApiKey}},
|
||||||
Space{8},
|
Space{8},
|
||||||
Group{title(Tr::tr("Claude Settings")), Column{claudeApiKey}},
|
Group{title(Tr::tr("Claude Settings")), Column{claudeApiKey}},
|
||||||
@@ -101,6 +111,7 @@ void ProviderSettings::setupConnections()
|
|||||||
openAiCompatApiKey.writeSettings();
|
openAiCompatApiKey.writeSettings();
|
||||||
});
|
});
|
||||||
connect(&claudeApiKey, &ButtonAspect::changed, this, [this]() { claudeApiKey.writeSettings(); });
|
connect(&claudeApiKey, &ButtonAspect::changed, this, [this]() { claudeApiKey.writeSettings(); });
|
||||||
|
connect(&openAiApiKey, &ButtonAspect::changed, this, [this]() { openAiApiKey.writeSettings(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProviderSettings::resetSettingsToDefaults()
|
void ProviderSettings::resetSettingsToDefaults()
|
||||||
@@ -116,6 +127,7 @@ void ProviderSettings::resetSettingsToDefaults()
|
|||||||
resetAspect(openRouterApiKey);
|
resetAspect(openRouterApiKey);
|
||||||
resetAspect(openAiCompatApiKey);
|
resetAspect(openAiCompatApiKey);
|
||||||
resetAspect(claudeApiKey);
|
resetAspect(claudeApiKey);
|
||||||
|
resetAspect(openAiApiKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ public:
|
|||||||
Utils::StringAspect openRouterApiKey{this};
|
Utils::StringAspect openRouterApiKey{this};
|
||||||
Utils::StringAspect openAiCompatApiKey{this};
|
Utils::StringAspect openAiCompatApiKey{this};
|
||||||
Utils::StringAspect claudeApiKey{this};
|
Utils::StringAspect claudeApiKey{this};
|
||||||
|
Utils::StringAspect openAiApiKey{this};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupConnections();
|
void setupConnections();
|
||||||
|
|||||||
@@ -46,10 +46,21 @@ const char CA_TEMPLATE[] = "QodeAssist.caTemplate";
|
|||||||
const char CA_URL[] = "QodeAssist.caUrl";
|
const char CA_URL[] = "QodeAssist.caUrl";
|
||||||
const char CA_URL_HISTORY[] = "QodeAssist.caUrlHistory";
|
const char CA_URL_HISTORY[] = "QodeAssist.caUrlHistory";
|
||||||
|
|
||||||
|
const char CC_SPECIFY_PRESET1[] = "QodeAssist.ccSpecifyPreset1";
|
||||||
|
const char CC_PRESET1_LANGUAGE[] = "QodeAssist.ccPreset1Language";
|
||||||
|
const char CC_PRESET1_PROVIDER[] = "QodeAssist.ccPreset1Provider";
|
||||||
|
const char CC_PRESET1_MODEL[] = "QodeAssist.ccPreset1Model";
|
||||||
|
const char CC_PRESET1_MODEL_HISTORY[] = "QodeAssist.ccPreset1ModelHistory";
|
||||||
|
const char CC_PRESET1_TEMPLATE[] = "QodeAssist.ccPreset1Template";
|
||||||
|
const char CC_PRESET1_URL[] = "QodeAssist.ccPreset1Url";
|
||||||
|
const char CC_PRESET1_URL_HISTORY[] = "QodeAssist.ccPreset1UrlHistory";
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
const char ENABLE_QODE_ASSIST[] = "QodeAssist.enableQodeAssist";
|
const char ENABLE_QODE_ASSIST[] = "QodeAssist.enableQodeAssist";
|
||||||
const char CC_AUTO_COMPLETION[] = "QodeAssist.ccAutoCompletion";
|
const char CC_AUTO_COMPLETION[] = "QodeAssist.ccAutoCompletion";
|
||||||
const char ENABLE_LOGGING[] = "QodeAssist.enableLogging";
|
const char ENABLE_LOGGING[] = "QodeAssist.enableLogging";
|
||||||
|
const char ENABLE_CHECK_UPDATE[] = "QodeAssist.enableCheckUpdate";
|
||||||
|
|
||||||
const char PROVIDER_PATHS[] = "QodeAssist.providerPaths";
|
const char PROVIDER_PATHS[] = "QodeAssist.providerPaths";
|
||||||
const char СС_START_SUGGESTION_TIMER[] = "QodeAssist.startSuggestionTimer";
|
const char СС_START_SUGGESTION_TIMER[] = "QodeAssist.startSuggestionTimer";
|
||||||
const char СС_AUTO_COMPLETION_CHAR_THRESHOLD[] = "QodeAssist.autoCompletionCharThreshold";
|
const char СС_AUTO_COMPLETION_CHAR_THRESHOLD[] = "QodeAssist.autoCompletionCharThreshold";
|
||||||
@@ -60,7 +71,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";
|
||||||
|
|
||||||
@@ -87,6 +98,8 @@ const char OPEN_AI_COMPAT_API_KEY[] = "QodeAssist.openAiCompatApiKey";
|
|||||||
const char OPEN_AI_COMPAT_API_KEY_HISTORY[] = "QodeAssist.openAiCompatApiKeyHistory";
|
const char OPEN_AI_COMPAT_API_KEY_HISTORY[] = "QodeAssist.openAiCompatApiKeyHistory";
|
||||||
const char CLAUDE_API_KEY[] = "QodeAssist.claudeApiKey";
|
const char CLAUDE_API_KEY[] = "QodeAssist.claudeApiKey";
|
||||||
const char CLAUDE_API_KEY_HISTORY[] = "QodeAssist.claudeApiKeyHistory";
|
const char CLAUDE_API_KEY_HISTORY[] = "QodeAssist.claudeApiKeyHistory";
|
||||||
|
const char OPEN_AI_API_KEY[] = "QodeAssist.openAiApiKey";
|
||||||
|
const char OPEN_AI_API_KEY_HISTORY[] = "QodeAssist.openAiApiKeyHistory";
|
||||||
|
|
||||||
// context settings
|
// context settings
|
||||||
const char CC_READ_FULL_FILE[] = "QodeAssist.ccReadFullFile";
|
const char CC_READ_FULL_FILE[] = "QodeAssist.ccReadFullFile";
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ inline const char *ENABLE_QODE_ASSIST = QT_TRANSLATE_NOOP("QtC::QodeAssist", "En
|
|||||||
inline const char *GENERAL = QT_TRANSLATE_NOOP("QtC::QodeAssist", "General");
|
inline const char *GENERAL = QT_TRANSLATE_NOOP("QtC::QodeAssist", "General");
|
||||||
inline const char *RESET_TO_DEFAULTS = QT_TRANSLATE_NOOP("QtC::QodeAssist",
|
inline const char *RESET_TO_DEFAULTS = QT_TRANSLATE_NOOP("QtC::QodeAssist",
|
||||||
"Reset Page to Defaults");
|
"Reset Page to Defaults");
|
||||||
|
inline const char *CHECK_UPDATE = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Check Update");
|
||||||
inline const char *SELECT = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Select...");
|
inline const char *SELECT = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Select...");
|
||||||
inline const char *PROVIDER = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Provider:");
|
inline const char *PROVIDER = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Provider:");
|
||||||
inline const char *MODEL = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Model:");
|
inline const char *MODEL = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Model:");
|
||||||
@@ -36,6 +37,9 @@ inline const char *URL = QT_TRANSLATE_NOOP("QtC::QodeAssist", "URL:");
|
|||||||
inline const char *STATUS = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Status:");
|
inline const char *STATUS = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Status:");
|
||||||
inline const char *TEST = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Test");
|
inline const char *TEST = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Test");
|
||||||
inline const char *ENABLE_LOG = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Enable Logging");
|
inline const char *ENABLE_LOG = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Enable Logging");
|
||||||
|
inline const char *ENABLE_CHECK_UPDATE_ON_START
|
||||||
|
= QT_TRANSLATE_NOOP("QtC::QodeAssist", "Check for updates when Qt Creator starts");
|
||||||
|
|
||||||
inline const char *CODE_COMPLETION = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Code Completion");
|
inline const char *CODE_COMPLETION = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Code Completion");
|
||||||
inline const char *CHAT_ASSISTANT = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Chat Assistant");
|
inline const char *CHAT_ASSISTANT = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Chat Assistant");
|
||||||
inline const char *RESET_SETTINGS = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Reset Settings");
|
inline const char *RESET_SETTINGS = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Reset Settings");
|
||||||
@@ -82,6 +86,9 @@ inline const char ENTER_MODEL_MANUALLY_BUTTON[]
|
|||||||
inline const char AUTO_COMPLETION_SETTINGS[]
|
inline const char AUTO_COMPLETION_SETTINGS[]
|
||||||
= QT_TRANSLATE_NOOP("QtC::QodeAssist", "Auto Completion Settings");
|
= QT_TRANSLATE_NOOP("QtC::QodeAssist", "Auto Completion Settings");
|
||||||
|
|
||||||
|
inline const char ADD_NEW_PRESET[]
|
||||||
|
= QT_TRANSLATE_NOOP("QtC::QodeAssist", "Add new preset for language");
|
||||||
|
|
||||||
} // namespace TrConstants
|
} // namespace TrConstants
|
||||||
|
|
||||||
struct Tr
|
struct Tr
|
||||||
|
|||||||
210
settings/UpdateDialog.cpp
Normal file
210
settings/UpdateDialog.cpp
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
/*
|
||||||
|
* 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 "UpdateDialog.hpp"
|
||||||
|
|
||||||
|
#include <coreplugin/icore.h>
|
||||||
|
#include <extensionsystem/pluginmanager.h>
|
||||||
|
#include <extensionsystem/pluginspec.h>
|
||||||
|
#include <QDesktopServices>
|
||||||
|
|
||||||
|
namespace QodeAssist {
|
||||||
|
|
||||||
|
UpdateDialog::UpdateDialog(QWidget *parent)
|
||||||
|
: QDialog(parent)
|
||||||
|
, m_updater(new PluginUpdater(this))
|
||||||
|
{
|
||||||
|
setWindowTitle(tr("QodeAssist Update"));
|
||||||
|
setMinimumWidth(400);
|
||||||
|
setMinimumHeight(500);
|
||||||
|
|
||||||
|
m_layout = new QVBoxLayout(this);
|
||||||
|
m_layout->setSpacing(12);
|
||||||
|
|
||||||
|
auto *supportLabel = new QLabel(
|
||||||
|
tr("QodeAssist is an open-source project that helps\n"
|
||||||
|
"developers write better code. If you find it useful, please"),
|
||||||
|
this);
|
||||||
|
supportLabel->setAlignment(Qt::AlignCenter);
|
||||||
|
m_layout->addWidget(supportLabel);
|
||||||
|
|
||||||
|
auto *supportLink = new QLabel(
|
||||||
|
tr("<a href='https://ko-fi.com/qodeassist' style='color: #0066cc;'>Support on Ko-fi "
|
||||||
|
"☕</a>"),
|
||||||
|
this);
|
||||||
|
supportLink->setOpenExternalLinks(true);
|
||||||
|
supportLink->setTextFormat(Qt::RichText);
|
||||||
|
supportLink->setAlignment(Qt::AlignCenter);
|
||||||
|
m_layout->addWidget(supportLink);
|
||||||
|
|
||||||
|
m_layout->addSpacing(20);
|
||||||
|
|
||||||
|
m_titleLabel = new QLabel(tr("A new version of QodeAssist is available!"), this);
|
||||||
|
m_titleLabel->setStyleSheet("font-weight: bold; font-size: 14px;");
|
||||||
|
m_titleLabel->setAlignment(Qt::AlignCenter);
|
||||||
|
m_layout->addWidget(m_titleLabel);
|
||||||
|
|
||||||
|
m_versionLabel = new QLabel(
|
||||||
|
tr("Version %1 is now available - you have %2").arg(m_updater->currentVersion()), this);
|
||||||
|
m_versionLabel->setAlignment(Qt::AlignCenter);
|
||||||
|
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) {
|
||||||
|
m_changelogLabel = new QLabel(tr("Release Notes:"), this);
|
||||||
|
m_layout->addWidget(m_changelogLabel);
|
||||||
|
|
||||||
|
m_changelogText = new QTextEdit(this);
|
||||||
|
m_changelogText->setReadOnly(true);
|
||||||
|
m_changelogText->setMinimumHeight(100);
|
||||||
|
m_layout->addWidget(m_changelogText);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_progress = new QProgressBar(this);
|
||||||
|
m_progress->setVisible(false);
|
||||||
|
m_layout->addWidget(m_progress);
|
||||||
|
|
||||||
|
auto *buttonLayout = new QHBoxLayout;
|
||||||
|
m_downloadButton = new QPushButton(tr("Download"), this);
|
||||||
|
m_downloadButton->setEnabled(false);
|
||||||
|
buttonLayout->addWidget(m_downloadButton);
|
||||||
|
|
||||||
|
m_closeButton = new QPushButton(tr("Close"), this);
|
||||||
|
buttonLayout->addWidget(m_closeButton);
|
||||||
|
|
||||||
|
m_layout->addLayout(buttonLayout);
|
||||||
|
|
||||||
|
connect(m_updater, &PluginUpdater::updateCheckFinished, this, &UpdateDialog::handleUpdateInfo);
|
||||||
|
connect(m_updater, &PluginUpdater::downloadProgress, this, &UpdateDialog::updateProgress);
|
||||||
|
connect(m_updater, &PluginUpdater::downloadFinished, this, &UpdateDialog::handleDownloadFinished);
|
||||||
|
connect(m_updater, &PluginUpdater::downloadError, this, &UpdateDialog::handleDownloadError);
|
||||||
|
|
||||||
|
connect(m_downloadButton, &QPushButton::clicked, this, &UpdateDialog::startDownload);
|
||||||
|
connect(m_closeButton, &QPushButton::clicked, this, &QDialog::reject);
|
||||||
|
|
||||||
|
m_updater->checkForUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateDialog::checkForUpdatesAndShow(QWidget *parent)
|
||||||
|
{
|
||||||
|
auto *dialog = new UpdateDialog(parent);
|
||||||
|
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
dialog->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
m_titleLabel->setText(tr("Incompatible Qt Creator Version"));
|
||||||
|
m_versionLabel->setText(tr("This update requires Qt Creator %1, current is %2.\n"
|
||||||
|
"Please upgrade Qt Creator to use this version of QodeAssist.")
|
||||||
|
.arg(info.targetIdeVersion, info.currentIdeVersion));
|
||||||
|
m_downloadButton->setEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!info.isUpdateAvailable) {
|
||||||
|
m_titleLabel->setText(tr("QodeAssist is up to date"));
|
||||||
|
m_downloadButton->setEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_titleLabel->setText(tr("A new version of QodeAssist is available!"));
|
||||||
|
m_versionLabel->setText(tr("Version %1 is now available - you have %2")
|
||||||
|
.arg(info.version, m_updater->currentVersion()));
|
||||||
|
|
||||||
|
if (!info.changeLog.isEmpty()) {
|
||||||
|
if (!m_changelogLabel) {
|
||||||
|
m_changelogLabel = new QLabel(tr("Release Notes:"), this);
|
||||||
|
m_layout->insertWidget(2, m_changelogLabel);
|
||||||
|
|
||||||
|
m_changelogText = new QTextEdit(this);
|
||||||
|
m_changelogText->setReadOnly(true);
|
||||||
|
m_changelogText->setMaximumHeight(200);
|
||||||
|
m_layout->insertWidget(3, m_changelogText);
|
||||||
|
}
|
||||||
|
m_changelogText->setText(info.changeLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_downloadButton->setEnabled(true);
|
||||||
|
m_updateInfo = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateDialog::startDownload()
|
||||||
|
{
|
||||||
|
m_downloadButton->setEnabled(false);
|
||||||
|
m_progress->setVisible(true);
|
||||||
|
m_updater->downloadUpdate(m_updateInfo.downloadUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateDialog::updateProgress(qint64 received, qint64 total)
|
||||||
|
{
|
||||||
|
m_progress->setMaximum(total);
|
||||||
|
m_progress->setValue(received);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateDialog::handleDownloadFinished(const QString &path)
|
||||||
|
{
|
||||||
|
m_progress->setVisible(false);
|
||||||
|
|
||||||
|
QMessageBox msgBox(this);
|
||||||
|
msgBox.setWindowTitle(tr("Update Downloaded"));
|
||||||
|
msgBox.setText(tr("The update has been downloaded successfully.\n"
|
||||||
|
"Would you like to close Qt Creator now and open the plugin folder?"));
|
||||||
|
msgBox.setInformativeText(tr("To complete the update:\n\n"
|
||||||
|
"1. Close Qt Creator completely\n"
|
||||||
|
"2. Navigate to the plugin folder\n"
|
||||||
|
"3. Remove the old version of QodeAssist plugin\n"
|
||||||
|
"4. Install plugin as usual via About plugin menu"));
|
||||||
|
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||||
|
msgBox.setDefaultButton(QMessageBox::Yes);
|
||||||
|
msgBox.setIcon(QMessageBox::Information);
|
||||||
|
|
||||||
|
if (msgBox.exec() == QMessageBox::Yes) {
|
||||||
|
const auto pluginSpecs = ExtensionSystem::PluginManager::plugins();
|
||||||
|
for (const ExtensionSystem::PluginSpec *spec : pluginSpecs) {
|
||||||
|
if (spec->name() == QLatin1String("QodeAssist")) {
|
||||||
|
const auto pluginPath = spec->filePath().path();
|
||||||
|
QFileInfo fileInfo(pluginPath);
|
||||||
|
QDesktopServices::openUrl(QUrl::fromLocalFile(fileInfo.absolutePath()));
|
||||||
|
Core::ICore::exit();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateDialog::handleDownloadError(const QString &error)
|
||||||
|
{
|
||||||
|
m_progress->setVisible(false);
|
||||||
|
m_downloadButton->setEnabled(true);
|
||||||
|
QMessageBox::critical(this, tr("Update Error"), tr("Failed to update: %1").arg(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist
|
||||||
64
settings/UpdateDialog.hpp
Normal file
64
settings/UpdateDialog.hpp
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* 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 <QDialog>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QProgressBar>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QTextEdit>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include "PluginUpdater.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist {
|
||||||
|
|
||||||
|
class UpdateDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit UpdateDialog(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
static void checkForUpdatesAndShow(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void handleUpdateInfo(const PluginUpdater::UpdateInfo &info);
|
||||||
|
void startDownload();
|
||||||
|
void updateProgress(qint64 received, qint64 total);
|
||||||
|
void handleDownloadFinished(const QString &path);
|
||||||
|
void handleDownloadError(const QString &error);
|
||||||
|
|
||||||
|
private:
|
||||||
|
PluginUpdater *m_updater;
|
||||||
|
QVBoxLayout *m_layout;
|
||||||
|
QLabel *m_titleLabel;
|
||||||
|
QLabel *m_versionLabel;
|
||||||
|
QLabel *m_releaseLink;
|
||||||
|
QLabel *m_changelogLabel{nullptr};
|
||||||
|
QTextEdit *m_changelogText{nullptr};
|
||||||
|
QProgressBar *m_progress;
|
||||||
|
QPushButton *m_downloadButton;
|
||||||
|
QPushButton *m_closeButton;
|
||||||
|
PluginUpdater::UpdateInfo m_updateInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist
|
||||||
48
templates/CodeLlamaQMLFim.hpp
Normal file
48
templates/CodeLlamaQMLFim.hpp
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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 "llmcore/PromptTemplate.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Templates {
|
||||||
|
|
||||||
|
class CodeLlamaQMLFim : public LLMCore::PromptTemplate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
|
||||||
|
QString name() const override { return "CodeLlama QML FIM"; }
|
||||||
|
QString promptTemplate() const override { return "<SUF>%1<PRE>%2<MID>"; }
|
||||||
|
QStringList stopWords() const override
|
||||||
|
{
|
||||||
|
return QStringList() << "<SUF>" << "<PRE>" << "</PRE>" << "</SUF>" << "< EOT >" << "\\end"
|
||||||
|
<< "<MID>" << "</MID>" << "##";
|
||||||
|
}
|
||||||
|
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||||
|
{
|
||||||
|
QString formattedPrompt = promptTemplate().arg(context.suffix, context.prefix);
|
||||||
|
request["prompt"] = formattedPrompt;
|
||||||
|
}
|
||||||
|
QString description() const override
|
||||||
|
{
|
||||||
|
return "The message will contain the following tokens: <SUF>%1<PRE>%2<MID>";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Templates
|
||||||
39
templates/OpenAI.hpp
Normal file
39
templates/OpenAI.hpp
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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 <QJsonArray>
|
||||||
|
|
||||||
|
#include "llmcore/PromptTemplate.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Templates {
|
||||||
|
|
||||||
|
class OpenAI : public LLMCore::PromptTemplate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||||
|
QString name() const override { return "OpenAI"; }
|
||||||
|
QString promptTemplate() const override { return {}; }
|
||||||
|
QStringList stopWords() const override { return QStringList(); }
|
||||||
|
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override {}
|
||||||
|
QString description() const override { return "OpenAI"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Templates
|
||||||
@@ -25,11 +25,13 @@
|
|||||||
#include "templates/ChatML.hpp"
|
#include "templates/ChatML.hpp"
|
||||||
#include "templates/Claude.hpp"
|
#include "templates/Claude.hpp"
|
||||||
#include "templates/CodeLlamaFim.hpp"
|
#include "templates/CodeLlamaFim.hpp"
|
||||||
|
#include "templates/CodeLlamaQMLFim.hpp"
|
||||||
#include "templates/CustomFimTemplate.hpp"
|
#include "templates/CustomFimTemplate.hpp"
|
||||||
#include "templates/DeepSeekCoderFim.hpp"
|
#include "templates/DeepSeekCoderFim.hpp"
|
||||||
#include "templates/Llama2.hpp"
|
#include "templates/Llama2.hpp"
|
||||||
#include "templates/Llama3.hpp"
|
#include "templates/Llama3.hpp"
|
||||||
#include "templates/Ollama.hpp"
|
#include "templates/Ollama.hpp"
|
||||||
|
#include "templates/OpenAI.hpp"
|
||||||
#include "templates/Qwen.hpp"
|
#include "templates/Qwen.hpp"
|
||||||
#include "templates/StarCoder2Fim.hpp"
|
#include "templates/StarCoder2Fim.hpp"
|
||||||
|
|
||||||
@@ -51,6 +53,8 @@ inline void registerTemplates()
|
|||||||
templateManager.registerTemplate<Alpaca>();
|
templateManager.registerTemplate<Alpaca>();
|
||||||
templateManager.registerTemplate<Llama2>();
|
templateManager.registerTemplate<Llama2>();
|
||||||
templateManager.registerTemplate<Claude>();
|
templateManager.registerTemplate<Claude>();
|
||||||
|
templateManager.registerTemplate<OpenAI>();
|
||||||
|
templateManager.registerTemplate<CodeLlamaQMLFim>();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Templates
|
} // namespace QodeAssist::Templates
|
||||||
|
|||||||
Reference in New Issue
Block a user