Compare commits

...

35 Commits

Author SHA1 Message Date
142afa725f feat: Add using vector and chunks in Context Manager 2025-02-21 18:19:16 +01:00
f36db033e6 refactor: Improvment rag storage 2025-02-09 10:53:35 +01:00
5dfcf74128 feat: add chunking 2025-02-09 10:10:06 +01:00
02101665ca feat: Add version to vector db 2025-02-09 01:00:30 +01:00
77a03d42ed feat: add enhancedSearch 2025-02-09 00:04:45 +01:00
09c38c8b0e fix: Rebase on main 2025-02-09 00:04:45 +01:00
7b73d7af7b feat: Add similarity search 2025-02-09 00:04:44 +01:00
5a426b4d9f feat: RAG init 2025-02-09 00:04:44 +01:00
1fa6a225a4 Add discord invite
Some checks failed
Build plugin / ${{ matrix.config.name }} (map[artifact:Linux-x64 cc:gcc cxx:g++ name:Ubuntu Latest GCC os:ubuntu-latest platform:linux_x64]) (push) Has been cancelled
Build plugin / ${{ matrix.config.name }} (map[artifact:Windows-x64 cc:cl cxx:cl environment_script:C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat name:Windows Latest MSVC os:windows-latest platform:windows_x64]) (push) Has been cancelled
Build plugin / ${{ matrix.config.name }} (map[artifact:macOS-universal cc:clang cxx:clang++ name:macOS Latest Clang os:macos-latest platform:mac_x64]) (push) Has been cancelled
Build plugin / update_json (push) Has been cancelled
Build plugin / release (push) Has been cancelled
2025-02-05 02:01:32 +01:00
31133e3378 chore: Update plugin to 0.4.13
Some checks failed
Build plugin / ${{ matrix.config.name }} (map[artifact:Linux-x64 cc:gcc cxx:g++ name:Ubuntu Latest GCC os:ubuntu-latest platform:linux_x64]) (push) Has been cancelled
Build plugin / ${{ matrix.config.name }} (map[artifact:Windows-x64 cc:cl cxx:cl environment_script:C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat name:Windows Latest MSVC os:windows-latest platform:windows_x64]) (push) Has been cancelled
Build plugin / ${{ matrix.config.name }} (map[artifact:macOS-universal cc:clang cxx:clang++ name:macOS Latest Clang os:macos-latest platform:mac_x64]) (push) Has been cancelled
Build plugin / update_json (push) Has been cancelled
Build plugin / release (push) Has been cancelled
2025-02-02 23:28:31 +01:00
a2f15fc843 refactor: Remove experimental Ubuntu 22.04 build from releases 2025-02-02 23:03:51 +01:00
2a0beb6c4c feat: Add language-specific LLM preset configuration
- Add ability to configure separate provider/model/template for specific programming language
- Add UI controls for language preset configuration
- Support custom provider selection per language
- Support custom model selection per language
- Support custom template selection per language
2025-02-02 22:57:18 +01:00
e836b86569 chore: Upgrade plugin to 0.4.12
Some checks failed
Build plugin / ${{ matrix.config.name }} (map[artifact:Linux-x64 cc:gcc cxx:g++ name:Ubuntu Latest GCC os:ubuntu-latest platform:linux_x64]) (push) Has been cancelled
Build plugin / ${{ matrix.config.name }} (map[artifact:Linux-x64(Ubuntu-22.04-experimental) cc:gcc cxx:g++ name:Ubuntu 22.04 GCC os:ubuntu-22.04 platform:linux_x64]) (push) Has been cancelled
Build plugin / ${{ matrix.config.name }} (map[artifact:Windows-x64 cc:cl cxx:cl environment_script:C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat name:Windows Latest MSVC os:windows-latest platform:windows_x64]) (push) Has been cancelled
Build plugin / ${{ matrix.config.name }} (map[artifact:macOS-universal cc:clang cxx:clang++ name:macOS Latest Clang os:macos-latest platform:mac_x64]) (push) Has been cancelled
Build plugin / update_json (push) Has been cancelled
Build plugin / release (push) Has been cancelled
2025-02-01 09:37:21 +01:00
288fefebe5 feat: Add CodeLlama QML FIM 2025-02-01 09:36:14 +01:00
528badbf1e Add commercial support to README.md
Some checks failed
Build plugin / ${{ matrix.config.name }} (map[artifact:Linux-x64 cc:gcc cxx:g++ name:Ubuntu Latest GCC os:ubuntu-latest platform:linux_x64]) (push) Has been cancelled
Build plugin / ${{ matrix.config.name }} (map[artifact:Linux-x64(Ubuntu-22.04-experimental) cc:gcc cxx:g++ name:Ubuntu 22.04 GCC os:ubuntu-22.04 platform:linux_x64]) (push) Has been cancelled
Build plugin / ${{ matrix.config.name }} (map[artifact:Windows-x64 cc:cl cxx:cl environment_script:C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat name:Windows Latest MSVC os:windows-latest platform:windows_x64]) (push) Has been cancelled
Build plugin / ${{ matrix.config.name }} (map[artifact:macOS-universal cc:clang cxx:clang++ name:macOS Latest Clang os:macos-latest platform:mac_x64]) (push) Has been cancelled
Build plugin / update_json (push) Has been cancelled
Build plugin / release (push) Has been cancelled
2025-01-29 15:00:16 +01:00
b789e42602 chore: Upgrade plugin to 0.4.11
Some checks failed
Build plugin / ${{ matrix.config.name }} (map[artifact:Linux-x64 cc:gcc cxx:g++ name:Ubuntu Latest GCC os:ubuntu-latest platform:linux_x64]) (push) Has been cancelled
Build plugin / ${{ matrix.config.name }} (map[artifact:Linux-x64(Ubuntu-22.04-experimental) cc:gcc cxx:g++ name:Ubuntu 22.04 GCC os:ubuntu-22.04 platform:linux_x64]) (push) Has been cancelled
Build plugin / ${{ matrix.config.name }} (map[artifact:Windows-x64 cc:cl cxx:cl environment_script:C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat name:Windows Latest MSVC os:windows-latest platform:windows_x64]) (push) Has been cancelled
Build plugin / ${{ matrix.config.name }} (map[artifact:macOS-universal cc:clang cxx:clang++ name:macOS Latest Clang os:macos-latest platform:mac_x64]) (push) Has been cancelled
Build plugin / update_json (push) Has been cancelled
Build plugin / release (push) Has been cancelled
2025-01-27 00:55:39 +01:00
4bf955462f feat: Update dialog offer open plugin folder 2025-01-27 00:54:49 +01:00
5b99e68e53 doc: Added file context feature description 2025-01-27 00:14:38 +01:00
0f1b277ef7 chore: Upgrade plugin to 0.4.10 2025-01-26 23:11:58 +01:00
56995c9edf fix: Saving name of chat in native language
- Add button to show chat history folder in system viewer
2025-01-26 23:06:51 +01:00
45aba6b6be fix: Sync editors and chat when sync enable 2025-01-26 22:35:43 +01:00
1dfb3feb96 doc: Update info for QtC 15.0.1 changes
Some checks are pending
Build plugin / ${{ matrix.config.name }} (map[artifact:Linux-x64 cc:gcc cxx:g++ name:Ubuntu Latest GCC os:ubuntu-latest platform:linux_x64]) (push) Waiting to run
Build plugin / ${{ matrix.config.name }} (map[artifact:Linux-x64(Ubuntu-22.04-experimental) cc:gcc cxx:g++ name:Ubuntu 22.04 GCC os:ubuntu-22.04 platform:linux_x64]) (push) Waiting to run
Build plugin / ${{ matrix.config.name }} (map[artifact:Windows-x64 cc:cl cxx:cl environment_script:C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat name:Windows Latest MSVC os:windows-latest platform:windows_x64]) (push) Waiting to run
Build plugin / ${{ matrix.config.name }} (map[artifact:macOS-universal cc:clang cxx:clang++ name:macOS Latest Clang os:macos-latest platform:mac_x64]) (push) Waiting to run
Build plugin / update_json (push) Blocked by required conditions
Build plugin / release (push) Blocked by required conditions
2025-01-26 10:08:03 +01:00
2c49d45297 fix: Keep name of chat after saving 2025-01-26 10:03:34 +01:00
31145f191b chore: Upgrade plugin to 0.4.9
Some checks failed
Build plugin / ${{ matrix.config.name }} (map[artifact:Linux-x64 cc:gcc cxx:g++ name:Ubuntu Latest GCC os:ubuntu-latest platform:linux_x64]) (push) Has been cancelled
Build plugin / ${{ matrix.config.name }} (map[artifact:Linux-x64(Ubuntu-22.04-experimental) cc:gcc cxx:g++ name:Ubuntu 22.04 GCC os:ubuntu-22.04 platform:linux_x64]) (push) Has been cancelled
Build plugin / ${{ matrix.config.name }} (map[artifact:Windows-x64 cc:cl cxx:cl environment_script:C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat name:Windows Latest MSVC os:windows-latest platform:windows_x64]) (push) Has been cancelled
Build plugin / ${{ matrix.config.name }} (map[artifact:macOS-universal cc:clang cxx:clang++ name:macOS Latest Clang os:macos-latest platform:mac_x64]) (push) Has been cancelled
Build plugin / update_json (push) Has been cancelled
Build plugin / release (push) Has been cancelled
2025-01-24 18:27:07 +01:00
9096adde6f fix: Remove installing plugin from update dialog 2025-01-24 18:23:11 +01:00
b8e578d2d7 chore: Update plugin to QtCreator 15.0.1
* fix: Additional check qtc version
* build: Upgrade plugin to QtC 15.0.1
* chore: Upgrade plugin version to 0.4.8
2025-01-24 13:29:44 +01:00
4e45774bce chore: Upgrade version to 0.4.7 2025-01-24 01:52:09 +01:00
928490d31f fix: small style changes 2025-01-24 01:50:56 +01:00
97163cf6c9 fix: Add calculate tokens after clean chat 2025-01-24 01:08:30 +01:00
f85c162692 fix: Improve scroll bar style 2025-01-24 00:59:26 +01:00
258053d826 feat: Add chat file name in top bar 2025-01-24 00:52:10 +01:00
bf63ae5714 refactor: Improve systemPrompt for code completion 2025-01-24 00:37:52 +01:00
ae76850e78 fix: Buttons order in urls dialog on general page 2025-01-24 00:29:15 +01:00
bf3c0b3aa0 feat: Add auto sync open files with model context 2025-01-24 00:22:44 +01:00
9add61c805 feat: Add possibility to link files to the current system prompt
- Add linking files to chat
- Rework tokens counting
2025-01-23 10:17:38 +01:00
59 changed files with 4010 additions and 166 deletions

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;

View File

@ -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

View 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

View 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

View 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

View File

@ -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()
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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]() {

View File

@ -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);

View File

@ -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;

View File

@ -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",

View File

@ -2,8 +2,8 @@
[![Build plugin](https://github.com/Palm1r/QodeAssist/actions/workflows/build_cmake.yml/badge.svg?branch=main)](https://github.com/Palm1r/QodeAssist/actions/workflows/build_cmake.yml)
![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/Palm1r/QodeAssist/total?color=41%2C173%2C71)
![GitHub Tag](https://img.shields.io/github/v/tag/Palm1r/QodeAssist)
![Static Badge](https://img.shields.io/badge/QtCreator-15.0.0-brightgreen)
![Static Badge](https://img.shields.io/badge/donations:0-brightgreen)
![Static Badge](https://img.shields.io/badge/QtCreator-15.0.1-brightgreen)
[![](https://dcbadge.limes.pink/api/server/BGMkUsXUgf?style=flat)](https://discord.gg/BGMkUsXUgf)
![qodeassist-icon](https://github.com/user-attachments/assets/dc336712-83cb-440d-8761-8d0a31de898d) 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

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View 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
View 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
View 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

View 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

View 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
View File

@ -0,0 +1,7 @@
#pragma once
#include <vector>
namespace QodeAssist::Context {
using RAGVector = std::vector<float>;
}

443
context/RAGManager.cpp Normal file
View 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
View 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

View File

@ -0,0 +1,2 @@
#include "RAGPreprocessor.hpp"

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

174
context/RAGStorage.hpp Normal file
View 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
View 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
View 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
View 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
View 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);
};
}

View File

@ -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)

View File

@ -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;
};

View File

@ -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);
}
}

View File

@ -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};

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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();

View File

@ -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();
}

View File

@ -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";

View File

@ -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

View File

@ -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();
}

View File

@ -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;

View 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

View File

@ -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