mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-02-12 10:10:44 -05:00
Compare commits
35 Commits
v0.4.6
...
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 |
10
.github/workflows/build_cmake.yml
vendored
10
.github/workflows/build_cmake.yml
vendored
@ -13,8 +13,8 @@ on:
|
||||
env:
|
||||
PLUGIN_NAME: QodeAssist
|
||||
QT_VERSION: 6.8.1
|
||||
QT_CREATOR_VERSION: 15.0.0
|
||||
QT_CREATOR_VERSION_INTERNAL: 15.0.0
|
||||
QT_CREATOR_VERSION: 15.0.1
|
||||
QT_CREATOR_VERSION_INTERNAL: 15.0.1
|
||||
MACOS_DEPLOYMENT_TARGET: "11.0"
|
||||
CMAKE_VERSION: "3.29.6"
|
||||
NINJA_VERSION: "1.12.1"
|
||||
@ -41,12 +41,6 @@ jobs:
|
||||
platform: linux_x64,
|
||||
cc: "gcc", cxx: "g++"
|
||||
}
|
||||
- {
|
||||
name: "Ubuntu 22.04 GCC", artifact: "Linux-x64(Ubuntu-22.04-experimental)",
|
||||
os: ubuntu-22.04,
|
||||
platform: linux_x64,
|
||||
cc: "gcc", cxx: "g++"
|
||||
}
|
||||
- {
|
||||
name: "macOS Latest Clang", artifact: "macOS-universal",
|
||||
os: macos-latest,
|
||||
|
||||
@ -55,6 +55,7 @@ add_qtc_plugin(QodeAssist
|
||||
templates/Llama2.hpp
|
||||
templates/Claude.hpp
|
||||
templates/OpenAI.hpp
|
||||
templates/CodeLlamaQMLFim.hpp
|
||||
providers/Providers.hpp
|
||||
providers/OllamaProvider.hpp providers/OllamaProvider.cpp
|
||||
providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp
|
||||
|
||||
@ -18,9 +18,12 @@ qt_add_qml_module(QodeAssistChatView
|
||||
qml/parts/BottomBar.qml
|
||||
qml/parts/AttachedFilesPlace.qml
|
||||
RESOURCES
|
||||
icons/attach-file.svg
|
||||
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
|
||||
ChatWidget.hpp ChatWidget.cpp
|
||||
ChatModel.hpp ChatModel.cpp
|
||||
|
||||
@ -28,7 +28,6 @@ namespace QodeAssist::Chat {
|
||||
|
||||
ChatModel::ChatModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, m_totalTokens(0)
|
||||
{
|
||||
auto &settings = Settings::chatAssistantSettings();
|
||||
|
||||
@ -90,26 +89,19 @@ void ChatModel::addMessage(
|
||||
.arg(attachment.filename, attachment.content);
|
||||
}
|
||||
}
|
||||
int tokenCount = estimateTokenCount(fullContent);
|
||||
|
||||
if (!m_messages.isEmpty() && !id.isEmpty() && m_messages.last().id == id) {
|
||||
Message &lastMessage = m_messages.last();
|
||||
int oldTokenCount = lastMessage.tokenCount;
|
||||
lastMessage.content = content;
|
||||
lastMessage.attachments = attachments;
|
||||
lastMessage.tokenCount = tokenCount;
|
||||
m_totalTokens += (tokenCount - oldTokenCount);
|
||||
emit dataChanged(index(m_messages.size() - 1), index(m_messages.size() - 1));
|
||||
} else {
|
||||
beginInsertRows(QModelIndex(), m_messages.size(), m_messages.size());
|
||||
Message newMessage{role, content, tokenCount, id};
|
||||
Message newMessage{role, content, id};
|
||||
newMessage.attachments = attachments;
|
||||
m_messages.append(newMessage);
|
||||
m_totalTokens += tokenCount;
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
emit totalTokensChanged();
|
||||
}
|
||||
|
||||
QVector<ChatModel::Message> ChatModel::getChatHistory() const
|
||||
@ -117,18 +109,11 @@ QVector<ChatModel::Message> ChatModel::getChatHistory() const
|
||||
return m_messages;
|
||||
}
|
||||
|
||||
int ChatModel::estimateTokenCount(const QString &text) const
|
||||
{
|
||||
return text.length() / 4;
|
||||
}
|
||||
|
||||
void ChatModel::clear()
|
||||
{
|
||||
beginResetModel();
|
||||
m_messages.clear();
|
||||
m_totalTokens = 0;
|
||||
endResetModel();
|
||||
emit totalTokensChanged();
|
||||
emit modelReseted();
|
||||
}
|
||||
|
||||
@ -199,11 +184,6 @@ QJsonArray ChatModel::prepareMessagesForRequest(const QString &systemPrompt) con
|
||||
return messages;
|
||||
}
|
||||
|
||||
int ChatModel::totalTokens() const
|
||||
{
|
||||
return m_totalTokens;
|
||||
}
|
||||
|
||||
int ChatModel::tokensThreshold() const
|
||||
{
|
||||
auto &settings = Settings::chatAssistantSettings();
|
||||
|
||||
@ -33,7 +33,6 @@ namespace QodeAssist::Chat {
|
||||
class ChatModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int totalTokens READ totalTokens NOTIFY totalTokensChanged FINAL)
|
||||
Q_PROPERTY(int tokensThreshold READ tokensThreshold NOTIFY tokensThresholdChanged FINAL)
|
||||
QML_ELEMENT
|
||||
|
||||
@ -47,7 +46,6 @@ public:
|
||||
{
|
||||
ChatRole role;
|
||||
QString content;
|
||||
int tokenCount;
|
||||
QString id;
|
||||
|
||||
QList<Context::ContentFile> attachments;
|
||||
@ -70,22 +68,17 @@ public:
|
||||
QVector<Message> getChatHistory() const;
|
||||
QJsonArray prepareMessagesForRequest(const QString &systemPrompt) const;
|
||||
|
||||
int totalTokens() const;
|
||||
int tokensThreshold() const;
|
||||
|
||||
QString currentModel() const;
|
||||
QString lastMessageId() const;
|
||||
|
||||
signals:
|
||||
void totalTokensChanged();
|
||||
void tokensThresholdChanged();
|
||||
void modelReseted();
|
||||
|
||||
private:
|
||||
int estimateTokenCount(const QString &text) const;
|
||||
|
||||
QVector<Message> m_messages;
|
||||
int m_totalTokens = 0;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
|
||||
@ -20,13 +20,16 @@
|
||||
#include "ChatRootView.hpp"
|
||||
|
||||
#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/utilsicons.h>
|
||||
|
||||
@ -35,6 +38,10 @@
|
||||
#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 {
|
||||
|
||||
@ -43,6 +50,13 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
||||
, m_chatModel(new 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();
|
||||
|
||||
connect(&settings.caModel,
|
||||
@ -50,18 +64,44 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
||||
this,
|
||||
&ChatRootView::currentTemplateChanged);
|
||||
|
||||
connect(&Settings::chatAssistantSettings().sharingCurrentFile,
|
||||
&Utils::BaseAspect::changed,
|
||||
this,
|
||||
&ChatRootView::isSharingCurrentFileChanged);
|
||||
|
||||
connect(
|
||||
m_clientInterface,
|
||||
&ClientInterface::messageReceivedCompletely,
|
||||
this,
|
||||
&ChatRootView::autosave);
|
||||
|
||||
connect(m_chatModel, &ChatModel::modelReseted, [this]() { m_recentFilePath = QString(); });
|
||||
connect(
|
||||
m_clientInterface,
|
||||
&ClientInterface::messageReceivedCompletely,
|
||||
this,
|
||||
&ChatRootView::updateInputTokensCount);
|
||||
|
||||
connect(m_chatModel, &ChatModel::modelReseted, this, [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
|
||||
@ -69,9 +109,9 @@ ChatModel *ChatRootView::chatModel() const
|
||||
return m_chatModel;
|
||||
}
|
||||
|
||||
void ChatRootView::sendMessage(const QString &message, bool sharingCurrentFile)
|
||||
void ChatRootView::sendMessage(const QString &message)
|
||||
{
|
||||
if (m_chatModel->totalTokens() > m_chatModel->tokensThreshold()) {
|
||||
if (m_inputTokensCount > m_chatModel->tokensThreshold()) {
|
||||
QMessageBox::StandardButton reply = QMessageBox::question(
|
||||
Core::ICore::dialogParent(),
|
||||
tr("Token Limit Exceeded"),
|
||||
@ -82,12 +122,12 @@ void ChatRootView::sendMessage(const QString &message, bool sharingCurrentFile)
|
||||
if (reply == QMessageBox::Yes) {
|
||||
autosave();
|
||||
m_chatModel->clear();
|
||||
m_recentFilePath = QString{};
|
||||
setRecentFilePath(QString{});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_clientInterface->sendMessage(message, m_attachmentFiles, sharingCurrentFile);
|
||||
m_clientInterface->sendMessage(message, m_attachmentFiles, m_linkedFiles);
|
||||
clearAttachmentFiles();
|
||||
}
|
||||
|
||||
@ -109,6 +149,14 @@ void ChatRootView::clearAttachmentFiles()
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRootView::clearLinkedFiles()
|
||||
{
|
||||
if (!m_linkedFiles.isEmpty()) {
|
||||
m_linkedFiles.clear();
|
||||
emit linkedFilesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QString ChatRootView::getChatsHistoryDir() const
|
||||
{
|
||||
QString path;
|
||||
@ -135,16 +183,13 @@ QString ChatRootView::currentTemplate() const
|
||||
return settings.caModel();
|
||||
}
|
||||
|
||||
bool ChatRootView::isSharingCurrentFile() const
|
||||
{
|
||||
return Settings::chatAssistantSettings().sharingCurrentFile();
|
||||
}
|
||||
|
||||
void ChatRootView::saveHistory(const QString &filePath)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,8 +199,9 @@ void ChatRootView::loadHistory(const QString &filePath)
|
||||
if (!result.success) {
|
||||
LOG_MESSAGE(QString("Failed to load chat history: %1").arg(result.errorMessage));
|
||||
} else {
|
||||
m_recentFilePath = filePath;
|
||||
setRecentFilePath(filePath);
|
||||
}
|
||||
updateInputTokensCount();
|
||||
}
|
||||
|
||||
void ChatRootView::showSaveDialog()
|
||||
@ -214,17 +260,50 @@ 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);
|
||||
shortMessage.replace(QRegularExpression("[^a-zA-Z0-9_-]"), "_");
|
||||
parts << shortMessage;
|
||||
|
||||
QString sanitizedMessage = shortMessage;
|
||||
sanitizedMessage.replace(saitizeSymbols, "_");
|
||||
sanitizedMessage.replace(underSymbols, "_");
|
||||
sanitizedMessage = sanitizedMessage.trimmed();
|
||||
|
||||
if (!sanitizedMessage.isEmpty()) {
|
||||
if (sanitizedMessage.startsWith('_')) {
|
||||
sanitizedMessage.remove(0, 1);
|
||||
}
|
||||
if (sanitizedMessage.endsWith('_')) {
|
||||
sanitizedMessage.chop(1);
|
||||
}
|
||||
|
||||
QString targetDir = getChatsHistoryDir();
|
||||
QString fullPath = QDir(targetDir).filePath(sanitizedMessage);
|
||||
|
||||
QFileInfo fileInfo(fullPath);
|
||||
if (!fileInfo.exists() && QFileInfo(fileInfo.path()).isWritable()) {
|
||||
parts << sanitizedMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parts << QDateTime::currentDateTime().toString("yyyy-MM-dd_HH-mm");
|
||||
|
||||
return parts.join("_");
|
||||
QString fileName = parts.join("_");
|
||||
|
||||
QString fullPath = QDir(getChatsHistoryDir()).filePath(fileName);
|
||||
QFileInfo finalCheck(fullPath);
|
||||
|
||||
if (fileName.isEmpty() || finalCheck.exists() || !QFileInfo(finalCheck.path()).isWritable()) {
|
||||
fileName = QString("chat_%1").arg(
|
||||
QDateTime::currentDateTime().toString("yyyy-MM-dd_HH-mm"));
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
void ChatRootView::autosave()
|
||||
@ -236,7 +315,7 @@ void ChatRootView::autosave()
|
||||
QString filePath = getAutosaveFilePath();
|
||||
if (!filePath.isEmpty()) {
|
||||
ChatSerializer::saveToFile(m_chatModel, filePath);
|
||||
m_recentFilePath = filePath;
|
||||
setRecentFilePath(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,6 +333,16 @@ QString ChatRootView::getAutosaveFilePath() const
|
||||
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"));
|
||||
@ -267,7 +356,7 @@ void ChatRootView::showAttachFilesDialog()
|
||||
QStringList newFilePaths = dialog.selectedFiles();
|
||||
if (!newFilePaths.isEmpty()) {
|
||||
bool filesAdded = false;
|
||||
for (const QString &filePath : newFilePaths) {
|
||||
for (const QString &filePath : std::as_const(newFilePaths)) {
|
||||
if (!m_attachmentFiles.contains(filePath)) {
|
||||
m_attachmentFiles.append(filePath);
|
||||
filesAdded = true;
|
||||
@ -280,4 +369,231 @@ void ChatRootView::showAttachFilesDialog()
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRootView::removeFileFromAttachList(int index)
|
||||
{
|
||||
if (index >= 0 && index < m_attachmentFiles.size()) {
|
||||
m_attachmentFiles.removeAt(index);
|
||||
emit attachmentFilesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRootView::showLinkFilesDialog()
|
||||
{
|
||||
QFileDialog dialog(nullptr, tr("Select Files to Attach"));
|
||||
dialog.setFileMode(QFileDialog::ExistingFiles);
|
||||
|
||||
if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
|
||||
dialog.setDirectory(project->projectDirectory().toString());
|
||||
}
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
QStringList newFilePaths = dialog.selectedFiles();
|
||||
if (!newFilePaths.isEmpty()) {
|
||||
bool filesAdded = false;
|
||||
for (const QString &filePath : 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
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
|
||||
#include "ChatModel.hpp"
|
||||
#include "ClientInterface.hpp"
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
@ -31,9 +32,11 @@ class ChatRootView : public QQuickItem
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QodeAssist::Chat::ChatModel *chatModel READ chatModel NOTIFY chatModelChanged FINAL)
|
||||
Q_PROPERTY(QString currentTemplate READ currentTemplate NOTIFY currentTemplateChanged FINAL)
|
||||
Q_PROPERTY(bool isSharingCurrentFile READ isSharingCurrentFile NOTIFY
|
||||
isSharingCurrentFileChanged FINAL)
|
||||
Q_PROPERTY(QStringList attachmentFiles MEMBER m_attachmentFiles NOTIFY attachmentFilesChanged)
|
||||
Q_PROPERTY(bool isSyncOpenFiles READ isSyncOpenFiles NOTIFY isSyncOpenFilesChanged FINAL)
|
||||
Q_PROPERTY(QStringList attachmentFiles READ attachmentFiles NOTIFY attachmentFilesChanged FINAL)
|
||||
Q_PROPERTY(QStringList linkedFiles READ linkedFiles NOTIFY linkedFilesChanged FINAL)
|
||||
Q_PROPERTY(int inputTokensCount READ inputTokensCount NOTIFY inputTokensCountChanged FINAL)
|
||||
Q_PROPERTY(QString chatFileName READ chatFileName NOTIFY chatFileNameChanged FINAL)
|
||||
|
||||
QML_ELEMENT
|
||||
|
||||
@ -43,8 +46,6 @@ public:
|
||||
ChatModel *chatModel() const;
|
||||
QString currentTemplate() const;
|
||||
|
||||
bool isSharingCurrentFile() const;
|
||||
|
||||
void saveHistory(const QString &filePath);
|
||||
void loadHistory(const QString &filePath);
|
||||
|
||||
@ -54,19 +55,46 @@ public:
|
||||
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:
|
||||
void sendMessage(const QString &message, bool sharingCurrentFile = false);
|
||||
void sendMessage(const QString &message);
|
||||
void copyToClipboard(const QString &text);
|
||||
void cancelRequest();
|
||||
void clearAttachmentFiles();
|
||||
void clearLinkedFiles();
|
||||
|
||||
signals:
|
||||
void chatModelChanged();
|
||||
void currentTemplateChanged();
|
||||
void isSharingCurrentFileChanged();
|
||||
void attachmentFilesChanged();
|
||||
void linkedFilesChanged();
|
||||
void inputTokensCountChanged();
|
||||
void isSyncOpenFilesChanged();
|
||||
void chatFileNameChanged();
|
||||
|
||||
private:
|
||||
QString getChatsHistoryDir() const;
|
||||
@ -77,6 +105,11 @@ private:
|
||||
QString m_currentTemplate;
|
||||
QString m_recentFilePath;
|
||||
QStringList m_attachmentFiles;
|
||||
QStringList m_linkedFiles;
|
||||
int m_messageTokensCount{0};
|
||||
int m_inputTokensCount{0};
|
||||
bool m_isSyncOpenFiles;
|
||||
QList<Core::IEditor *> m_currentEditors;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
|
||||
@ -82,7 +82,6 @@ QJsonObject ChatSerializer::serializeMessage(const ChatModel::Message &message)
|
||||
QJsonObject messageObj;
|
||||
messageObj["role"] = static_cast<int>(message.role);
|
||||
messageObj["content"] = message.content;
|
||||
messageObj["tokenCount"] = message.tokenCount;
|
||||
messageObj["id"] = message.id;
|
||||
return messageObj;
|
||||
}
|
||||
@ -92,7 +91,6 @@ 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.tokenCount = json["tokenCount"].toInt();
|
||||
message.id = json["id"].toString();
|
||||
return message;
|
||||
}
|
||||
@ -107,7 +105,6 @@ QJsonObject ChatSerializer::serializeChat(const ChatModel *model)
|
||||
QJsonObject root;
|
||||
root["version"] = VERSION;
|
||||
root["messages"] = messagesArray;
|
||||
root["totalTokens"] = model->totalTokens();
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ ClientInterface::ClientInterface(ChatModel *chatModel, QObject *parent)
|
||||
ClientInterface::~ClientInterface() = default;
|
||||
|
||||
void ClientInterface::sendMessage(
|
||||
const QString &message, const QList<QString> &attachments, bool includeCurrentFile)
|
||||
const QString &message, const QList<QString> &attachments, const QList<QString> &linkedFiles)
|
||||
{
|
||||
cancelRequest();
|
||||
|
||||
@ -100,11 +100,8 @@ void ClientInterface::sendMessage(
|
||||
if (chatAssistantSettings.useSystemPrompt())
|
||||
systemPrompt = chatAssistantSettings.systemPrompt();
|
||||
|
||||
if (includeCurrentFile) {
|
||||
QString fileContext = getCurrentFileContext();
|
||||
if (!fileContext.isEmpty()) {
|
||||
systemPrompt = systemPrompt.append(fileContext);
|
||||
}
|
||||
if (!linkedFiles.isEmpty()) {
|
||||
systemPrompt = getSystemPromptWithLinkedFiles(systemPrompt, linkedFiles);
|
||||
}
|
||||
|
||||
QJsonObject providerRequest;
|
||||
@ -198,4 +195,21 @@ QString ClientInterface::getCurrentFileContext() const
|
||||
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
|
||||
|
||||
@ -39,7 +39,7 @@ public:
|
||||
void sendMessage(
|
||||
const QString &message,
|
||||
const QList<QString> &attachments = {},
|
||||
bool includeCurrentFile = false);
|
||||
const QList<QString> &linkedFiles = {});
|
||||
void clearMessages();
|
||||
void cancelRequest();
|
||||
|
||||
@ -50,6 +50,9 @@ signals:
|
||||
private:
|
||||
void handleLLMResponse(const QString &response, const QJsonObject &request, bool isComplete);
|
||||
QString getCurrentFileContext() const;
|
||||
QString getSystemPromptWithLinkedFiles(
|
||||
const QString &basePrompt,
|
||||
const QList<QString> &linkedFiles) const;
|
||||
|
||||
LLMCore::RequestHandler *m_requestHandler;
|
||||
ChatModel *m_chatModel;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<svg width="24" height="48" viewBox="0 0 24 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<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">
|
||||
|
Before Width: | Height: | Size: 555 B After Width: | Height: | Size: 869 B |
11
ChatView/icons/attach-file-light.svg
Normal file
11
ChatView/icons/attach-file-light.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<svg width="24" height="48" viewBox="0 0 24 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_51_20)">
|
||||
<path d="M22 10.1053V36.7368C22 41.8547 17.525 46 12 46C6.475 46 2 41.8547 2 36.7368V7.78947C2 4.59368 4.8 2 8.25 2C11.7 2 15.75 4.59368 15.75 7.78947V35.5789C15.75 36.8526 13.375 39.0526 12 39.0526C10.625 39.0526 8.25 36.8526 8.25 35.5789V21.6842V8.94737" stroke="white" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M22 10.1053V36.7368C22 41.8547 17.525 46 12 46C6.475 46 2 41.8547 2 36.7368V7.78947C2 4.59368 4.8 2 8.25 2C11.7 2 15.75 4.59368 15.75 7.78947V35.5789C15.75 36.8526 13.375 39.0526 12 39.0526C10.625 39.0526 8.25 36.8526 8.25 35.5789V21.6842V8.94737" stroke="white" stroke-width="3" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_51_20">
|
||||
<rect width="24" height="48" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 869 B |
12
ChatView/icons/link-file-dark.svg
Normal file
12
ChatView/icons/link-file-dark.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<svg width="20" height="44" viewBox="0 0 20 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_49_24)">
|
||||
<path d="M10 12L10 32L10 12Z" fill="black"/>
|
||||
<path d="M10 12L10 32" stroke="black" stroke-width="3"/>
|
||||
<path d="M1.50001 12.484C1.50001 -1.99999 18.5 -1.99999 18.5 12.484M1.5 31.5334C1.50001 46 18.5 46 18.5 31.5334" stroke="black" stroke-width="3" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_49_24">
|
||||
<rect width="20" height="44" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 513 B |
12
ChatView/icons/link-file-light.svg
Normal file
12
ChatView/icons/link-file-light.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<svg width="20" height="44" viewBox="0 0 20 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_51_24)">
|
||||
<path d="M10 12L10 32Z" fill="white"/>
|
||||
<path d="M10 12L10 32" stroke="white" stroke-width="3"/>
|
||||
<path d="M1.50001 12.484C1.50001 -1.99999 18.5 -1.99999 18.5 12.484M1.5 31.5334C1.50001 46 18.5 46 18.5 31.5334" stroke="white" stroke-width="3" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_51_24">
|
||||
<rect width="20" height="44" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 507 B |
@ -70,8 +70,12 @@ ChatRootView {
|
||||
loadButton.onClicked: root.showLoadDialog()
|
||||
clearButton.onClicked: root.clearChat()
|
||||
tokensBadge {
|
||||
text: qsTr("tokens:%1/%2").arg(root.chatModel.totalTokens).arg(root.chatModel.tokensThreshold)
|
||||
text: qsTr("tokens:%1/%2").arg(root.inputTokensCount).arg(root.chatModel.tokensThreshold)
|
||||
}
|
||||
recentPath {
|
||||
text: qsTr("Latest chat file name: %1").arg(root.chatFileName.length > 0 ? root.chatFileName : "Unsaved")
|
||||
}
|
||||
openChatHistory.onClicked: root.openChatHistoryFolder()
|
||||
}
|
||||
|
||||
ListView {
|
||||
@ -101,7 +105,7 @@ ChatRootView {
|
||||
height: 30
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
ScrollBar.vertical: QQC.ScrollBar {
|
||||
id: scroll
|
||||
}
|
||||
|
||||
@ -147,6 +151,8 @@ ChatRootView {
|
||||
}
|
||||
}
|
||||
|
||||
onTextChanged: root.calculateMessageTokensCount(messageInput.text)
|
||||
|
||||
Keys.onPressed: function(event) {
|
||||
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && !(event.modifiers & Qt.ShiftModifier)) {
|
||||
root.sendChatMessage()
|
||||
@ -161,6 +167,21 @@ ChatRootView {
|
||||
|
||||
Layout.fillWidth: true
|
||||
attachedFilesModel: root.attachmentFiles
|
||||
iconPath: palette.window.hslLightness > 0.5 ? "qrc:/qt/qml/ChatView/icons/attach-file-dark.svg"
|
||||
: "qrc:/qt/qml/ChatView/icons/attach-file-light.svg"
|
||||
accentColor: Qt.tint(palette.mid, Qt.rgba(0, 0.8, 0.3, 0.4))
|
||||
onRemoveFileFromListByIndex: (index) => root.removeFileFromAttachList(index)
|
||||
}
|
||||
|
||||
AttachedFilesPlace {
|
||||
id: linkedFilesPlace
|
||||
|
||||
Layout.fillWidth: true
|
||||
attachedFilesModel: root.linkedFiles
|
||||
iconPath: palette.window.hslLightness > 0.5 ? "qrc:/qt/qml/ChatView/icons/link-file-dark.svg"
|
||||
: "qrc:/qt/qml/ChatView/icons/link-file-light.svg"
|
||||
accentColor: Qt.tint(palette.mid, Qt.rgba(0, 0.3, 0.8, 0.4))
|
||||
onRemoveFileFromListByIndex: (index) => root.removeFileFromLinkList(index)
|
||||
}
|
||||
|
||||
BottomBar {
|
||||
@ -171,13 +192,21 @@ ChatRootView {
|
||||
|
||||
sendButton.onClicked: root.sendChatMessage()
|
||||
stopButton.onClicked: root.cancelRequest()
|
||||
sharingCurrentFile.checked: root.isSharingCurrentFile
|
||||
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() {
|
||||
root.chatModel.clear()
|
||||
root.clearAttachmentFiles()
|
||||
root.updateInputTokensCount()
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
@ -185,7 +214,7 @@ ChatRootView {
|
||||
}
|
||||
|
||||
function sendChatMessage() {
|
||||
root.sendMessage(messageInput.text, bottomBar.sharingCurrentFile.checked)
|
||||
root.sendMessage(messageInput.text)
|
||||
messageInput.text = ""
|
||||
scrollToBottom()
|
||||
}
|
||||
|
||||
@ -23,9 +23,13 @@ import QtQuick.Layouts
|
||||
import ChatView
|
||||
|
||||
Flow {
|
||||
id: attachFilesPlace
|
||||
id: root
|
||||
|
||||
property alias attachedFilesModel: attachRepeater.model
|
||||
property color accentColor: palette.mid
|
||||
property string iconPath
|
||||
|
||||
signal removeFileFromListByIndex(index: int)
|
||||
|
||||
spacing: 5
|
||||
leftPadding: 5
|
||||
@ -41,17 +45,32 @@ Flow {
|
||||
required property string modelData
|
||||
|
||||
height: 30
|
||||
width: fileNameText.width + closeButton.width + 20
|
||||
width: contentRow.width + 10
|
||||
radius: 4
|
||||
color: palette.button
|
||||
border.width: 1
|
||||
border.color: palette.mid
|
||||
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
|
||||
@ -69,14 +88,10 @@ Flow {
|
||||
id: closeButton
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: closeIcon.width
|
||||
height: closeButton.width
|
||||
width: closeIcon.width + 5
|
||||
height: closeButton.width + 5
|
||||
|
||||
onClicked: {
|
||||
const newList = [...root.attachmentFiles];
|
||||
newList.splice(index, 1);
|
||||
root.attachmentFiles = newList;
|
||||
}
|
||||
onClicked: root.removeFileFromListByIndex(index)
|
||||
|
||||
Image {
|
||||
id: closeIcon
|
||||
|
||||
@ -27,8 +27,11 @@ Rectangle {
|
||||
|
||||
property alias sendButton: sendButtonId
|
||||
property alias stopButton: stopButtonId
|
||||
property alias sharingCurrentFile: sharingCurrentFileId
|
||||
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) :
|
||||
@ -59,23 +62,49 @@ Rectangle {
|
||||
text: qsTr("Stop")
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: sharingCurrentFileId
|
||||
|
||||
text: qsTr("Share current file with models")
|
||||
}
|
||||
|
||||
QoAButton {
|
||||
id: attachFilesId
|
||||
|
||||
icon {
|
||||
source: "qrc:/qt/qml/ChatView/icons/attach-file.svg"
|
||||
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
|
||||
}
|
||||
|
||||
@ -28,6 +28,8 @@ Rectangle {
|
||||
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) :
|
||||
@ -62,6 +64,19 @@ Rectangle {
|
||||
text: qsTr("Clear")
|
||||
}
|
||||
|
||||
Text {
|
||||
id: recentPathId
|
||||
|
||||
elide: Text.ElideMiddle
|
||||
color: palette.text
|
||||
}
|
||||
|
||||
QoAButton {
|
||||
id: openChatHistoryId
|
||||
|
||||
text: qsTr("Show in system")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
@ -57,6 +57,13 @@ void ConfigurationManager::setupConnections()
|
||||
connect(&m_generalSettings.caSelectTemplate, &Button::clicked, this, &Config::selectTemplate);
|
||||
connect(&m_generalSettings.ccSetUrl, &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()
|
||||
@ -69,6 +76,8 @@ void ConfigurationManager::selectProvider()
|
||||
|
||||
auto &targetSettings = (settingsButton == &m_generalSettings.ccSelectProvider)
|
||||
? m_generalSettings.ccProvider
|
||||
: settingsButton == &m_generalSettings.ccPreset1SelectProvider
|
||||
? m_generalSettings.ccPreset1Provider
|
||||
: m_generalSettings.caProvider;
|
||||
|
||||
QTimer::singleShot(0, this, [this, providersList, &targetSettings] {
|
||||
@ -86,14 +95,19 @@ void ConfigurationManager::selectModel()
|
||||
return;
|
||||
|
||||
const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectModel);
|
||||
const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectModel);
|
||||
|
||||
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()
|
||||
: isPreset1 ? m_generalSettings.ccPreset1Url.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 (!provider->supportsModelListing()) {
|
||||
@ -122,11 +136,13 @@ void ConfigurationManager::selectTemplate()
|
||||
return;
|
||||
|
||||
const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectTemplate);
|
||||
const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectTemplate);
|
||||
|
||||
const auto templateList = isCodeCompletion ? m_templateManger.fimTemplatesNames()
|
||||
: m_templateManger.chatTemplatesNames();
|
||||
const auto templateList = isCodeCompletion || isPreset1 ? m_templateManger.fimTemplatesNames()
|
||||
: m_templateManger.chatTemplatesNames();
|
||||
|
||||
auto &targetSettings = isCodeCompletion ? m_generalSettings.ccTemplate
|
||||
: isPreset1 ? m_generalSettings.ccPreset1Template
|
||||
: m_generalSettings.caTemplate;
|
||||
|
||||
QTimer::singleShot(0, &m_generalSettings, [this, templateList, &targetSettings]() {
|
||||
@ -150,8 +166,9 @@ void ConfigurationManager::selectUrl()
|
||||
urls.append(url);
|
||||
}
|
||||
|
||||
auto &targetSettings = (settingsButton == &m_generalSettings.ccSetUrl)
|
||||
? m_generalSettings.ccUrl
|
||||
auto &targetSettings = (settingsButton == &m_generalSettings.ccSetUrl) ? m_generalSettings.ccUrl
|
||||
: settingsButton == &m_generalSettings.ccPreset1SetUrl
|
||||
? m_generalSettings.ccPreset1Url
|
||||
: m_generalSettings.caUrl;
|
||||
|
||||
QTimer::singleShot(0, &m_generalSettings, [this, urls, &targetSettings]() {
|
||||
|
||||
@ -146,20 +146,41 @@ void LLMClientInterface::handleExit(const QJsonObject &request)
|
||||
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)
|
||||
{
|
||||
auto updatedContext = prepareContext(request);
|
||||
const auto updatedContext = prepareContext(request);
|
||||
auto &completeSettings = Settings::codeCompletionSettings();
|
||||
auto &generalSettings = Settings::generalSettings();
|
||||
|
||||
auto providerName = Settings::generalSettings().ccProvider();
|
||||
auto provider = LLMCore::ProvidersManager::instance().getProviderByName(providerName);
|
||||
bool isPreset1Active = isSpecifyCompletion(request);
|
||||
|
||||
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) {
|
||||
LOG_MESSAGE(QString("No provider found with name: %1").arg(providerName));
|
||||
return;
|
||||
}
|
||||
|
||||
auto templateName = Settings::generalSettings().ccTemplate();
|
||||
auto templateName = !isPreset1Active ? generalSettings.ccTemplate()
|
||||
: generalSettings.ccPreset1Template();
|
||||
|
||||
auto promptTemplate = LLMCore::PromptTemplateManager::instance().getFimTemplateByName(
|
||||
templateName);
|
||||
|
||||
@ -168,19 +189,18 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO refactor to dynamic presets system
|
||||
LLMCore::LLMConfig config;
|
||||
config.requestType = LLMCore::RequestType::CodeCompletion;
|
||||
config.provider = provider;
|
||||
config.promptTemplate = promptTemplate;
|
||||
config.url = QUrl(QString("%1%2").arg(
|
||||
Settings::generalSettings().ccUrl(),
|
||||
url,
|
||||
promptTemplate->type() == LLMCore::TemplateType::Fim ? provider->completionEndpoint()
|
||||
: provider->chatEndpoint()));
|
||||
config.apiKey = provider->apiKey();
|
||||
|
||||
config.providerRequest
|
||||
= {{"model", Settings::generalSettings().ccModel()},
|
||||
{"stream", Settings::codeCompletionSettings().stream()}};
|
||||
config.providerRequest = {{"model", modelName}, {"stream", completeSettings.stream()}};
|
||||
|
||||
config.multiLineCompletion = completeSettings.multiLineCompletion();
|
||||
|
||||
@ -246,11 +266,33 @@ LLMCore::ContextData LLMClientInterface::prepareContext(const QJsonObject &reque
|
||||
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,
|
||||
const QJsonObject &request,
|
||||
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(
|
||||
templateName);
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
#include <languageclient/languageclientinterface.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
|
||||
#include <context/ProgrammingLanguage.hpp>
|
||||
#include <llmcore/ContextData.hpp>
|
||||
#include <llmcore/RequestHandler.hpp>
|
||||
|
||||
@ -58,8 +59,10 @@ private:
|
||||
void handleExit(const QJsonObject &request);
|
||||
void handleCancelRequest(const QJsonObject &request);
|
||||
|
||||
LLMCore::ContextData prepareContext(const QJsonObject &request,
|
||||
const QStringView &accumulatedCompletion = QString{});
|
||||
LLMCore::ContextData prepareContext(
|
||||
const QJsonObject &request, const QStringView &accumulatedCompletion = QString{});
|
||||
Context::ProgrammingLanguage getDocumentLanguage(const QJsonObject &request) const;
|
||||
bool isSpecifyCompletion(const QJsonObject &request);
|
||||
|
||||
LLMCore::RequestHandler m_requestHandler;
|
||||
QElapsedTimer m_completionTimer;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"Id" : "qodeassist",
|
||||
"Name" : "QodeAssist",
|
||||
"Version" : "0.4.6",
|
||||
"Version" : "0.4.13",
|
||||
"Vendor" : "Petr Mironychev",
|
||||
"VendorId" : "petrmironychev",
|
||||
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",
|
||||
|
||||
83
README.md
83
README.md
@ -2,8 +2,8 @@
|
||||
[](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.
|
||||
|
||||
@ -13,6 +13,15 @@
|
||||
> - 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
|
||||
1. [Overview](#overview)
|
||||
2. [Install plugin to QtCreator](#install-plugin-to-qtcreator)
|
||||
@ -20,19 +29,25 @@
|
||||
4. [Configure for OpenAI](#configure-for-openai)
|
||||
5. [Configure for using Ollama](#configure-for-using-ollama)
|
||||
6. [System Prompt Configuration](#system-prompt-configuration)
|
||||
7. [Template-Model Compatibility](#template-model-compatibility)
|
||||
8. [QtCreator Version Compatibility](#qtcreator-version-compatibility)
|
||||
9. [Development Progress](#development-progress)
|
||||
10. [Hotkeys](#hotkeys)
|
||||
11. [Troubleshooting](#troubleshooting)
|
||||
12. [Support the Development](#support-the-development-of-qodeassist)
|
||||
13. [How to Build](#how-to-build)
|
||||
7. [File Context Features](#file-context-features)
|
||||
8. [Template-Model Compatibility](#template-model-compatibility)
|
||||
9. [QtCreator Version Compatibility](#qtcreator-version-compatibility)
|
||||
10. [Development Progress](#development-progress)
|
||||
11. [Hotkeys](#hotkeys)
|
||||
12. [Troubleshooting](#troubleshooting)
|
||||
13. [Support the Development](#support-the-development-of-qodeassist)
|
||||
14. [How to Build](#how-to-build)
|
||||
|
||||
## Overview
|
||||
|
||||
- AI-powered code completion
|
||||
- Chat functionality:
|
||||
- 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:
|
||||
- Ollama
|
||||
- OpenAI
|
||||
@ -63,9 +78,15 @@
|
||||
<img width="326" alt="QodeAssistBottomPanel" src="https://github.com/user-attachments/assets/4cc64c23-a294-4df8-9153-39ad6fdab34b">
|
||||
</details>
|
||||
|
||||
<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>
|
||||
|
||||
## Install plugin to QtCreator
|
||||
1. Install Latest Qt Creator
|
||||
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...
|
||||
@ -136,25 +157,59 @@ You're all set! QodeAssist is now ready to use in Qt Creator.
|
||||
|
||||
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.
|
||||
|
||||
## File Context Features
|
||||
|
||||
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.
|
||||
|
||||
### Attached Files
|
||||
|
||||
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
|
||||
|
||||
### Linked Files
|
||||
|
||||
Linked files provide persistent context throughout the conversation:
|
||||
|
||||
- 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 |
|
||||
|----------|------------------|----------|
|
||||
| CodeLlama FIM | `codellama:code` | Code completion |
|
||||
| DeepSeekCoder FIM | `deepseek-coder-v2`, `deepseek-v2.5` | Code completion |
|
||||
| Ollama Auto FIM | `Any Ollama base model` | Code completion |
|
||||
| Qwen FIM | `Qwen 2.5 models` | Code completion |
|
||||
| Ollama Auto FIM | `Any Ollama base/fim models` | Code completion |
|
||||
| Qwen FIM | `Qwen 2.5 models(exclude instruct)` | Code completion |
|
||||
| StarCoder2 FIM | `starcoder2 base model` | Code completion |
|
||||
| Alpaca | `starcoder2:instruct` | 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 |
|
||||
| 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 |
|
||||
|
||||
## 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.1 - 0.2.2 plugin version and below
|
||||
|
||||
|
||||
@ -3,11 +3,22 @@ add_library(Context STATIC
|
||||
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
|
||||
|
||||
@ -23,6 +23,11 @@
|
||||
#include <QFileInfo>
|
||||
#include <QTextStream>
|
||||
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectnodes.h>
|
||||
|
||||
#include "FileChunker.hpp"
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
ContextManager &ContextManager::instance()
|
||||
@ -64,4 +69,108 @@ ContentFile ContextManager::createContentFile(const QString &filePath) const
|
||||
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
|
||||
|
||||
@ -19,10 +19,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ContentFile.hpp"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "ContentFile.hpp"
|
||||
#include "FileChunker.hpp"
|
||||
|
||||
namespace ProjectExplorer {
|
||||
class Project;
|
||||
}
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
@ -32,15 +38,23 @@ class ContextManager : public QObject
|
||||
|
||||
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
|
||||
|
||||
265
context/EnhancedRAGSimilaritySearch.cpp
Normal file
265
context/EnhancedRAGSimilaritySearch.cpp
Normal file
@ -0,0 +1,265 @@
|
||||
#include "EnhancedRAGSimilaritySearch.hpp"
|
||||
|
||||
#include <QSet>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
// Static regex getters
|
||||
const QRegularExpression &EnhancedRAGSimilaritySearch::getNamespaceRegex()
|
||||
{
|
||||
static const QRegularExpression regex(R"(namespace\s+(?:\w+\s*::\s*)*\w+\s*\{)");
|
||||
return regex;
|
||||
}
|
||||
|
||||
const QRegularExpression &EnhancedRAGSimilaritySearch::getClassRegex()
|
||||
{
|
||||
static const QRegularExpression regex(
|
||||
R"((?:template\s*<[^>]*>\s*)?(?:class|struct)\s+(\w+)\s*(?:final\s*)?(?::\s*(?:public|protected|private)\s+\w+(?:\s*,\s*(?:public|protected|private)\s+\w+)*\s*)?{)");
|
||||
return regex;
|
||||
}
|
||||
|
||||
const QRegularExpression &EnhancedRAGSimilaritySearch::getFunctionRegex()
|
||||
{
|
||||
static const QRegularExpression regex(
|
||||
R"((?:virtual\s+)?(?:static\s+)?(?:inline\s+)?(?:explicit\s+)?(?:constexpr\s+)?(?:[\w:]+\s+)?(?:\w+\s*::\s*)*\w+\s*\([^)]*\)\s*(?:const\s*)?(?:noexcept\s*)?(?:override\s*)?(?:final\s*)?(?:=\s*0\s*)?(?:=\s*default\s*)?(?:=\s*delete\s*)?(?:\s*->.*?)?\s*{)");
|
||||
return regex;
|
||||
}
|
||||
|
||||
const QRegularExpression &EnhancedRAGSimilaritySearch::getTemplateRegex()
|
||||
{
|
||||
static const QRegularExpression regex(R"(template\s*<[^>]*>\s*(?:class|struct|typename)\s+\w+)");
|
||||
return regex;
|
||||
}
|
||||
|
||||
// Cache getters
|
||||
QCache<QString, EnhancedRAGSimilaritySearch::SimilarityScore> &
|
||||
EnhancedRAGSimilaritySearch::getScoreCache()
|
||||
{
|
||||
static QCache<QString, SimilarityScore> cache(1000); // Cache size of 1000 entries
|
||||
return cache;
|
||||
}
|
||||
|
||||
QCache<QString, QStringList> &EnhancedRAGSimilaritySearch::getStructureCache()
|
||||
{
|
||||
static QCache<QString, QStringList> cache(500); // Cache size of 500 entries
|
||||
return cache;
|
||||
}
|
||||
|
||||
// Main public interface
|
||||
EnhancedRAGSimilaritySearch::SimilarityScore EnhancedRAGSimilaritySearch::calculateSimilarity(
|
||||
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2)
|
||||
{
|
||||
// Generate cache key based on content hashes
|
||||
QString cacheKey = QString("%1_%2").arg(qHash(code1)).arg(qHash(code2));
|
||||
|
||||
// Check cache first
|
||||
auto &scoreCache = getScoreCache();
|
||||
if (auto *cached = scoreCache.object(cacheKey)) {
|
||||
return *cached;
|
||||
}
|
||||
|
||||
// Calculate new similarity score
|
||||
SimilarityScore score = calculateSimilarityInternal(v1, v2, code1, code2);
|
||||
|
||||
// Cache the result
|
||||
scoreCache.insert(cacheKey, new SimilarityScore(score));
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
// Internal implementation
|
||||
EnhancedRAGSimilaritySearch::SimilarityScore EnhancedRAGSimilaritySearch::calculateSimilarityInternal(
|
||||
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2)
|
||||
{
|
||||
if (v1.empty() || v2.empty()) {
|
||||
LOG_MESSAGE("Warning: Empty vectors in similarity calculation");
|
||||
return SimilarityScore(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
if (v1.size() != v2.size()) {
|
||||
LOG_MESSAGE(QString("Vector size mismatch: %1 vs %2").arg(v1.size()).arg(v2.size()));
|
||||
return SimilarityScore(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
// Calculate semantic similarity using vector embeddings
|
||||
float semantic_similarity = 0.0f;
|
||||
|
||||
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||
if (v1.size() >= 4) { // Use SSE for vectors of 4 or more elements
|
||||
semantic_similarity = calculateCosineSimilaritySSE(v1, v2);
|
||||
} else {
|
||||
semantic_similarity = calculateCosineSimilarity(v1, v2);
|
||||
}
|
||||
#else
|
||||
semantic_similarity = calculateCosineSimilarity(v1, v2);
|
||||
#endif
|
||||
|
||||
// If semantic similarity is very low, skip structural analysis
|
||||
if (semantic_similarity < 0.0001f) {
|
||||
return SimilarityScore(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
// Calculate structural similarity
|
||||
float structural_similarity = calculateStructuralSimilarity(code1, code2);
|
||||
|
||||
// Calculate combined score with dynamic weights
|
||||
float semantic_weight = 0.7f;
|
||||
const int large_file_threshold = 10000;
|
||||
|
||||
if (code1.size() > large_file_threshold || code2.size() > large_file_threshold) {
|
||||
semantic_weight = 0.8f; // Increase semantic weight for large files
|
||||
}
|
||||
|
||||
float combined_score = semantic_weight * semantic_similarity
|
||||
+ (1.0f - semantic_weight) * structural_similarity;
|
||||
|
||||
return SimilarityScore(semantic_similarity, structural_similarity, combined_score);
|
||||
}
|
||||
|
||||
float EnhancedRAGSimilaritySearch::calculateCosineSimilarity(const RAGVector &v1, const RAGVector &v2)
|
||||
{
|
||||
float dotProduct = 0.0f;
|
||||
float norm1 = 0.0f;
|
||||
float norm2 = 0.0f;
|
||||
|
||||
for (size_t i = 0; i < v1.size(); ++i) {
|
||||
dotProduct += v1[i] * v2[i];
|
||||
norm1 += v1[i] * v1[i];
|
||||
norm2 += v2[i] * v2[i];
|
||||
}
|
||||
|
||||
norm1 = std::sqrt(norm1);
|
||||
norm2 = std::sqrt(norm2);
|
||||
|
||||
if (norm1 == 0.0f || norm2 == 0.0f) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return dotProduct / (norm1 * norm2);
|
||||
}
|
||||
|
||||
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||
float EnhancedRAGSimilaritySearch::calculateCosineSimilaritySSE(
|
||||
const RAGVector &v1, const RAGVector &v2)
|
||||
{
|
||||
const float *p1 = v1.data();
|
||||
const float *p2 = v2.data();
|
||||
const size_t size = v1.size();
|
||||
const size_t alignedSize = size & ~3ULL; // Round down to multiple of 4
|
||||
|
||||
__m128 sum = _mm_setzero_ps();
|
||||
__m128 norm1 = _mm_setzero_ps();
|
||||
__m128 norm2 = _mm_setzero_ps();
|
||||
|
||||
// Process 4 elements at a time using SSE
|
||||
for (size_t i = 0; i < alignedSize; i += 4) {
|
||||
__m128 v1_vec = _mm_loadu_ps(p1 + i); // Use unaligned load for safety
|
||||
__m128 v2_vec = _mm_loadu_ps(p2 + i);
|
||||
|
||||
sum = _mm_add_ps(sum, _mm_mul_ps(v1_vec, v2_vec));
|
||||
norm1 = _mm_add_ps(norm1, _mm_mul_ps(v1_vec, v1_vec));
|
||||
norm2 = _mm_add_ps(norm2, _mm_mul_ps(v2_vec, v2_vec));
|
||||
}
|
||||
|
||||
float dotProduct = horizontalSum(sum);
|
||||
float n1 = std::sqrt(horizontalSum(norm1));
|
||||
float n2 = std::sqrt(horizontalSum(norm2));
|
||||
|
||||
// Process remaining elements
|
||||
for (size_t i = alignedSize; i < size; ++i) {
|
||||
dotProduct += v1[i] * v2[i];
|
||||
n1 += v1[i] * v1[i];
|
||||
n2 += v2[i] * v2[i];
|
||||
}
|
||||
|
||||
if (n1 == 0.0f || n2 == 0.0f) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return dotProduct / (std::sqrt(n1) * std::sqrt(n2));
|
||||
}
|
||||
|
||||
float EnhancedRAGSimilaritySearch::horizontalSum(__m128 x)
|
||||
{
|
||||
__m128 shuf = _mm_shuffle_ps(x, x, _MM_SHUFFLE(2, 3, 0, 1));
|
||||
__m128 sums = _mm_add_ps(x, shuf);
|
||||
shuf = _mm_movehl_ps(shuf, sums);
|
||||
sums = _mm_add_ss(sums, shuf);
|
||||
return _mm_cvtss_f32(sums);
|
||||
}
|
||||
#endif
|
||||
|
||||
float EnhancedRAGSimilaritySearch::calculateStructuralSimilarity(
|
||||
const QString &code1, const QString &code2)
|
||||
{
|
||||
QStringList structures1 = extractStructures(code1);
|
||||
QStringList structures2 = extractStructures(code2);
|
||||
|
||||
return calculateJaccardSimilarity(structures1, structures2);
|
||||
}
|
||||
|
||||
QStringList EnhancedRAGSimilaritySearch::extractStructures(const QString &code)
|
||||
{
|
||||
// Check cache first
|
||||
auto &structureCache = getStructureCache();
|
||||
QString cacheKey = QString::number(qHash(code));
|
||||
|
||||
if (auto *cached = structureCache.object(cacheKey)) {
|
||||
return *cached;
|
||||
}
|
||||
|
||||
QStringList structures;
|
||||
structures.reserve(100); // Reserve space for typical file
|
||||
|
||||
// Extract namespaces
|
||||
auto namespaceMatches = getNamespaceRegex().globalMatch(code);
|
||||
while (namespaceMatches.hasNext()) {
|
||||
structures.append(namespaceMatches.next().captured().trimmed());
|
||||
}
|
||||
|
||||
// Extract classes
|
||||
auto classMatches = getClassRegex().globalMatch(code);
|
||||
while (classMatches.hasNext()) {
|
||||
structures.append(classMatches.next().captured().trimmed());
|
||||
}
|
||||
|
||||
// Extract functions
|
||||
auto functionMatches = getFunctionRegex().globalMatch(code);
|
||||
while (functionMatches.hasNext()) {
|
||||
structures.append(functionMatches.next().captured().trimmed());
|
||||
}
|
||||
|
||||
// Extract templates
|
||||
auto templateMatches = getTemplateRegex().globalMatch(code);
|
||||
while (templateMatches.hasNext()) {
|
||||
structures.append(templateMatches.next().captured().trimmed());
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
structureCache.insert(cacheKey, new QStringList(structures));
|
||||
|
||||
return structures;
|
||||
}
|
||||
|
||||
float EnhancedRAGSimilaritySearch::calculateJaccardSimilarity(
|
||||
const QStringList &set1, const QStringList &set2)
|
||||
{
|
||||
if (set1.isEmpty() && set2.isEmpty()) {
|
||||
return 1.0f; // Пустые множества считаем идентичными
|
||||
}
|
||||
if (set1.isEmpty() || set2.isEmpty()) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
QSet<QString> set1Unique = QSet<QString>(set1.begin(), set1.end());
|
||||
QSet<QString> set2Unique = QSet<QString>(set2.begin(), set2.end());
|
||||
|
||||
QSet<QString> intersection = set1Unique;
|
||||
intersection.intersect(set2Unique);
|
||||
|
||||
QSet<QString> union_set = set1Unique;
|
||||
union_set.unite(set2Unique);
|
||||
|
||||
return static_cast<float>(intersection.size()) / union_set.size();
|
||||
}
|
||||
} // namespace QodeAssist::Context
|
||||
74
context/EnhancedRAGSimilaritySearch.hpp
Normal file
74
context/EnhancedRAGSimilaritySearch.hpp
Normal file
@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include <QCache>
|
||||
#include <QHash>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||
#include <emmintrin.h>
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
#include "RAGData.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
class EnhancedRAGSimilaritySearch
|
||||
{
|
||||
public:
|
||||
struct SimilarityScore
|
||||
{
|
||||
float semantic_similarity{0.0f};
|
||||
float structural_similarity{0.0f};
|
||||
float combined_score{0.0f};
|
||||
|
||||
SimilarityScore() = default;
|
||||
SimilarityScore(float sem, float str, float comb)
|
||||
: semantic_similarity(sem)
|
||||
, structural_similarity(str)
|
||||
, combined_score(comb)
|
||||
{}
|
||||
};
|
||||
|
||||
static SimilarityScore calculateSimilarity(
|
||||
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2);
|
||||
|
||||
private:
|
||||
static SimilarityScore calculateSimilarityInternal(
|
||||
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2);
|
||||
|
||||
static float calculateCosineSimilarity(const RAGVector &v1, const RAGVector &v2);
|
||||
|
||||
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||
static float calculateCosineSimilaritySSE(const RAGVector &v1, const RAGVector &v2);
|
||||
static float horizontalSum(__m128 x);
|
||||
#endif
|
||||
|
||||
static float calculateStructuralSimilarity(const QString &code1, const QString &code2);
|
||||
static QStringList extractStructures(const QString &code);
|
||||
static float calculateJaccardSimilarity(const QStringList &set1, const QStringList &set2);
|
||||
|
||||
static const QRegularExpression &getNamespaceRegex();
|
||||
static const QRegularExpression &getClassRegex();
|
||||
static const QRegularExpression &getFunctionRegex();
|
||||
static const QRegularExpression &getTemplateRegex();
|
||||
|
||||
// Cache for similarity scores
|
||||
static QCache<QString, SimilarityScore> &getScoreCache();
|
||||
|
||||
// Cache for extracted structures
|
||||
static QCache<QString, QStringList> &getStructureCache();
|
||||
|
||||
EnhancedRAGSimilaritySearch() = delete; // Prevent instantiation
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
198
context/FileChunker.cpp
Normal file
198
context/FileChunker.cpp
Normal file
@ -0,0 +1,198 @@
|
||||
// FileChunker.cpp
|
||||
#include "FileChunker.hpp"
|
||||
|
||||
#include <coreplugin/idocument.h>
|
||||
#include <texteditor/syntaxhighlighter.h>
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <texteditor/texteditorconstants.h>
|
||||
|
||||
#include <QFutureWatcher>
|
||||
#include <QTimer>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
FileChunker::FileChunker(QObject *parent)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
FileChunker::FileChunker(const ChunkingConfig &config, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_config(config)
|
||||
{}
|
||||
|
||||
QFuture<QList<FileChunk>> FileChunker::chunkFiles(const QStringList &filePaths)
|
||||
{
|
||||
qDebug() << "\nStarting chunking process for" << filePaths.size() << "files";
|
||||
qDebug() << "Configuration:"
|
||||
<< "\n Max lines per chunk:" << m_config.maxLinesPerChunk
|
||||
<< "\n Overlap lines:" << m_config.overlapLines
|
||||
<< "\n Skip empty lines:" << m_config.skipEmptyLines
|
||||
<< "\n Preserve functions:" << m_config.preserveFunctions
|
||||
<< "\n Preserve classes:" << m_config.preserveClasses
|
||||
<< "\n Batch size:" << m_config.batchSize;
|
||||
|
||||
auto promise = std::make_shared<QPromise<QList<FileChunk>>>();
|
||||
promise->start();
|
||||
|
||||
if (filePaths.isEmpty()) {
|
||||
qDebug() << "No files to process";
|
||||
promise->addResult({});
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
processNextBatch(promise, filePaths, 0);
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
void FileChunker::processNextBatch(
|
||||
std::shared_ptr<QPromise<QList<FileChunk>>> promise, const QStringList &files, int startIndex)
|
||||
{
|
||||
if (startIndex >= files.size()) {
|
||||
emit chunkingComplete();
|
||||
promise->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
int endIndex = qMin(startIndex + m_config.batchSize, files.size());
|
||||
QList<FileChunk> batchChunks;
|
||||
|
||||
for (int i = startIndex; i < endIndex; ++i) {
|
||||
try {
|
||||
auto chunks = processFile(files[i]);
|
||||
batchChunks.append(chunks);
|
||||
} catch (const std::exception &e) {
|
||||
emit error(QString("Error processing file %1: %2").arg(files[i], e.what()));
|
||||
}
|
||||
emit progressUpdated(i + 1, files.size());
|
||||
}
|
||||
|
||||
promise->addResult(batchChunks);
|
||||
|
||||
// Планируем обработку следующего батча
|
||||
QTimer::singleShot(0, this, [this, promise, files, endIndex]() {
|
||||
processNextBatch(promise, files, endIndex);
|
||||
});
|
||||
}
|
||||
|
||||
QList<FileChunk> FileChunker::processFile(const QString &filePath)
|
||||
{
|
||||
qDebug() << "\nProcessing file:" << filePath;
|
||||
|
||||
auto document = new TextEditor::TextDocument;
|
||||
auto filePathObj = Utils::FilePath::fromString(filePath);
|
||||
auto result = document->open(&m_error, filePathObj, filePathObj);
|
||||
if (result != Core::IDocument::OpenResult::Success) {
|
||||
qDebug() << "Failed to open document:" << filePath << "-" << m_error;
|
||||
emit error(QString("Failed to open document: %1 - %2").arg(filePath, m_error));
|
||||
delete document;
|
||||
return {};
|
||||
}
|
||||
|
||||
qDebug() << "Document opened successfully. Line count:" << document->document()->blockCount();
|
||||
|
||||
auto chunks = createChunksForDocument(document);
|
||||
qDebug() << "Created" << chunks.size() << "chunks for file";
|
||||
|
||||
delete document;
|
||||
return chunks;
|
||||
}
|
||||
|
||||
QList<FileChunk> FileChunker::createChunksForDocument(TextEditor::TextDocument *document)
|
||||
{
|
||||
QList<FileChunk> chunks;
|
||||
QString filePath = document->filePath().toString();
|
||||
qDebug() << "\nCreating chunks for document:" << filePath << "\nConfiguration:"
|
||||
<< "\n Max lines per chunk:" << m_config.maxLinesPerChunk
|
||||
<< "\n Min lines per chunk:" << m_config.minLinesPerChunk
|
||||
<< "\n Overlap lines:" << m_config.overlapLines;
|
||||
// Если файл меньше минимального размера чанка, создаем один чанк
|
||||
if (document->document()->blockCount() <= m_config.minLinesPerChunk) {
|
||||
FileChunk chunk;
|
||||
chunk.filePath = filePath;
|
||||
chunk.startLine = 0;
|
||||
chunk.endLine = document->document()->blockCount() - 1;
|
||||
chunk.createdAt = QDateTime::currentDateTime();
|
||||
chunk.updatedAt = chunk.createdAt;
|
||||
|
||||
QString content;
|
||||
QTextBlock block = document->document()->firstBlock();
|
||||
while (block.isValid()) {
|
||||
content += block.text() + "\n";
|
||||
block = block.next();
|
||||
}
|
||||
chunk.content = content;
|
||||
|
||||
qDebug() << "File is smaller than minimum chunk size. Creating single chunk:"
|
||||
<< "\n Lines:" << chunk.lineCount() << "\n Content size:" << chunk.content.size()
|
||||
<< "bytes";
|
||||
|
||||
chunks.append(chunk);
|
||||
return chunks;
|
||||
}
|
||||
|
||||
// Для больших файлов создаем чанки фиксированного размера с перекрытием
|
||||
int currentStartLine = 0;
|
||||
int lineCount = 0;
|
||||
QString content;
|
||||
QTextBlock block = document->document()->firstBlock();
|
||||
|
||||
while (block.isValid()) {
|
||||
content += block.text() + "\n";
|
||||
lineCount++;
|
||||
|
||||
// Если достигли размера чанка или это последний блок
|
||||
if (lineCount >= m_config.maxLinesPerChunk || !block.next().isValid()) {
|
||||
FileChunk chunk;
|
||||
chunk.filePath = filePath;
|
||||
chunk.startLine = currentStartLine;
|
||||
chunk.endLine = currentStartLine + lineCount - 1;
|
||||
chunk.content = content;
|
||||
chunk.createdAt = QDateTime::currentDateTime();
|
||||
chunk.updatedAt = chunk.createdAt;
|
||||
|
||||
qDebug() << "Creating chunk:"
|
||||
<< "\n Start line:" << chunk.startLine << "\n End line:" << chunk.endLine
|
||||
<< "\n Lines:" << chunk.lineCount()
|
||||
<< "\n Content size:" << chunk.content.size() << "bytes";
|
||||
|
||||
chunks.append(chunk);
|
||||
|
||||
// Начинаем новый чанк с учетом перекрытия
|
||||
if (block.next().isValid()) {
|
||||
// Отступаем назад на размер перекрытия
|
||||
int overlapLines = qMin(m_config.overlapLines, lineCount);
|
||||
currentStartLine = chunk.endLine - overlapLines + 1;
|
||||
|
||||
// Сбрасываем контент, но добавляем перекрывающиеся строки
|
||||
content.clear();
|
||||
QTextBlock overlapBlock = document->document()->findBlockByLineNumber(
|
||||
currentStartLine);
|
||||
while (overlapBlock.isValid() && overlapBlock.blockNumber() <= chunk.endLine) {
|
||||
content += overlapBlock.text() + "\n";
|
||||
overlapBlock = overlapBlock.next();
|
||||
}
|
||||
lineCount = overlapLines;
|
||||
}
|
||||
}
|
||||
|
||||
block = block.next();
|
||||
}
|
||||
|
||||
qDebug() << "Finished creating chunks for file:" << filePath
|
||||
<< "\nTotal chunks:" << chunks.size();
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
void FileChunker::setConfig(const ChunkingConfig &config)
|
||||
{
|
||||
m_config = config;
|
||||
}
|
||||
|
||||
FileChunker::ChunkingConfig FileChunker::config() const
|
||||
{
|
||||
return m_config;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
68
context/FileChunker.hpp
Normal file
68
context/FileChunker.hpp
Normal file
@ -0,0 +1,68 @@
|
||||
// FileChunker.hpp
|
||||
#pragma once
|
||||
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <QDateTime>
|
||||
#include <QFuture>
|
||||
#include <QString>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
struct FileChunk
|
||||
{
|
||||
QString filePath; // Path to the source file
|
||||
int startLine; // Starting line of the chunk
|
||||
int endLine; // Ending line of the chunk
|
||||
QDateTime createdAt; // When the chunk was created
|
||||
QDateTime updatedAt; // When the chunk was last updated
|
||||
QString content; // Content of the chunk
|
||||
|
||||
// Helper methods
|
||||
int lineCount() const { return endLine - startLine + 1; }
|
||||
bool isValid() const { return !filePath.isEmpty() && startLine >= 0 && endLine >= startLine; }
|
||||
};
|
||||
|
||||
class FileChunker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct ChunkingConfig
|
||||
{
|
||||
int maxLinesPerChunk = 80;
|
||||
int minLinesPerChunk = 40;
|
||||
int overlapLines = 20;
|
||||
bool skipEmptyLines = true;
|
||||
bool preserveFunctions = true;
|
||||
bool preserveClasses = true;
|
||||
int batchSize = 10;
|
||||
};
|
||||
|
||||
explicit FileChunker(QObject *parent = nullptr);
|
||||
explicit FileChunker(const ChunkingConfig &config, QObject *parent = nullptr);
|
||||
|
||||
// Main chunking method
|
||||
QFuture<QList<FileChunk>> chunkFiles(const QStringList &filePaths);
|
||||
|
||||
// Configuration
|
||||
void setConfig(const ChunkingConfig &config);
|
||||
ChunkingConfig config() const;
|
||||
|
||||
signals:
|
||||
void progressUpdated(int processedFiles, int totalFiles);
|
||||
void chunkingComplete();
|
||||
void error(const QString &errorMessage);
|
||||
|
||||
private:
|
||||
QList<FileChunk> processFile(const QString &filePath);
|
||||
QList<FileChunk> createChunksForDocument(TextEditor::TextDocument *document);
|
||||
void processNextBatch(
|
||||
std::shared_ptr<QPromise<QList<FileChunk>>> promise,
|
||||
const QStringList &files,
|
||||
int startIndex);
|
||||
|
||||
ChunkingConfig m_config;
|
||||
QString m_error;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
70
context/ProgrammingLanguage.cpp
Normal file
70
context/ProgrammingLanguage.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ProgrammingLanguage.hpp"
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
ProgrammingLanguage ProgrammingLanguageUtils::fromMimeType(const QString &mimeType)
|
||||
{
|
||||
if (mimeType == "text/x-qml" || mimeType == "application/javascript"
|
||||
|| mimeType == "text/javascript" || mimeType == "text/x-javascript") {
|
||||
return ProgrammingLanguage::QML;
|
||||
}
|
||||
if (mimeType == "text/x-c++src" || mimeType == "text/x-c++hdr" || mimeType == "text/x-csrc"
|
||||
|| mimeType == "text/x-chdr") {
|
||||
return ProgrammingLanguage::Cpp;
|
||||
}
|
||||
if (mimeType == "text/x-python") {
|
||||
return ProgrammingLanguage::Python;
|
||||
}
|
||||
return ProgrammingLanguage::Unknown;
|
||||
}
|
||||
|
||||
QString ProgrammingLanguageUtils::toString(ProgrammingLanguage language)
|
||||
{
|
||||
switch (language) {
|
||||
case ProgrammingLanguage::Cpp:
|
||||
return "c/c++";
|
||||
case ProgrammingLanguage::QML:
|
||||
return "qml";
|
||||
case ProgrammingLanguage::Python:
|
||||
return "python";
|
||||
case ProgrammingLanguage::Unknown:
|
||||
default:
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
ProgrammingLanguage ProgrammingLanguageUtils::fromString(const QString &str)
|
||||
{
|
||||
QString lower = str.toLower();
|
||||
if (lower == "c/c++") {
|
||||
return ProgrammingLanguage::Cpp;
|
||||
}
|
||||
if (lower == "qml") {
|
||||
return ProgrammingLanguage::QML;
|
||||
}
|
||||
if (lower == "python") {
|
||||
return ProgrammingLanguage::Python;
|
||||
}
|
||||
return ProgrammingLanguage::Unknown;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
43
context/ProgrammingLanguage.hpp
Normal file
43
context/ProgrammingLanguage.hpp
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
enum class ProgrammingLanguage {
|
||||
QML,
|
||||
Cpp,
|
||||
Python,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
namespace ProgrammingLanguageUtils {
|
||||
|
||||
ProgrammingLanguage fromMimeType(const QString &mimeType);
|
||||
|
||||
QString toString(ProgrammingLanguage language);
|
||||
|
||||
ProgrammingLanguage fromString(const QString &str);
|
||||
|
||||
} // namespace ProgrammingLanguageUtils
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
7
context/RAGData.hpp
Normal file
7
context/RAGData.hpp
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
using RAGVector = std::vector<float>;
|
||||
}
|
||||
443
context/RAGManager.cpp
Normal file
443
context/RAGManager.cpp
Normal file
@ -0,0 +1,443 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "RAGManager.hpp"
|
||||
#include "EnhancedRAGSimilaritySearch.hpp"
|
||||
#include "RAGPreprocessor.hpp"
|
||||
#include "RAGSimilaritySearch.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <QFile>
|
||||
#include <QtConcurrent>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
RAGManager &RAGManager::instance()
|
||||
{
|
||||
static RAGManager manager;
|
||||
return manager;
|
||||
}
|
||||
|
||||
RAGManager::RAGManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_vectorizer(std::make_unique<RAGVectorizer>())
|
||||
{}
|
||||
|
||||
RAGManager::~RAGManager() {}
|
||||
|
||||
QString RAGManager::getStoragePath(ProjectExplorer::Project *project) const
|
||||
{
|
||||
return QString("%1/qodeassist/%2/rag/vectors.db")
|
||||
.arg(Core::ICore::userResourcePath().toString(), project->displayName());
|
||||
}
|
||||
|
||||
std::optional<QString> RAGManager::loadFileContent(const QString &filePath)
|
||||
{
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
qDebug() << "ERROR: Failed to open file for reading:" << filePath
|
||||
<< "Error:" << file.errorString();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QFileInfo fileInfo(filePath);
|
||||
qDebug() << "Loading content from file:" << fileInfo.fileName() << "Size:" << fileInfo.size()
|
||||
<< "bytes";
|
||||
|
||||
QString content = QString::fromUtf8(file.readAll());
|
||||
if (content.isEmpty()) {
|
||||
qDebug() << "WARNING: Empty content read from file:" << filePath;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
void RAGManager::ensureStorageForProject(ProjectExplorer::Project *project) const
|
||||
{
|
||||
qDebug() << "Ensuring storage for project:" << project->displayName();
|
||||
|
||||
if (m_currentProject == project && m_currentStorage) {
|
||||
qDebug() << "Using existing storage";
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Creating new storage";
|
||||
m_currentStorage.reset();
|
||||
m_currentProject = project;
|
||||
|
||||
if (project) {
|
||||
QString storagePath = getStoragePath(project);
|
||||
qDebug() << "Storage path:" << storagePath;
|
||||
|
||||
StorageOptions options;
|
||||
m_currentStorage = std::make_unique<RAGStorage>(storagePath, options);
|
||||
|
||||
qDebug() << "Initializing storage...";
|
||||
if (!m_currentStorage->init()) {
|
||||
qDebug() << "Failed to initialize storage";
|
||||
m_currentStorage.reset();
|
||||
return;
|
||||
}
|
||||
qDebug() << "Storage initialized successfully";
|
||||
}
|
||||
}
|
||||
|
||||
QFuture<void> RAGManager::processProjectFiles(
|
||||
ProjectExplorer::Project *project,
|
||||
const QStringList &filePaths,
|
||||
const FileChunker::ChunkingConfig &config)
|
||||
{
|
||||
qDebug() << "\nStarting batch processing of" << filePaths.size()
|
||||
<< "files for project:" << project->displayName();
|
||||
|
||||
auto promise = std::make_shared<QPromise<void>>();
|
||||
promise->start();
|
||||
|
||||
qDebug() << "Initializing storage...";
|
||||
ensureStorageForProject(project);
|
||||
|
||||
if (!m_currentStorage) {
|
||||
qDebug() << "Failed to initialize storage for project:" << project->displayName();
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
qDebug() << "Storage initialized successfully";
|
||||
|
||||
qDebug() << "Checking files for processing...";
|
||||
QSet<QString> uniqueFiles;
|
||||
for (const QString &filePath : filePaths) {
|
||||
qDebug() << "Checking file:" << filePath;
|
||||
if (isFileStorageOutdated(project, filePath)) {
|
||||
qDebug() << "File needs processing:" << filePath;
|
||||
uniqueFiles.insert(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
QStringList filesToProcess = uniqueFiles.values();
|
||||
|
||||
if (filesToProcess.isEmpty()) {
|
||||
qDebug() << "No files need processing";
|
||||
emit vectorizationFinished();
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
qDebug() << "Starting to process" << filesToProcess.size() << "files";
|
||||
const int batchSize = 10;
|
||||
processNextFileBatch(promise, project, filesToProcess, config, 0, batchSize);
|
||||
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
void RAGManager::processNextFileBatch(
|
||||
std::shared_ptr<QPromise<void>> promise,
|
||||
ProjectExplorer::Project *project,
|
||||
const QStringList &files,
|
||||
const FileChunker::ChunkingConfig &config,
|
||||
int startIndex,
|
||||
int batchSize)
|
||||
{
|
||||
if (startIndex >= files.size()) {
|
||||
qDebug() << "All batches processed successfully";
|
||||
emit vectorizationFinished();
|
||||
promise->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
int endIndex = qMin(startIndex + batchSize, files.size());
|
||||
auto currentBatch = files.mid(startIndex, endIndex - startIndex);
|
||||
|
||||
qDebug() << "\nProcessing batch" << (startIndex / batchSize + 1) << "(" << currentBatch.size()
|
||||
<< "files)"
|
||||
<< "\nProgress:" << startIndex << "to" << endIndex << "of" << files.size();
|
||||
|
||||
for (const QString &filePath : currentBatch) {
|
||||
qDebug() << "Starting processing file:" << filePath;
|
||||
auto future = processFileWithChunks(project, filePath, config);
|
||||
auto watcher = new QFutureWatcher<bool>;
|
||||
watcher->setFuture(future);
|
||||
|
||||
connect(
|
||||
watcher,
|
||||
&QFutureWatcher<bool>::finished,
|
||||
this,
|
||||
[this,
|
||||
watcher,
|
||||
promise,
|
||||
project,
|
||||
files,
|
||||
startIndex,
|
||||
endIndex,
|
||||
batchSize,
|
||||
config,
|
||||
filePath]() {
|
||||
bool success = watcher->result();
|
||||
qDebug() << "File processed:" << filePath << "success:" << success;
|
||||
|
||||
bool isLastFileInBatch = (filePath == files[endIndex - 1]);
|
||||
if (isLastFileInBatch) {
|
||||
qDebug() << "Batch completed, moving to next batch";
|
||||
emit vectorizationProgress(endIndex, files.size());
|
||||
processNextFileBatch(promise, project, files, config, endIndex, batchSize);
|
||||
}
|
||||
|
||||
watcher->deleteLater();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QFuture<bool> RAGManager::processFileWithChunks(
|
||||
ProjectExplorer::Project *project,
|
||||
const QString &filePath,
|
||||
const FileChunker::ChunkingConfig &config)
|
||||
{
|
||||
auto promise = std::make_shared<QPromise<bool>>();
|
||||
promise->start();
|
||||
|
||||
ensureStorageForProject(project);
|
||||
if (!m_currentStorage) {
|
||||
qDebug() << "Storage not initialized for file:" << filePath;
|
||||
promise->addResult(false);
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
auto fileContent = loadFileContent(filePath);
|
||||
if (!fileContent) {
|
||||
qDebug() << "Failed to load content for file:" << filePath;
|
||||
promise->addResult(false);
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
qDebug() << "Creating chunks for file:" << filePath;
|
||||
auto chunksFuture = m_chunker.chunkFiles({filePath});
|
||||
auto chunks = chunksFuture.result();
|
||||
|
||||
if (chunks.isEmpty()) {
|
||||
qDebug() << "No chunks created for file:" << filePath;
|
||||
promise->addResult(false);
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
qDebug() << "Created" << chunks.size() << "chunks for file:" << filePath;
|
||||
|
||||
// Преобразуем FileChunk в FileChunkData
|
||||
QList<FileChunkData> chunkData;
|
||||
for (const auto &chunk : chunks) {
|
||||
FileChunkData data;
|
||||
data.filePath = chunk.filePath;
|
||||
data.startLine = chunk.startLine;
|
||||
data.endLine = chunk.endLine;
|
||||
data.content = chunk.content;
|
||||
chunkData.append(data);
|
||||
}
|
||||
|
||||
qDebug() << "Deleting old chunks for file:" << filePath;
|
||||
m_currentStorage->deleteChunksForFile(filePath);
|
||||
|
||||
auto vectorizeFuture = vectorizeAndStoreChunks(filePath, chunkData);
|
||||
auto watcher = new QFutureWatcher<void>;
|
||||
watcher->setFuture(vectorizeFuture);
|
||||
|
||||
connect(watcher, &QFutureWatcher<void>::finished, this, [promise, watcher, filePath]() {
|
||||
qDebug() << "Completed processing file:" << filePath;
|
||||
promise->addResult(true);
|
||||
promise->finish();
|
||||
watcher->deleteLater();
|
||||
});
|
||||
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
QFuture<void> RAGManager::vectorizeAndStoreChunks(
|
||||
const QString &filePath, const QList<FileChunkData> &chunks)
|
||||
{
|
||||
qDebug() << "Vectorizing and storing" << chunks.size() << "chunks for file:" << filePath;
|
||||
|
||||
auto promise = std::make_shared<QPromise<void>>();
|
||||
promise->start();
|
||||
|
||||
// Обрабатываем чанки последовательно
|
||||
processNextChunk(promise, chunks, 0);
|
||||
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
void RAGManager::processNextChunk(
|
||||
std::shared_ptr<QPromise<void>> promise, const QList<FileChunkData> &chunks, int currentIndex)
|
||||
{
|
||||
if (currentIndex >= chunks.size()) {
|
||||
promise->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &chunk = chunks[currentIndex];
|
||||
QString processedContent = RAGPreprocessor::preprocessCode(chunk.content);
|
||||
qDebug() << "Processing chunk" << currentIndex + 1 << "of" << chunks.size();
|
||||
|
||||
auto vectorFuture = m_vectorizer->vectorizeText(processedContent);
|
||||
auto watcher = new QFutureWatcher<RAGVector>;
|
||||
watcher->setFuture(vectorFuture);
|
||||
|
||||
connect(
|
||||
watcher,
|
||||
&QFutureWatcher<RAGVector>::finished,
|
||||
this,
|
||||
[this, watcher, promise, chunks, currentIndex, chunk]() {
|
||||
auto vector = watcher->result();
|
||||
|
||||
if (!vector.empty()) {
|
||||
qDebug() << "Storing vector and chunk for file:" << chunk.filePath;
|
||||
bool vectorStored = m_currentStorage->storeVector(chunk.filePath, vector);
|
||||
bool chunkStored = m_currentStorage->storeChunk(chunk);
|
||||
qDebug() << "Storage results - Vector:" << vectorStored << "Chunk:" << chunkStored;
|
||||
} else {
|
||||
qDebug() << "Failed to vectorize chunk content";
|
||||
}
|
||||
|
||||
processNextChunk(promise, chunks, currentIndex + 1);
|
||||
|
||||
watcher->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
QFuture<QList<RAGManager::ChunkSearchResult>> RAGManager::findRelevantChunks(
|
||||
const QString &query, ProjectExplorer::Project *project, int topK)
|
||||
{
|
||||
auto promise = std::make_shared<QPromise<QList<ChunkSearchResult>>>();
|
||||
promise->start();
|
||||
|
||||
ensureStorageForProject(project);
|
||||
if (!m_currentStorage) {
|
||||
qDebug() << "Storage not initialized for project:" << project->displayName();
|
||||
promise->addResult({});
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
QString processedQuery = RAGPreprocessor::preprocessCode(query);
|
||||
|
||||
auto vectorFuture = m_vectorizer->vectorizeText(processedQuery);
|
||||
vectorFuture.then([this, promise, project, processedQuery, topK](const RAGVector &queryVector) {
|
||||
if (queryVector.empty()) {
|
||||
qDebug() << "Failed to vectorize query";
|
||||
promise->addResult({});
|
||||
promise->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
auto files = m_currentStorage->getFilesWithChunks();
|
||||
QList<FileChunkData> allChunks;
|
||||
|
||||
for (const auto &filePath : files) {
|
||||
auto fileChunks = m_currentStorage->getChunksForFile(filePath);
|
||||
allChunks.append(fileChunks);
|
||||
}
|
||||
|
||||
auto results = rankChunks(queryVector, processedQuery, allChunks);
|
||||
|
||||
if (results.size() > topK) {
|
||||
results = results.mid(0, topK);
|
||||
}
|
||||
|
||||
qDebug() << "Found" << results.size() << "relevant chunks";
|
||||
promise->addResult(results);
|
||||
promise->finish();
|
||||
|
||||
closeStorage();
|
||||
});
|
||||
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
QList<RAGManager::ChunkSearchResult> RAGManager::rankChunks(
|
||||
const RAGVector &queryVector, const QString &queryText, const QList<FileChunkData> &chunks)
|
||||
{
|
||||
QList<ChunkSearchResult> results;
|
||||
results.reserve(chunks.size());
|
||||
|
||||
for (const auto &chunk : chunks) {
|
||||
auto chunkVector = m_currentStorage->getVector(chunk.filePath);
|
||||
if (!chunkVector.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString processedChunk = RAGPreprocessor::preprocessCode(chunk.content);
|
||||
|
||||
auto similarity = EnhancedRAGSimilaritySearch::calculateSimilarity(
|
||||
queryVector, chunkVector.value(), queryText, processedChunk);
|
||||
|
||||
results.append(ChunkSearchResult{
|
||||
chunk.filePath,
|
||||
chunk.startLine,
|
||||
chunk.endLine,
|
||||
chunk.content,
|
||||
similarity.semantic_similarity,
|
||||
similarity.structural_similarity,
|
||||
similarity.combined_score});
|
||||
}
|
||||
|
||||
std::sort(results.begin(), results.end());
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
QStringList RAGManager::getStoredFiles(ProjectExplorer::Project *project) const
|
||||
{
|
||||
ensureStorageForProject(project);
|
||||
if (!m_currentStorage) {
|
||||
return {};
|
||||
}
|
||||
return m_currentStorage->getAllFiles();
|
||||
}
|
||||
|
||||
bool RAGManager::isFileStorageOutdated(
|
||||
ProjectExplorer::Project *project, const QString &filePath) const
|
||||
{
|
||||
ensureStorageForProject(project);
|
||||
if (!m_currentStorage) {
|
||||
return true;
|
||||
}
|
||||
return m_currentStorage->needsUpdate(filePath);
|
||||
}
|
||||
|
||||
std::optional<RAGVector> RAGManager::loadVectorFromStorage(
|
||||
ProjectExplorer::Project *project, const QString &filePath)
|
||||
{
|
||||
ensureStorageForProject(project);
|
||||
if (!m_currentStorage) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return m_currentStorage->getVector(filePath);
|
||||
}
|
||||
|
||||
void RAGManager::closeStorage()
|
||||
{
|
||||
qDebug() << "Closing storage...";
|
||||
if (m_currentStorage) {
|
||||
m_currentStorage.reset();
|
||||
m_currentProject = nullptr;
|
||||
qDebug() << "Storage closed";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
119
context/RAGManager.hpp
Normal file
119
context/RAGManager.hpp
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <QFuture>
|
||||
#include <QObject>
|
||||
|
||||
#include "FileChunker.hpp"
|
||||
#include "RAGData.hpp"
|
||||
#include "RAGStorage.hpp"
|
||||
#include "RAGVectorizer.hpp"
|
||||
|
||||
namespace ProjectExplorer {
|
||||
class Project;
|
||||
}
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
class RAGManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct ChunkSearchResult
|
||||
{
|
||||
QString filePath;
|
||||
int startLine;
|
||||
int endLine;
|
||||
QString content;
|
||||
float semanticScore;
|
||||
float structuralScore;
|
||||
float combinedScore;
|
||||
|
||||
bool operator<(const ChunkSearchResult &other) const
|
||||
{
|
||||
return combinedScore > other.combinedScore;
|
||||
}
|
||||
};
|
||||
|
||||
static RAGManager &instance();
|
||||
|
||||
QFuture<void> processProjectFiles(
|
||||
ProjectExplorer::Project *project,
|
||||
const QStringList &filePaths,
|
||||
const FileChunker::ChunkingConfig &config = FileChunker::ChunkingConfig());
|
||||
|
||||
QFuture<QList<ChunkSearchResult>> findRelevantChunks(
|
||||
const QString &query, ProjectExplorer::Project *project, int topK = 5);
|
||||
|
||||
QStringList getStoredFiles(ProjectExplorer::Project *project) const;
|
||||
bool isFileStorageOutdated(ProjectExplorer::Project *project, const QString &filePath) const;
|
||||
|
||||
void processNextChunk(
|
||||
std::shared_ptr<QPromise<void>> promise,
|
||||
const QList<FileChunkData> &chunks,
|
||||
int currentIndex);
|
||||
void closeStorage();
|
||||
signals:
|
||||
void vectorizationProgress(int processed, int total);
|
||||
void vectorizationFinished();
|
||||
|
||||
private:
|
||||
explicit RAGManager(QObject *parent = nullptr);
|
||||
~RAGManager();
|
||||
RAGManager(const RAGManager &) = delete;
|
||||
RAGManager &operator=(const RAGManager &) = delete;
|
||||
|
||||
QString getStoragePath(ProjectExplorer::Project *project) const;
|
||||
void ensureStorageForProject(ProjectExplorer::Project *project) const;
|
||||
std::optional<QString> loadFileContent(const QString &filePath);
|
||||
std::optional<RAGVector> loadVectorFromStorage(
|
||||
ProjectExplorer::Project *project, const QString &filePath);
|
||||
|
||||
void processNextFileBatch(
|
||||
std::shared_ptr<QPromise<void>> promise,
|
||||
ProjectExplorer::Project *project,
|
||||
const QStringList &files,
|
||||
const FileChunker::ChunkingConfig &config,
|
||||
int startIndex,
|
||||
int batchSize);
|
||||
|
||||
QFuture<bool> processFileWithChunks(
|
||||
ProjectExplorer::Project *project,
|
||||
const QString &filePath,
|
||||
const FileChunker::ChunkingConfig &config);
|
||||
|
||||
QFuture<void> vectorizeAndStoreChunks(
|
||||
const QString &filePath, const QList<FileChunkData> &chunks);
|
||||
|
||||
QList<ChunkSearchResult> rankChunks(
|
||||
const RAGVector &queryVector, const QString &queryText, const QList<FileChunkData> &chunks);
|
||||
|
||||
private:
|
||||
mutable std::unique_ptr<RAGVectorizer> m_vectorizer;
|
||||
mutable std::unique_ptr<RAGStorage> m_currentStorage;
|
||||
mutable ProjectExplorer::Project *m_currentProject{nullptr};
|
||||
FileChunker m_chunker;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
2
context/RAGPreprocessor.cpp
Normal file
2
context/RAGPreprocessor.cpp
Normal file
@ -0,0 +1,2 @@
|
||||
#include "RAGPreprocessor.hpp"
|
||||
|
||||
64
context/RAGPreprocessor.hpp
Normal file
64
context/RAGPreprocessor.hpp
Normal file
@ -0,0 +1,64 @@
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
|
||||
#include "Logger.hpp"
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
class RAGPreprocessor
|
||||
{
|
||||
public:
|
||||
static const QRegularExpression &getLicenseRegex()
|
||||
{
|
||||
static const QRegularExpression regex(
|
||||
R"((/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)|//[^\n]*(?:\n|$))",
|
||||
QRegularExpression::MultilineOption);
|
||||
return regex;
|
||||
}
|
||||
|
||||
static const QRegularExpression &getClassRegex()
|
||||
{
|
||||
static const QRegularExpression regex(
|
||||
R"((?:template\s*<[^>]*>\s*)?(?:class|struct)\s+(\w+)\s*(?:final\s*)?(?::\s*(?:public|protected|private)\s+\w+(?:\s*,\s*(?:public|protected|private)\s+\w+)*\s*)?{)");
|
||||
return regex;
|
||||
}
|
||||
|
||||
static QString preprocessCode(const QString &code)
|
||||
{
|
||||
if (code.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
try {
|
||||
QStringList lines = code.split('\n', Qt::SkipEmptyParts);
|
||||
return processLines(lines);
|
||||
} catch (const std::exception &e) {
|
||||
LOG_MESSAGE(QString("Error preprocessing code: %1").arg(e.what()));
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static QString processLines(const QStringList &lines)
|
||||
{
|
||||
const int estimatedAvgLength = 80;
|
||||
QString result;
|
||||
result.reserve(lines.size() * estimatedAvgLength);
|
||||
|
||||
for (const QString &line : lines) {
|
||||
const QString trimmed = line.trimmed();
|
||||
if (!trimmed.isEmpty()) {
|
||||
result += trimmed;
|
||||
result += QLatin1Char('\n');
|
||||
}
|
||||
}
|
||||
|
||||
if (result.endsWith('\n')) {
|
||||
result.chop(1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
67
context/RAGSimilaritySearch.cpp
Normal file
67
context/RAGSimilaritySearch.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "RAGSimilaritySearch.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
float RAGSimilaritySearch::l2Distance(const RAGVector &v1, const RAGVector &v2)
|
||||
{
|
||||
if (v1.size() != v2.size()) {
|
||||
LOG_MESSAGE(QString("Vector size mismatch: %1 vs %2").arg(v1.size()).arg(v2.size()));
|
||||
return std::numeric_limits<float>::max();
|
||||
}
|
||||
|
||||
float sum = 0.0f;
|
||||
for (size_t i = 0; i < v1.size(); ++i) {
|
||||
float diff = v1[i] - v2[i];
|
||||
sum += diff * diff;
|
||||
}
|
||||
return std::sqrt(sum);
|
||||
}
|
||||
|
||||
float RAGSimilaritySearch::cosineSimilarity(const RAGVector &v1, const RAGVector &v2)
|
||||
{
|
||||
if (v1.size() != v2.size()) {
|
||||
LOG_MESSAGE(QString("Vector size mismatch: %1 vs %2").arg(v1.size()).arg(v2.size()));
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float dotProduct = 0.0f;
|
||||
float norm1 = 0.0f;
|
||||
float norm2 = 0.0f;
|
||||
|
||||
for (size_t i = 0; i < v1.size(); ++i) {
|
||||
dotProduct += v1[i] * v2[i];
|
||||
norm1 += v1[i] * v1[i];
|
||||
norm2 += v2[i] * v2[i];
|
||||
}
|
||||
|
||||
norm1 = std::sqrt(norm1);
|
||||
norm2 = std::sqrt(norm2);
|
||||
|
||||
if (norm1 == 0.0f || norm2 == 0.0f)
|
||||
return 0.0f;
|
||||
return dotProduct / (norm1 * norm2);
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
37
context/RAGSimilaritySearch.hpp
Normal file
37
context/RAGSimilaritySearch.hpp
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RAGData.hpp"
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
class RAGSimilaritySearch
|
||||
{
|
||||
public:
|
||||
static float l2Distance(const RAGVector &v1, const RAGVector &v2);
|
||||
|
||||
static float cosineSimilarity(const RAGVector &v1, const RAGVector &v2);
|
||||
|
||||
private:
|
||||
RAGSimilaritySearch() = delete;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
1047
context/RAGStorage.cpp
Normal file
1047
context/RAGStorage.cpp
Normal file
File diff suppressed because it is too large
Load Diff
174
context/RAGStorage.hpp
Normal file
174
context/RAGStorage.hpp
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// RAGStorage.hpp
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <QDateTime>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QSqlDatabase>
|
||||
#include <QString>
|
||||
#include <qsqlquery.h>
|
||||
|
||||
#include <RAGData.hpp>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
struct FileChunkData
|
||||
{
|
||||
QString filePath;
|
||||
int startLine;
|
||||
int endLine;
|
||||
QString content;
|
||||
QDateTime createdAt;
|
||||
QDateTime updatedAt;
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return !filePath.isEmpty() && startLine >= 0 && endLine >= startLine && !content.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
struct StorageOptions
|
||||
{
|
||||
int maxChunkSize = 1024 * 1024;
|
||||
int maxVectorSize = 1024;
|
||||
bool useCompression = false;
|
||||
bool enableLogging = false;
|
||||
};
|
||||
|
||||
struct StorageStatistics
|
||||
{
|
||||
int totalChunks;
|
||||
int totalVectors;
|
||||
int totalFiles;
|
||||
qint64 totalSize;
|
||||
QDateTime lastUpdate;
|
||||
};
|
||||
|
||||
class RAGStorage : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static constexpr int CURRENT_VERSION = 1;
|
||||
|
||||
enum class Status { Ok, DatabaseError, ValidationError, VersionError, ConnectionError };
|
||||
|
||||
struct ValidationResult
|
||||
{
|
||||
bool isValid;
|
||||
QString errorMessage;
|
||||
Status errorStatus;
|
||||
};
|
||||
|
||||
struct Error
|
||||
{
|
||||
QString message;
|
||||
QString sqlError;
|
||||
QString query;
|
||||
Status status;
|
||||
};
|
||||
|
||||
explicit RAGStorage(
|
||||
const QString &dbPath,
|
||||
const StorageOptions &options = StorageOptions(),
|
||||
QObject *parent = nullptr);
|
||||
~RAGStorage();
|
||||
|
||||
bool init();
|
||||
Status status() const;
|
||||
Error lastError() const;
|
||||
bool isReady() const;
|
||||
QString dbPath() const;
|
||||
|
||||
bool beginTransaction();
|
||||
bool commitTransaction();
|
||||
bool rollbackTransaction();
|
||||
|
||||
bool storeVector(const QString &filePath, const RAGVector &vector);
|
||||
bool updateVector(const QString &filePath, const RAGVector &vector);
|
||||
std::optional<RAGVector> getVector(const QString &filePath);
|
||||
bool needsUpdate(const QString &filePath);
|
||||
QStringList getAllFiles();
|
||||
|
||||
bool storeChunk(const FileChunkData &chunk);
|
||||
bool storeChunks(const QList<FileChunkData> &chunks);
|
||||
bool updateChunk(const FileChunkData &chunk);
|
||||
bool updateChunks(const QList<FileChunkData> &chunks);
|
||||
bool deleteChunksForFile(const QString &filePath);
|
||||
std::optional<FileChunkData> getChunk(const QString &filePath, int startLine, int endLine);
|
||||
QList<FileChunkData> getChunksForFile(const QString &filePath);
|
||||
bool chunkExists(const QString &filePath, int startLine, int endLine);
|
||||
|
||||
int getChunkCount(const QString &filePath);
|
||||
bool deleteOldChunks(const QString &filePath, const QDateTime &olderThan);
|
||||
bool deleteAllChunks();
|
||||
QStringList getFilesWithChunks();
|
||||
bool vacuum();
|
||||
bool backup(const QString &backupPath);
|
||||
bool restore(const QString &backupPath);
|
||||
StorageStatistics getStatistics() const;
|
||||
|
||||
int getStorageVersion() const;
|
||||
bool isVersionCompatible() const;
|
||||
|
||||
bool applyMigration(int version);
|
||||
signals:
|
||||
void errorOccurred(const Error &error);
|
||||
void operationCompleted(const QString &operation);
|
||||
|
||||
private:
|
||||
bool createTables();
|
||||
bool createIndices();
|
||||
bool createVersionTable();
|
||||
bool createChunksTable();
|
||||
bool createVectorsTable();
|
||||
bool openDatabase();
|
||||
bool initializeNewStorage();
|
||||
bool upgradeStorage(int fromVersion);
|
||||
bool validateSchema() const;
|
||||
|
||||
QDateTime getFileLastModified(const QString &filePath);
|
||||
RAGVector blobToVector(const QByteArray &blob);
|
||||
QByteArray vectorToBlob(const RAGVector &vector);
|
||||
|
||||
void setError(const QString &message, Status status = Status::DatabaseError);
|
||||
void clearError();
|
||||
bool prepareStatements();
|
||||
ValidationResult validateChunk(const FileChunkData &chunk) const;
|
||||
ValidationResult validateVector(const RAGVector &vector) const;
|
||||
|
||||
private:
|
||||
QSqlDatabase m_db;
|
||||
QString m_dbPath;
|
||||
StorageOptions m_options;
|
||||
mutable QMutex m_mutex;
|
||||
Error m_lastError;
|
||||
Status m_status;
|
||||
|
||||
QSqlQuery m_insertChunkQuery;
|
||||
QSqlQuery m_updateChunkQuery;
|
||||
QSqlQuery m_insertVectorQuery;
|
||||
QSqlQuery m_updateVectorQuery;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
116
context/RAGVectorizer.cpp
Normal file
116
context/RAGVectorizer.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "RAGVectorizer.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
RAGVectorizer::RAGVectorizer(const QString &providerUrl,
|
||||
const QString &modelName,
|
||||
QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_network(new QNetworkAccessManager(this))
|
||||
, m_embedProviderUrl(providerUrl)
|
||||
, m_model(modelName)
|
||||
{}
|
||||
|
||||
RAGVectorizer::~RAGVectorizer() {}
|
||||
|
||||
QJsonObject RAGVectorizer::prepareEmbeddingRequest(const QString &text) const
|
||||
{
|
||||
return QJsonObject{{"model", m_model}, {"prompt", text}};
|
||||
}
|
||||
|
||||
RAGVector RAGVectorizer::parseEmbeddingResponse(const QByteArray &response) const
|
||||
{
|
||||
QJsonDocument doc = QJsonDocument::fromJson(response);
|
||||
if (doc.isNull()) {
|
||||
qDebug() << "Failed to parse JSON response";
|
||||
return RAGVector();
|
||||
}
|
||||
|
||||
QJsonObject obj = doc.object();
|
||||
if (!obj.contains("embedding")) {
|
||||
qDebug() << "Response does not contain 'embedding' field";
|
||||
// qDebug() << "Response content:" << response;
|
||||
return RAGVector();
|
||||
}
|
||||
|
||||
QJsonArray array = obj["embedding"].toArray();
|
||||
if (array.isEmpty()) {
|
||||
qDebug() << "Embedding array is empty";
|
||||
return RAGVector();
|
||||
}
|
||||
|
||||
RAGVector result;
|
||||
result.reserve(array.size());
|
||||
for (const auto &value : array) {
|
||||
result.push_back(value.toDouble());
|
||||
}
|
||||
|
||||
qDebug() << "Successfully parsed vector with size:" << result.size();
|
||||
return result;
|
||||
}
|
||||
|
||||
QFuture<RAGVector> RAGVectorizer::vectorizeText(const QString &text)
|
||||
{
|
||||
qDebug() << "Vectorizing text, length:" << text.length();
|
||||
qDebug() << "Using embedding provider:" << m_embedProviderUrl;
|
||||
|
||||
auto promise = std::make_shared<QPromise<RAGVector>>();
|
||||
promise->start();
|
||||
|
||||
QNetworkRequest request(QUrl(m_embedProviderUrl + "/api/embeddings"));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QJsonObject requestData = prepareEmbeddingRequest(text);
|
||||
QByteArray jsonData = QJsonDocument(requestData).toJson();
|
||||
qDebug() << "Sending request to embeddings API:" << jsonData;
|
||||
|
||||
auto reply = m_network->post(request, jsonData);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [promise, reply, this]() {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QByteArray response = reply->readAll();
|
||||
// qDebug() << "Received response from embeddings API:" << response;
|
||||
|
||||
auto vector = parseEmbeddingResponse(response);
|
||||
qDebug() << "Parsed vector size:" << vector.size();
|
||||
promise->addResult(vector);
|
||||
} else {
|
||||
qDebug() << "Network error:" << reply->errorString();
|
||||
qDebug() << "HTTP status code:"
|
||||
<< reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
qDebug() << "Response:" << reply->readAll();
|
||||
|
||||
promise->addResult(RAGVector());
|
||||
}
|
||||
promise->finish();
|
||||
reply->deleteLater();
|
||||
});
|
||||
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
51
context/RAGVectorizer.hpp
Normal file
51
context/RAGVectorizer.hpp
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QFuture>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QObject>
|
||||
|
||||
#include <RAGData.hpp>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
class RAGVectorizer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit RAGVectorizer(
|
||||
const QString &providerUrl = "http://localhost:11434",
|
||||
const QString &modelName = "all-minilm:33m-l12-v2-fp16",
|
||||
QObject *parent = nullptr);
|
||||
~RAGVectorizer();
|
||||
|
||||
QFuture<RAGVector> vectorizeText(const QString &text);
|
||||
|
||||
private:
|
||||
QJsonObject prepareEmbeddingRequest(const QString &text) const;
|
||||
RAGVector parseEmbeddingResponse(const QByteArray &response) const;
|
||||
|
||||
QNetworkAccessManager *m_network;
|
||||
QString m_embedProviderUrl;
|
||||
QString m_model;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
54
context/TokenUtils.cpp
Normal file
54
context/TokenUtils.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "TokenUtils.hpp"
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
int TokenUtils::estimateTokens(const QString& text)
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: need to improve
|
||||
return text.length() / 4;
|
||||
}
|
||||
|
||||
int TokenUtils::estimateFileTokens(const Context::ContentFile& file)
|
||||
{
|
||||
int total = 0;
|
||||
|
||||
total += estimateTokens(file.filename);
|
||||
total += estimateTokens(file.content);
|
||||
total += 5;
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
int TokenUtils::estimateFilesTokens(const QList<Context::ContentFile>& files)
|
||||
{
|
||||
int total = 0;
|
||||
for (const auto& file : files) {
|
||||
total += estimateFileTokens(file);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
}
|
||||
36
context/TokenUtils.hpp
Normal file
36
context/TokenUtils.hpp
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include "ContentFile.hpp"
|
||||
#include <QList>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
class TokenUtils
|
||||
{
|
||||
public:
|
||||
static int estimateTokens(const QString& text);
|
||||
static int estimateFileTokens(const Context::ContentFile& file);
|
||||
static int estimateFilesTokens(const QList<Context::ContentFile>& files);
|
||||
};
|
||||
|
||||
}
|
||||
@ -155,7 +155,9 @@ private:
|
||||
|
||||
void handleUpdateCheckResult(const PluginUpdater::UpdateInfo &info)
|
||||
{
|
||||
if (!info.isUpdateAvailable)
|
||||
if (!info.isUpdateAvailable
|
||||
|| QVersionNumber::fromString(info.currentIdeVersion)
|
||||
> QVersionNumber::fromString(info.targetIdeVersion))
|
||||
return;
|
||||
|
||||
if (m_statusWidget)
|
||||
|
||||
@ -35,11 +35,26 @@ public:
|
||||
void addToLayoutImpl(Layouting::Layout &parent) override
|
||||
{
|
||||
auto button = new QPushButton(m_buttonText);
|
||||
button->setVisible(m_visible);
|
||||
connect(button, &QPushButton::clicked, this, &ButtonAspect::clicked);
|
||||
connect(this, &ButtonAspect::visibleChanged, button, &QPushButton::setVisible);
|
||||
parent.addItem(button);
|
||||
}
|
||||
|
||||
void updateVisibility(bool visible)
|
||||
{
|
||||
if (m_visible == visible)
|
||||
return;
|
||||
m_visible = visible;
|
||||
emit visibleChanged(visible);
|
||||
}
|
||||
|
||||
QString m_buttonText;
|
||||
|
||||
signals:
|
||||
void clicked();
|
||||
void visibleChanged(bool state);
|
||||
|
||||
private:
|
||||
bool m_visible = true;
|
||||
};
|
||||
|
||||
@ -44,15 +44,15 @@ ChatAssistantSettings::ChatAssistantSettings()
|
||||
|
||||
// Chat Settings
|
||||
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 "
|
||||
"exceeded, oldest messages will be removed."));
|
||||
chatTokensThreshold.setRange(1, 900000);
|
||||
chatTokensThreshold.setDefaultValue(8000);
|
||||
|
||||
sharingCurrentFile.setSettingsKey(Constants::CA_SHARING_CURRENT_FILE);
|
||||
sharingCurrentFile.setLabelText(Tr::tr("Share Current File With Assistant by Default"));
|
||||
sharingCurrentFile.setDefaultValue(true);
|
||||
linkOpenFiles.setSettingsKey(Constants::CA_LINK_OPEN_FILES);
|
||||
linkOpenFiles.setLabelText(Tr::tr("Sync open files with assistant by default"));
|
||||
linkOpenFiles.setDefaultValue(false);
|
||||
|
||||
stream.setSettingsKey(Constants::CA_STREAM);
|
||||
stream.setDefaultValue(true);
|
||||
@ -171,7 +171,7 @@ ChatAssistantSettings::ChatAssistantSettings()
|
||||
Space{8},
|
||||
Group{
|
||||
title(Tr::tr("Chat Settings")),
|
||||
Column{Row{chatTokensThreshold, Stretch{1}}, sharingCurrentFile, stream, autosave}},
|
||||
Column{Row{chatTokensThreshold, Stretch{1}}, linkOpenFiles, stream, autosave}},
|
||||
Space{8},
|
||||
Group{
|
||||
title(Tr::tr("General Parameters")),
|
||||
@ -227,6 +227,7 @@ void ChatAssistantSettings::resetSettingsToDefaults()
|
||||
resetAspect(systemPrompt);
|
||||
resetAspect(ollamaLivetime);
|
||||
resetAspect(contextWindow);
|
||||
resetAspect(linkOpenFiles);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ public:
|
||||
|
||||
// Chat settings
|
||||
Utils::IntegerAspect chatTokensThreshold{this};
|
||||
Utils::BoolAspect sharingCurrentFile{this};
|
||||
Utils::BoolAspect linkOpenFiles{this};
|
||||
Utils::BoolAspect stream{this};
|
||||
Utils::BoolAspect autosave{this};
|
||||
|
||||
|
||||
@ -153,16 +153,16 @@ CodeCompletionSettings::CodeCompletionSettings()
|
||||
systemPrompt.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||
systemPrompt.setDefaultValue(
|
||||
"You are an expert in C++, Qt, and QML programming. Your task is to provide code "
|
||||
"suggestions that seamlessly integrate with existing code. You will receive a code context "
|
||||
"with specified insertion points. Your goal is to complete only one logic expression "
|
||||
"within these points."
|
||||
"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. Remember to include only the new code to be inserted.");
|
||||
"your code suggestion.");
|
||||
|
||||
useUserMessageTemplateForCC.setSettingsKey(Constants::CC_USE_USER_TEMPLATE);
|
||||
useUserMessageTemplateForCC.setDefaultValue(true);
|
||||
@ -310,6 +310,7 @@ void CodeCompletionSettings::resetSettingsToDefaults()
|
||||
resetAspect(autoCompletion);
|
||||
resetAspect(multiLineCompletion);
|
||||
resetAspect(stream);
|
||||
resetAspect(smartProcessInstuctText);
|
||||
resetAspect(temperature);
|
||||
resetAspect(maxTokens);
|
||||
resetAspect(useTopP);
|
||||
|
||||
@ -89,6 +89,39 @@ GeneralSettings::GeneralSettings()
|
||||
ccStatus.setDefaultValue("");
|
||||
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");
|
||||
caProvider.setReadOnly(true);
|
||||
caSelectProvider.m_buttonText = TrConstants::SELECT;
|
||||
@ -117,6 +150,8 @@ GeneralSettings::GeneralSettings()
|
||||
|
||||
setupConnections();
|
||||
|
||||
updatePreset1Visiblity(specifyPreset1.value());
|
||||
|
||||
setLayouter([this]() {
|
||||
using namespace Layouting;
|
||||
|
||||
@ -126,13 +161,21 @@ GeneralSettings::GeneralSettings()
|
||||
ccGrid.addRow({ccModel, ccSelectModel});
|
||||
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{};
|
||||
caGrid.addRow({caProvider, caSelectProvider});
|
||||
caGrid.addRow({caUrl, caSetUrl});
|
||||
caGrid.addRow({caModel, caSelectModel});
|
||||
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 rootLayout = Column{
|
||||
@ -267,9 +310,11 @@ void GeneralSettings::showUrlSelectionDialog(
|
||||
dialog.addSpacing();
|
||||
|
||||
QStringList allUrls = predefinedUrls;
|
||||
QString key
|
||||
= QString("CompleterHistory/")
|
||||
.append((&aspect == &ccUrl) ? Constants::CC_URL_HISTORY : Constants::CA_URL_HISTORY);
|
||||
QString key = QString("CompleterHistory/")
|
||||
.append(
|
||||
(&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();
|
||||
allUrls.append(historyList);
|
||||
allUrls.removeDuplicates();
|
||||
@ -297,6 +342,18 @@ void GeneralSettings::showUrlSelectionDialog(
|
||||
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()
|
||||
{
|
||||
connect(&enableLogging, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
@ -306,6 +363,10 @@ void GeneralSettings::setupConnections()
|
||||
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()
|
||||
@ -328,6 +389,12 @@ void GeneralSettings::resetPageToDefaults()
|
||||
resetAspect(caTemplate);
|
||||
resetAspect(caUrl);
|
||||
resetAspect(enableCheckUpdate);
|
||||
resetAspect(specifyPreset1);
|
||||
resetAspect(preset1Language);
|
||||
resetAspect(ccPreset1Provider);
|
||||
resetAspect(ccPreset1Model);
|
||||
resetAspect(ccPreset1Template);
|
||||
resetAspect(ccPreset1Url);
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,6 +55,23 @@ public:
|
||||
Utils::StringAspect ccStatus{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
|
||||
Utils::StringAspect caProvider{this};
|
||||
ButtonAspect caSelectProvider{this};
|
||||
@ -82,6 +99,8 @@ public:
|
||||
|
||||
void showUrlSelectionDialog(Utils::StringAspect &aspect, const QStringList &predefinedUrls);
|
||||
|
||||
void updatePreset1Visiblity(bool state);
|
||||
|
||||
private:
|
||||
void setupConnections();
|
||||
void resetPageToDefaults();
|
||||
|
||||
@ -160,12 +160,18 @@ void PluginUpdater::handleDownloadFinished()
|
||||
}
|
||||
|
||||
QString downloadPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)
|
||||
+ QDir::separator() + "qodeassisttemp";
|
||||
+ QDir::separator() + "QodeAssist_v" + m_lastUpdateInfo.version;
|
||||
QDir().mkpath(downloadPath);
|
||||
|
||||
QString filePath = downloadPath + "/" + m_lastUpdateInfo.fileName;
|
||||
QFile file(filePath);
|
||||
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();
|
||||
@ -175,17 +181,7 @@ void PluginUpdater::handleDownloadFinished()
|
||||
file.write(reply->readAll());
|
||||
file.close();
|
||||
|
||||
if (!Core::executePluginInstallWizard(Utils::FilePath::fromString(filePath))) {
|
||||
emit downloadError(tr("Failed to install the update"));
|
||||
} else {
|
||||
emit downloadFinished(filePath);
|
||||
}
|
||||
|
||||
auto tempDir = QDir(downloadPath);
|
||||
if (tempDir.exists()) {
|
||||
tempDir.removeRecursively();
|
||||
}
|
||||
|
||||
emit downloadFinished(filePath);
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
|
||||
@ -46,6 +46,15 @@ const char CA_TEMPLATE[] = "QodeAssist.caTemplate";
|
||||
const char CA_URL[] = "QodeAssist.caUrl";
|
||||
const char CA_URL_HISTORY[] = "QodeAssist.caUrlHistory";
|
||||
|
||||
const char CC_SPECIFY_PRESET1[] = "QodeAssist.ccSpecifyPreset1";
|
||||
const char CC_PRESET1_LANGUAGE[] = "QodeAssist.ccPreset1Language";
|
||||
const char CC_PRESET1_PROVIDER[] = "QodeAssist.ccPreset1Provider";
|
||||
const char CC_PRESET1_MODEL[] = "QodeAssist.ccPreset1Model";
|
||||
const char CC_PRESET1_MODEL_HISTORY[] = "QodeAssist.ccPreset1ModelHistory";
|
||||
const char CC_PRESET1_TEMPLATE[] = "QodeAssist.ccPreset1Template";
|
||||
const char CC_PRESET1_URL[] = "QodeAssist.ccPreset1Url";
|
||||
const char CC_PRESET1_URL_HISTORY[] = "QodeAssist.ccPreset1UrlHistory";
|
||||
|
||||
// settings
|
||||
const char ENABLE_QODE_ASSIST[] = "QodeAssist.enableQodeAssist";
|
||||
const char CC_AUTO_COMPLETION[] = "QodeAssist.ccAutoCompletion";
|
||||
@ -62,7 +71,7 @@ const char CC_STREAM[] = "QodeAssist.ccStream";
|
||||
const char CC_SMART_PROCESS_INSTRUCT_TEXT[] = "QodeAssist.ccSmartProcessInstructText";
|
||||
const char CUSTOM_JSON_TEMPLATE[] = "QodeAssist.customJsonTemplate";
|
||||
const char CA_TOKENS_THRESHOLD[] = "QodeAssist.caTokensThreshold";
|
||||
const char CA_SHARING_CURRENT_FILE[] = "QodeAssist.caSharingCurrentFile";
|
||||
const char CA_LINK_OPEN_FILES[] = "QodeAssist.caLinkOpenFiles";
|
||||
const char CA_STREAM[] = "QodeAssist.caStream";
|
||||
const char CA_AUTOSAVE[] = "QodeAssist.caAutosave";
|
||||
|
||||
|
||||
@ -86,6 +86,9 @@ inline const char ENTER_MODEL_MANUALLY_BUTTON[]
|
||||
inline const char AUTO_COMPLETION_SETTINGS[]
|
||||
= QT_TRANSLATE_NOOP("QtC::QodeAssist", "Auto Completion Settings");
|
||||
|
||||
inline const char ADD_NEW_PRESET[]
|
||||
= QT_TRANSLATE_NOOP("QtC::QodeAssist", "Add new preset for language");
|
||||
|
||||
} // namespace TrConstants
|
||||
|
||||
struct Tr
|
||||
|
||||
@ -19,6 +19,11 @@
|
||||
|
||||
#include "UpdateDialog.hpp"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
#include <extensionsystem/pluginmanager.h>
|
||||
#include <extensionsystem/pluginspec.h>
|
||||
#include <QDesktopServices>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
UpdateDialog::UpdateDialog(QWidget *parent)
|
||||
@ -60,6 +65,12 @@ UpdateDialog::UpdateDialog(QWidget *parent)
|
||||
m_versionLabel->setAlignment(Qt::AlignCenter);
|
||||
m_layout->addWidget(m_versionLabel);
|
||||
|
||||
m_releaseLink = new QLabel(this);
|
||||
m_releaseLink->setOpenExternalLinks(true);
|
||||
m_releaseLink->setTextFormat(Qt::RichText);
|
||||
m_releaseLink->setAlignment(Qt::AlignCenter);
|
||||
m_layout->addWidget(m_releaseLink);
|
||||
|
||||
if (!m_changelogLabel) {
|
||||
m_changelogLabel = new QLabel(tr("Release Notes:"), this);
|
||||
m_layout->addWidget(m_changelogLabel);
|
||||
@ -75,7 +86,7 @@ UpdateDialog::UpdateDialog(QWidget *parent)
|
||||
m_layout->addWidget(m_progress);
|
||||
|
||||
auto *buttonLayout = new QHBoxLayout;
|
||||
m_downloadButton = new QPushButton(tr("Download and Install"), this);
|
||||
m_downloadButton = new QPushButton(tr("Download"), this);
|
||||
m_downloadButton->setEnabled(false);
|
||||
buttonLayout->addWidget(m_downloadButton);
|
||||
|
||||
@ -104,6 +115,10 @@ void UpdateDialog::checkForUpdatesAndShow(QWidget *parent)
|
||||
|
||||
void UpdateDialog::handleUpdateInfo(const PluginUpdater::UpdateInfo &info)
|
||||
{
|
||||
m_releaseLink->setText(
|
||||
tr("<a href='https://github.com/Palm1r/QodeAssist/releases'>You can also download "
|
||||
"from GitHub Releases</a>"));
|
||||
|
||||
if (info.incompatibleIdeVersion) {
|
||||
m_titleLabel->setText(tr("Incompatible Qt Creator Version"));
|
||||
m_versionLabel->setText(tr("This update requires Qt Creator %1, current is %2.\n"
|
||||
@ -156,11 +171,32 @@ void UpdateDialog::updateProgress(qint64 received, qint64 total)
|
||||
void UpdateDialog::handleDownloadFinished(const QString &path)
|
||||
{
|
||||
m_progress->setVisible(false);
|
||||
QMessageBox::information(
|
||||
this,
|
||||
tr("Update Successful"),
|
||||
tr("Update has been downloaded and installed. "
|
||||
"Please restart Qt Creator to apply changes."));
|
||||
|
||||
QMessageBox msgBox(this);
|
||||
msgBox.setWindowTitle(tr("Update Downloaded"));
|
||||
msgBox.setText(tr("The update has been downloaded successfully.\n"
|
||||
"Would you like to close Qt Creator now and open the plugin folder?"));
|
||||
msgBox.setInformativeText(tr("To complete the update:\n\n"
|
||||
"1. Close Qt Creator completely\n"
|
||||
"2. Navigate to the plugin folder\n"
|
||||
"3. Remove the old version of QodeAssist plugin\n"
|
||||
"4. Install plugin as usual via About plugin menu"));
|
||||
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
msgBox.setDefaultButton(QMessageBox::Yes);
|
||||
msgBox.setIcon(QMessageBox::Information);
|
||||
|
||||
if (msgBox.exec() == QMessageBox::Yes) {
|
||||
const auto pluginSpecs = ExtensionSystem::PluginManager::plugins();
|
||||
for (const ExtensionSystem::PluginSpec *spec : pluginSpecs) {
|
||||
if (spec->name() == QLatin1String("QodeAssist")) {
|
||||
const auto pluginPath = spec->filePath().path();
|
||||
QFileInfo fileInfo(pluginPath);
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(fileInfo.absolutePath()));
|
||||
Core::ICore::exit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
accept();
|
||||
}
|
||||
|
||||
|
||||
@ -52,6 +52,7 @@ private:
|
||||
QVBoxLayout *m_layout;
|
||||
QLabel *m_titleLabel;
|
||||
QLabel *m_versionLabel;
|
||||
QLabel *m_releaseLink;
|
||||
QLabel *m_changelogLabel{nullptr};
|
||||
QTextEdit *m_changelogText{nullptr};
|
||||
QProgressBar *m_progress;
|
||||
|
||||
48
templates/CodeLlamaQMLFim.hpp
Normal file
48
templates/CodeLlamaQMLFim.hpp
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "llmcore/PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class CodeLlamaQMLFim : public LLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
|
||||
QString name() const override { return "CodeLlama QML FIM"; }
|
||||
QString promptTemplate() const override { return "<SUF>%1<PRE>%2<MID>"; }
|
||||
QStringList stopWords() const override
|
||||
{
|
||||
return QStringList() << "<SUF>" << "<PRE>" << "</PRE>" << "</SUF>" << "< EOT >" << "\\end"
|
||||
<< "<MID>" << "</MID>" << "##";
|
||||
}
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
QString formattedPrompt = promptTemplate().arg(context.suffix, context.prefix);
|
||||
request["prompt"] = formattedPrompt;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "The message will contain the following tokens: <SUF>%1<PRE>%2<MID>";
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
@ -25,6 +25,7 @@
|
||||
#include "templates/ChatML.hpp"
|
||||
#include "templates/Claude.hpp"
|
||||
#include "templates/CodeLlamaFim.hpp"
|
||||
#include "templates/CodeLlamaQMLFim.hpp"
|
||||
#include "templates/CustomFimTemplate.hpp"
|
||||
#include "templates/DeepSeekCoderFim.hpp"
|
||||
#include "templates/Llama2.hpp"
|
||||
@ -53,6 +54,7 @@ inline void registerTemplates()
|
||||
templateManager.registerTemplate<Llama2>();
|
||||
templateManager.registerTemplate<Claude>();
|
||||
templateManager.registerTemplate<OpenAI>();
|
||||
templateManager.registerTemplate<CodeLlamaQMLFim>();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
|
||||
Reference in New Issue
Block a user