mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-06-14 02:09:22 -04:00
Compare commits
61 Commits
v0.4.2
...
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 | ||
|
|
511f5b36eb | ||
|
|
35012865c7 | ||
|
|
f27429aa66 | ||
|
|
113d5adcf4 | ||
|
|
30ea89cdc2 | ||
|
|
13469edce6 | ||
|
|
ee2c3950e8 | ||
|
|
d04e5bc967 | ||
|
|
d8ef9d0120 | ||
|
|
e544e46d76 | ||
|
|
63f0900511 | ||
|
|
7dee6f62c0 |
14
.github/ISSUE_TEMPLATE/bug_report.md
vendored
14
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -2,7 +2,7 @@
|
|||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
title: ''
|
title: ''
|
||||||
labels: ''
|
labels: bug
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -23,16 +23,6 @@ A clear and concise description of what you expected to happen.
|
|||||||
**Screenshots**
|
**Screenshots**
|
||||||
If applicable, add screenshots to help explain your problem.
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
**Log**
|
||||||
- OS: [e.g. iOS]
|
|
||||||
- Browser [e.g. chrome, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Smartphone (please complete the following information):**
|
|
||||||
- Device: [e.g. iPhone6]
|
|
||||||
- OS: [e.g. iOS8.1]
|
|
||||||
- Browser [e.g. stock browser, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
Add any other context about the problem here.
|
Add any other context about the problem here.
|
||||||
|
|||||||
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"
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ add_subdirectory(llmcore)
|
|||||||
add_subdirectory(settings)
|
add_subdirectory(settings)
|
||||||
add_subdirectory(logger)
|
add_subdirectory(logger)
|
||||||
add_subdirectory(ChatView)
|
add_subdirectory(ChatView)
|
||||||
|
add_subdirectory(context)
|
||||||
|
|
||||||
add_qtc_plugin(QodeAssist
|
add_qtc_plugin(QodeAssist
|
||||||
PLUGIN_DEPENDS
|
PLUGIN_DEPENDS
|
||||||
@@ -52,20 +53,23 @@ add_qtc_plugin(QodeAssist
|
|||||||
templates/ChatML.hpp
|
templates/ChatML.hpp
|
||||||
templates/Alpaca.hpp
|
templates/Alpaca.hpp
|
||||||
templates/Llama2.hpp
|
templates/Llama2.hpp
|
||||||
|
templates/Claude.hpp
|
||||||
|
templates/OpenAI.hpp
|
||||||
|
templates/CodeLlamaQMLFim.hpp
|
||||||
providers/Providers.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/OpenAIProvider.hpp providers/OpenAIProvider.cpp
|
||||||
QodeAssist.qrc
|
QodeAssist.qrc
|
||||||
LSPCompletion.hpp
|
LSPCompletion.hpp
|
||||||
LLMSuggestion.hpp LLMSuggestion.cpp
|
LLMSuggestion.hpp LLMSuggestion.cpp
|
||||||
QodeAssistClient.hpp QodeAssistClient.cpp
|
QodeAssistClient.hpp QodeAssistClient.cpp
|
||||||
DocumentContextReader.hpp DocumentContextReader.cpp
|
|
||||||
utils/CounterTooltip.hpp utils/CounterTooltip.cpp
|
|
||||||
core/ChangesManager.h core/ChangesManager.cpp
|
|
||||||
chat/ChatOutputPane.h chat/ChatOutputPane.cpp
|
chat/ChatOutputPane.h chat/ChatOutputPane.cpp
|
||||||
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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
qt_add_library(QodeAssistChatView STATIC)
|
qt_add_library(QodeAssistChatView STATIC)
|
||||||
|
|
||||||
qt_policy(SET QTP0001 NEW)
|
qt_policy(SET QTP0001 NEW)
|
||||||
|
qt_policy(SET QTP0004 NEW)
|
||||||
|
|
||||||
# URI name should match the subdirectory name to suppress the warning
|
|
||||||
qt_add_qml_module(QodeAssistChatView
|
qt_add_qml_module(QodeAssistChatView
|
||||||
URI ChatView
|
URI ChatView
|
||||||
VERSION 1.0
|
VERSION 1.0
|
||||||
@@ -13,6 +13,17 @@ qt_add_qml_module(QodeAssistChatView
|
|||||||
qml/Badge.qml
|
qml/Badge.qml
|
||||||
qml/dialog/CodeBlock.qml
|
qml/dialog/CodeBlock.qml
|
||||||
qml/dialog/TextBlock.qml
|
qml/dialog/TextBlock.qml
|
||||||
|
qml/controls/QoAButton.qml
|
||||||
|
qml/parts/TopBar.qml
|
||||||
|
qml/parts/BottomBar.qml
|
||||||
|
qml/parts/AttachedFilesPlace.qml
|
||||||
|
RESOURCES
|
||||||
|
icons/attach-file-light.svg
|
||||||
|
icons/attach-file-dark.svg
|
||||||
|
icons/close-dark.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
|
||||||
@@ -20,6 +31,7 @@ qt_add_qml_module(QodeAssistChatView
|
|||||||
ClientInterface.hpp ClientInterface.cpp
|
ClientInterface.hpp ClientInterface.cpp
|
||||||
MessagePart.hpp
|
MessagePart.hpp
|
||||||
ChatUtils.h ChatUtils.cpp
|
ChatUtils.h ChatUtils.cpp
|
||||||
|
ChatSerializer.hpp ChatSerializer.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(QodeAssistChatView
|
target_link_libraries(QodeAssistChatView
|
||||||
@@ -32,6 +44,7 @@ target_link_libraries(QodeAssistChatView
|
|||||||
QtCreator::Utils
|
QtCreator::Utils
|
||||||
LLMCore
|
LLMCore
|
||||||
QodeAssistSettings
|
QodeAssistSettings
|
||||||
|
Context
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(QodeAssistChatView
|
target_include_directories(QodeAssistChatView
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
@@ -55,6 +54,13 @@ QVariant ChatModel::data(const QModelIndex &index, int role) const
|
|||||||
case Roles::Content: {
|
case Roles::Content: {
|
||||||
return message.content;
|
return message.content;
|
||||||
}
|
}
|
||||||
|
case Roles::Attachments: {
|
||||||
|
QStringList filenames;
|
||||||
|
for (const auto &attachment : message.attachments) {
|
||||||
|
filenames << attachment.filename;
|
||||||
|
}
|
||||||
|
return filenames;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
@@ -65,29 +71,37 @@ QHash<int, QByteArray> ChatModel::roleNames() const
|
|||||||
QHash<int, QByteArray> roles;
|
QHash<int, QByteArray> roles;
|
||||||
roles[Roles::RoleType] = "roleType";
|
roles[Roles::RoleType] = "roleType";
|
||||||
roles[Roles::Content] = "content";
|
roles[Roles::Content] = "content";
|
||||||
|
roles[Roles::Attachments] = "attachments";
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatModel::addMessage(const QString &content, ChatRole role, const QString &id)
|
void ChatModel::addMessage(
|
||||||
|
const QString &content,
|
||||||
|
ChatRole role,
|
||||||
|
const QString &id,
|
||||||
|
const QList<Context::ContentFile> &attachments)
|
||||||
{
|
{
|
||||||
int tokenCount = estimateTokenCount(content);
|
QString fullContent = content;
|
||||||
|
if (!attachments.isEmpty()) {
|
||||||
|
fullContent += "\n\nAttached files list:";
|
||||||
|
for (const auto &attachment : attachments) {
|
||||||
|
fullContent += QString("\nname: %1\nfile content:\n%2")
|
||||||
|
.arg(attachment.filename, attachment.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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.tokenCount = tokenCount;
|
lastMessage.attachments = attachments;
|
||||||
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());
|
||||||
m_messages.append({role, content, tokenCount, id});
|
Message newMessage{role, content, id};
|
||||||
m_totalTokens += tokenCount;
|
newMessage.attachments = attachments;
|
||||||
|
m_messages.append(newMessage);
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
trim();
|
|
||||||
emit totalTokensChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<ChatModel::Message> ChatModel::getChatHistory() const
|
QVector<ChatModel::Message> ChatModel::getChatHistory() const
|
||||||
@@ -95,32 +109,12 @@ QVector<ChatModel::Message> ChatModel::getChatHistory() const
|
|||||||
return m_messages;
|
return m_messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatModel::trim()
|
|
||||||
{
|
|
||||||
while (m_totalTokens > tokensThreshold()) {
|
|
||||||
if (!m_messages.isEmpty()) {
|
|
||||||
m_totalTokens -= m_messages.first().tokenCount;
|
|
||||||
beginRemoveRows(QModelIndex(), 0, 0);
|
|
||||||
m_messages.removeFirst();
|
|
||||||
endRemoveRows();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int ChatModel::estimateTokenCount(const QString &text) const
|
|
||||||
{
|
|
||||||
return text.length() / 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatModel::clear()
|
void ChatModel::clear()
|
||||||
{
|
{
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
m_messages.clear();
|
m_messages.clear();
|
||||||
m_totalTokens = 0;
|
|
||||||
endResetModel();
|
endResetModel();
|
||||||
emit totalTokensChanged();
|
emit modelReseted();
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<MessagePart> ChatModel::processMessageContent(const QString &content) const
|
QList<MessagePart> ChatModel::processMessageContent(const QString &content) const
|
||||||
@@ -155,7 +149,6 @@ QList<MessagePart> ChatModel::processMessageContent(const QString &content) cons
|
|||||||
QJsonArray ChatModel::prepareMessagesForRequest(const QString &systemPrompt) const
|
QJsonArray ChatModel::prepareMessagesForRequest(const QString &systemPrompt) const
|
||||||
{
|
{
|
||||||
QJsonArray messages;
|
QJsonArray messages;
|
||||||
|
|
||||||
messages.append(QJsonObject{{"role", "system"}, {"content", systemPrompt}});
|
messages.append(QJsonObject{{"role", "system"}, {"content", systemPrompt}});
|
||||||
|
|
||||||
for (const auto &message : m_messages) {
|
for (const auto &message : m_messages) {
|
||||||
@@ -170,17 +163,27 @@ QJsonArray ChatModel::prepareMessagesForRequest(const QString &systemPrompt) con
|
|||||||
default:
|
default:
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
messages.append(QJsonObject{{"role", role}, {"content", message.content}});
|
|
||||||
|
QString content
|
||||||
|
= message.attachments.isEmpty()
|
||||||
|
? message.content
|
||||||
|
: message.content + "\n\nAttached files list:"
|
||||||
|
+ std::accumulate(
|
||||||
|
message.attachments.begin(),
|
||||||
|
message.attachments.end(),
|
||||||
|
QString(),
|
||||||
|
[](QString acc, const Context::ContentFile &attachment) {
|
||||||
|
return acc
|
||||||
|
+ QString("\nname: %1\nfile content:\n%2")
|
||||||
|
.arg(attachment.filename, attachment.content);
|
||||||
|
});
|
||||||
|
|
||||||
|
messages.append(QJsonObject{{"role", role}, {"content", content}});
|
||||||
}
|
}
|
||||||
|
|
||||||
return messages;
|
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();
|
||||||
|
|||||||
@@ -26,27 +26,29 @@
|
|||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QtQmlIntegration>
|
#include <QtQmlIntegration>
|
||||||
|
|
||||||
|
#include "context/ContentFile.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
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
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Roles { RoleType = Qt::UserRole, Content };
|
|
||||||
|
|
||||||
enum ChatRole { System, User, Assistant };
|
enum ChatRole { System, User, Assistant };
|
||||||
Q_ENUM(ChatRole)
|
Q_ENUM(ChatRole)
|
||||||
|
|
||||||
|
enum Roles { RoleType = Qt::UserRole, Content, Attachments };
|
||||||
|
|
||||||
struct Message
|
struct Message
|
||||||
{
|
{
|
||||||
ChatRole role;
|
ChatRole role;
|
||||||
QString content;
|
QString content;
|
||||||
int tokenCount;
|
|
||||||
QString id;
|
QString id;
|
||||||
|
|
||||||
|
QList<Context::ContentFile> attachments;
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit ChatModel(QObject *parent = nullptr);
|
explicit ChatModel(QObject *parent = nullptr);
|
||||||
@@ -55,29 +57,28 @@ public:
|
|||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
Q_INVOKABLE void addMessage(const QString &content, ChatRole role, const QString &id);
|
Q_INVOKABLE void addMessage(
|
||||||
|
const QString &content,
|
||||||
|
ChatRole role,
|
||||||
|
const QString &id,
|
||||||
|
const QList<Context::ContentFile> &attachments = {});
|
||||||
Q_INVOKABLE void clear();
|
Q_INVOKABLE void clear();
|
||||||
Q_INVOKABLE QList<MessagePart> processMessageContent(const QString &content) const;
|
Q_INVOKABLE QList<MessagePart> processMessageContent(const QString &content) const;
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void trim();
|
|
||||||
int estimateTokenCount(const QString &text) const;
|
|
||||||
|
|
||||||
QVector<Message> m_messages;
|
QVector<Message> m_messages;
|
||||||
int m_totalTokens = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
|||||||
@@ -18,12 +18,30 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ChatRootView.hpp"
|
#include "ChatRootView.hpp"
|
||||||
#include <QtGui/qclipboard.h>
|
|
||||||
|
#include <QClipboard>
|
||||||
|
#include <QDesktopServices>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
|
#include <coreplugin/icore.h>
|
||||||
|
#include <projectexplorer/project.h>
|
||||||
|
#include <projectexplorer/projectexplorer.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>
|
||||||
|
|
||||||
#include "ChatAssistantSettings.hpp"
|
#include "ChatAssistantSettings.hpp"
|
||||||
|
#include "ChatSerializer.hpp"
|
||||||
#include "GeneralSettings.hpp"
|
#include "GeneralSettings.hpp"
|
||||||
|
#include "Logger.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 {
|
||||||
|
|
||||||
@@ -32,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,
|
||||||
@@ -39,12 +64,44 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
|||||||
this,
|
this,
|
||||||
&ChatRootView::currentTemplateChanged);
|
&ChatRootView::currentTemplateChanged);
|
||||||
|
|
||||||
connect(&Settings::chatAssistantSettings().sharingCurrentFile,
|
connect(
|
||||||
&Utils::BaseAspect::changed,
|
m_clientInterface,
|
||||||
this,
|
&ClientInterface::messageReceivedCompletely,
|
||||||
&ChatRootView::isSharingCurrentFileChanged);
|
this,
|
||||||
|
&ChatRootView::autosave);
|
||||||
|
|
||||||
generateColors();
|
connect(
|
||||||
|
m_clientInterface,
|
||||||
|
&ClientInterface::messageReceivedCompletely,
|
||||||
|
this,
|
||||||
|
&ChatRootView::updateInputTokensCount);
|
||||||
|
|
||||||
|
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
|
||||||
@@ -52,14 +109,26 @@ 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()) {
|
||||||
}
|
QMessageBox::StandardButton reply = QMessageBox::question(
|
||||||
|
Core::ICore::dialogParent(),
|
||||||
|
tr("Token Limit Exceeded"),
|
||||||
|
tr("The chat history has exceeded the token limit.\n"
|
||||||
|
"Would you like to create new chat?"),
|
||||||
|
QMessageBox::Yes | QMessageBox::No);
|
||||||
|
|
||||||
void ChatRootView::sendMessage(const QString &message, bool sharingCurrentFile) const
|
if (reply == QMessageBox::Yes) {
|
||||||
{
|
autosave();
|
||||||
m_clientInterface->sendMessage(message, sharingCurrentFile);
|
m_chatModel->clear();
|
||||||
|
setRecentFilePath(QString{});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_clientInterface->sendMessage(message, m_attachmentFiles, m_linkedFiles);
|
||||||
|
clearAttachmentFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatRootView::copyToClipboard(const QString &text)
|
void ChatRootView::copyToClipboard(const QString &text)
|
||||||
@@ -72,47 +141,40 @@ void ChatRootView::cancelRequest()
|
|||||||
m_clientInterface->cancelRequest();
|
m_clientInterface->cancelRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatRootView::generateColors()
|
void ChatRootView::clearAttachmentFiles()
|
||||||
{
|
{
|
||||||
QColor baseColor = backgroundColor();
|
if (!m_attachmentFiles.isEmpty()) {
|
||||||
bool isDarkTheme = baseColor.lightness() < 128;
|
m_attachmentFiles.clear();
|
||||||
|
emit attachmentFilesChanged();
|
||||||
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,
|
void ChatRootView::clearLinkedFiles()
|
||||||
float hueShift,
|
|
||||||
float saturationMod,
|
|
||||||
float lightnessMod)
|
|
||||||
{
|
{
|
||||||
float h, s, l, a;
|
if (!m_linkedFiles.isEmpty()) {
|
||||||
baseColor.getHslF(&h, &s, &l, &a);
|
m_linkedFiles.clear();
|
||||||
bool isDarkTheme = l < 0.5;
|
emit linkedFilesChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
h = fmod(h + hueShift + 1.0, 1.0);
|
QString ChatRootView::getChatsHistoryDir() const
|
||||||
|
{
|
||||||
|
QString path;
|
||||||
|
|
||||||
s = qBound(0.0f, s * saturationMod, 1.0f);
|
if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
|
||||||
|
Settings::ProjectSettings projectSettings(project);
|
||||||
if (isDarkTheme) {
|
path = projectSettings.chatHistoryPath().toString();
|
||||||
l = qBound(0.0f, l * lightnessMod, 1.0f);
|
|
||||||
} else {
|
} else {
|
||||||
l = qBound(0.0f, l / lightnessMod, 1.0f);
|
path = QString("%1/qodeassist/chat_history").arg(Core::ICore::userResourcePath().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
h = qBound(0.0f, h, 1.0f);
|
QDir dir(path);
|
||||||
s = qBound(0.0f, s, 1.0f);
|
if (!dir.exists() && !dir.mkpath(".")) {
|
||||||
l = qBound(0.0f, l, 1.0f);
|
LOG_MESSAGE(QString("Failed to create directory: %1").arg(path));
|
||||||
a = qBound(0.0f, a, 1.0f);
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
return QColor::fromHslF(h, s, l, a);
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ChatRootView::currentTemplate() const
|
QString ChatRootView::currentTemplate() const
|
||||||
@@ -121,24 +183,417 @@ QString ChatRootView::currentTemplate() const
|
|||||||
return settings.caModel();
|
return settings.caModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
QColor ChatRootView::primaryColor() const
|
void ChatRootView::saveHistory(const QString &filePath)
|
||||||
{
|
{
|
||||||
return m_primaryColor;
|
auto result = ChatSerializer::saveToFile(m_chatModel, filePath);
|
||||||
|
if (!result.success) {
|
||||||
|
LOG_MESSAGE(QString("Failed to save chat history: %1").arg(result.errorMessage));
|
||||||
|
} else {
|
||||||
|
setRecentFilePath(filePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QColor ChatRootView::secondaryColor() const
|
void ChatRootView::loadHistory(const QString &filePath)
|
||||||
{
|
{
|
||||||
return m_secondaryColor;
|
auto result = ChatSerializer::loadFromFile(m_chatModel, filePath);
|
||||||
|
if (!result.success) {
|
||||||
|
LOG_MESSAGE(QString("Failed to load chat history: %1").arg(result.errorMessage));
|
||||||
|
} else {
|
||||||
|
setRecentFilePath(filePath);
|
||||||
|
}
|
||||||
|
updateInputTokensCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
QColor ChatRootView::codeColor() const
|
void ChatRootView::showSaveDialog()
|
||||||
{
|
{
|
||||||
return m_codeColor;
|
QString initialDir = getChatsHistoryDir();
|
||||||
|
|
||||||
|
QFileDialog *dialog = new QFileDialog(nullptr, tr("Save Chat History"));
|
||||||
|
dialog->setAcceptMode(QFileDialog::AcceptSave);
|
||||||
|
dialog->setFileMode(QFileDialog::AnyFile);
|
||||||
|
dialog->setNameFilter(tr("JSON files (*.json)"));
|
||||||
|
dialog->setDefaultSuffix("json");
|
||||||
|
if (!initialDir.isEmpty()) {
|
||||||
|
dialog->setDirectory(initialDir);
|
||||||
|
dialog->selectFile(getSuggestedFileName() + ".json");
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(dialog, &QFileDialog::finished, this, [this, dialog](int result) {
|
||||||
|
if (result == QFileDialog::Accepted) {
|
||||||
|
QStringList files = dialog->selectedFiles();
|
||||||
|
if (!files.isEmpty()) {
|
||||||
|
saveHistory(files.first());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialog->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog->open();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChatRootView::isSharingCurrentFile() const
|
void ChatRootView::showLoadDialog()
|
||||||
{
|
{
|
||||||
return Settings::chatAssistantSettings().sharingCurrentFile();
|
QString initialDir = getChatsHistoryDir();
|
||||||
|
|
||||||
|
QFileDialog *dialog = new QFileDialog(nullptr, tr("Load Chat History"));
|
||||||
|
dialog->setAcceptMode(QFileDialog::AcceptOpen);
|
||||||
|
dialog->setFileMode(QFileDialog::ExistingFile);
|
||||||
|
dialog->setNameFilter(tr("JSON files (*.json)"));
|
||||||
|
if (!initialDir.isEmpty()) {
|
||||||
|
dialog->setDirectory(initialDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(dialog, &QFileDialog::finished, this, [this, dialog](int result) {
|
||||||
|
if (result == QFileDialog::Accepted) {
|
||||||
|
QStringList files = dialog->selectedFiles();
|
||||||
|
if (!files.isEmpty()) {
|
||||||
|
loadHistory(files.first());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialog->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog->open();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ChatRootView::getSuggestedFileName() const
|
||||||
|
{
|
||||||
|
QStringList parts;
|
||||||
|
|
||||||
|
static const QRegularExpression saitizeSymbols = QRegularExpression("[\\/:*?\"<>|\\s]");
|
||||||
|
static const QRegularExpression underSymbols = QRegularExpression("_+");
|
||||||
|
|
||||||
|
if (m_chatModel->rowCount() > 0) {
|
||||||
|
QString firstMessage
|
||||||
|
= m_chatModel->data(m_chatModel->index(0), ChatModel::Content).toString();
|
||||||
|
QString shortMessage = firstMessage.split('\n').first().simplified().left(30);
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
if (m_chatModel->rowCount() == 0 || !Settings::chatAssistantSettings().autosave()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString filePath = getAutosaveFilePath();
|
||||||
|
if (!filePath.isEmpty()) {
|
||||||
|
ChatSerializer::saveToFile(m_chatModel, filePath);
|
||||||
|
setRecentFilePath(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ChatRootView::getAutosaveFilePath() const
|
||||||
|
{
|
||||||
|
if (!m_recentFilePath.isEmpty()) {
|
||||||
|
return m_recentFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString dir = getChatsHistoryDir();
|
||||||
|
if (dir.isEmpty()) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return QDir(dir).filePath(getSuggestedFileName() + ".json");
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ChatRootView::attachmentFiles() const
|
||||||
|
{
|
||||||
|
return m_attachmentFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ChatRootView::linkedFiles() const
|
||||||
|
{
|
||||||
|
return m_linkedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::showAttachFilesDialog()
|
||||||
|
{
|
||||||
|
QFileDialog dialog(nullptr, tr("Select Files to Attach"));
|
||||||
|
dialog.setFileMode(QFileDialog::ExistingFiles);
|
||||||
|
|
||||||
|
if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
|
||||||
|
dialog.setDirectory(project->projectDirectory().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dialog.exec() == QDialog::Accepted) {
|
||||||
|
QStringList newFilePaths = dialog.selectedFiles();
|
||||||
|
if (!newFilePaths.isEmpty()) {
|
||||||
|
bool filesAdded = false;
|
||||||
|
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,24 +23,21 @@
|
|||||||
|
|
||||||
#include "ChatModel.hpp"
|
#include "ChatModel.hpp"
|
||||||
#include "ClientInterface.hpp"
|
#include "ClientInterface.hpp"
|
||||||
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
class ChatRootView : public QQuickItem
|
class ChatRootView : public QQuickItem
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
// Possibly Qt bug: QTBUG-131004
|
|
||||||
// The class type name must be fully qualified
|
|
||||||
// including the namespace.
|
|
||||||
// Otherwise qmlls can't find it.
|
|
||||||
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)
|
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -49,38 +46,70 @@ public:
|
|||||||
ChatModel *chatModel() const;
|
ChatModel *chatModel() const;
|
||||||
QString currentTemplate() const;
|
QString currentTemplate() const;
|
||||||
|
|
||||||
QColor backgroundColor() const;
|
void saveHistory(const QString &filePath);
|
||||||
QColor primaryColor() const;
|
void loadHistory(const QString &filePath);
|
||||||
QColor secondaryColor() const;
|
|
||||||
|
|
||||||
QColor codeColor() const;
|
Q_INVOKABLE void showSaveDialog();
|
||||||
|
Q_INVOKABLE void showLoadDialog();
|
||||||
|
|
||||||
bool isSharingCurrentFile() const;
|
void autosave();
|
||||||
|
QString getAutosaveFilePath() const;
|
||||||
|
|
||||||
|
QStringList attachmentFiles() const;
|
||||||
|
QStringList linkedFiles() const;
|
||||||
|
|
||||||
|
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) const;
|
void sendMessage(const QString &message);
|
||||||
void copyToClipboard(const QString &text);
|
void copyToClipboard(const QString &text);
|
||||||
void cancelRequest();
|
void cancelRequest();
|
||||||
|
void clearAttachmentFiles();
|
||||||
|
void clearLinkedFiles();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void chatModelChanged();
|
void chatModelChanged();
|
||||||
void currentTemplateChanged();
|
void currentTemplateChanged();
|
||||||
|
void attachmentFilesChanged();
|
||||||
void isSharingCurrentFileChanged();
|
void linkedFilesChanged();
|
||||||
|
void inputTokensCountChanged();
|
||||||
|
void isSyncOpenFilesChanged();
|
||||||
|
void chatFileNameChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void generateColors();
|
QString getChatsHistoryDir() const;
|
||||||
QColor generateColor(const QColor &baseColor,
|
QString getSuggestedFileName() const;
|
||||||
float hueShift,
|
|
||||||
float saturationMod,
|
|
||||||
float lightnessMod);
|
|
||||||
|
|
||||||
ChatModel *m_chatModel;
|
ChatModel *m_chatModel;
|
||||||
ClientInterface *m_clientInterface;
|
ClientInterface *m_clientInterface;
|
||||||
QString m_currentTemplate;
|
QString m_currentTemplate;
|
||||||
QColor m_primaryColor;
|
QString m_recentFilePath;
|
||||||
QColor m_secondaryColor;
|
QStringList m_attachmentFiles;
|
||||||
QColor m_codeColor;
|
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
|
||||||
|
|||||||
142
ChatView/ChatSerializer.cpp
Normal file
142
ChatView/ChatSerializer.cpp
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* 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 "ChatSerializer.hpp"
|
||||||
|
#include "Logger.hpp"
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
|
||||||
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
|
const QString ChatSerializer::VERSION = "0.1";
|
||||||
|
|
||||||
|
SerializationResult ChatSerializer::saveToFile(const ChatModel *model, const QString &filePath)
|
||||||
|
{
|
||||||
|
if (!ensureDirectoryExists(filePath)) {
|
||||||
|
return {false, "Failed to create directory structure"};
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::WriteOnly)) {
|
||||||
|
return {false, QString("Failed to open file for writing: %1").arg(filePath)};
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject root = serializeChat(model);
|
||||||
|
QJsonDocument doc(root);
|
||||||
|
|
||||||
|
if (file.write(doc.toJson(QJsonDocument::Indented)) == -1) {
|
||||||
|
return {false, QString("Failed to write to file: %1").arg(file.errorString())};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {true, QString()};
|
||||||
|
}
|
||||||
|
|
||||||
|
SerializationResult ChatSerializer::loadFromFile(ChatModel *model, const QString &filePath)
|
||||||
|
{
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
return {false, QString("Failed to open file for reading: %1").arg(filePath)};
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonParseError error;
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error);
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
return {false, QString("JSON parse error: %1").arg(error.errorString())};
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject root = doc.object();
|
||||||
|
QString version = root["version"].toString();
|
||||||
|
|
||||||
|
if (!validateVersion(version)) {
|
||||||
|
return {false, QString("Unsupported version: %1").arg(version)};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deserializeChat(model, root)) {
|
||||||
|
return {false, "Failed to deserialize chat data"};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {true, QString()};
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject ChatSerializer::serializeMessage(const ChatModel::Message &message)
|
||||||
|
{
|
||||||
|
QJsonObject messageObj;
|
||||||
|
messageObj["role"] = static_cast<int>(message.role);
|
||||||
|
messageObj["content"] = message.content;
|
||||||
|
messageObj["id"] = message.id;
|
||||||
|
return messageObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatModel::Message ChatSerializer::deserializeMessage(const QJsonObject &json)
|
||||||
|
{
|
||||||
|
ChatModel::Message message;
|
||||||
|
message.role = static_cast<ChatModel::ChatRole>(json["role"].toInt());
|
||||||
|
message.content = json["content"].toString();
|
||||||
|
message.id = json["id"].toString();
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject ChatSerializer::serializeChat(const ChatModel *model)
|
||||||
|
{
|
||||||
|
QJsonArray messagesArray;
|
||||||
|
for (const auto &message : model->getChatHistory()) {
|
||||||
|
messagesArray.append(serializeMessage(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject root;
|
||||||
|
root["version"] = VERSION;
|
||||||
|
root["messages"] = messagesArray;
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatSerializer::deserializeChat(ChatModel *model, const QJsonObject &json)
|
||||||
|
{
|
||||||
|
QJsonArray messagesArray = json["messages"].toArray();
|
||||||
|
QVector<ChatModel::Message> messages;
|
||||||
|
messages.reserve(messagesArray.size());
|
||||||
|
|
||||||
|
for (const auto &messageValue : messagesArray) {
|
||||||
|
messages.append(deserializeMessage(messageValue.toObject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
model->clear();
|
||||||
|
for (const auto &message : messages) {
|
||||||
|
model->addMessage(message.content, message.role, message.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatSerializer::ensureDirectoryExists(const QString &filePath)
|
||||||
|
{
|
||||||
|
QFileInfo fileInfo(filePath);
|
||||||
|
QDir dir = fileInfo.dir();
|
||||||
|
return dir.exists() || dir.mkpath(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatSerializer::validateVersion(const QString &version)
|
||||||
|
{
|
||||||
|
return version == VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Chat
|
||||||
56
ChatView/ChatSerializer.hpp
Normal file
56
ChatView/ChatSerializer.hpp
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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 <QJsonObject>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "ChatModel.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
|
struct SerializationResult
|
||||||
|
{
|
||||||
|
bool success{false};
|
||||||
|
QString errorMessage;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ChatSerializer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static SerializationResult saveToFile(const ChatModel *model, const QString &filePath);
|
||||||
|
static SerializationResult loadFromFile(ChatModel *model, const QString &filePath);
|
||||||
|
|
||||||
|
// Public for testing purposes
|
||||||
|
static QJsonObject serializeMessage(const ChatModel::Message &message);
|
||||||
|
static ChatModel::Message deserializeMessage(const QJsonObject &json);
|
||||||
|
static QJsonObject serializeChat(const ChatModel *model);
|
||||||
|
static bool deserializeChat(ChatModel *model, const QJsonObject &json);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const QString VERSION;
|
||||||
|
static constexpr int CURRENT_VERSION = 1;
|
||||||
|
|
||||||
|
static bool ensureDirectoryExists(const QString &filePath);
|
||||||
|
static bool validateVersion(const QString &version);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Chat
|
||||||
@@ -23,7 +23,6 @@
|
|||||||
#include <qqmlintegration.h>
|
#include <qqmlintegration.h>
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
// Q_NAMESPACE
|
|
||||||
|
|
||||||
class ChatUtils : public QObject
|
class ChatUtils : public QObject
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
#include <texteditor/texteditor.h>
|
#include <texteditor/texteditor.h>
|
||||||
|
|
||||||
#include "ChatAssistantSettings.hpp"
|
#include "ChatAssistantSettings.hpp"
|
||||||
|
#include "ContextManager.hpp"
|
||||||
#include "GeneralSettings.hpp"
|
#include "GeneralSettings.hpp"
|
||||||
#include "Logger.hpp"
|
#include "Logger.hpp"
|
||||||
#include "PromptTemplateManager.hpp"
|
#include "PromptTemplateManager.hpp"
|
||||||
@@ -64,11 +65,13 @@ ClientInterface::ClientInterface(ChatModel *chatModel, QObject *parent)
|
|||||||
|
|
||||||
ClientInterface::~ClientInterface() = default;
|
ClientInterface::~ClientInterface() = default;
|
||||||
|
|
||||||
void ClientInterface::sendMessage(const QString &message, bool includeCurrentFile)
|
void ClientInterface::sendMessage(
|
||||||
|
const QString &message, const QList<QString> &attachments, const QList<QString> &linkedFiles)
|
||||||
{
|
{
|
||||||
cancelRequest();
|
cancelRequest();
|
||||||
|
|
||||||
m_chatModel->addMessage(message, ChatModel::ChatRole::User, "");
|
auto attachFiles = Context::ContextManager::instance().getContentFiles(attachments);
|
||||||
|
m_chatModel->addMessage(message, ChatModel::ChatRole::User, "", attachFiles);
|
||||||
|
|
||||||
auto &chatAssistantSettings = Settings::chatAssistantSettings();
|
auto &chatAssistantSettings = Settings::chatAssistantSettings();
|
||||||
|
|
||||||
@@ -97,11 +100,8 @@ void ClientInterface::sendMessage(const QString &message, bool includeCurrentFil
|
|||||||
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;
|
||||||
@@ -126,7 +126,7 @@ void ClientInterface::sendMessage(const QString &message, bool includeCurrentFil
|
|||||||
config.url = QString("%1%2").arg(Settings::generalSettings().caUrl(), provider->chatEndpoint());
|
config.url = QString("%1%2").arg(Settings::generalSettings().caUrl(), provider->chatEndpoint());
|
||||||
config.providerRequest = providerRequest;
|
config.providerRequest = providerRequest;
|
||||||
config.multiLineCompletion = false;
|
config.multiLineCompletion = false;
|
||||||
config.apiKey = Settings::chatAssistantSettings().apiKey();
|
config.apiKey = provider->apiKey();
|
||||||
|
|
||||||
QJsonObject request;
|
QJsonObject request;
|
||||||
request["id"] = QUuid::createUuid().toString();
|
request["id"] = QUuid::createUuid().toString();
|
||||||
@@ -166,6 +166,7 @@ void ClientInterface::handleLLMResponse(const QString &response,
|
|||||||
if (isComplete) {
|
if (isComplete) {
|
||||||
LOG_MESSAGE(
|
LOG_MESSAGE(
|
||||||
"Message completed. Final response for message " + messageId + ": " + response);
|
"Message completed. Final response for message " + messageId + ": " + response);
|
||||||
|
emit messageReceivedCompletely();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,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
|
||||||
|
|||||||
@@ -36,16 +36,23 @@ public:
|
|||||||
explicit ClientInterface(ChatModel *chatModel, QObject *parent = nullptr);
|
explicit ClientInterface(ChatModel *chatModel, QObject *parent = nullptr);
|
||||||
~ClientInterface();
|
~ClientInterface();
|
||||||
|
|
||||||
void sendMessage(const QString &message, bool includeCurrentFile = false);
|
void sendMessage(
|
||||||
|
const QString &message,
|
||||||
|
const QList<QString> &attachments = {},
|
||||||
|
const QList<QString> &linkedFiles = {});
|
||||||
void clearMessages();
|
void clearMessages();
|
||||||
void cancelRequest();
|
void cancelRequest();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void errorOccurred(const QString &error);
|
void errorOccurred(const QString &error);
|
||||||
|
void messageReceivedCompletely();
|
||||||
|
|
||||||
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;
|
||||||
|
|||||||
11
ChatView/icons/attach-file-dark.svg
Normal file
11
ChatView/icons/attach-file-dark.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_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"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_37_14">
|
||||||
|
<rect width="24" height="48" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
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 |
10
ChatView/icons/close-dark.svg
Normal file
10
ChatView/icons/close-dark.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_41_14)">
|
||||||
|
<path d="M0 0L24 24M0 24L24 0" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_41_14">
|
||||||
|
<rect width="24" height="24" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 353 B |
10
ChatView/icons/close-light.svg
Normal file
10
ChatView/icons/close-light.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_41_14)">
|
||||||
|
<path d="M0 0L24 24M0 24L24 0" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_41_14">
|
||||||
|
<rect width="24" height="24" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 353 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 |
@@ -23,18 +23,18 @@ Rectangle {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property alias text: badgeText.text
|
property alias text: badgeText.text
|
||||||
property alias fontColor: badgeText.color
|
|
||||||
|
|
||||||
width: badgeText.implicitWidth + radius
|
implicitWidth: badgeText.implicitWidth + root.radius
|
||||||
height: badgeText.implicitHeight + 6
|
implicitHeight: badgeText.implicitHeight + 6
|
||||||
color: "lightgreen"
|
color: palette.button
|
||||||
radius: height / 2
|
radius: root.height / 2
|
||||||
|
border.color: palette.mid
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: "gray"
|
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: badgeText
|
id: badgeText
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
color: palette.buttonText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,28 +17,25 @@
|
|||||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import ChatView
|
import ChatView
|
||||||
|
import QtQuick.Layouts
|
||||||
import "./dialog"
|
import "./dialog"
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property alias msgModel: msgCreator.model
|
property alias msgModel: msgCreator.model
|
||||||
property color fontColor
|
property alias messageAttachments: attachmentsModel.model
|
||||||
property color codeBgColor
|
|
||||||
property color selectionColor
|
|
||||||
|
|
||||||
height: msgColumn.height
|
height: msgColumn.implicitHeight + 10
|
||||||
radius: 8
|
radius: 8
|
||||||
|
|
||||||
Column {
|
ColumnLayout {
|
||||||
id: msgColumn
|
id: msgColumn
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: 5
|
spacing: 5
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
@@ -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
|
||||||
@@ -80,6 +77,40 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Flow {
|
||||||
|
id: attachmentsFlow
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: attachmentsModel.model && attachmentsModel.model.length > 0
|
||||||
|
leftPadding: 10
|
||||||
|
rightPadding: 10
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: attachmentsModel
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
required property int index
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
height: attachText.implicitHeight + 8
|
||||||
|
width: attachText.implicitWidth + 16
|
||||||
|
radius: 4
|
||||||
|
color: palette.button
|
||||||
|
border.width: 1
|
||||||
|
border.color: palette.mid
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: attachText
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: modelData
|
||||||
|
color: palette.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
component TextComponent : TextBlock {
|
component TextComponent : TextBlock {
|
||||||
@@ -88,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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -104,8 +133,5 @@ Rectangle {
|
|||||||
|
|
||||||
code: itemData.text
|
code: itemData.text
|
||||||
language: itemData.language
|
language: itemData.language
|
||||||
color: root.codeBgColor
|
|
||||||
selectionColor: root.selectionColor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,28 +17,66 @@
|
|||||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Controls.Basic as QQC
|
import QtQuick.Controls.Basic as QQC
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import ChatView
|
import ChatView
|
||||||
|
import "./controls"
|
||||||
|
import "./parts"
|
||||||
|
|
||||||
ChatRootView {
|
ChatRootView {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property SystemPalette sysPalette: SystemPalette {
|
||||||
|
colorGroup: SystemPalette.Active
|
||||||
|
}
|
||||||
|
|
||||||
|
palette {
|
||||||
|
window: sysPalette.window
|
||||||
|
windowText: sysPalette.windowText
|
||||||
|
base: sysPalette.base
|
||||||
|
alternateBase: sysPalette.alternateBase
|
||||||
|
text: sysPalette.text
|
||||||
|
button: sysPalette.button
|
||||||
|
buttonText: sysPalette.buttonText
|
||||||
|
highlight: sysPalette.highlight
|
||||||
|
highlightedText: sysPalette.highlightedText
|
||||||
|
light: sysPalette.light
|
||||||
|
mid: sysPalette.mid
|
||||||
|
dark: sysPalette.dark
|
||||||
|
shadow: sysPalette.shadow
|
||||||
|
brightText: sysPalette.brightText
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: bg
|
id: bg
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: root.backgroundColor
|
color: palette.window
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors {
|
anchors.fill: parent
|
||||||
fill: parent
|
spacing: 0
|
||||||
|
|
||||||
|
TopBar {
|
||||||
|
id: topBar
|
||||||
|
|
||||||
|
Layout.preferredWidth: parent.width
|
||||||
|
Layout.preferredHeight: 40
|
||||||
|
|
||||||
|
saveButton.onClicked: root.showSaveDialog()
|
||||||
|
loadButton.onClicked: root.showLoadDialog()
|
||||||
|
clearButton.onClicked: root.clearChat()
|
||||||
|
tokensBadge {
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
spacing: 10
|
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: chatListView
|
id: chatListView
|
||||||
@@ -54,14 +92,12 @@ ChatRootView {
|
|||||||
|
|
||||||
delegate: ChatItem {
|
delegate: ChatItem {
|
||||||
required property var model
|
required property var model
|
||||||
|
|
||||||
width: ListView.view.width - scroll.width
|
width: ListView.view.width - scroll.width
|
||||||
msgModel: root.chatModel.processMessageContent(model.content)
|
msgModel: root.chatModel.processMessageContent(model.content)
|
||||||
color: model.roleType === ChatModel.User ? root.primaryColor : root.secondaryColor
|
messageAttachments: model.attachments
|
||||||
fontColor: root.primaryColor.hslLightness > 0.5 ? "black" : "white"
|
color: model.roleType === ChatModel.User ? palette.alternateBase
|
||||||
codeBgColor: root.codeColor
|
: palette.base
|
||||||
selectionColor: root.primaryColor.hslLightness > 0.5 ? Qt.darker(root.primaryColor, 1.5)
|
|
||||||
: Qt.lighter(root.primaryColor, 1.5)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
header: Item {
|
header: Item {
|
||||||
@@ -69,7 +105,7 @@ ChatRootView {
|
|||||||
height: 30
|
height: 30
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
ScrollBar.vertical: QQC.ScrollBar {
|
||||||
id: scroll
|
id: scroll
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,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()
|
||||||
@@ -113,65 +162,51 @@ ChatRootView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
AttachedFilesPlace {
|
||||||
|
id: attachedFilesPlace
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 5
|
attachedFilesModel: root.attachmentFiles
|
||||||
|
iconPath: palette.window.hslLightness > 0.5 ? "qrc:/qt/qml/ChatView/icons/attach-file-dark.svg"
|
||||||
Button {
|
: "qrc:/qt/qml/ChatView/icons/attach-file-light.svg"
|
||||||
id: sendButton
|
accentColor: Qt.tint(palette.mid, Qt.rgba(0, 0.8, 0.3, 0.4))
|
||||||
|
onRemoveFileFromListByIndex: (index) => root.removeFileFromAttachList(index)
|
||||||
Layout.alignment: Qt.AlignBottom
|
|
||||||
text: qsTr("Send")
|
|
||||||
onClicked: root.sendChatMessage()
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: stopButton
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignBottom
|
|
||||||
text: qsTr("Stop")
|
|
||||||
onClicked: root.cancelRequest()
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: clearButton
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignBottom
|
|
||||||
text: qsTr("Clear Chat")
|
|
||||||
onClicked: root.clearChat()
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckBox {
|
|
||||||
id: sharingCurrentFile
|
|
||||||
|
|
||||||
text: "Share current file with models"
|
|
||||||
checked: root.isSharingCurrentFile
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
AttachedFilesPlace {
|
||||||
id: bar
|
id: linkedFilesPlace
|
||||||
|
|
||||||
layoutDirection: Qt.RightToLeft
|
Layout.fillWidth: true
|
||||||
|
attachedFilesModel: root.linkedFiles
|
||||||
anchors {
|
iconPath: palette.window.hslLightness > 0.5 ? "qrc:/qt/qml/ChatView/icons/link-file-dark.svg"
|
||||||
left: parent.left
|
: "qrc:/qt/qml/ChatView/icons/link-file-light.svg"
|
||||||
leftMargin: 5
|
accentColor: Qt.tint(palette.mid, Qt.rgba(0, 0.3, 0.8, 0.4))
|
||||||
right: parent.right
|
onRemoveFileFromListByIndex: (index) => root.removeFileFromLinkList(index)
|
||||||
rightMargin: scroll.width
|
|
||||||
}
|
}
|
||||||
spacing: 10
|
|
||||||
|
|
||||||
Badge {
|
BottomBar {
|
||||||
text: qsTr("tokens:%1/%2").arg(root.chatModel.totalTokens).arg(root.chatModel.tokensThreshold)
|
id: bottomBar
|
||||||
color: root.codeColor
|
|
||||||
fontColor: root.primaryColor.hslLightness > 0.5 ? "black" : "white"
|
Layout.preferredWidth: parent.width
|
||||||
|
Layout.preferredHeight: 40
|
||||||
|
|
||||||
|
sendButton.onClicked: root.sendChatMessage()
|
||||||
|
stopButton.onClicked: root.cancelRequest()
|
||||||
|
syncOpenFiles {
|
||||||
|
checked: root.isSyncOpenFiles
|
||||||
|
onCheckedChanged: root.setIsSyncOpenFiles(bottomBar.syncOpenFiles.checked)
|
||||||
|
}
|
||||||
|
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() {
|
||||||
@@ -179,7 +214,7 @@ ChatRootView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sendChatMessage() {
|
function sendChatMessage() {
|
||||||
root.sendMessage(messageInput.text, sharingCurrentFile.checked)
|
root.sendMessage(messageInput.text)
|
||||||
messageInput.text = ""
|
messageInput.text = ""
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
}
|
}
|
||||||
|
|||||||
54
ChatView/qml/controls/QoAButton.qml
Normal file
54
ChatView/qml/controls/QoAButton.qml
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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls.Basic
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: control
|
||||||
|
|
||||||
|
padding: 4
|
||||||
|
|
||||||
|
icon.width: 16
|
||||||
|
icon.height: 16
|
||||||
|
|
||||||
|
contentItem.height: 20
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
id: bg
|
||||||
|
|
||||||
|
implicitHeight: 20
|
||||||
|
|
||||||
|
color: !control.enabled || !control.down ? control.palette.button : control.palette.dark
|
||||||
|
border.color: !control.enabled || (!control.hovered && !control.visualFocus) ? control.palette.mid : control.palette.highlight
|
||||||
|
border.width: 1
|
||||||
|
radius: 4
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: bg
|
||||||
|
radius: bg.radius
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop { position: 0.0; color: Qt.alpha(control.palette.highlight, 0.4) }
|
||||||
|
GradientStop { position: 1.0; color: Qt.alpha(control.palette.highlight, 0.2) }
|
||||||
|
}
|
||||||
|
opacity: control.hovered ? 0.3 : 0.01
|
||||||
|
Behavior on opacity {NumberAnimation{duration: 250}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
109
ChatView/qml/parts/AttachedFilesPlace.qml
Normal file
109
ChatView/qml/parts/AttachedFilesPlace.qml
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import ChatView
|
||||||
|
|
||||||
|
Flow {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property alias attachedFilesModel: attachRepeater.model
|
||||||
|
property color accentColor: palette.mid
|
||||||
|
property string iconPath
|
||||||
|
|
||||||
|
signal removeFileFromListByIndex(index: int)
|
||||||
|
|
||||||
|
spacing: 5
|
||||||
|
leftPadding: 5
|
||||||
|
rightPadding: 5
|
||||||
|
topPadding: attachRepeater.model.length > 0 ? 2 : 0
|
||||||
|
bottomPadding: attachRepeater.model.length > 0 ? 2 : 0
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: attachRepeater
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
required property int index
|
||||||
|
required property string modelData
|
||||||
|
|
||||||
|
height: 30
|
||||||
|
width: contentRow.width + 10
|
||||||
|
radius: 4
|
||||||
|
color: palette.button
|
||||||
|
border.width: 1
|
||||||
|
border.color: mouse.hovered ? palette.highlight : root.accentColor
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
id: mouse
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: contentRow
|
||||||
|
|
||||||
|
spacing: 5
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 5
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: icon
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
source: root.iconPath
|
||||||
|
sourceSize.width: 8
|
||||||
|
sourceSize.height: 15
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: fileNameText
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: palette.buttonText
|
||||||
|
|
||||||
|
text: {
|
||||||
|
const parts = modelData.split('/');
|
||||||
|
return parts[parts.length - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: closeButton
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: closeIcon.width + 5
|
||||||
|
height: closeButton.width + 5
|
||||||
|
|
||||||
|
onClicked: root.removeFileFromListByIndex(index)
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: closeIcon
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
source: palette.window.hslLightness > 0.5 ? "qrc:/qt/qml/ChatView/icons/close-dark.svg"
|
||||||
|
: "qrc:/qt/qml/ChatView/icons/close-light.svg"
|
||||||
|
width: 6
|
||||||
|
height: 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
112
ChatView/qml/parts/BottomBar.qml
Normal file
112
ChatView/qml/parts/BottomBar.qml
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import ChatView
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property alias sendButton: sendButtonId
|
||||||
|
property alias stopButton: stopButtonId
|
||||||
|
property alias syncOpenFiles: syncOpenFilesId
|
||||||
|
property alias attachFiles: attachFilesId
|
||||||
|
property alias linkFiles: linkFilesId
|
||||||
|
property alias testRag: testRagId
|
||||||
|
property alias testChunks: testChunksId
|
||||||
|
|
||||||
|
color: palette.window.hslLightness > 0.5 ?
|
||||||
|
Qt.darker(palette.window, 1.1) :
|
||||||
|
Qt.lighter(palette.window, 1.1)
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: bottomBar
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
leftMargin: 5
|
||||||
|
right: parent.right
|
||||||
|
rightMargin: 5
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: sendButtonId
|
||||||
|
|
||||||
|
text: qsTr("Send")
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: stopButtonId
|
||||||
|
|
||||||
|
text: qsTr("Stop")
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: attachFilesId
|
||||||
|
|
||||||
|
icon {
|
||||||
|
source: "qrc:/qt/qml/ChatView/icons/attach-file-dark.svg"
|
||||||
|
height: 15
|
||||||
|
width: 8
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
88
ChatView/qml/parts/TopBar.qml
Normal file
88
ChatView/qml/parts/TopBar.qml
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import ChatView
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property alias saveButton: saveButtonId
|
||||||
|
property alias loadButton: loadButtonId
|
||||||
|
property alias clearButton: clearButtonId
|
||||||
|
property alias tokensBadge: tokensBadgeId
|
||||||
|
property alias recentPath: recentPathId
|
||||||
|
property alias openChatHistory: openChatHistoryId
|
||||||
|
|
||||||
|
color: palette.window.hslLightness > 0.5 ?
|
||||||
|
Qt.darker(palette.window, 1.1) :
|
||||||
|
Qt.lighter(palette.window, 1.1)
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
leftMargin: 5
|
||||||
|
right: parent.right
|
||||||
|
rightMargin: 5
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: saveButtonId
|
||||||
|
|
||||||
|
text: qsTr("Save")
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: loadButtonId
|
||||||
|
|
||||||
|
text: qsTr("Load")
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: clearButtonId
|
||||||
|
|
||||||
|
text: qsTr("Clear")
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: recentPathId
|
||||||
|
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
color: palette.text
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: openChatHistoryId
|
||||||
|
|
||||||
|
text: qsTr("Show in system")
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Badge {
|
||||||
|
id: tokensBadgeId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,6 +64,19 @@ QString CodeHandler::processText(QString text)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!pendingComments.isEmpty()) {
|
||||||
|
QStringList commentLines = pendingComments.split('\n');
|
||||||
|
QString commentPrefix = getCommentPrefix(currentLanguage);
|
||||||
|
|
||||||
|
for (const QString &commentLine : commentLines) {
|
||||||
|
if (!commentLine.trimmed().isEmpty()) {
|
||||||
|
result += commentPrefix + " " + commentLine.trimmed() + "\n";
|
||||||
|
} else {
|
||||||
|
result += "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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]() {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
#include <texteditor/textdocument.h>
|
#include <texteditor/textdocument.h>
|
||||||
|
|
||||||
#include "CodeHandler.hpp"
|
#include "CodeHandler.hpp"
|
||||||
#include "DocumentContextReader.hpp"
|
#include "context/DocumentContextReader.hpp"
|
||||||
#include "llmcore/MessageBuilder.hpp"
|
#include "llmcore/MessageBuilder.hpp"
|
||||||
#include "llmcore/PromptTemplateManager.hpp"
|
#include "llmcore/PromptTemplateManager.hpp"
|
||||||
#include "llmcore/ProvidersManager.hpp"
|
#include "llmcore/ProvidersManager.hpp"
|
||||||
@@ -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 = Settings::codeCompletionSettings().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();
|
||||||
|
|
||||||
@@ -194,11 +214,18 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
|||||||
if (!updatedContext.fileContext.isEmpty())
|
if (!updatedContext.fileContext.isEmpty())
|
||||||
systemPrompt.append(updatedContext.fileContext);
|
systemPrompt.append(updatedContext.fileContext);
|
||||||
|
|
||||||
|
QString userMessage;
|
||||||
|
if (completeSettings.useUserMessageTemplateForCC() && promptTemplate->type() == LLMCore::TemplateType::Chat) {
|
||||||
|
userMessage = completeSettings.userMessageTemplateForCC().arg(updatedContext.prefix, updatedContext.suffix);
|
||||||
|
} else {
|
||||||
|
userMessage = updatedContext.prefix;
|
||||||
|
}
|
||||||
|
|
||||||
auto message = LLMCore::MessageBuilder()
|
auto message = LLMCore::MessageBuilder()
|
||||||
.addSystemMessage(systemPrompt)
|
.addSystemMessage(systemPrompt)
|
||||||
.addUserMessage(updatedContext.prefix)
|
.addUserMessage(userMessage)
|
||||||
.addSuffix(updatedContext.suffix)
|
.addSuffix(updatedContext.suffix)
|
||||||
.addtTokenizer(promptTemplate);
|
.addTokenizer(promptTemplate);
|
||||||
|
|
||||||
message.saveTo(
|
message.saveTo(
|
||||||
config.providerRequest,
|
config.providerRequest,
|
||||||
@@ -235,15 +262,37 @@ LLMCore::ContextData LLMClientInterface::prepareContext(const QJsonObject &reque
|
|||||||
int cursorPosition = position["character"].toInt();
|
int cursorPosition = position["character"].toInt();
|
||||||
int lineNumber = position["line"].toInt();
|
int lineNumber = position["line"].toInt();
|
||||||
|
|
||||||
DocumentContextReader reader(textDocument);
|
Context::DocumentContextReader reader(textDocument);
|
||||||
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.2",
|
"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",
|
||||||
|
|||||||
@@ -31,9 +31,10 @@
|
|||||||
|
|
||||||
#include "LLMClientInterface.hpp"
|
#include "LLMClientInterface.hpp"
|
||||||
#include "LLMSuggestion.hpp"
|
#include "LLMSuggestion.hpp"
|
||||||
#include "core/ChangesManager.h"
|
|
||||||
#include "settings/CodeCompletionSettings.hpp"
|
#include "settings/CodeCompletionSettings.hpp"
|
||||||
#include "settings/GeneralSettings.hpp"
|
#include "settings/GeneralSettings.hpp"
|
||||||
|
#include "settings/ProjectSettings.hpp"
|
||||||
|
#include <context/ChangesManager.h>
|
||||||
|
|
||||||
using namespace LanguageServerProtocol;
|
using namespace LanguageServerProtocol;
|
||||||
using namespace TextEditor;
|
using namespace TextEditor;
|
||||||
@@ -70,48 +71,63 @@ void QodeAssistClient::openDocument(TextEditor::TextDocument *document)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
Client::openDocument(document);
|
Client::openDocument(document);
|
||||||
connect(document,
|
connect(
|
||||||
&TextDocument::contentsChangedWithPosition,
|
document,
|
||||||
this,
|
&TextDocument::contentsChangedWithPosition,
|
||||||
[this, document](int position, int charsRemoved, int charsAdded) {
|
this,
|
||||||
Q_UNUSED(charsRemoved)
|
[this, document](int position, int charsRemoved, int charsAdded) {
|
||||||
if (!Settings::codeCompletionSettings().autoCompletion())
|
if (!Settings::codeCompletionSettings().autoCompletion())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto project = ProjectManager::projectForFile(document->filePath());
|
auto project = ProjectManager::projectForFile(document->filePath());
|
||||||
if (!isEnabled(project))
|
if (!isEnabled(project))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto textEditor = BaseTextEditor::currentTextEditor();
|
auto textEditor = BaseTextEditor::currentTextEditor();
|
||||||
if (!textEditor || textEditor->document() != document)
|
if (!textEditor || textEditor->document() != document)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (Settings::codeCompletionSettings().useProjectChangesCache())
|
if (Settings::codeCompletionSettings().useProjectChangesCache())
|
||||||
ChangesManager::instance().addChange(document,
|
Context::ChangesManager::instance()
|
||||||
position,
|
.addChange(document, position, charsRemoved, charsAdded);
|
||||||
charsRemoved,
|
|
||||||
charsAdded);
|
|
||||||
|
|
||||||
TextEditorWidget *widget = textEditor->editorWidget();
|
TextEditorWidget *widget = textEditor->editorWidget();
|
||||||
if (widget->isReadOnly() || widget->multiTextCursor().hasMultipleCursors())
|
if (widget->isReadOnly() || widget->multiTextCursor().hasMultipleCursors())
|
||||||
return;
|
return;
|
||||||
const int cursorPosition = widget->textCursor().position();
|
|
||||||
if (cursorPosition < position || cursorPosition > position + charsAdded)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_recentCharCount += charsAdded;
|
const int cursorPosition = widget->textCursor().position();
|
||||||
|
if (cursorPosition < position || cursorPosition > position + charsAdded)
|
||||||
|
return;
|
||||||
|
|
||||||
if (m_typingTimer.elapsed()
|
if (charsRemoved > 0 || charsAdded <= 0) {
|
||||||
> Settings::codeCompletionSettings().autoCompletionTypingInterval()) {
|
m_recentCharCount = 0;
|
||||||
m_recentCharCount = charsAdded;
|
m_typingTimer.restart();
|
||||||
m_typingTimer.restart();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_recentCharCount
|
QTextCursor cursor = widget->textCursor();
|
||||||
> Settings::codeCompletionSettings().autoCompletionCharThreshold()) {
|
cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, 1);
|
||||||
scheduleRequest(widget);
|
QString lastChar = cursor.selectedText();
|
||||||
}
|
|
||||||
});
|
if (lastChar.isEmpty() || lastChar[0].isPunct()) {
|
||||||
|
m_recentCharCount = 0;
|
||||||
|
m_typingTimer.restart();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_recentCharCount += charsAdded;
|
||||||
|
|
||||||
|
if (m_typingTimer.elapsed()
|
||||||
|
> Settings::codeCompletionSettings().autoCompletionTypingInterval()) {
|
||||||
|
m_recentCharCount = charsAdded;
|
||||||
|
m_typingTimer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_recentCharCount
|
||||||
|
> Settings::codeCompletionSettings().autoCompletionCharThreshold()) {
|
||||||
|
scheduleRequest(widget);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QodeAssistClient::canOpenProject(ProjectExplorer::Project *project)
|
bool QodeAssistClient::canOpenProject(ProjectExplorer::Project *project)
|
||||||
@@ -237,7 +253,11 @@ void QodeAssistClient::cancelRunningRequest(TextEditor::TextEditorWidget *editor
|
|||||||
|
|
||||||
bool QodeAssistClient::isEnabled(ProjectExplorer::Project *project) const
|
bool QodeAssistClient::isEnabled(ProjectExplorer::Project *project) const
|
||||||
{
|
{
|
||||||
return Settings::generalSettings().enableQodeAssist();
|
if (!project)
|
||||||
|
return Settings::generalSettings().enableQodeAssist();
|
||||||
|
|
||||||
|
Settings::ProjectSettings settings(project);
|
||||||
|
return settings.isEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
void QodeAssistClient::setupConnections()
|
void QodeAssistClient::setupConnections()
|
||||||
|
|||||||
195
README.md
195
README.md
@@ -1,34 +1,57 @@
|
|||||||
# 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.
|
||||||
|
|
||||||
|
⚠️ **Important Notice About Paid Providers**
|
||||||
|
> When using paid providers like Claude, OpenRouter or OpenAI-compatible services:
|
||||||
|
> - These services will consume API tokens which may result in charges to your account
|
||||||
|
> - The QodeAssist developer bears no responsibility for any charges incurred
|
||||||
|
> - 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](#installation)
|
2. [Install plugin to QtCreator](#install-plugin-to-qtcreator)
|
||||||
3. [Configure Plugin](#configure-plugin)
|
3. [Configure for Anthropic Claude](#configure-for-anthropic-claude)
|
||||||
4. [Supported LLM Providers](#supported-llm-providers)
|
4. [Configure for OpenAI](#configure-for-openai)
|
||||||
5. [Recommended Models](#recommended-models)
|
5. [Configure for using Ollama](#configure-for-using-ollama)
|
||||||
- [Ollama](#ollama)
|
6. [System Prompt Configuration](#system-prompt-configuration)
|
||||||
6. [QtCreator Version Compatibility](#qtcreator-version-compatibility)
|
7. [File Context Features](#file-context-features)
|
||||||
7. [Development Progress](#development-progress)
|
8. [Template-Model Compatibility](#template-model-compatibility)
|
||||||
8. [Hotkeys](#hotkeys)
|
9. [QtCreator Version Compatibility](#qtcreator-version-compatibility)
|
||||||
9. [Troubleshooting](#troubleshooting)
|
10. [Development Progress](#development-progress)
|
||||||
10. [Support the Development](#support-the-development-of-qodeassist)
|
11. [Hotkeys](#hotkeys)
|
||||||
11. [How to Build](#how-to-build)
|
12. [Troubleshooting](#troubleshooting)
|
||||||
|
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
|
||||||
|
- 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
|
||||||
@@ -55,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
|
<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):
|
||||||
```
|
```
|
||||||
@@ -73,16 +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
|
|
||||||
|
|
||||||
## 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
|
||||||
@@ -90,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
|
||||||
@@ -17,32 +17,31 @@
|
|||||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "CounterTooltip.hpp"
|
#pragma once
|
||||||
|
|
||||||
|
#include <QFrame>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLayout>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QToolButton>
|
||||||
|
|
||||||
namespace QodeAssist {
|
namespace QodeAssist {
|
||||||
|
|
||||||
CounterTooltip::CounterTooltip(int count)
|
class UpdateStatusWidget : public QFrame
|
||||||
: m_count(count)
|
|
||||||
{
|
{
|
||||||
m_label = new QLabel(this);
|
Q_OBJECT
|
||||||
addWidget(m_label);
|
public:
|
||||||
updateLabel();
|
explicit UpdateStatusWidget(QWidget *parent = nullptr);
|
||||||
|
|
||||||
m_timer = new QTimer(this);
|
void setDefaultAction(QAction *action);
|
||||||
m_timer->setSingleShot(true);
|
void showUpdateAvailable(const QString &version);
|
||||||
m_timer->setInterval(2000);
|
void hideUpdateInfo();
|
||||||
|
|
||||||
connect(m_timer, &QTimer::timeout, this, [this] { emit finished(m_count); });
|
QPushButton *updateButton() const;
|
||||||
|
|
||||||
m_timer->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
CounterTooltip::~CounterTooltip() {}
|
|
||||||
|
|
||||||
void CounterTooltip::updateLabel()
|
|
||||||
{
|
|
||||||
const auto hotkey = QKeySequence(QKeySequence::MoveToNextWord).toString();
|
|
||||||
m_label->setText(QString("Insert Next %1 line(s) (%2)").arg(m_count).arg(hotkey));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
QToolButton *m_actionButton;
|
||||||
|
QLabel *m_versionLabel;
|
||||||
|
QPushButton *m_updateButton;
|
||||||
|
};
|
||||||
} // namespace QodeAssist
|
} // namespace QodeAssist
|
||||||
31
context/CMakeLists.txt
Normal file
31
context/CMakeLists.txt
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
add_library(Context STATIC
|
||||||
|
DocumentContextReader.hpp DocumentContextReader.cpp
|
||||||
|
ChangesManager.h ChangesManager.cpp
|
||||||
|
ContextManager.hpp ContextManager.cpp
|
||||||
|
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
|
||||||
|
PUBLIC
|
||||||
|
Qt::Core
|
||||||
|
Qt::Sql
|
||||||
|
QtCreator::Core
|
||||||
|
QtCreator::TextEditor
|
||||||
|
QtCreator::Utils
|
||||||
|
QtCreator::ProjectExplorer
|
||||||
|
PRIVATE
|
||||||
|
LLMCore
|
||||||
|
QodeAssistSettings
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(Context PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR})
|
||||||
@@ -18,9 +18,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ChangesManager.h"
|
#include "ChangesManager.h"
|
||||||
#include "settings/CodeCompletionSettings.hpp"
|
#include "CodeCompletionSettings.hpp"
|
||||||
|
|
||||||
namespace QodeAssist {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
ChangesManager &ChangesManager::instance()
|
ChangesManager &ChangesManager::instance()
|
||||||
{
|
{
|
||||||
@@ -79,4 +79,4 @@ QString ChangesManager::getRecentChangesContext(const TextEditor::TextDocument *
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist
|
} // namespace QodeAssist::Context
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <texteditor/textdocument.h>
|
#include <texteditor/textdocument.h>
|
||||||
|
|
||||||
namespace QodeAssist {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
class ChangesManager : public QObject
|
class ChangesManager : public QObject
|
||||||
{
|
{
|
||||||
@@ -58,4 +58,4 @@ private:
|
|||||||
QHash<TextEditor::TextDocument *, QQueue<ChangeInfo>> m_documentChanges;
|
QHash<TextEditor::TextDocument *, QQueue<ChangeInfo>> m_documentChanges;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist
|
} // namespace QodeAssist::Context
|
||||||
32
context/ContentFile.hpp
Normal file
32
context/ContentFile.hpp
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
|
||||||
|
struct ContentFile
|
||||||
|
{
|
||||||
|
QString filename;
|
||||||
|
QString content;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
176
context/ContextManager.cpp
Normal file
176
context/ContextManager.cpp
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
/*
|
||||||
|
* 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 "ContextManager.hpp"
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QTextStream>
|
||||||
|
|
||||||
|
#include <projectexplorer/project.h>
|
||||||
|
#include <projectexplorer/projectnodes.h>
|
||||||
|
|
||||||
|
#include "FileChunker.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
ContextManager &ContextManager::instance()
|
||||||
|
{
|
||||||
|
static ContextManager manager;
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextManager::ContextManager(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{}
|
||||||
|
|
||||||
|
QString ContextManager::readFile(const QString &filePath) const
|
||||||
|
{
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||||
|
return QString();
|
||||||
|
|
||||||
|
QTextStream in(&file);
|
||||||
|
return in.readAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ContentFile> ContextManager::getContentFiles(const QStringList &filePaths) const
|
||||||
|
{
|
||||||
|
QList<ContentFile> files;
|
||||||
|
for (const QString &path : filePaths) {
|
||||||
|
ContentFile contentFile = createContentFile(path);
|
||||||
|
files.append(contentFile);
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentFile ContextManager::createContentFile(const QString &filePath) const
|
||||||
|
{
|
||||||
|
ContentFile contentFile;
|
||||||
|
QFileInfo fileInfo(filePath);
|
||||||
|
contentFile.filename = fileInfo.fileName();
|
||||||
|
contentFile.content = readFile(filePath);
|
||||||
|
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
|
||||||
60
context/ContextManager.hpp
Normal file
60
context/ContextManager.hpp
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* 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 "ContentFile.hpp"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "FileChunker.hpp"
|
||||||
|
|
||||||
|
namespace ProjectExplorer {
|
||||||
|
class Project;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
class ContextManager : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
static ContextManager &instance();
|
||||||
|
|
||||||
|
QString readFile(const QString &filePath) const;
|
||||||
|
QList<ContentFile> getContentFiles(const QStringList &filePaths) const;
|
||||||
|
QStringList getProjectSourceFiles(ProjectExplorer::Project *project) const;
|
||||||
|
|
||||||
|
void testProjectChunks(
|
||||||
|
ProjectExplorer::Project *project, const FileChunker::ChunkingConfig &config);
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit ContextManager(QObject *parent = nullptr);
|
||||||
|
~ContextManager() = default;
|
||||||
|
ContextManager(const ContextManager &) = delete;
|
||||||
|
ContextManager &operator=(const ContextManager &) = delete;
|
||||||
|
|
||||||
|
ContentFile createContentFile(const QString &filePath) const;
|
||||||
|
bool shouldProcessFile(const QString &filePath) const;
|
||||||
|
bool isInBuildDirectory(const QString &filePath) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
@@ -23,8 +23,9 @@
|
|||||||
#include <QTextBlock>
|
#include <QTextBlock>
|
||||||
#include <languageserverprotocol/lsptypes.h>
|
#include <languageserverprotocol/lsptypes.h>
|
||||||
|
|
||||||
#include "core/ChangesManager.h"
|
#include "CodeCompletionSettings.hpp"
|
||||||
#include "settings/CodeCompletionSettings.hpp"
|
|
||||||
|
#include "ChangesManager.h"
|
||||||
|
|
||||||
const QRegularExpression &getYearRegex()
|
const QRegularExpression &getYearRegex()
|
||||||
{
|
{
|
||||||
@@ -46,7 +47,7 @@ const QRegularExpression &getCommentRegex()
|
|||||||
return commentRegex;
|
return commentRegex;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace QodeAssist {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
DocumentContextReader::DocumentContextReader(TextEditor::TextDocument *textDocument)
|
DocumentContextReader::DocumentContextReader(TextEditor::TextDocument *textDocument)
|
||||||
: m_textDocument(textDocument)
|
: m_textDocument(textDocument)
|
||||||
@@ -246,4 +247,4 @@ QString DocumentContextReader::getContextAfter(int lineNumber, int cursorPositio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist
|
} // namespace QodeAssist::Context
|
||||||
@@ -19,12 +19,12 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QTextDocument>
|
|
||||||
#include <texteditor/textdocument.h>
|
#include <texteditor/textdocument.h>
|
||||||
|
#include <QTextDocument>
|
||||||
|
|
||||||
#include <llmcore/ContextData.hpp>
|
#include <llmcore/ContextData.hpp>
|
||||||
|
|
||||||
namespace QodeAssist {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
struct CopyrightInfo
|
struct CopyrightInfo
|
||||||
{
|
{
|
||||||
@@ -61,4 +61,4 @@ private:
|
|||||||
CopyrightInfo m_copyrightInfo;
|
CopyrightInfo m_copyrightInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist
|
} // 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
|
||||||
@@ -19,30 +19,19 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QLabel>
|
#include "RAGData.hpp"
|
||||||
#include <QTimer>
|
|
||||||
#include <QToolBar>
|
|
||||||
#include <QWidget>
|
|
||||||
|
|
||||||
namespace QodeAssist {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
class CounterTooltip : public QToolBar
|
class RAGSimilaritySearch
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CounterTooltip(int count);
|
static float l2Distance(const RAGVector &v1, const RAGVector &v2);
|
||||||
~CounterTooltip();
|
|
||||||
|
|
||||||
signals:
|
static float cosineSimilarity(const RAGVector &v1, const RAGVector &v2);
|
||||||
void finished(int count);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateLabel();
|
RAGSimilaritySearch() = delete;
|
||||||
|
|
||||||
QLabel *m_label;
|
|
||||||
QTimer *m_timer;
|
|
||||||
int m_count;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist
|
} // 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -40,7 +40,7 @@ QodeAssist::LLMCore::MessageBuilder &QodeAssist::LLMCore::MessageBuilder::addSuf
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
QodeAssist::LLMCore::MessageBuilder &QodeAssist::LLMCore::MessageBuilder::addtTokenizer(
|
QodeAssist::LLMCore::MessageBuilder &QodeAssist::LLMCore::MessageBuilder::addTokenizer(
|
||||||
PromptTemplate *promptTemplate)
|
PromptTemplate *promptTemplate)
|
||||||
{
|
{
|
||||||
m_promptTemplate = promptTemplate;
|
m_promptTemplate = promptTemplate;
|
||||||
@@ -72,6 +72,7 @@ void QodeAssist::LLMCore::MessageBuilder::saveTo(QJsonObject &request, Providers
|
|||||||
|
|
||||||
if (api == ProvidersApi::Ollama) {
|
if (api == ProvidersApi::Ollama) {
|
||||||
if (m_promptTemplate->type() == TemplateType::Fim) {
|
if (m_promptTemplate->type() == TemplateType::Fim) {
|
||||||
|
request["system"] = m_systemMessage;
|
||||||
m_promptTemplate->prepareRequest(request, context);
|
m_promptTemplate->prepareRequest(request, context);
|
||||||
} else {
|
} else {
|
||||||
QJsonArray messages;
|
QJsonArray messages;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ enum class MessageRole { System, User, Assistant };
|
|||||||
|
|
||||||
enum class OllamaFormat { Messages, Completions };
|
enum class OllamaFormat { Messages, Completions };
|
||||||
|
|
||||||
enum class ProvidersApi { Ollama, OpenAI };
|
enum class ProvidersApi { Ollama, OpenAI, Claude };
|
||||||
|
|
||||||
static const QString ROLE_SYSTEM = "system";
|
static const QString ROLE_SYSTEM = "system";
|
||||||
static const QString ROLE_USER = "user";
|
static const QString ROLE_USER = "user";
|
||||||
@@ -53,7 +53,7 @@ public:
|
|||||||
|
|
||||||
MessageBuilder &addSuffix(const QString &content);
|
MessageBuilder &addSuffix(const QString &content);
|
||||||
|
|
||||||
MessageBuilder &addtTokenizer(PromptTemplate *promptTemplate);
|
MessageBuilder &addTokenizer(PromptTemplate *promptTemplate);
|
||||||
|
|
||||||
QString roleToString(MessageRole role) const;
|
QString roleToString(MessageRole role) const;
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,9 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
#include <utils/environment.h>
|
#include <utils/environment.h>
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
#include "PromptTemplate.hpp"
|
#include "PromptTemplate.hpp"
|
||||||
#include "RequestType.hpp"
|
#include "RequestType.hpp"
|
||||||
@@ -45,6 +46,8 @@ public:
|
|||||||
virtual bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) = 0;
|
virtual bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) = 0;
|
||||||
virtual QList<QString> getInstalledModels(const QString &url) = 0;
|
virtual QList<QString> getInstalledModels(const QString &url) = 0;
|
||||||
virtual QList<QString> validateRequest(const QJsonObject &request, TemplateType type) = 0;
|
virtual QList<QString> validateRequest(const QJsonObject &request, TemplateType type) = 0;
|
||||||
|
virtual QString apiKey() const = 0;
|
||||||
|
virtual void prepareNetworkRequest(QNetworkRequest &networkRequest) const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::LLMCore
|
} // namespace QodeAssist::LLMCore
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ void RequestHandler::sendLLMRequest(const LLMConfig &config, const QJsonObject &
|
|||||||
QJsonDocument(config.providerRequest).toJson(QJsonDocument::Indented))));
|
QJsonDocument(config.providerRequest).toJson(QJsonDocument::Indented))));
|
||||||
|
|
||||||
QNetworkRequest networkRequest(config.url);
|
QNetworkRequest networkRequest(config.url);
|
||||||
prepareNetworkRequest(networkRequest, config.apiKey);
|
config.provider->prepareNetworkRequest(networkRequest);
|
||||||
|
|
||||||
QNetworkReply *reply = m_manager->post(networkRequest,
|
QNetworkReply *reply = m_manager->post(networkRequest,
|
||||||
QJsonDocument(config.providerRequest).toJson());
|
QJsonDocument(config.providerRequest).toJson());
|
||||||
@@ -108,16 +108,6 @@ bool RequestHandler::cancelRequest(const QString &id)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RequestHandler::prepareNetworkRequest(
|
|
||||||
QNetworkRequest &networkRequest, const QString &apiKey) const
|
|
||||||
{
|
|
||||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
||||||
|
|
||||||
if (!apiKey.isEmpty()) {
|
|
||||||
networkRequest.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey).toUtf8());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RequestHandler::processSingleLineCompletion(
|
bool RequestHandler::processSingleLineCompletion(
|
||||||
QNetworkReply *reply,
|
QNetworkReply *reply,
|
||||||
const QJsonObject &request,
|
const QJsonObject &request,
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ private:
|
|||||||
QMap<QString, QNetworkReply *> m_activeRequests;
|
QMap<QString, QNetworkReply *> m_activeRequests;
|
||||||
QMap<QNetworkReply *, QString> m_accumulatedResponses;
|
QMap<QNetworkReply *, QString> m_accumulatedResponses;
|
||||||
|
|
||||||
void prepareNetworkRequest(QNetworkRequest &networkRequest, const QString &apiKey) const;
|
|
||||||
bool processSingleLineCompletion(QNetworkReply *reply,
|
bool processSingleLineCompletion(QNetworkReply *reply,
|
||||||
const QJsonObject &request,
|
const QJsonObject &request,
|
||||||
const QString &accumulatedResponse,
|
const QString &accumulatedResponse,
|
||||||
|
|||||||
238
providers/ClaudeProvider.cpp
Normal file
238
providers/ClaudeProvider.cpp
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
/*
|
||||||
|
* 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 "ClaudeProvider.hpp"
|
||||||
|
|
||||||
|
#include <QEventLoop>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
|
||||||
|
#include "llmcore/ValidationUtils.hpp"
|
||||||
|
#include "logger/Logger.hpp"
|
||||||
|
#include "settings/ChatAssistantSettings.hpp"
|
||||||
|
#include "settings/CodeCompletionSettings.hpp"
|
||||||
|
#include "settings/GeneralSettings.hpp"
|
||||||
|
#include "settings/ProviderSettings.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Providers {
|
||||||
|
|
||||||
|
ClaudeProvider::ClaudeProvider() {}
|
||||||
|
|
||||||
|
QString ClaudeProvider::name() const
|
||||||
|
{
|
||||||
|
return "Claude";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ClaudeProvider::url() const
|
||||||
|
{
|
||||||
|
return "https://api.anthropic.com";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ClaudeProvider::completionEndpoint() const
|
||||||
|
{
|
||||||
|
return "/v1/messages";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ClaudeProvider::chatEndpoint() const
|
||||||
|
{
|
||||||
|
return "/v1/messages";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClaudeProvider::supportsModelListing() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClaudeProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||||
|
{
|
||||||
|
auto prepareMessages = [](QJsonObject &req) -> QJsonArray {
|
||||||
|
QJsonArray messages;
|
||||||
|
if (req.contains("messages")) {
|
||||||
|
QJsonArray origMessages = req["messages"].toArray();
|
||||||
|
for (const auto &msg : origMessages) {
|
||||||
|
QJsonObject message = msg.toObject();
|
||||||
|
if (message["role"].toString() == "system") {
|
||||||
|
req["system"] = message["content"];
|
||||||
|
} else {
|
||||||
|
messages.append(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (req.contains("system")) {
|
||||||
|
req["system"] = req["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();
|
||||||
|
request["stream"] = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 ClaudeProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
|
||||||
|
{
|
||||||
|
bool isComplete = false;
|
||||||
|
QString tempResponse;
|
||||||
|
|
||||||
|
while (reply->canReadLine()) {
|
||||||
|
QByteArray line = reply->readLine().trimmed();
|
||||||
|
if (line.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!line.startsWith("data:")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
line = line.mid(6);
|
||||||
|
|
||||||
|
QJsonDocument jsonResponse = QJsonDocument::fromJson(line);
|
||||||
|
if (jsonResponse.isNull()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject responseObj = jsonResponse.object();
|
||||||
|
QString eventType = responseObj["type"].toString();
|
||||||
|
|
||||||
|
if (eventType == "message_delta") {
|
||||||
|
if (responseObj.contains("delta")) {
|
||||||
|
QJsonObject delta = responseObj["delta"].toObject();
|
||||||
|
if (delta.contains("stop_reason")) {
|
||||||
|
isComplete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (eventType == "content_block_delta") {
|
||||||
|
QJsonObject delta = responseObj["delta"].toObject();
|
||||||
|
if (delta["type"].toString() == "text_delta") {
|
||||||
|
tempResponse += delta["text"].toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tempResponse.isEmpty()) {
|
||||||
|
accumulatedResponse += tempResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QString> ClaudeProvider::getInstalledModels(const QString &baseUrl)
|
||||||
|
{
|
||||||
|
QList<QString> models;
|
||||||
|
QNetworkAccessManager manager;
|
||||||
|
|
||||||
|
QUrl url(baseUrl + "/v1/models");
|
||||||
|
QUrlQuery query;
|
||||||
|
query.addQueryItem("limit", "1000");
|
||||||
|
url.setQuery(query);
|
||||||
|
|
||||||
|
QNetworkRequest request(url);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
request.setRawHeader("anthropic-version", "2023-06-01");
|
||||||
|
|
||||||
|
if (!apiKey().isEmpty()) {
|
||||||
|
request.setRawHeader("x-api-key", 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();
|
||||||
|
models.append(modelId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG_MESSAGE(QString("Error fetching Claude models: %1").arg(reply->errorString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
reply->deleteLater();
|
||||||
|
return models;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QString> ClaudeProvider::validateRequest(const QJsonObject &request, LLMCore::TemplateType type)
|
||||||
|
{
|
||||||
|
const auto templateReq = QJsonObject{
|
||||||
|
{"model", {}},
|
||||||
|
{"system", {}},
|
||||||
|
{"messages", QJsonArray{{QJsonObject{{"role", {}}, {"content", {}}}}}},
|
||||||
|
{"temperature", {}},
|
||||||
|
{"max_tokens", {}},
|
||||||
|
{"anthropic-version", {}},
|
||||||
|
{"top_p", {}},
|
||||||
|
{"top_k", {}},
|
||||||
|
{"stop", QJsonArray{}},
|
||||||
|
{"stream", {}}};
|
||||||
|
|
||||||
|
return LLMCore::ValidationUtils::validateRequestFields(request, templateReq);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ClaudeProvider::apiKey() const
|
||||||
|
{
|
||||||
|
return Settings::providerSettings().claudeApiKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClaudeProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) const
|
||||||
|
{
|
||||||
|
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
if (!apiKey().isEmpty()) {
|
||||||
|
networkRequest.setRawHeader("x-api-key", apiKey().toUtf8());
|
||||||
|
networkRequest.setRawHeader("anthropic-version", "2023-06-01");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Providers
|
||||||
44
providers/ClaudeProvider.hpp
Normal file
44
providers/ClaudeProvider.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 ClaudeProvider : public LLMCore::Provider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ClaudeProvider();
|
||||||
|
|
||||||
|
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
|
||||||
@@ -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)
|
||||||
@@ -188,4 +201,14 @@ QList<QString> LMStudioProvider::validateRequest(
|
|||||||
return LLMCore::ValidationUtils::validateRequestFields(request, templateReq);
|
return LLMCore::ValidationUtils::validateRequestFields(request, templateReq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString LMStudioProvider::apiKey() const
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void LMStudioProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) const
|
||||||
|
{
|
||||||
|
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ public:
|
|||||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
QList<QString> getInstalledModels(const QString &url) override;
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||||
|
QString apiKey() const override;
|
||||||
|
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -175,6 +193,16 @@ QList<QString> OllamaProvider::validateRequest(const QJsonObject &request, LLMCo
|
|||||||
|
|
||||||
return LLMCore::ValidationUtils::validateRequestFields(
|
return LLMCore::ValidationUtils::validateRequestFields(
|
||||||
request, type == LLMCore::TemplateType::Fim ? fimReq : messageReq);
|
request, type == LLMCore::TemplateType::Fim ? fimReq : messageReq);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString OllamaProvider::apiKey() const
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void OllamaProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) const
|
||||||
|
{
|
||||||
|
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ public:
|
|||||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
QList<QString> getInstalledModels(const QString &url) override;
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||||
|
QString apiKey() const override;
|
||||||
|
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -18,8 +18,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "OpenAICompatProvider.hpp"
|
#include "OpenAICompatProvider.hpp"
|
||||||
|
|
||||||
#include "settings/ChatAssistantSettings.hpp"
|
#include "settings/ChatAssistantSettings.hpp"
|
||||||
#include "settings/CodeCompletionSettings.hpp"
|
#include "settings/CodeCompletionSettings.hpp"
|
||||||
|
#include "settings/ProviderSettings.hpp"
|
||||||
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
@@ -107,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;
|
||||||
@@ -131,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)
|
||||||
@@ -161,4 +176,18 @@ QList<QString> OpenAICompatProvider::validateRequest(
|
|||||||
return LLMCore::ValidationUtils::validateRequestFields(request, templateReq);
|
return LLMCore::ValidationUtils::validateRequestFields(request, templateReq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString OpenAICompatProvider::apiKey() const
|
||||||
|
{
|
||||||
|
return Settings::providerSettings().openAiCompatApiKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenAICompatProvider::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
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ public:
|
|||||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
QList<QString> getInstalledModels(const QString &url) override;
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||||
|
QString apiKey() const override;
|
||||||
|
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
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
|
||||||
@@ -18,8 +18,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "OpenRouterAIProvider.hpp"
|
#include "OpenRouterAIProvider.hpp"
|
||||||
|
|
||||||
#include "settings/ChatAssistantSettings.hpp"
|
#include "settings/ChatAssistantSettings.hpp"
|
||||||
#include "settings/CodeCompletionSettings.hpp"
|
#include "settings/CodeCompletionSettings.hpp"
|
||||||
|
#include "settings/ProviderSettings.hpp"
|
||||||
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
@@ -91,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;
|
||||||
@@ -112,15 +120,26 @@ 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
|
||||||
|
{
|
||||||
|
return Settings::providerSettings().openRouterApiKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ public:
|
|||||||
QString url() const override;
|
QString url() const override;
|
||||||
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
|
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
|
||||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||||
|
QString apiKey() const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -20,9 +20,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "llmcore/ProvidersManager.hpp"
|
#include "llmcore/ProvidersManager.hpp"
|
||||||
|
#include "providers/ClaudeProvider.hpp"
|
||||||
#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 {
|
||||||
@@ -34,6 +36,8 @@ inline void registerProviders()
|
|||||||
providerManager.registerProvider<LMStudioProvider>();
|
providerManager.registerProvider<LMStudioProvider>();
|
||||||
providerManager.registerProvider<OpenAICompatProvider>();
|
providerManager.registerProvider<OpenAICompatProvider>();
|
||||||
providerManager.registerProvider<OpenRouterProvider>();
|
providerManager.registerProvider<OpenRouterProvider>();
|
||||||
|
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,18 +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 "UpdateStatusWidget.hpp"
|
||||||
#include "providers/Providers.hpp"
|
#include "providers/Providers.hpp"
|
||||||
#include "templates/Templates.hpp"
|
#include "templates/Templates.hpp"
|
||||||
|
|
||||||
@@ -60,8 +65,8 @@ class QodeAssistPlugin final : public ExtensionSystem::IPlugin
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
QodeAssistPlugin()
|
QodeAssistPlugin()
|
||||||
{
|
: m_updater(new PluginUpdater(this))
|
||||||
}
|
{}
|
||||||
|
|
||||||
~QodeAssistPlugin() final
|
~QodeAssistPlugin() final
|
||||||
{
|
{
|
||||||
@@ -95,31 +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();
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,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
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"prompt": "{{QODE_INSTRUCTIONS}}<fim_prefix>{{QODE_PREFIX}}<fim_suffix>{{QODE_SUFFIX}}<fim_middle>",
|
|
||||||
"options": {
|
|
||||||
"temperature": 0.7,
|
|
||||||
"top_p": 0.95,
|
|
||||||
"top_k": 40,
|
|
||||||
"num_predict": 175,
|
|
||||||
"stop": [
|
|
||||||
"<|endoftext|>",
|
|
||||||
"<file_sep>",
|
|
||||||
"<fim_prefix>",
|
|
||||||
"<fim_suffix>",
|
|
||||||
"<fim_middle>"
|
|
||||||
],
|
|
||||||
"frequency_penalty": 0,
|
|
||||||
"presence_penalty": 0
|
|
||||||
},
|
|
||||||
"stream": true
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"max_tokens": 150,
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"content": "{{QODE_INSTRUCTIONS}}\n### Instruction:{{QODE_PREFIX}}{{QODE_SUFFIX}} ### Response:\n",
|
|
||||||
"role": "user"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stop": [
|
|
||||||
"### Instruction:",
|
|
||||||
"### Response:",
|
|
||||||
"\n\n### "
|
|
||||||
],
|
|
||||||
"stream": true,
|
|
||||||
"temperature": 0.2
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ add_library(QodeAssistSettings STATIC
|
|||||||
CodeCompletionSettings.hpp CodeCompletionSettings.cpp
|
CodeCompletionSettings.hpp CodeCompletionSettings.cpp
|
||||||
ChatAssistantSettings.hpp ChatAssistantSettings.cpp
|
ChatAssistantSettings.hpp ChatAssistantSettings.cpp
|
||||||
SettingsDialog.hpp SettingsDialog.cpp
|
SettingsDialog.hpp SettingsDialog.cpp
|
||||||
|
ProjectSettings.hpp ProjectSettings.cpp
|
||||||
|
ProjectSettingsPanel.hpp ProjectSettingsPanel.cpp
|
||||||
|
ProviderSettings.hpp ProviderSettings.cpp
|
||||||
|
PluginUpdater.hpp PluginUpdater.cpp
|
||||||
|
UpdateDialog.hpp UpdateDialog.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(QodeAssistSettings
|
target_link_libraries(QodeAssistSettings
|
||||||
|
|||||||
@@ -44,20 +44,24 @@ 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(1000, 16000);
|
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);
|
||||||
stream.setLabelText(Tr::tr("Enable stream option"));
|
stream.setLabelText(Tr::tr("Enable stream option"));
|
||||||
|
|
||||||
|
autosave.setSettingsKey(Constants::CA_AUTOSAVE);
|
||||||
|
autosave.setDefaultValue(true);
|
||||||
|
autosave.setLabelText(Tr::tr("Enable autosave when message received"));
|
||||||
|
|
||||||
// General Parameters Settings
|
// General Parameters Settings
|
||||||
temperature.setSettingsKey(Constants::CA_TEMPERATURE);
|
temperature.setSettingsKey(Constants::CA_TEMPERATURE);
|
||||||
temperature.setLabelText(Tr::tr("Temperature:"));
|
temperature.setLabelText(Tr::tr("Temperature:"));
|
||||||
@@ -135,7 +139,7 @@ ChatAssistantSettings::ChatAssistantSettings()
|
|||||||
|
|
||||||
// API Configuration Settings
|
// API Configuration Settings
|
||||||
apiKey.setSettingsKey(Constants::CA_API_KEY);
|
apiKey.setSettingsKey(Constants::CA_API_KEY);
|
||||||
apiKey.setLabelText(Tr::tr("API Key:"));
|
apiKey.setLabelText(Tr::tr("[Deprecated, see Provider Settings]API Key:"));
|
||||||
apiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
apiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||||
apiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
|
apiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
|
||||||
|
|
||||||
@@ -167,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}},
|
Column{Row{chatTokensThreshold, Stretch{1}}, linkOpenFiles, stream, autosave}},
|
||||||
Space{8},
|
Space{8},
|
||||||
Group{
|
Group{
|
||||||
title(Tr::tr("General Parameters")),
|
title(Tr::tr("General Parameters")),
|
||||||
@@ -223,6 +227,7 @@ void ChatAssistantSettings::resetSettingsToDefaults()
|
|||||||
resetAspect(systemPrompt);
|
resetAspect(systemPrompt);
|
||||||
resetAspect(ollamaLivetime);
|
resetAspect(ollamaLivetime);
|
||||||
resetAspect(contextWindow);
|
resetAspect(contextWindow);
|
||||||
|
resetAspect(linkOpenFiles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,9 @@ 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};
|
||||||
|
|
||||||
// General Parameters Settings
|
// General Parameters Settings
|
||||||
Utils::DoubleAspect temperature{this};
|
Utils::DoubleAspect temperature{this};
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ CodeCompletionSettings::CodeCompletionSettings()
|
|||||||
startSuggestionTimer.setSettingsKey(Constants::СС_START_SUGGESTION_TIMER);
|
startSuggestionTimer.setSettingsKey(Constants::СС_START_SUGGESTION_TIMER);
|
||||||
startSuggestionTimer.setLabelText(Tr::tr("with delay(ms)"));
|
startSuggestionTimer.setLabelText(Tr::tr("with delay(ms)"));
|
||||||
startSuggestionTimer.setRange(10, 10000);
|
startSuggestionTimer.setRange(10, 10000);
|
||||||
startSuggestionTimer.setDefaultValue(500);
|
startSuggestionTimer.setDefaultValue(350);
|
||||||
|
|
||||||
autoCompletionCharThreshold.setSettingsKey(Constants::СС_AUTO_COMPLETION_CHAR_THRESHOLD);
|
autoCompletionCharThreshold.setSettingsKey(Constants::СС_AUTO_COMPLETION_CHAR_THRESHOLD);
|
||||||
autoCompletionCharThreshold.setLabelText(Tr::tr("AI suggestion triggers after typing"));
|
autoCompletionCharThreshold.setLabelText(Tr::tr("AI suggestion triggers after typing"));
|
||||||
@@ -70,7 +70,7 @@ CodeCompletionSettings::CodeCompletionSettings()
|
|||||||
Tr::tr("The number of characters that need to be typed within the typing interval "
|
Tr::tr("The number of characters that need to be typed within the typing interval "
|
||||||
"before an AI suggestion request is sent."));
|
"before an AI suggestion request is sent."));
|
||||||
autoCompletionCharThreshold.setRange(0, 10);
|
autoCompletionCharThreshold.setRange(0, 10);
|
||||||
autoCompletionCharThreshold.setDefaultValue(0);
|
autoCompletionCharThreshold.setDefaultValue(1);
|
||||||
|
|
||||||
autoCompletionTypingInterval.setSettingsKey(Constants::СС_AUTO_COMPLETION_TYPING_INTERVAL);
|
autoCompletionTypingInterval.setSettingsKey(Constants::СС_AUTO_COMPLETION_TYPING_INTERVAL);
|
||||||
autoCompletionTypingInterval.setLabelText(Tr::tr("character(s) within(ms)"));
|
autoCompletionTypingInterval.setLabelText(Tr::tr("character(s) within(ms)"));
|
||||||
@@ -78,7 +78,7 @@ CodeCompletionSettings::CodeCompletionSettings()
|
|||||||
Tr::tr("The time window (in milliseconds) during which the character threshold "
|
Tr::tr("The time window (in milliseconds) during which the character threshold "
|
||||||
"must be met to trigger an AI suggestion request."));
|
"must be met to trigger an AI suggestion request."));
|
||||||
autoCompletionTypingInterval.setRange(500, 5000);
|
autoCompletionTypingInterval.setRange(500, 5000);
|
||||||
autoCompletionTypingInterval.setDefaultValue(2000);
|
autoCompletionTypingInterval.setDefaultValue(1200);
|
||||||
|
|
||||||
// General Parameters Settings
|
// General Parameters Settings
|
||||||
temperature.setSettingsKey(Constants::CC_TEMPERATURE);
|
temperature.setSettingsKey(Constants::CC_TEMPERATURE);
|
||||||
@@ -89,7 +89,7 @@ CodeCompletionSettings::CodeCompletionSettings()
|
|||||||
|
|
||||||
maxTokens.setSettingsKey(Constants::CC_MAX_TOKENS);
|
maxTokens.setSettingsKey(Constants::CC_MAX_TOKENS);
|
||||||
maxTokens.setLabelText(Tr::tr("Max Tokens:"));
|
maxTokens.setLabelText(Tr::tr("Max Tokens:"));
|
||||||
maxTokens.setRange(-1, 10000);
|
maxTokens.setRange(-1, 900000);
|
||||||
maxTokens.setDefaultValue(50);
|
maxTokens.setDefaultValue(50);
|
||||||
|
|
||||||
// Advanced Parameters
|
// Advanced Parameters
|
||||||
@@ -151,8 +151,27 @@ CodeCompletionSettings::CodeCompletionSettings()
|
|||||||
|
|
||||||
systemPrompt.setSettingsKey(Constants::CC_SYSTEM_PROMPT);
|
systemPrompt.setSettingsKey(Constants::CC_SYSTEM_PROMPT);
|
||||||
systemPrompt.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
systemPrompt.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||||
systemPrompt.setDefaultValue("You are an expert C++, Qt, and QML code completion AI. Answer "
|
systemPrompt.setDefaultValue(
|
||||||
"should be ONLY in CODE and without repeating current.");
|
"You are an expert in C++, Qt, and QML programming. Your task is to provide code "
|
||||||
|
"suggestions that seamlessly integrate with existing code. Do not repeat code from position "
|
||||||
|
"before or after <cursor>. You will receive a code context with specified insertion points. "
|
||||||
|
"Your goal is to complete only one code block."
|
||||||
|
"Here is the code context with insertion points:<code_context>Before: {{variable}}After: "
|
||||||
|
"{{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 "
|
||||||
|
"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 "
|
||||||
|
"a code block using triple backticks. 6. Do not include any comments or descriptions with "
|
||||||
|
"your code suggestion.");
|
||||||
|
|
||||||
|
useUserMessageTemplateForCC.setSettingsKey(Constants::CC_USE_USER_TEMPLATE);
|
||||||
|
useUserMessageTemplateForCC.setDefaultValue(true);
|
||||||
|
useUserMessageTemplateForCC.setLabelText(Tr::tr("Use User Template for code completion message for non-FIM models"));
|
||||||
|
|
||||||
|
userMessageTemplateForCC.setSettingsKey(Constants::CC_USER_TEMPLATE);
|
||||||
|
userMessageTemplateForCC.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||||
|
userMessageTemplateForCC.setDefaultValue("Here is the code context with insertion points: <code_context>"
|
||||||
|
"\nBefore: %1After: %2\n </code_context>");
|
||||||
|
|
||||||
useFilePathInContext.setSettingsKey(Constants::CC_USE_FILE_PATH_IN_CONTEXT);
|
useFilePathInContext.setSettingsKey(Constants::CC_USE_FILE_PATH_IN_CONTEXT);
|
||||||
useFilePathInContext.setDefaultValue(true);
|
useFilePathInContext.setDefaultValue(true);
|
||||||
@@ -182,7 +201,7 @@ CodeCompletionSettings::CodeCompletionSettings()
|
|||||||
|
|
||||||
// API Configuration Settings
|
// API Configuration Settings
|
||||||
apiKey.setSettingsKey(Constants::CC_API_KEY);
|
apiKey.setSettingsKey(Constants::CC_API_KEY);
|
||||||
apiKey.setLabelText(Tr::tr("API Key:"));
|
apiKey.setLabelText(Tr::tr("[Deprecated, see Provider Settings]API Key:"));
|
||||||
apiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
apiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||||
apiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
|
apiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
|
||||||
|
|
||||||
@@ -218,6 +237,8 @@ CodeCompletionSettings::CodeCompletionSettings()
|
|||||||
auto contextItem = Column{Row{contextGrid, Stretch{1}},
|
auto contextItem = Column{Row{contextGrid, Stretch{1}},
|
||||||
Row{useSystemPrompt, Stretch{1}},
|
Row{useSystemPrompt, Stretch{1}},
|
||||||
systemPrompt,
|
systemPrompt,
|
||||||
|
Row{useUserMessageTemplateForCC, Stretch{1}},
|
||||||
|
userMessageTemplateForCC,
|
||||||
Row{useFilePathInContext, Stretch{1}},
|
Row{useFilePathInContext, Stretch{1}},
|
||||||
Row{useProjectChangesCache, maxChangesCacheSize, Stretch{1}}};
|
Row{useProjectChangesCache, maxChangesCacheSize, Stretch{1}}};
|
||||||
|
|
||||||
@@ -289,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);
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ public:
|
|||||||
Utils::IntegerAspect readStringsAfterCursor{this};
|
Utils::IntegerAspect readStringsAfterCursor{this};
|
||||||
Utils::BoolAspect useSystemPrompt{this};
|
Utils::BoolAspect useSystemPrompt{this};
|
||||||
Utils::StringAspect systemPrompt{this};
|
Utils::StringAspect systemPrompt{this};
|
||||||
|
Utils::BoolAspect useUserMessageTemplateForCC{this};
|
||||||
|
Utils::StringAspect userMessageTemplateForCC{this};
|
||||||
Utils::BoolAspect useFilePathInContext{this};
|
Utils::BoolAspect useFilePathInContext{this};
|
||||||
Utils::BoolAspect useProjectChangesCache{this};
|
Utils::BoolAspect useProjectChangesCache{this};
|
||||||
Utils::IntegerAspect maxChangesCacheSize{this};
|
Utils::IntegerAspect maxChangesCacheSize{this};
|
||||||
|
|||||||
@@ -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
|
||||||
81
settings/ProjectSettings.cpp
Normal file
81
settings/ProjectSettings.cpp
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* 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 "ProjectSettings.hpp"
|
||||||
|
|
||||||
|
#include "GeneralSettings.hpp"
|
||||||
|
#include "SettingsConstants.hpp"
|
||||||
|
#include "SettingsTr.hpp"
|
||||||
|
#include <coreplugin/icore.h>
|
||||||
|
#include <projectexplorer/project.h>
|
||||||
|
|
||||||
|
namespace QodeAssist::Settings {
|
||||||
|
|
||||||
|
ProjectSettings::ProjectSettings(ProjectExplorer::Project *project)
|
||||||
|
{
|
||||||
|
setAutoApply(true);
|
||||||
|
|
||||||
|
useGlobalSettings.setSettingsKey(Constants::QODE_ASSIST_USE_GLOBAL_SETTINGS);
|
||||||
|
useGlobalSettings.setDefaultValue(true);
|
||||||
|
|
||||||
|
enableQodeAssist.setSettingsKey(Constants::QODE_ASSIST_ENABLE_IN_PROJECT);
|
||||||
|
enableQodeAssist.setDisplayName(Tr::tr("Enable Qode Assist"));
|
||||||
|
enableQodeAssist.setLabelText(Tr::tr("Enable Qode Assist"));
|
||||||
|
enableQodeAssist.setDefaultValue(false);
|
||||||
|
|
||||||
|
chatHistoryPath.setSettingsKey(Constants::QODE_ASSIST_CHAT_HISTORY_PATH);
|
||||||
|
chatHistoryPath.setExpectedKind(Utils::PathChooser::ExistingDirectory);
|
||||||
|
chatHistoryPath.setLabelText(Tr::tr("Chat History Path:"));
|
||||||
|
|
||||||
|
QString projectChatHistoryPath
|
||||||
|
= QString("%1/qodeassist/chat_history").arg(Core::ICore::userResourcePath().toString());
|
||||||
|
|
||||||
|
chatHistoryPath.setDefaultValue(projectChatHistoryPath);
|
||||||
|
|
||||||
|
Utils::Store map = Utils::storeFromVariant(
|
||||||
|
project->namedSettings(Constants::QODE_ASSIST_PROJECT_SETTINGS_ID));
|
||||||
|
fromMap(map);
|
||||||
|
|
||||||
|
enableQodeAssist.addOnChanged(this, [this, project] { save(project); });
|
||||||
|
useGlobalSettings.addOnChanged(this, [this, project] { save(project); });
|
||||||
|
chatHistoryPath.addOnChanged(this, [this, project] { save(project); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectSettings::setUseGlobalSettings(bool useGlobal)
|
||||||
|
{
|
||||||
|
useGlobalSettings.setValue(useGlobal);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProjectSettings::isEnabled() const
|
||||||
|
{
|
||||||
|
if (useGlobalSettings())
|
||||||
|
return generalSettings().enableQodeAssist();
|
||||||
|
return enableQodeAssist();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectSettings::save(ProjectExplorer::Project *project)
|
||||||
|
{
|
||||||
|
Utils::Store map;
|
||||||
|
toMap(map);
|
||||||
|
project
|
||||||
|
->setNamedSettings(Constants::QODE_ASSIST_PROJECT_SETTINGS_ID, Utils::variantFromStore(map));
|
||||||
|
generalSettings().apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Settings
|
||||||
44
settings/ProjectSettings.hpp
Normal file
44
settings/ProjectSettings.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 <utils/aspects.h>
|
||||||
|
|
||||||
|
namespace ProjectExplorer {
|
||||||
|
class Project;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace QodeAssist::Settings {
|
||||||
|
|
||||||
|
class ProjectSettings : public Utils::AspectContainer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit ProjectSettings(ProjectExplorer::Project *project);
|
||||||
|
void save(ProjectExplorer::Project *project);
|
||||||
|
|
||||||
|
void setUseGlobalSettings(bool useGlobalSettings);
|
||||||
|
bool isEnabled() const;
|
||||||
|
|
||||||
|
Utils::BoolAspect enableQodeAssist{this};
|
||||||
|
Utils::BoolAspect useGlobalSettings{this};
|
||||||
|
Utils::FilePathAspect chatHistoryPath{this};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Settings
|
||||||
93
settings/ProjectSettingsPanel.cpp
Normal file
93
settings/ProjectSettingsPanel.cpp
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* 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 "ProjectSettingsPanel.hpp"
|
||||||
|
|
||||||
|
#include <projectexplorer/project.h>
|
||||||
|
#include <projectexplorer/projectpanelfactory.h>
|
||||||
|
#include <projectexplorer/projectsettingswidget.h>
|
||||||
|
#include <utils/layoutbuilder.h>
|
||||||
|
|
||||||
|
#include "ProjectSettings.hpp"
|
||||||
|
#include "SettingsConstants.hpp"
|
||||||
|
#include "SettingsTr.hpp"
|
||||||
|
|
||||||
|
using namespace ProjectExplorer;
|
||||||
|
|
||||||
|
namespace QodeAssist::Settings {
|
||||||
|
|
||||||
|
class ProjectSettingsWidget final : public ProjectExplorer::ProjectSettingsWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ProjectSettingsWidget()
|
||||||
|
{
|
||||||
|
setGlobalSettingsId(Constants::QODE_ASSIST_GENERAL_OPTIONS_ID);
|
||||||
|
setUseGlobalSettingsCheckBoxVisible(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static ProjectSettingsWidget *createProjectPanel(Project *project)
|
||||||
|
{
|
||||||
|
using namespace Layouting;
|
||||||
|
|
||||||
|
auto widget = new ProjectSettingsWidget;
|
||||||
|
auto settings = new ProjectSettings(project);
|
||||||
|
settings->setParent(widget);
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
widget,
|
||||||
|
&ProjectSettingsWidget::useGlobalSettingsChanged,
|
||||||
|
settings,
|
||||||
|
&ProjectSettings::setUseGlobalSettings);
|
||||||
|
|
||||||
|
widget->setUseGlobalSettings(settings->useGlobalSettings());
|
||||||
|
widget->setEnabled(!settings->useGlobalSettings());
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
widget, &ProjectSettingsWidget::useGlobalSettingsChanged, widget, [widget](bool useGlobal) {
|
||||||
|
widget->setEnabled(!useGlobal);
|
||||||
|
});
|
||||||
|
|
||||||
|
Column{
|
||||||
|
settings->enableQodeAssist,
|
||||||
|
Space{8},
|
||||||
|
settings->chatHistoryPath,
|
||||||
|
}
|
||||||
|
.attachTo(widget);
|
||||||
|
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProjectPanelFactory final : public ProjectExplorer::ProjectPanelFactory
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ProjectPanelFactory()
|
||||||
|
{
|
||||||
|
setPriority(1000);
|
||||||
|
setDisplayName(Tr::tr("Qode Assist"));
|
||||||
|
setCreateWidgetFunction(&createProjectPanel);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void setupProjectPanel()
|
||||||
|
{
|
||||||
|
static ProjectPanelFactory theProjectPanelFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Settings
|
||||||
26
settings/ProjectSettingsPanel.hpp
Normal file
26
settings/ProjectSettingsPanel.hpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
namespace QodeAssist::Settings {
|
||||||
|
|
||||||
|
void setupProjectPanel();
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Settings
|
||||||
148
settings/ProviderSettings.cpp
Normal file
148
settings/ProviderSettings.cpp
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* 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 "ProviderSettings.hpp"
|
||||||
|
|
||||||
|
#include <coreplugin/dialogs/ioptionspage.h>
|
||||||
|
#include <coreplugin/icore.h>
|
||||||
|
#include <utils/layoutbuilder.h>
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
#include "SettingsConstants.hpp"
|
||||||
|
#include "SettingsTr.hpp"
|
||||||
|
#include "SettingsUtils.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Settings {
|
||||||
|
|
||||||
|
ProviderSettings &providerSettings()
|
||||||
|
{
|
||||||
|
static ProviderSettings settings;
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProviderSettings::ProviderSettings()
|
||||||
|
{
|
||||||
|
setAutoApply(false);
|
||||||
|
|
||||||
|
setDisplayName(Tr::tr("Provider Settings"));
|
||||||
|
|
||||||
|
// OpenRouter Settings
|
||||||
|
openRouterApiKey.setSettingsKey(Constants::OPEN_ROUTER_API_KEY);
|
||||||
|
openRouterApiKey.setLabelText(Tr::tr("OpenRouter API Key:"));
|
||||||
|
openRouterApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||||
|
openRouterApiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
|
||||||
|
openRouterApiKey.setHistoryCompleter(Constants::OPEN_ROUTER_API_KEY_HISTORY);
|
||||||
|
openRouterApiKey.setDefaultValue("");
|
||||||
|
openRouterApiKey.setAutoApply(true);
|
||||||
|
|
||||||
|
// OpenAI Compatible Settings
|
||||||
|
openAiCompatApiKey.setSettingsKey(Constants::OPEN_AI_COMPAT_API_KEY);
|
||||||
|
openAiCompatApiKey.setLabelText(Tr::tr("OpenAI Compatible API Key:"));
|
||||||
|
openAiCompatApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||||
|
openAiCompatApiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
|
||||||
|
openAiCompatApiKey.setHistoryCompleter(Constants::OPEN_AI_COMPAT_API_KEY_HISTORY);
|
||||||
|
openAiCompatApiKey.setDefaultValue("");
|
||||||
|
openAiCompatApiKey.setAutoApply(true);
|
||||||
|
|
||||||
|
// Claude Compatible Settings
|
||||||
|
claudeApiKey.setSettingsKey(Constants::CLAUDE_API_KEY);
|
||||||
|
claudeApiKey.setLabelText(Tr::tr("Claude API Key:"));
|
||||||
|
claudeApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||||
|
claudeApiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
|
||||||
|
claudeApiKey.setHistoryCompleter(Constants::CLAUDE_API_KEY_HISTORY);
|
||||||
|
claudeApiKey.setDefaultValue("");
|
||||||
|
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");
|
||||||
|
|
||||||
|
readSettings();
|
||||||
|
|
||||||
|
setupConnections();
|
||||||
|
|
||||||
|
setLayouter([this]() {
|
||||||
|
using namespace Layouting;
|
||||||
|
|
||||||
|
return Column{
|
||||||
|
Row{Stretch{1}, resetToDefaults},
|
||||||
|
Space{8},
|
||||||
|
Group{title(Tr::tr("OpenRouter Settings")), Column{openRouterApiKey}},
|
||||||
|
Space{8},
|
||||||
|
Group{title(Tr::tr("OpenAI Settings")), Column{openAiApiKey}},
|
||||||
|
Space{8},
|
||||||
|
Group{title(Tr::tr("OpenAI Compatible Settings")), Column{openAiCompatApiKey}},
|
||||||
|
Space{8},
|
||||||
|
Group{title(Tr::tr("Claude Settings")), Column{claudeApiKey}},
|
||||||
|
Stretch{1}};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProviderSettings::setupConnections()
|
||||||
|
{
|
||||||
|
connect(
|
||||||
|
&resetToDefaults, &ButtonAspect::clicked, this, &ProviderSettings::resetSettingsToDefaults);
|
||||||
|
connect(&openRouterApiKey, &ButtonAspect::changed, this, [this]() {
|
||||||
|
openRouterApiKey.writeSettings();
|
||||||
|
});
|
||||||
|
connect(&openAiCompatApiKey, &ButtonAspect::changed, this, [this]() {
|
||||||
|
openAiCompatApiKey.writeSettings();
|
||||||
|
});
|
||||||
|
connect(&claudeApiKey, &ButtonAspect::changed, this, [this]() { claudeApiKey.writeSettings(); });
|
||||||
|
connect(&openAiApiKey, &ButtonAspect::changed, this, [this]() { openAiApiKey.writeSettings(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProviderSettings::resetSettingsToDefaults()
|
||||||
|
{
|
||||||
|
QMessageBox::StandardButton reply;
|
||||||
|
reply = QMessageBox::question(
|
||||||
|
Core::ICore::dialogParent(),
|
||||||
|
Tr::tr("Reset Settings"),
|
||||||
|
Tr::tr("Are you sure you want to reset all settings to default values?"),
|
||||||
|
QMessageBox::Yes | QMessageBox::No);
|
||||||
|
|
||||||
|
if (reply == QMessageBox::Yes) {
|
||||||
|
resetAspect(openRouterApiKey);
|
||||||
|
resetAspect(openAiCompatApiKey);
|
||||||
|
resetAspect(claudeApiKey);
|
||||||
|
resetAspect(openAiApiKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProviderSettingsPage : public Core::IOptionsPage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ProviderSettingsPage()
|
||||||
|
{
|
||||||
|
setId(Constants::QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID);
|
||||||
|
setDisplayName(Tr::tr("Provider Settings"));
|
||||||
|
setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY);
|
||||||
|
setSettingsProvider([] { return &providerSettings(); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProviderSettingsPage providerSettingsPage;
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Settings
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user