mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-02-09 16:50:13 -05:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e836b86569 | |||
| 288fefebe5 | |||
| 528badbf1e | |||
| b789e42602 | |||
| 4bf955462f | |||
| 5b99e68e53 | |||
| 0f1b277ef7 | |||
| 56995c9edf | |||
| 45aba6b6be | |||
| 1dfb3feb96 | |||
| 2c49d45297 | |||
| 31145f191b | |||
| 9096adde6f | |||
| b8e578d2d7 | |||
| 4e45774bce | |||
| 928490d31f | |||
| 97163cf6c9 | |||
| f85c162692 | |||
| 258053d826 | |||
| bf63ae5714 | |||
| ae76850e78 | |||
| bf3c0b3aa0 | |||
| 9add61c805 | |||
| add86d2e67 | |||
| a6c909d34d | |||
| 2814dec3e5 | |||
| 1b86b60de8 | |||
| 4b7f638731 | |||
| de046f0529 | |||
| e975e143b1 | |||
| c97c0f62e8 | |||
| 61fded34ea | |||
| 289a19ac1a | |||
| 43ac662671 | |||
| 1d64d2afc9 | |||
| 9db61119aa | |||
| 70481b3116 |
10
.github/workflows/build_cmake.yml
vendored
10
.github/workflows/build_cmake.yml
vendored
@ -13,8 +13,8 @@ on:
|
||||
env:
|
||||
PLUGIN_NAME: QodeAssist
|
||||
QT_VERSION: 6.8.1
|
||||
QT_CREATOR_VERSION: 15.0.0
|
||||
QT_CREATOR_VERSION_INTERNAL: 15.0.0
|
||||
QT_CREATOR_VERSION: 15.0.1
|
||||
QT_CREATOR_VERSION_INTERNAL: 15.0.1
|
||||
MACOS_DEPLOYMENT_TARGET: "11.0"
|
||||
CMAKE_VERSION: "3.29.6"
|
||||
NINJA_VERSION: "1.12.1"
|
||||
@ -41,6 +41,12 @@ 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,
|
||||
|
||||
@ -53,13 +53,16 @@ add_qtc_plugin(QodeAssist
|
||||
templates/ChatML.hpp
|
||||
templates/Alpaca.hpp
|
||||
templates/Llama2.hpp
|
||||
providers/Providers.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
|
||||
providers/OpenAICompatProvider.hpp providers/OpenAICompatProvider.cpp
|
||||
providers/OpenRouterAIProvider.hpp providers/OpenRouterAIProvider.cpp
|
||||
providers/ClaudeProvider.hpp providers/ClaudeProvider.cpp
|
||||
providers/OpenAIProvider.hpp providers/OpenAIProvider.cpp
|
||||
QodeAssist.qrc
|
||||
LSPCompletion.hpp
|
||||
LLMSuggestion.hpp LLMSuggestion.cpp
|
||||
@ -68,4 +71,5 @@ add_qtc_plugin(QodeAssist
|
||||
chat/NavigationPanel.hpp chat/NavigationPanel.cpp
|
||||
ConfigurationManager.hpp ConfigurationManager.cpp
|
||||
CodeHandler.hpp CodeHandler.cpp
|
||||
UpdateStatusWidget.hpp UpdateStatusWidget.cpp
|
||||
)
|
||||
|
||||
@ -18,9 +18,12 @@ qt_add_qml_module(QodeAssistChatView
|
||||
qml/parts/BottomBar.qml
|
||||
qml/parts/AttachedFilesPlace.qml
|
||||
RESOURCES
|
||||
icons/attach-file.svg
|
||||
icons/attach-file-light.svg
|
||||
icons/attach-file-dark.svg
|
||||
icons/close-dark.svg
|
||||
icons/close-light.svg
|
||||
icons/link-file-light.svg
|
||||
icons/link-file-dark.svg
|
||||
SOURCES
|
||||
ChatWidget.hpp ChatWidget.cpp
|
||||
ChatModel.hpp ChatModel.cpp
|
||||
|
||||
@ -28,7 +28,6 @@ namespace QodeAssist::Chat {
|
||||
|
||||
ChatModel::ChatModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, m_totalTokens(0)
|
||||
{
|
||||
auto &settings = Settings::chatAssistantSettings();
|
||||
|
||||
@ -90,26 +89,19 @@ void ChatModel::addMessage(
|
||||
.arg(attachment.filename, attachment.content);
|
||||
}
|
||||
}
|
||||
int tokenCount = estimateTokenCount(fullContent);
|
||||
|
||||
if (!m_messages.isEmpty() && !id.isEmpty() && m_messages.last().id == id) {
|
||||
Message &lastMessage = m_messages.last();
|
||||
int oldTokenCount = lastMessage.tokenCount;
|
||||
lastMessage.content = content;
|
||||
lastMessage.attachments = attachments;
|
||||
lastMessage.tokenCount = tokenCount;
|
||||
m_totalTokens += (tokenCount - oldTokenCount);
|
||||
emit dataChanged(index(m_messages.size() - 1), index(m_messages.size() - 1));
|
||||
} else {
|
||||
beginInsertRows(QModelIndex(), m_messages.size(), m_messages.size());
|
||||
Message newMessage{role, content, tokenCount, id};
|
||||
Message newMessage{role, content, id};
|
||||
newMessage.attachments = attachments;
|
||||
m_messages.append(newMessage);
|
||||
m_totalTokens += tokenCount;
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
emit totalTokensChanged();
|
||||
}
|
||||
|
||||
QVector<ChatModel::Message> ChatModel::getChatHistory() const
|
||||
@ -117,18 +109,11 @@ QVector<ChatModel::Message> ChatModel::getChatHistory() const
|
||||
return m_messages;
|
||||
}
|
||||
|
||||
int ChatModel::estimateTokenCount(const QString &text) const
|
||||
{
|
||||
return text.length() / 4;
|
||||
}
|
||||
|
||||
void ChatModel::clear()
|
||||
{
|
||||
beginResetModel();
|
||||
m_messages.clear();
|
||||
m_totalTokens = 0;
|
||||
endResetModel();
|
||||
emit totalTokensChanged();
|
||||
emit modelReseted();
|
||||
}
|
||||
|
||||
@ -199,11 +184,6 @@ QJsonArray ChatModel::prepareMessagesForRequest(const QString &systemPrompt) con
|
||||
return messages;
|
||||
}
|
||||
|
||||
int ChatModel::totalTokens() const
|
||||
{
|
||||
return m_totalTokens;
|
||||
}
|
||||
|
||||
int ChatModel::tokensThreshold() const
|
||||
{
|
||||
auto &settings = Settings::chatAssistantSettings();
|
||||
|
||||
@ -33,7 +33,6 @@ namespace QodeAssist::Chat {
|
||||
class ChatModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int totalTokens READ totalTokens NOTIFY totalTokensChanged FINAL)
|
||||
Q_PROPERTY(int tokensThreshold READ tokensThreshold NOTIFY tokensThresholdChanged FINAL)
|
||||
QML_ELEMENT
|
||||
|
||||
@ -47,7 +46,6 @@ public:
|
||||
{
|
||||
ChatRole role;
|
||||
QString content;
|
||||
int tokenCount;
|
||||
QString id;
|
||||
|
||||
QList<Context::ContentFile> attachments;
|
||||
@ -70,22 +68,17 @@ public:
|
||||
QVector<Message> getChatHistory() const;
|
||||
QJsonArray prepareMessagesForRequest(const QString &systemPrompt) const;
|
||||
|
||||
int totalTokens() const;
|
||||
int tokensThreshold() const;
|
||||
|
||||
QString currentModel() const;
|
||||
QString lastMessageId() const;
|
||||
|
||||
signals:
|
||||
void totalTokensChanged();
|
||||
void tokensThresholdChanged();
|
||||
void modelReseted();
|
||||
|
||||
private:
|
||||
int estimateTokenCount(const QString &text) const;
|
||||
|
||||
QVector<Message> m_messages;
|
||||
int m_totalTokens = 0;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
#include "ChatRootView.hpp"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QDesktopServices>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
|
||||
@ -29,12 +30,15 @@
|
||||
#include <projectexplorer/projectmanager.h>
|
||||
#include <utils/theme/theme.h>
|
||||
#include <utils/utilsicons.h>
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
|
||||
#include "ChatAssistantSettings.hpp"
|
||||
#include "ChatSerializer.hpp"
|
||||
#include "GeneralSettings.hpp"
|
||||
#include "Logger.hpp"
|
||||
#include "ProjectSettings.hpp"
|
||||
#include "context/TokenUtils.hpp"
|
||||
#include "context/ContextManager.hpp"
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
@ -43,6 +47,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,20 +61,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);
|
||||
|
||||
generateColors();
|
||||
connect(m_chatModel, &ChatModel::modelReseted, this, [this]() { setRecentFilePath(QString{}); });
|
||||
connect(this, &ChatRootView::attachmentFilesChanged, &ChatRootView::updateInputTokensCount);
|
||||
connect(this, &ChatRootView::linkedFilesChanged, &ChatRootView::updateInputTokensCount);
|
||||
connect(&Settings::chatAssistantSettings().useSystemPrompt, &Utils::BaseAspect::changed,
|
||||
this, &ChatRootView::updateInputTokensCount);
|
||||
connect(&Settings::chatAssistantSettings().systemPrompt, &Utils::BaseAspect::changed,
|
||||
this, &ChatRootView::updateInputTokensCount);
|
||||
|
||||
auto editors = Core::EditorManager::instance();
|
||||
|
||||
connect(editors, &Core::EditorManager::editorCreated, this, &ChatRootView::onEditorCreated);
|
||||
connect(
|
||||
editors,
|
||||
&Core::EditorManager::editorAboutToClose,
|
||||
this,
|
||||
&ChatRootView::onEditorAboutToClose);
|
||||
|
||||
connect(editors, &Core::EditorManager::currentEditorAboutToChange, this, [this]() {
|
||||
if (m_isSyncOpenFiles) {
|
||||
for (auto editor : std::as_const(m_currentEditors)) {
|
||||
onAppendLinkFileFromEditor(editor);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
updateInputTokensCount();
|
||||
}
|
||||
|
||||
ChatModel *ChatRootView::chatModel() const
|
||||
@ -71,14 +106,9 @@ ChatModel *ChatRootView::chatModel() const
|
||||
return m_chatModel;
|
||||
}
|
||||
|
||||
QColor ChatRootView::backgroundColor() const
|
||||
void ChatRootView::sendMessage(const QString &message)
|
||||
{
|
||||
return Utils::creatorColor(Utils::Theme::BackgroundColorNormal);
|
||||
}
|
||||
|
||||
void ChatRootView::sendMessage(const QString &message, bool sharingCurrentFile)
|
||||
{
|
||||
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"),
|
||||
@ -89,12 +119,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();
|
||||
}
|
||||
|
||||
@ -116,49 +146,14 @@ void ChatRootView::clearAttachmentFiles()
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRootView::generateColors()
|
||||
void ChatRootView::clearLinkedFiles()
|
||||
{
|
||||
QColor baseColor = backgroundColor();
|
||||
bool isDarkTheme = baseColor.lightness() < 128;
|
||||
|
||||
if (isDarkTheme) {
|
||||
m_primaryColor = generateColor(baseColor, 0.1, 1.2, 1.4);
|
||||
m_secondaryColor = generateColor(baseColor, -0.1, 1.1, 1.2);
|
||||
m_codeColor = generateColor(baseColor, 0.05, 0.8, 1.1);
|
||||
} else {
|
||||
m_primaryColor = generateColor(baseColor, 0.05, 1.05, 1.1);
|
||||
m_secondaryColor = generateColor(baseColor, -0.05, 1.1, 1.2);
|
||||
m_codeColor = generateColor(baseColor, 0.02, 0.95, 1.05);
|
||||
if (!m_linkedFiles.isEmpty()) {
|
||||
m_linkedFiles.clear();
|
||||
emit linkedFilesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QColor ChatRootView::generateColor(const QColor &baseColor,
|
||||
float hueShift,
|
||||
float saturationMod,
|
||||
float lightnessMod)
|
||||
{
|
||||
float h, s, l, a;
|
||||
baseColor.getHslF(&h, &s, &l, &a);
|
||||
bool isDarkTheme = l < 0.5;
|
||||
|
||||
h = fmod(h + hueShift + 1.0, 1.0);
|
||||
|
||||
s = qBound(0.0f, s * saturationMod, 1.0f);
|
||||
|
||||
if (isDarkTheme) {
|
||||
l = qBound(0.0f, l * lightnessMod, 1.0f);
|
||||
} else {
|
||||
l = qBound(0.0f, l / lightnessMod, 1.0f);
|
||||
}
|
||||
|
||||
h = qBound(0.0f, h, 1.0f);
|
||||
s = qBound(0.0f, s, 1.0f);
|
||||
l = qBound(0.0f, l, 1.0f);
|
||||
a = qBound(0.0f, a, 1.0f);
|
||||
|
||||
return QColor::fromHslF(h, s, l, a);
|
||||
}
|
||||
|
||||
QString ChatRootView::getChatsHistoryDir() const
|
||||
{
|
||||
QString path;
|
||||
@ -185,31 +180,13 @@ QString ChatRootView::currentTemplate() const
|
||||
return settings.caModel();
|
||||
}
|
||||
|
||||
QColor ChatRootView::primaryColor() const
|
||||
{
|
||||
return m_primaryColor;
|
||||
}
|
||||
|
||||
QColor ChatRootView::secondaryColor() const
|
||||
{
|
||||
return m_secondaryColor;
|
||||
}
|
||||
|
||||
QColor ChatRootView::codeColor() const
|
||||
{
|
||||
return m_codeColor;
|
||||
}
|
||||
|
||||
bool ChatRootView::isSharingCurrentFile() const
|
||||
{
|
||||
return Settings::chatAssistantSettings().sharingCurrentFile();
|
||||
}
|
||||
|
||||
void ChatRootView::saveHistory(const QString &filePath)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,8 +196,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()
|
||||
@ -279,17 +257,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()
|
||||
@ -301,7 +312,7 @@ void ChatRootView::autosave()
|
||||
QString filePath = getAutosaveFilePath();
|
||||
if (!filePath.isEmpty()) {
|
||||
ChatSerializer::saveToFile(m_chatModel, filePath);
|
||||
m_recentFilePath = filePath;
|
||||
setRecentFilePath(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -319,6 +330,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"));
|
||||
@ -329,12 +350,184 @@ void ChatRootView::showAttachFilesDialog()
|
||||
}
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
QStringList filePaths = dialog.selectedFiles();
|
||||
if (!filePaths.isEmpty()) {
|
||||
m_attachmentFiles = filePaths;
|
||||
emit attachmentFilesChanged();
|
||||
QStringList newFilePaths = dialog.selectedFiles();
|
||||
if (!newFilePaths.isEmpty()) {
|
||||
bool filesAdded = false;
|
||||
for (const QString &filePath : std::as_const(newFilePaths)) {
|
||||
if (!m_attachmentFiles.contains(filePath)) {
|
||||
m_attachmentFiles.append(filePath);
|
||||
filesAdded = true;
|
||||
}
|
||||
}
|
||||
if (filesAdded) {
|
||||
emit attachmentFilesChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRootView::removeFileFromAttachList(int index)
|
||||
{
|
||||
if (index >= 0 && index < m_attachmentFiles.size()) {
|
||||
m_attachmentFiles.removeAt(index);
|
||||
emit attachmentFilesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRootView::showLinkFilesDialog()
|
||||
{
|
||||
QFileDialog dialog(nullptr, tr("Select Files to Attach"));
|
||||
dialog.setFileMode(QFileDialog::ExistingFiles);
|
||||
|
||||
if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
|
||||
dialog.setDirectory(project->projectDirectory().toString());
|
||||
}
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
QStringList newFilePaths = dialog.selectedFiles();
|
||||
if (!newFilePaths.isEmpty()) {
|
||||
bool filesAdded = false;
|
||||
for (const QString &filePath : std::as_const(newFilePaths)) {
|
||||
if (!m_linkedFiles.contains(filePath)) {
|
||||
m_linkedFiles.append(filePath);
|
||||
filesAdded = true;
|
||||
}
|
||||
}
|
||||
if (filesAdded) {
|
||||
emit linkedFilesChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRootView::removeFileFromLinkList(int index)
|
||||
{
|
||||
if (index >= 0 && index < m_linkedFiles.size()) {
|
||||
m_linkedFiles.removeAt(index);
|
||||
emit linkedFilesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRootView::calculateMessageTokensCount(const QString &message)
|
||||
{
|
||||
m_messageTokensCount = Context::TokenUtils::estimateTokens(message);
|
||||
updateInputTokensCount();
|
||||
}
|
||||
|
||||
void ChatRootView::setIsSyncOpenFiles(bool state)
|
||||
{
|
||||
if (m_isSyncOpenFiles != state) {
|
||||
m_isSyncOpenFiles = state;
|
||||
emit isSyncOpenFilesChanged();
|
||||
}
|
||||
|
||||
if (m_isSyncOpenFiles) {
|
||||
for (auto editor : std::as_const(m_currentEditors)) {
|
||||
onAppendLinkFileFromEditor(editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRootView::openChatHistoryFolder()
|
||||
{
|
||||
QString path;
|
||||
if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
|
||||
Settings::ProjectSettings projectSettings(project);
|
||||
path = projectSettings.chatHistoryPath().toString();
|
||||
} else {
|
||||
path = QString("%1/qodeassist/chat_history").arg(Core::ICore::userResourcePath().toString());
|
||||
}
|
||||
|
||||
QDir dir(path);
|
||||
if (!dir.exists()) {
|
||||
dir.mkpath(".");
|
||||
}
|
||||
|
||||
QUrl url = QUrl::fromLocalFile(dir.absolutePath());
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
|
||||
void ChatRootView::updateInputTokensCount()
|
||||
{
|
||||
int inputTokens = m_messageTokensCount;
|
||||
auto& settings = Settings::chatAssistantSettings();
|
||||
|
||||
if (settings.useSystemPrompt()) {
|
||||
inputTokens += Context::TokenUtils::estimateTokens(settings.systemPrompt());
|
||||
}
|
||||
|
||||
if (!m_attachmentFiles.isEmpty()) {
|
||||
auto attachFiles = Context::ContextManager::instance().getContentFiles(m_attachmentFiles);
|
||||
inputTokens += Context::TokenUtils::estimateFilesTokens(attachFiles);
|
||||
}
|
||||
|
||||
if (!m_linkedFiles.isEmpty()) {
|
||||
auto linkFiles = Context::ContextManager::instance().getContentFiles(m_linkedFiles);
|
||||
inputTokens += Context::TokenUtils::estimateFilesTokens(linkFiles);
|
||||
}
|
||||
|
||||
const auto& history = m_chatModel->getChatHistory();
|
||||
for (const auto& message : history) {
|
||||
inputTokens += Context::TokenUtils::estimateTokens(message.content);
|
||||
inputTokens += 4; // + role
|
||||
}
|
||||
|
||||
m_inputTokensCount = inputTokens;
|
||||
emit inputTokensCountChanged();
|
||||
}
|
||||
|
||||
int ChatRootView::inputTokensCount() const
|
||||
{
|
||||
return m_inputTokensCount;
|
||||
}
|
||||
|
||||
bool ChatRootView::isSyncOpenFiles() const
|
||||
{
|
||||
return m_isSyncOpenFiles;
|
||||
}
|
||||
|
||||
void ChatRootView::onEditorAboutToClose(Core::IEditor *editor)
|
||||
{
|
||||
if (auto document = editor->document(); document && isSyncOpenFiles()) {
|
||||
QString filePath = document->filePath().toString();
|
||||
m_linkedFiles.removeOne(filePath);
|
||||
emit linkedFilesChanged();
|
||||
}
|
||||
|
||||
if (editor) {
|
||||
m_currentEditors.removeOne(editor);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRootView::onAppendLinkFileFromEditor(Core::IEditor *editor)
|
||||
{
|
||||
if (auto document = editor->document(); document && isSyncOpenFiles()) {
|
||||
QString filePath = document->filePath().toString();
|
||||
if (!m_linkedFiles.contains(filePath)) {
|
||||
m_linkedFiles.append(filePath);
|
||||
emit linkedFilesChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRootView::onEditorCreated(Core::IEditor *editor, const Utils::FilePath &filePath)
|
||||
{
|
||||
if (editor && editor->document()) {
|
||||
m_currentEditors.append(editor);
|
||||
}
|
||||
}
|
||||
|
||||
QString ChatRootView::chatFileName() const
|
||||
{
|
||||
return QFileInfo(m_recentFilePath).baseName();
|
||||
}
|
||||
|
||||
void ChatRootView::setRecentFilePath(const QString &filePath)
|
||||
{
|
||||
if (m_recentFilePath != filePath) {
|
||||
m_recentFilePath = filePath;
|
||||
emit chatFileNameChanged();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
|
||||
#include "ChatModel.hpp"
|
||||
#include "ClientInterface.hpp"
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
@ -31,13 +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(QColor backgroundColor READ backgroundColor CONSTANT FINAL)
|
||||
Q_PROPERTY(QColor primaryColor READ primaryColor CONSTANT FINAL)
|
||||
Q_PROPERTY(QColor secondaryColor READ secondaryColor CONSTANT FINAL)
|
||||
Q_PROPERTY(QColor codeColor READ codeColor CONSTANT 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
|
||||
|
||||
@ -47,14 +46,6 @@ public:
|
||||
ChatModel *chatModel() const;
|
||||
QString currentTemplate() const;
|
||||
|
||||
QColor backgroundColor() const;
|
||||
QColor primaryColor() const;
|
||||
QColor secondaryColor() const;
|
||||
|
||||
QColor codeColor() const;
|
||||
|
||||
bool isSharingCurrentFile() const;
|
||||
|
||||
void saveHistory(const QString &filePath);
|
||||
void loadHistory(const QString &filePath);
|
||||
|
||||
@ -64,38 +55,59 @@ 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 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:
|
||||
void generateColors();
|
||||
QColor generateColor(const QColor &baseColor,
|
||||
float hueShift,
|
||||
float saturationMod,
|
||||
float lightnessMod);
|
||||
|
||||
QString getChatsHistoryDir() const;
|
||||
QString getSuggestedFileName() const;
|
||||
|
||||
ChatModel *m_chatModel;
|
||||
ClientInterface *m_clientInterface;
|
||||
QString m_currentTemplate;
|
||||
QColor m_primaryColor;
|
||||
QColor m_secondaryColor;
|
||||
QColor m_codeColor;
|
||||
QString m_recentFilePath;
|
||||
QStringList m_attachmentFiles;
|
||||
QStringList m_linkedFiles;
|
||||
int m_messageTokensCount{0};
|
||||
int m_inputTokensCount{0};
|
||||
bool m_isSyncOpenFiles;
|
||||
QList<Core::IEditor *> m_currentEditors;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
|
||||
@ -82,7 +82,6 @@ QJsonObject ChatSerializer::serializeMessage(const ChatModel::Message &message)
|
||||
QJsonObject messageObj;
|
||||
messageObj["role"] = static_cast<int>(message.role);
|
||||
messageObj["content"] = message.content;
|
||||
messageObj["tokenCount"] = message.tokenCount;
|
||||
messageObj["id"] = message.id;
|
||||
return messageObj;
|
||||
}
|
||||
@ -92,7 +91,6 @@ ChatModel::Message ChatSerializer::deserializeMessage(const QJsonObject &json)
|
||||
ChatModel::Message message;
|
||||
message.role = static_cast<ChatModel::ChatRole>(json["role"].toInt());
|
||||
message.content = json["content"].toString();
|
||||
message.tokenCount = json["tokenCount"].toInt();
|
||||
message.id = json["id"].toString();
|
||||
return message;
|
||||
}
|
||||
@ -107,7 +105,6 @@ QJsonObject ChatSerializer::serializeChat(const ChatModel *model)
|
||||
QJsonObject root;
|
||||
root["version"] = VERSION;
|
||||
root["messages"] = messagesArray;
|
||||
root["totalTokens"] = model->totalTokens();
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ ClientInterface::ClientInterface(ChatModel *chatModel, QObject *parent)
|
||||
ClientInterface::~ClientInterface() = default;
|
||||
|
||||
void ClientInterface::sendMessage(
|
||||
const QString &message, const QList<QString> &attachments, bool includeCurrentFile)
|
||||
const QString &message, const QList<QString> &attachments, const QList<QString> &linkedFiles)
|
||||
{
|
||||
cancelRequest();
|
||||
|
||||
@ -100,11 +100,8 @@ void ClientInterface::sendMessage(
|
||||
if (chatAssistantSettings.useSystemPrompt())
|
||||
systemPrompt = chatAssistantSettings.systemPrompt();
|
||||
|
||||
if (includeCurrentFile) {
|
||||
QString fileContext = getCurrentFileContext();
|
||||
if (!fileContext.isEmpty()) {
|
||||
systemPrompt = systemPrompt.append(fileContext);
|
||||
}
|
||||
if (!linkedFiles.isEmpty()) {
|
||||
systemPrompt = getSystemPromptWithLinkedFiles(systemPrompt, linkedFiles);
|
||||
}
|
||||
|
||||
QJsonObject providerRequest;
|
||||
@ -198,4 +195,21 @@ QString ClientInterface::getCurrentFileContext() const
|
||||
return QString("Current file context:\n%1\nFile content:\n%2").arg(fileInfo, content);
|
||||
}
|
||||
|
||||
QString ClientInterface::getSystemPromptWithLinkedFiles(const QString &basePrompt, const QList<QString> &linkedFiles) const
|
||||
{
|
||||
QString updatedPrompt = basePrompt;
|
||||
|
||||
if (!linkedFiles.isEmpty()) {
|
||||
updatedPrompt += "\n\nLinked files for reference:\n";
|
||||
|
||||
auto contentFiles = Context::ContextManager::instance().getContentFiles(linkedFiles);
|
||||
for (const auto &file : contentFiles) {
|
||||
updatedPrompt += QString("\nFile: %1\nContent:\n%2\n")
|
||||
.arg(file.filename, file.content);
|
||||
}
|
||||
}
|
||||
|
||||
return updatedPrompt;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
|
||||
@ -39,7 +39,7 @@ public:
|
||||
void sendMessage(
|
||||
const QString &message,
|
||||
const QList<QString> &attachments = {},
|
||||
bool includeCurrentFile = false);
|
||||
const QList<QString> &linkedFiles = {});
|
||||
void clearMessages();
|
||||
void cancelRequest();
|
||||
|
||||
@ -50,6 +50,9 @@ signals:
|
||||
private:
|
||||
void handleLLMResponse(const QString &response, const QJsonObject &request, bool isComplete);
|
||||
QString getCurrentFileContext() const;
|
||||
QString getSystemPromptWithLinkedFiles(
|
||||
const QString &basePrompt,
|
||||
const QList<QString> &linkedFiles) const;
|
||||
|
||||
LLMCore::RequestHandler *m_requestHandler;
|
||||
ChatModel *m_chatModel;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<svg width="24" height="48" viewBox="0 0 24 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_37_14)">
|
||||
<path d="M22 10.1053V36.7368C22 41.8547 17.525 46 12 46C6.475 46 2 41.8547 2 36.7368V7.78947C2 4.59368 4.8 2 8.25 2C11.7 2 15.75 4.59368 15.75 7.78947V35.5789C15.75 36.8526 13.375 39.0526 12 39.0526C10.625 39.0526 8.25 36.8526 8.25 35.5789V21.6842V8.94737" stroke="black" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M22 10.1053V36.7368C22 41.8547 17.525 46 12 46C6.475 46 2 41.8547 2 36.7368V7.78947C2 4.59368 4.8 2 8.25 2C11.7 2 15.75 4.59368 15.75 7.78947V35.5789C15.75 36.8526 13.375 39.0526 12 39.0526C10.625 39.0526 8.25 36.8526 8.25 35.5789V21.6842V8.94737" stroke="black" stroke-width="3" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_37_14">
|
||||
|
Before Width: | Height: | Size: 555 B After Width: | Height: | Size: 869 B |
11
ChatView/icons/attach-file-light.svg
Normal file
11
ChatView/icons/attach-file-light.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<svg width="24" height="48" viewBox="0 0 24 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_51_20)">
|
||||
<path d="M22 10.1053V36.7368C22 41.8547 17.525 46 12 46C6.475 46 2 41.8547 2 36.7368V7.78947C2 4.59368 4.8 2 8.25 2C11.7 2 15.75 4.59368 15.75 7.78947V35.5789C15.75 36.8526 13.375 39.0526 12 39.0526C10.625 39.0526 8.25 36.8526 8.25 35.5789V21.6842V8.94737" stroke="white" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M22 10.1053V36.7368C22 41.8547 17.525 46 12 46C6.475 46 2 41.8547 2 36.7368V7.78947C2 4.59368 4.8 2 8.25 2C11.7 2 15.75 4.59368 15.75 7.78947V35.5789C15.75 36.8526 13.375 39.0526 12 39.0526C10.625 39.0526 8.25 36.8526 8.25 35.5789V21.6842V8.94737" stroke="white" stroke-width="3" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_51_20">
|
||||
<rect width="24" height="48" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 869 B |
12
ChatView/icons/link-file-dark.svg
Normal file
12
ChatView/icons/link-file-dark.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<svg width="20" height="44" viewBox="0 0 20 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_49_24)">
|
||||
<path d="M10 12L10 32L10 12Z" fill="black"/>
|
||||
<path d="M10 12L10 32" stroke="black" stroke-width="3"/>
|
||||
<path d="M1.50001 12.484C1.50001 -1.99999 18.5 -1.99999 18.5 12.484M1.5 31.5334C1.50001 46 18.5 46 18.5 31.5334" stroke="black" stroke-width="3" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_49_24">
|
||||
<rect width="20" height="44" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 513 B |
12
ChatView/icons/link-file-light.svg
Normal file
12
ChatView/icons/link-file-light.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<svg width="20" height="44" viewBox="0 0 20 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_51_24)">
|
||||
<path d="M10 12L10 32Z" fill="white"/>
|
||||
<path d="M10 12L10 32" stroke="white" stroke-width="3"/>
|
||||
<path d="M1.50001 12.484C1.50001 -1.99999 18.5 -1.99999 18.5 12.484M1.5 31.5334C1.50001 46 18.5 46 18.5 31.5334" stroke="white" stroke-width="3" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_51_24">
|
||||
<rect width="20" height="44" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 507 B |
@ -27,9 +27,6 @@ Rectangle {
|
||||
|
||||
property alias msgModel: msgCreator.model
|
||||
property alias messageAttachments: attachmentsModel.model
|
||||
property color fontColor
|
||||
property color codeBgColor
|
||||
property color selectionColor
|
||||
|
||||
height: msgColumn.implicitHeight + 10
|
||||
radius: 8
|
||||
@ -49,7 +46,7 @@ Rectangle {
|
||||
// why does `required property MessagePart modelData` not work?
|
||||
required property var modelData
|
||||
|
||||
width: parent.width
|
||||
Layout.preferredWidth: root.width
|
||||
sourceComponent: {
|
||||
// If `required property MessagePart modelData` is used
|
||||
// and conversion to MessagePart fails, you're left
|
||||
@ -100,14 +97,16 @@ Rectangle {
|
||||
height: attachText.implicitHeight + 8
|
||||
width: attachText.implicitWidth + 16
|
||||
radius: 4
|
||||
color: root.codeBgColor
|
||||
color: palette.button
|
||||
border.width: 1
|
||||
border.color: palette.mid
|
||||
|
||||
Text {
|
||||
id: attachText
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: modelData
|
||||
color: root.fontColor
|
||||
color: palette.text
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -120,8 +119,6 @@ Rectangle {
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
leftPadding: 10
|
||||
text: itemData.text
|
||||
color: root.fontColor
|
||||
selectionColor: root.selectionColor
|
||||
}
|
||||
|
||||
|
||||
@ -136,8 +133,5 @@ Rectangle {
|
||||
|
||||
code: itemData.text
|
||||
language: itemData.language
|
||||
color: root.codeBgColor
|
||||
selectionColor: root.selectionColor
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -58,6 +58,7 @@ ChatRootView {
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
TopBar {
|
||||
id: topBar
|
||||
@ -69,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 {
|
||||
@ -91,12 +96,8 @@ ChatRootView {
|
||||
width: ListView.view.width - scroll.width
|
||||
msgModel: root.chatModel.processMessageContent(model.content)
|
||||
messageAttachments: model.attachments
|
||||
color: model.roleType === ChatModel.User ? root.primaryColor : root.secondaryColor
|
||||
fontColor: root.primaryColor.hslLightness > 0.5 ? "black" : "white"
|
||||
codeBgColor: root.codeColor
|
||||
selectionColor: root.primaryColor.hslLightness > 0.5 ? Qt.darker(root.primaryColor, 1.5)
|
||||
: Qt.lighter(root.primaryColor, 1.5)
|
||||
|
||||
color: model.roleType === ChatModel.User ? palette.alternateBase
|
||||
: palette.base
|
||||
}
|
||||
|
||||
header: Item {
|
||||
@ -104,7 +105,7 @@ ChatRootView {
|
||||
height: 30
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
ScrollBar.vertical: QQC.ScrollBar {
|
||||
id: scroll
|
||||
}
|
||||
|
||||
@ -130,15 +131,28 @@ ChatRootView {
|
||||
id: messageInput
|
||||
|
||||
placeholderText: qsTr("Type your message here...")
|
||||
placeholderTextColor: "#888"
|
||||
color: root.primaryColor.hslLightness > 0.5 ? "black" : "white"
|
||||
placeholderTextColor: palette.mid
|
||||
color: palette.text
|
||||
background: Rectangle {
|
||||
radius: 2
|
||||
color: root.primaryColor
|
||||
border.color: root.primaryColor.hslLightness > 0.5 ? Qt.lighter(root.primaryColor, 1.5)
|
||||
: Qt.darker(root.primaryColor, 1.5)
|
||||
color: palette.base
|
||||
border.color: messageInput.activeFocus ? palette.highlight : palette.button
|
||||
border.width: 1
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation { duration: 150 }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: palette.highlight
|
||||
opacity: messageInput.hovered ? 0.1 : 0
|
||||
radius: parent.radius
|
||||
}
|
||||
}
|
||||
|
||||
onTextChanged: root.calculateMessageTokensCount(messageInput.text)
|
||||
|
||||
Keys.onPressed: function(event) {
|
||||
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && !(event.modifiers & Qt.ShiftModifier)) {
|
||||
root.sendChatMessage()
|
||||
@ -153,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 {
|
||||
@ -163,13 +192,19 @@ 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()
|
||||
}
|
||||
}
|
||||
|
||||
function clearChat() {
|
||||
root.chatModel.clear()
|
||||
root.clearAttachmentFiles()
|
||||
root.updateInputTokensCount()
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
@ -177,7 +212,7 @@ ChatRootView {
|
||||
}
|
||||
|
||||
function sendChatMessage() {
|
||||
root.sendMessage(messageInput.text, bottomBar.sharingCurrentFile.checked)
|
||||
root.sendMessage(messageInput.text)
|
||||
messageInput.text = ""
|
||||
scrollToBottom()
|
||||
}
|
||||
|
||||
@ -26,7 +26,6 @@ Rectangle {
|
||||
|
||||
property string code: ""
|
||||
property string language: ""
|
||||
property color selectionColor
|
||||
|
||||
readonly property string monospaceFont: {
|
||||
switch (Qt.platform.os) {
|
||||
@ -41,6 +40,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
color: palette.alternateBase
|
||||
border.color: root.color.hslLightness > 0.5 ? Qt.darker(root.color, 1.3)
|
||||
: Qt.lighter(root.color, 1.3)
|
||||
border.width: 2
|
||||
@ -62,10 +62,10 @@ Rectangle {
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
font.family: root.monospaceFont
|
||||
font.pointSize: 12
|
||||
font.pointSize: Qt.application.font.pointSize
|
||||
color: parent.color.hslLightness > 0.5 ? "black" : "white"
|
||||
wrapMode: Text.WordWrap
|
||||
selectionColor: root.selectionColor
|
||||
selectionColor: palette.highlight
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
@ -80,7 +80,7 @@ Rectangle {
|
||||
font.pointSize: 8
|
||||
}
|
||||
|
||||
Button {
|
||||
QoAButton {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 5
|
||||
|
||||
@ -26,4 +26,6 @@ TextEdit {
|
||||
selectByMouse: true
|
||||
wrapMode: Text.WordWrap
|
||||
textFormat: Text.StyledText
|
||||
selectionColor: palette.highlight
|
||||
color: palette.text
|
||||
}
|
||||
|
||||
@ -23,13 +23,19 @@ 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
|
||||
rightPadding: 5
|
||||
topPadding: attachRepeater.model.length > 0 ? 2 : 0
|
||||
bottomPadding: attachRepeater.model.length > 0 ? 2 : 0
|
||||
|
||||
Repeater {
|
||||
id: attachRepeater
|
||||
@ -39,15 +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: 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
|
||||
@ -65,14 +88,10 @@ Flow {
|
||||
id: closeButton
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: closeIcon.width
|
||||
height: closeButton.width
|
||||
width: closeIcon.width + 5
|
||||
height: closeButton.width + 5
|
||||
|
||||
onClicked: {
|
||||
const newList = [...root.attachmentFiles];
|
||||
newList.splice(index, 1);
|
||||
root.attachmentFiles = newList;
|
||||
}
|
||||
onClicked: root.removeFileFromListByIndex(index)
|
||||
|
||||
Image {
|
||||
id: closeIcon
|
||||
|
||||
@ -27,8 +27,9 @@ 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
|
||||
|
||||
color: palette.window.hslLightness > 0.5 ?
|
||||
Qt.darker(palette.window, 1.1) :
|
||||
@ -59,23 +60,37 @@ 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")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
@ -28,6 +28,8 @@ Rectangle {
|
||||
property alias loadButton: loadButtonId
|
||||
property alias clearButton: clearButtonId
|
||||
property alias tokensBadge: tokensBadgeId
|
||||
property alias recentPath: recentPathId
|
||||
property alias openChatHistory: openChatHistoryId
|
||||
|
||||
color: palette.window.hslLightness > 0.5 ?
|
||||
Qt.darker(palette.window, 1.1) :
|
||||
@ -62,6 +64,19 @@ Rectangle {
|
||||
text: qsTr("Clear")
|
||||
}
|
||||
|
||||
Text {
|
||||
id: recentPathId
|
||||
|
||||
elide: Text.ElideMiddle
|
||||
color: palette.text
|
||||
}
|
||||
|
||||
QoAButton {
|
||||
id: openChatHistoryId
|
||||
|
||||
text: qsTr("Show in system")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"Id" : "qodeassist",
|
||||
"Name" : "QodeAssist",
|
||||
"Version" : "0.4.4",
|
||||
"Version" : "0.4.12",
|
||||
"Vendor" : "Petr Mironychev",
|
||||
"VendorId" : "petrmironychev",
|
||||
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",
|
||||
|
||||
204
README.md
204
README.md
@ -1,9 +1,8 @@
|
||||
# QodeAssist - AI-powered coding assistant plugin for Qt Creator
|
||||
[](https://discord.gg/DGgMtTteAD)
|
||||
[](https://github.com/Palm1r/QodeAssist/actions/workflows/build_cmake.yml)
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
 QodeAssist is an AI-powered coding assistant plugin for Qt Creator. It provides intelligent code completion and suggestions for C++ and QML, leveraging large language models through local providers like Ollama. Enhance your coding productivity with context-aware AI assistance directly in your Qt development environment.
|
||||
@ -14,29 +13,45 @@
|
||||
> - The QodeAssist developer bears no responsibility for any charges incurred
|
||||
> - 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. [Installation for using Ollama](#installation-for-using-Ollama)
|
||||
3. [Installation for using Claude](#installation-for-using-Claude)
|
||||
3. [Configure Plugin](#configure-plugin)
|
||||
4. [Supported LLM Providers](#supported-llm-providers)
|
||||
5. [Recommended Models](#recommended-models)
|
||||
- [Ollama](#ollama)
|
||||
6. [QtCreator Version Compatibility](#qtcreator-version-compatibility)
|
||||
7. [Development Progress](#development-progress)
|
||||
8. [Hotkeys](#hotkeys)
|
||||
9. [Troubleshooting](#troubleshooting)
|
||||
10. [Support the Development](#support-the-development-of-qodeassist)
|
||||
11. [How to Build](#how-to-build)
|
||||
2. [Install plugin to QtCreator](#install-plugin-to-qtcreator)
|
||||
3. [Configure for Anthropic Claude](#configure-for-anthropic-claude)
|
||||
4. [Configure for OpenAI](#configure-for-openai)
|
||||
5. [Configure for using Ollama](#configure-for-using-ollama)
|
||||
6. [System Prompt Configuration](#system-prompt-configuration)
|
||||
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
|
||||
- Claude
|
||||
- OpenAI
|
||||
- Anthropic Claude
|
||||
- LM Studio
|
||||
- OpenAI-compatible providers(eg. https://openrouter.ai)
|
||||
- Extensive library of model-specific templates
|
||||
@ -63,11 +78,52 @@
|
||||
<img width="326" alt="QodeAssistBottomPanel" src="https://github.com/user-attachments/assets/4cc64c23-a294-4df8-9153-39ad6fdab34b">
|
||||
</details>
|
||||
|
||||
## Installation for using Ollama
|
||||
<details>
|
||||
<summary>Automatic syncing with open editor files: (click to expand)</summary>
|
||||
<img width="600" alt="OpenedDocumentsSync" src="https://github.com/user-attachments/assets/08efda2f-dc4d-44c3-927c-e6a975090d2f">
|
||||
</details>
|
||||
|
||||
1. Install Latest QtCreator
|
||||
2. Install [Ollama](https://ollama.com). Make sure to review the system requirements before installation.
|
||||
3. Install a language models in Ollama via terminal. For example, you can run:
|
||||
## 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...
|
||||
- Windows\Linux: Help -> About Plugins...
|
||||
- Click on "Install Plugin..."
|
||||
- Select the downloaded QodeAssist plugin archive file
|
||||
|
||||
## Configure for Anthropic Claude
|
||||
1. Open Qt Creator settings and navigate to the QodeAssist section
|
||||
2. Go to Provider Settings tab and configure Claude api key
|
||||
3. Return to General tab and configure:
|
||||
- Set "Claude" as the provider for code completion or/and chat assistant
|
||||
- Set the Claude URL (https://api.anthropic.com)
|
||||
- Select your preferred model (e.g., claude-3-5-sonnet-20241022)
|
||||
- Choose the Claude template for code completion or/and chat
|
||||
<details>
|
||||
<summary>Example of Claude settings: (click to expand)</summary>
|
||||
<img width="823" alt="Claude Settings" src="https://github.com/user-attachments/assets/828e09ea-e271-4a7a-8271-d3d5dd5c13fd" />
|
||||
</details>
|
||||
|
||||
## Configure for OpenAI
|
||||
1. Open Qt Creator settings and navigate to the QodeAssist section
|
||||
2. Go to Provider Settings tab and configure OpenAI api key
|
||||
3. Return to General tab and configure:
|
||||
- Set "OpenAI" as the provider for code completion or/and chat assistant
|
||||
- Set the OpenAI URL (https://api.openai.com)
|
||||
- Select your preferred model (e.g., gpt-4o)
|
||||
- Choose the OpenAI template for code completion or/and chat
|
||||
<details>
|
||||
<summary>Example of OpenAI settings: (click to expand)</summary>
|
||||
<img width="829" alt="OpenAI Settings" src="https://github.com/user-attachments/assets/4716f790-6159-44d0-a8f4-565ccb6eb713" />
|
||||
</details>
|
||||
|
||||
## Configure for using Ollama
|
||||
|
||||
1. Install [Ollama](https://ollama.com). Make sure to review the system requirements before installation.
|
||||
2. Install a language models in Ollama via terminal. For example, you can run:
|
||||
|
||||
For standard computers (minimum 8GB RAM):
|
||||
```
|
||||
@ -81,31 +137,6 @@ For high-end systems (32GB+ RAM):
|
||||
```
|
||||
ollama run qwen2.5-coder:32b
|
||||
```
|
||||
4. Download the QodeAssist plugin for your QtCreator.
|
||||
5. Launch Qt Creator and install the plugin:
|
||||
- Go to MacOS: Qt Creator -> About Plugins...
|
||||
Windows\Linux: Help -> About Plugins...
|
||||
- Click on "Install Plugin..."
|
||||
- Select the downloaded QodeAssist plugin archive file
|
||||
|
||||
## Installation for using Claude
|
||||
|
||||
1. Install Latest QtCreator
|
||||
2. Download the QodeAssist plugin for your QtCreator.
|
||||
3. Launch Qt Creator and install the plugin:
|
||||
- Go to MacOS: Qt Creator -> About Plugins...
|
||||
Windows\Linux: Help -> About Plugins...
|
||||
- Click on "Install Plugin..."
|
||||
- Select the downloaded QodeAssist plugin archive file
|
||||
4. Select Claude provider
|
||||
5. Select Claude api
|
||||
6. Fill in api key for Claude
|
||||
5. Select Claude templates for code completion and chat
|
||||
6. Enjoy!
|
||||
|
||||
## Configure Plugin
|
||||
|
||||
QodeAssist comes with default settings that should work immediately after installing a language model. The plugin is pre-configured to use Ollama with standard templates, so you may only need to verify the settings.
|
||||
|
||||
1. Open Qt Creator settings (Edit > Preferences on Linux/Windows, Qt Creator > Preferences on macOS)
|
||||
2. Navigate to the "Qode Assist" tab
|
||||
@ -113,75 +144,72 @@ QodeAssist comes with default settings that should work immediately after instal
|
||||
- Ollama is selected as your LLM provider
|
||||
- The URL is set to http://localhost:11434
|
||||
- Your installed model appears in the model selection
|
||||
- The prompt template is Ollama Auto FIM
|
||||
- The prompt template is Ollama Auto FIM or Ollama Auto Chat for chat assistance. You can specify template if it is not work correct
|
||||
4. Click Apply if you made any changes
|
||||
|
||||
You're all set! QodeAssist is now ready to use in Qt Creator.
|
||||
<details>
|
||||
<summary>Example of Ollama settings: (click to expand)</summary>
|
||||
<img width="824" alt="Ollama Settings" src="https://github.com/user-attachments/assets/ed64e03a-a923-467a-aa44-4f790e315b53" />
|
||||
</details>
|
||||
|
||||
## Supported LLM Providers
|
||||
QodeAssist currently supports the following LLM (Large Language Model) providers:
|
||||
- [Ollama](https://ollama.com)
|
||||
- [LM Studio](https://lmstudio.ai)
|
||||
- [OpenRouter](https://openrouter.ai)
|
||||
- OpenAI compatible providers
|
||||
## System Prompt Configuration
|
||||
|
||||
## Recommended Models:
|
||||
QodeAssist has been thoroughly tested and optimized for use with the following language models:
|
||||
The plugin comes with default system prompts optimized for chat and instruct models, as these currently provide better results for code assistance. If you prefer using FIM (Fill-in-Middle) models, you can easily customize the system prompt in the settings.
|
||||
|
||||
- Qwen2.5-coder
|
||||
- CodeLlama
|
||||
- StarCoder2
|
||||
- DeepSeek-Coder-V2
|
||||
## File Context Features
|
||||
|
||||
### Model Types
|
||||
QodeAssist provides two powerful ways to include source code files in your chat conversations: Attachments and Linked Files. Each serves a distinct purpose and helps provide better context for the AI assistant.
|
||||
|
||||
FIM models (codellama:7b-code, starcoder2:7b, etc.) - Optimized for code completion and suggestions
|
||||
### Attached Files
|
||||
|
||||
Instruct models (codellama:7b-instruct, starcoder2:instruct, etc.) - Better for chat assistance, explanations, and code review
|
||||
Attachments are designed for one-time code analysis and specific queries:
|
||||
- Files are included only in the current message
|
||||
- Content is discarded after the message is processed
|
||||
- Ideal for:
|
||||
- Getting specific feedback on code changes
|
||||
- Code review requests
|
||||
- Analyzing isolated code segments
|
||||
- Quick implementation questions
|
||||
- Files can be attached using the paperclip icon in the chat interface
|
||||
- Multiple files can be attached to a single message
|
||||
|
||||
For best results, use FIM models with code completion and Instruct models with chat features.
|
||||
### Linked Files
|
||||
|
||||
### Ollama:
|
||||
### For autocomplete(FIM)
|
||||
```
|
||||
ollama run codellama:7b-code
|
||||
ollama run starcoder2:7b
|
||||
ollama run qwen2.5-coder:7b-base
|
||||
ollama run deepseek-coder-v2:16b-lite-base-q3_K_M
|
||||
```
|
||||
### For chat and instruct
|
||||
```
|
||||
ollama run codellama:7b-instruct
|
||||
ollama run starcoder2:instruct
|
||||
ollama run qwen2.5-coder:7b-instruct
|
||||
ollama run deepseek-coder-v2
|
||||
```
|
||||
Linked files provide persistent context throughout the conversation:
|
||||
|
||||
### Template-Model Compatibility
|
||||
- Files remain accessible for the entire chat session
|
||||
- Content is included in every message exchange
|
||||
- Files are automatically refreshed - always using latest content from disk
|
||||
- Perfect for:
|
||||
- Long-term refactoring discussions
|
||||
- Complex architectural changes
|
||||
- Multi-file implementations
|
||||
- Maintaining context across related questions
|
||||
- Can be managed using the link icon in the chat interface
|
||||
- Supports automatic syncing with open editor files (can be enabled in settings)
|
||||
- Files can be added/removed at any time during the conversation
|
||||
|
||||
## Template-Model Compatibility
|
||||
|
||||
| Template | Compatible Models | Purpose |
|
||||
|----------|------------------|----------|
|
||||
| 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 |
|
||||
|
||||
> Note:
|
||||
> - FIM (Fill-in-Middle) templates are optimized for code completion
|
||||
> - Chat templates are designed for interactive dialogue
|
||||
> - The Ollama Auto templates automatically adapt to most Ollama models
|
||||
> - Custom Template allows you to define your own prompt format
|
||||
| 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
|
||||
|
||||
|
||||
72
UpdateStatusWidget.cpp
Normal file
72
UpdateStatusWidget.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "UpdateStatusWidget.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
UpdateStatusWidget::UpdateStatusWidget(QWidget *parent)
|
||||
: QFrame(parent)
|
||||
{
|
||||
setFrameStyle(QFrame::NoFrame);
|
||||
|
||||
auto layout = new QHBoxLayout(this);
|
||||
layout->setContentsMargins(4, 0, 4, 0);
|
||||
layout->setSpacing(4);
|
||||
|
||||
m_actionButton = new QToolButton(this);
|
||||
m_actionButton->setToolButtonStyle(Qt::ToolButtonIconOnly);
|
||||
|
||||
m_versionLabel = new QLabel(this);
|
||||
m_versionLabel->setVisible(false);
|
||||
|
||||
m_updateButton = new QPushButton(tr("Update"), this);
|
||||
m_updateButton->setVisible(false);
|
||||
m_updateButton->setStyleSheet("QPushButton { padding: 2px 8px; }");
|
||||
|
||||
layout->addWidget(m_actionButton);
|
||||
layout->addWidget(m_versionLabel);
|
||||
layout->addWidget(m_updateButton);
|
||||
}
|
||||
|
||||
void UpdateStatusWidget::setDefaultAction(QAction *action)
|
||||
{
|
||||
m_actionButton->setDefaultAction(action);
|
||||
}
|
||||
|
||||
void UpdateStatusWidget::showUpdateAvailable(const QString &version)
|
||||
{
|
||||
m_versionLabel->setText(tr("new version: v%1").arg(version));
|
||||
m_versionLabel->setVisible(true);
|
||||
m_updateButton->setVisible(true);
|
||||
m_updateButton->setToolTip(tr("Update QodeAssist to version %1").arg(version));
|
||||
}
|
||||
|
||||
void UpdateStatusWidget::hideUpdateInfo()
|
||||
{
|
||||
m_versionLabel->setVisible(false);
|
||||
m_updateButton->setVisible(false);
|
||||
}
|
||||
|
||||
QPushButton *UpdateStatusWidget::updateButton() const
|
||||
{
|
||||
return m_updateButton;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
47
UpdateStatusWidget.hpp
Normal file
47
UpdateStatusWidget.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QFrame>
|
||||
#include <QLabel>
|
||||
#include <QLayout>
|
||||
#include <QPushButton>
|
||||
#include <QToolButton>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
class UpdateStatusWidget : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit UpdateStatusWidget(QWidget *parent = nullptr);
|
||||
|
||||
void setDefaultAction(QAction *action);
|
||||
void showUpdateAvailable(const QString &version);
|
||||
void hideUpdateInfo();
|
||||
|
||||
QPushButton *updateButton() const;
|
||||
|
||||
private:
|
||||
QToolButton *m_actionButton;
|
||||
QLabel *m_versionLabel;
|
||||
QPushButton *m_updateButton;
|
||||
};
|
||||
} // namespace QodeAssist
|
||||
@ -3,6 +3,7 @@ add_library(Context STATIC
|
||||
ChangesManager.h ChangesManager.cpp
|
||||
ContextManager.hpp ContextManager.cpp
|
||||
ContentFile.hpp
|
||||
TokenUtils.hpp TokenUtils.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(Context
|
||||
|
||||
54
context/TokenUtils.cpp
Normal file
54
context/TokenUtils.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "TokenUtils.hpp"
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
int TokenUtils::estimateTokens(const QString& text)
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: need to improve
|
||||
return text.length() / 4;
|
||||
}
|
||||
|
||||
int TokenUtils::estimateFileTokens(const Context::ContentFile& file)
|
||||
{
|
||||
int total = 0;
|
||||
|
||||
total += estimateTokens(file.filename);
|
||||
total += estimateTokens(file.content);
|
||||
total += 5;
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
int TokenUtils::estimateFilesTokens(const QList<Context::ContentFile>& files)
|
||||
{
|
||||
int total = 0;
|
||||
for (const auto& file : files) {
|
||||
total += estimateFileTokens(file);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
}
|
||||
36
context/TokenUtils.hpp
Normal file
36
context/TokenUtils.hpp
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include "ContentFile.hpp"
|
||||
#include <QList>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
class TokenUtils
|
||||
{
|
||||
public:
|
||||
static int estimateTokens(const QString& text);
|
||||
static int estimateFileTokens(const Context::ContentFile& file);
|
||||
static int estimateFilesTokens(const QList<Context::ContentFile>& files);
|
||||
};
|
||||
|
||||
}
|
||||
@ -108,15 +108,22 @@ bool LMStudioProvider::handleResponse(QNetworkReply *reply, QString &accumulated
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArrayList chunks = data.split('\n');
|
||||
for (const QByteArray &chunk : chunks) {
|
||||
if (chunk.trimmed().isEmpty() || chunk == "data: [DONE]") {
|
||||
bool isDone = false;
|
||||
QByteArrayList lines = data.split('\n');
|
||||
|
||||
for (const QByteArray &line : lines) {
|
||||
if (line.trimmed().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QByteArray jsonData = chunk;
|
||||
if (chunk.startsWith("data: ")) {
|
||||
jsonData = chunk.mid(6);
|
||||
if (line == "data: [DONE]") {
|
||||
isDone = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
QByteArray jsonData = line;
|
||||
if (line.startsWith("data: ")) {
|
||||
jsonData = line.mid(6);
|
||||
}
|
||||
|
||||
QJsonParseError error;
|
||||
@ -128,15 +135,21 @@ bool LMStudioProvider::handleResponse(QNetworkReply *reply, QString &accumulated
|
||||
|
||||
auto message = LLMCore::OpenAIMessage::fromJson(doc.object());
|
||||
if (message.hasError()) {
|
||||
LOG_MESSAGE("Error in LMStudioProvider response: " + message.error);
|
||||
LOG_MESSAGE("Error in OpenAI response: " + message.error);
|
||||
continue;
|
||||
}
|
||||
|
||||
accumulatedResponse += message.getContent();
|
||||
return message.isDone();
|
||||
QString content = message.getContent();
|
||||
if (!content.isEmpty()) {
|
||||
accumulatedResponse += content;
|
||||
}
|
||||
|
||||
if (message.isDone()) {
|
||||
isDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return isDone;
|
||||
}
|
||||
|
||||
QList<QString> LMStudioProvider::getInstalledModels(const QString &url)
|
||||
|
||||
@ -95,18 +95,36 @@ bool OllamaProvider::handleResponse(QNetworkReply *reply, QString &accumulatedRe
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString endpoint = reply->url().path();
|
||||
auto messageType = endpoint == completionEndpoint() ? LLMCore::OllamaMessage::Type::Generate
|
||||
: LLMCore::OllamaMessage::Type::Chat;
|
||||
QByteArrayList lines = data.split('\n');
|
||||
bool isDone = false;
|
||||
|
||||
auto message = LLMCore::OllamaMessage::fromJson(data, messageType);
|
||||
if (message.hasError()) {
|
||||
LOG_MESSAGE("Error in Ollama response: " + message.error);
|
||||
return false;
|
||||
for (const QByteArray &line : lines) {
|
||||
if (line.trimmed().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QString endpoint = reply->url().path();
|
||||
auto messageType = endpoint == completionEndpoint()
|
||||
? LLMCore::OllamaMessage::Type::Generate
|
||||
: LLMCore::OllamaMessage::Type::Chat;
|
||||
|
||||
auto message = LLMCore::OllamaMessage::fromJson(line, messageType);
|
||||
if (message.hasError()) {
|
||||
LOG_MESSAGE("Error in Ollama response: " + message.error);
|
||||
continue;
|
||||
}
|
||||
|
||||
QString content = message.getContent();
|
||||
if (!content.isEmpty()) {
|
||||
accumulatedResponse += content;
|
||||
}
|
||||
|
||||
if (message.done) {
|
||||
isDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
accumulatedResponse += message.getContent();
|
||||
return message.done;
|
||||
return isDone;
|
||||
}
|
||||
|
||||
QList<QString> OllamaProvider::getInstalledModels(const QString &url)
|
||||
|
||||
@ -109,15 +109,22 @@ bool OpenAICompatProvider::handleResponse(QNetworkReply *reply, QString &accumul
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArrayList chunks = data.split('\n');
|
||||
for (const QByteArray &chunk : chunks) {
|
||||
if (chunk.trimmed().isEmpty() || chunk == "data: [DONE]") {
|
||||
bool isDone = false;
|
||||
QByteArrayList lines = data.split('\n');
|
||||
|
||||
for (const QByteArray &line : lines) {
|
||||
if (line.trimmed().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QByteArray jsonData = chunk;
|
||||
if (chunk.startsWith("data: ")) {
|
||||
jsonData = chunk.mid(6);
|
||||
if (line == "data: [DONE]") {
|
||||
isDone = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
QByteArray jsonData = line;
|
||||
if (line.startsWith("data: ")) {
|
||||
jsonData = line.mid(6);
|
||||
}
|
||||
|
||||
QJsonParseError error;
|
||||
@ -133,11 +140,17 @@ bool OpenAICompatProvider::handleResponse(QNetworkReply *reply, QString &accumul
|
||||
continue;
|
||||
}
|
||||
|
||||
accumulatedResponse += message.getContent();
|
||||
return message.isDone();
|
||||
QString content = message.getContent();
|
||||
if (!content.isEmpty()) {
|
||||
accumulatedResponse += content;
|
||||
}
|
||||
|
||||
if (message.isDone()) {
|
||||
isDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return isDone;
|
||||
}
|
||||
|
||||
QList<QString> OpenAICompatProvider::getInstalledModels(const QString &url)
|
||||
|
||||
229
providers/OpenAIProvider.cpp
Normal file
229
providers/OpenAIProvider.cpp
Normal file
@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "OpenAIProvider.hpp"
|
||||
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "llmcore/OpenAIMessage.hpp"
|
||||
#include "llmcore/ValidationUtils.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
OpenAIProvider::OpenAIProvider() {}
|
||||
|
||||
QString OpenAIProvider::name() const
|
||||
{
|
||||
return "OpenAI";
|
||||
}
|
||||
|
||||
QString OpenAIProvider::url() const
|
||||
{
|
||||
return "https://api.openai.com";
|
||||
}
|
||||
|
||||
QString OpenAIProvider::completionEndpoint() const
|
||||
{
|
||||
return "/v1/chat/completions";
|
||||
}
|
||||
|
||||
QString OpenAIProvider::chatEndpoint() const
|
||||
{
|
||||
return "/v1/chat/completions";
|
||||
}
|
||||
|
||||
bool OpenAIProvider::supportsModelListing() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenAIProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||
{
|
||||
auto prepareMessages = [](QJsonObject &req) -> QJsonArray {
|
||||
QJsonArray messages;
|
||||
if (req.contains("system")) {
|
||||
messages.append(
|
||||
QJsonObject{{"role", "system"}, {"content", req.take("system").toString()}});
|
||||
}
|
||||
if (req.contains("prompt")) {
|
||||
messages.append(
|
||||
QJsonObject{{"role", "user"}, {"content", req.take("prompt").toString()}});
|
||||
}
|
||||
return messages;
|
||||
};
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
request["temperature"] = settings.temperature();
|
||||
|
||||
if (settings.useTopP())
|
||||
request["top_p"] = settings.topP();
|
||||
if (settings.useTopK())
|
||||
request["top_k"] = settings.topK();
|
||||
if (settings.useFrequencyPenalty())
|
||||
request["frequency_penalty"] = settings.frequencyPenalty();
|
||||
if (settings.usePresencePenalty())
|
||||
request["presence_penalty"] = settings.presencePenalty();
|
||||
};
|
||||
|
||||
QJsonArray messages = prepareMessages(request);
|
||||
if (!messages.isEmpty()) {
|
||||
request["messages"] = std::move(messages);
|
||||
}
|
||||
|
||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||
applyModelParams(Settings::codeCompletionSettings());
|
||||
} else {
|
||||
applyModelParams(Settings::chatAssistantSettings());
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenAIProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
|
||||
{
|
||||
QByteArray data = reply->readAll();
|
||||
if (data.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isDone = false;
|
||||
QByteArrayList lines = data.split('\n');
|
||||
|
||||
for (const QByteArray &line : lines) {
|
||||
if (line.trimmed().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line == "data: [DONE]") {
|
||||
isDone = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
QByteArray jsonData = line;
|
||||
if (line.startsWith("data: ")) {
|
||||
jsonData = line.mid(6);
|
||||
}
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error);
|
||||
|
||||
if (doc.isNull()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto message = LLMCore::OpenAIMessage::fromJson(doc.object());
|
||||
if (message.hasError()) {
|
||||
LOG_MESSAGE("Error in OpenAI response: " + message.error);
|
||||
continue;
|
||||
}
|
||||
|
||||
QString content = message.getContent();
|
||||
if (!content.isEmpty()) {
|
||||
accumulatedResponse += content;
|
||||
}
|
||||
|
||||
if (message.isDone()) {
|
||||
isDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
return isDone;
|
||||
}
|
||||
|
||||
QList<QString> OpenAIProvider::getInstalledModels(const QString &url)
|
||||
{
|
||||
QList<QString> models;
|
||||
QNetworkAccessManager manager;
|
||||
QNetworkRequest request(QString("%1/v1/models").arg(url));
|
||||
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
if (!apiKey().isEmpty()) {
|
||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey()).toUtf8());
|
||||
}
|
||||
|
||||
QNetworkReply *reply = manager.get(request);
|
||||
QEventLoop loop;
|
||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
loop.exec();
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QByteArray responseData = reply->readAll();
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
|
||||
QJsonObject jsonObject = jsonResponse.object();
|
||||
|
||||
if (jsonObject.contains("data")) {
|
||||
QJsonArray modelArray = jsonObject["data"].toArray();
|
||||
for (const QJsonValue &value : modelArray) {
|
||||
QJsonObject modelObject = value.toObject();
|
||||
if (modelObject.contains("id")) {
|
||||
QString modelId = modelObject["id"].toString();
|
||||
if (modelId.startsWith("gpt")) {
|
||||
models.append(modelId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG_MESSAGE(QString("Error fetching ChatGPT models: %1").arg(reply->errorString()));
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
return models;
|
||||
}
|
||||
|
||||
QList<QString> OpenAIProvider::validateRequest(const QJsonObject &request, LLMCore::TemplateType type)
|
||||
{
|
||||
const auto templateReq = QJsonObject{
|
||||
{"model", {}},
|
||||
{"messages", QJsonArray{{QJsonObject{{"role", {}}, {"content", {}}}}}},
|
||||
{"temperature", {}},
|
||||
{"max_tokens", {}},
|
||||
{"top_p", {}},
|
||||
{"top_k", {}},
|
||||
{"frequency_penalty", {}},
|
||||
{"presence_penalty", {}},
|
||||
{"stop", QJsonArray{}},
|
||||
{"stream", {}}};
|
||||
|
||||
return LLMCore::ValidationUtils::validateRequestFields(request, templateReq);
|
||||
}
|
||||
|
||||
QString OpenAIProvider::apiKey() const
|
||||
{
|
||||
return Settings::providerSettings().openAiApiKey();
|
||||
}
|
||||
|
||||
void OpenAIProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) const
|
||||
{
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
if (!apiKey().isEmpty()) {
|
||||
networkRequest.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey()).toUtf8());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
44
providers/OpenAIProvider.hpp
Normal file
44
providers/OpenAIProvider.hpp
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "llmcore/Provider.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class OpenAIProvider : public LLMCore::Provider
|
||||
{
|
||||
public:
|
||||
OpenAIProvider();
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
QString completionEndpoint() const override;
|
||||
QString chatEndpoint() const override;
|
||||
bool supportsModelListing() const override;
|
||||
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
|
||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||
QList<QString> getInstalledModels(const QString &url) override;
|
||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||
QString apiKey() const override;
|
||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@ -93,16 +93,22 @@ bool OpenRouterProvider::handleResponse(QNetworkReply *reply, QString &accumulat
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArrayList chunks = data.split('\n');
|
||||
for (const QByteArray &chunk : chunks) {
|
||||
if (chunk.trimmed().isEmpty() || chunk.contains("OPENROUTER PROCESSING")
|
||||
|| chunk == "data: [DONE]") {
|
||||
bool isDone = false;
|
||||
QByteArrayList lines = data.split('\n');
|
||||
|
||||
for (const QByteArray &line : lines) {
|
||||
if (line.trimmed().isEmpty() || line.contains("OPENROUTER PROCESSING")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QByteArray jsonData = chunk;
|
||||
if (chunk.startsWith("data: ")) {
|
||||
jsonData = chunk.mid(6);
|
||||
if (line == "data: [DONE]") {
|
||||
isDone = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
QByteArray jsonData = line;
|
||||
if (line.startsWith("data: ")) {
|
||||
jsonData = line.mid(6);
|
||||
}
|
||||
|
||||
QJsonParseError error;
|
||||
@ -114,15 +120,21 @@ bool OpenRouterProvider::handleResponse(QNetworkReply *reply, QString &accumulat
|
||||
|
||||
auto message = LLMCore::OpenAIMessage::fromJson(doc.object());
|
||||
if (message.hasError()) {
|
||||
LOG_MESSAGE("Error in OpenRouter response: " + message.error);
|
||||
LOG_MESSAGE("Error in OpenAI response: " + message.error);
|
||||
continue;
|
||||
}
|
||||
|
||||
accumulatedResponse += message.getContent();
|
||||
return message.isDone();
|
||||
QString content = message.getContent();
|
||||
if (!content.isEmpty()) {
|
||||
accumulatedResponse += content;
|
||||
}
|
||||
|
||||
if (message.isDone()) {
|
||||
isDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return isDone;
|
||||
}
|
||||
|
||||
QString OpenRouterProvider::apiKey() const
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
#include "providers/LMStudioProvider.hpp"
|
||||
#include "providers/OllamaProvider.hpp"
|
||||
#include "providers/OpenAICompatProvider.hpp"
|
||||
#include "providers/OpenAIProvider.hpp"
|
||||
#include "providers/OpenRouterAIProvider.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
@ -36,6 +37,7 @@ inline void registerProviders()
|
||||
providerManager.registerProvider<OpenAICompatProvider>();
|
||||
providerManager.registerProvider<OpenRouterProvider>();
|
||||
providerManager.registerProvider<ClaudeProvider>();
|
||||
providerManager.registerProvider<OpenAIProvider>();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@ -19,6 +19,8 @@
|
||||
|
||||
#include "QodeAssistConstants.hpp"
|
||||
#include "QodeAssisttr.h"
|
||||
#include "settings/PluginUpdater.hpp"
|
||||
#include "settings/UpdateDialog.hpp"
|
||||
|
||||
#include <coreplugin/actionmanager/actioncontainer.h>
|
||||
#include <coreplugin/actionmanager/actionmanager.h>
|
||||
@ -32,19 +34,21 @@
|
||||
#include <extensionsystem/iplugin.h>
|
||||
#include <languageclient/languageclientmanager.h>
|
||||
|
||||
#include <texteditor/texteditor.h>
|
||||
#include <utils/icon.h>
|
||||
#include <QAction>
|
||||
#include <QMainWindow>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <texteditor/texteditor.h>
|
||||
#include <utils/icon.h>
|
||||
|
||||
#include "ConfigurationManager.hpp"
|
||||
#include "QodeAssistClient.hpp"
|
||||
#include "chat/ChatOutputPane.h"
|
||||
#include "chat/NavigationPanel.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProjectSettingsPanel.hpp"
|
||||
|
||||
#include "UpdateStatusWidget.hpp"
|
||||
#include "providers/Providers.hpp"
|
||||
#include "templates/Templates.hpp"
|
||||
|
||||
@ -61,8 +65,8 @@ class QodeAssistPlugin final : public ExtensionSystem::IPlugin
|
||||
|
||||
public:
|
||||
QodeAssistPlugin()
|
||||
{
|
||||
}
|
||||
: m_updater(new PluginUpdater(this))
|
||||
{}
|
||||
|
||||
~QodeAssistPlugin() final
|
||||
{
|
||||
@ -96,33 +100,36 @@ public:
|
||||
}
|
||||
});
|
||||
|
||||
auto toggleButton = new QToolButton;
|
||||
toggleButton->setDefaultAction(requestAction.contextAction());
|
||||
StatusBarManager::addStatusBarWidget(toggleButton, StatusBarManager::RightCorner);
|
||||
m_statusWidget = new UpdateStatusWidget;
|
||||
m_statusWidget->setDefaultAction(requestAction.contextAction());
|
||||
StatusBarManager::addStatusBarWidget(m_statusWidget, StatusBarManager::RightCorner);
|
||||
|
||||
connect(m_statusWidget->updateButton(), &QPushButton::clicked, this, [this]() {
|
||||
UpdateDialog::checkForUpdatesAndShow(Core::ICore::mainWindow());
|
||||
});
|
||||
|
||||
m_chatOutputPane = new Chat::ChatOutputPane(this);
|
||||
m_navigationPanel = new Chat::NavigationPanel();
|
||||
|
||||
Settings::setupProjectPanel();
|
||||
|
||||
ConfigurationManager::instance().init();
|
||||
|
||||
if (Settings::generalSettings().enableCheckUpdate()) {
|
||||
QTimer::singleShot(3000, this, &QodeAssistPlugin::checkForUpdates);
|
||||
}
|
||||
}
|
||||
|
||||
void extensionsInitialized() final
|
||||
{
|
||||
}
|
||||
void extensionsInitialized() final {}
|
||||
|
||||
void restartClient()
|
||||
{
|
||||
LanguageClient::LanguageClientManager::shutdownClient(m_qodeAssistClient);
|
||||
|
||||
m_qodeAssistClient = new QodeAssistClient();
|
||||
}
|
||||
|
||||
bool delayedInitialize() final
|
||||
{
|
||||
restartClient();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -130,17 +137,38 @@ public:
|
||||
{
|
||||
if (!m_qodeAssistClient)
|
||||
return SynchronousShutdown;
|
||||
connect(m_qodeAssistClient,
|
||||
&QObject::destroyed,
|
||||
this,
|
||||
&IPlugin::asynchronousShutdownFinished);
|
||||
connect(m_qodeAssistClient, &QObject::destroyed, this, &IPlugin::asynchronousShutdownFinished);
|
||||
return AsynchronousShutdown;
|
||||
}
|
||||
|
||||
private:
|
||||
void checkForUpdates()
|
||||
{
|
||||
connect(
|
||||
m_updater,
|
||||
&PluginUpdater::updateCheckFinished,
|
||||
this,
|
||||
&QodeAssistPlugin::handleUpdateCheckResult,
|
||||
Qt::UniqueConnection);
|
||||
m_updater->checkForUpdates();
|
||||
}
|
||||
|
||||
void handleUpdateCheckResult(const PluginUpdater::UpdateInfo &info)
|
||||
{
|
||||
if (!info.isUpdateAvailable
|
||||
|| QVersionNumber::fromString(info.currentIdeVersion)
|
||||
> QVersionNumber::fromString(info.targetIdeVersion))
|
||||
return;
|
||||
|
||||
if (m_statusWidget)
|
||||
m_statusWidget->showUpdateAvailable(info.version);
|
||||
}
|
||||
|
||||
QPointer<QodeAssistClient> m_qodeAssistClient;
|
||||
QPointer<Chat::ChatOutputPane> m_chatOutputPane;
|
||||
QPointer<Chat::NavigationPanel> m_navigationPanel;
|
||||
QPointer<PluginUpdater> m_updater;
|
||||
UpdateStatusWidget *m_statusWidget{nullptr};
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Internal
|
||||
|
||||
@ -11,6 +11,8 @@ add_library(QodeAssistSettings STATIC
|
||||
ProjectSettings.hpp ProjectSettings.cpp
|
||||
ProjectSettingsPanel.hpp ProjectSettingsPanel.cpp
|
||||
ProviderSettings.hpp ProviderSettings.cpp
|
||||
PluginUpdater.hpp PluginUpdater.cpp
|
||||
UpdateDialog.hpp UpdateDialog.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(QodeAssistSettings
|
||||
|
||||
@ -44,15 +44,15 @@ ChatAssistantSettings::ChatAssistantSettings()
|
||||
|
||||
// Chat Settings
|
||||
chatTokensThreshold.setSettingsKey(Constants::CA_TOKENS_THRESHOLD);
|
||||
chatTokensThreshold.setLabelText(Tr::tr("Chat History Token Limit:"));
|
||||
chatTokensThreshold.setLabelText(Tr::tr("Chat history token limit:"));
|
||||
chatTokensThreshold.setToolTip(Tr::tr("Maximum number of tokens in chat history. When "
|
||||
"exceeded, oldest messages will be removed."));
|
||||
chatTokensThreshold.setRange(1, 900000);
|
||||
chatTokensThreshold.setDefaultValue(8000);
|
||||
|
||||
sharingCurrentFile.setSettingsKey(Constants::CA_SHARING_CURRENT_FILE);
|
||||
sharingCurrentFile.setLabelText(Tr::tr("Share Current File With Assistant by Default"));
|
||||
sharingCurrentFile.setDefaultValue(true);
|
||||
linkOpenFiles.setSettingsKey(Constants::CA_LINK_OPEN_FILES);
|
||||
linkOpenFiles.setLabelText(Tr::tr("Sync open files with assistant by default"));
|
||||
linkOpenFiles.setDefaultValue(false);
|
||||
|
||||
stream.setSettingsKey(Constants::CA_STREAM);
|
||||
stream.setDefaultValue(true);
|
||||
@ -171,7 +171,7 @@ ChatAssistantSettings::ChatAssistantSettings()
|
||||
Space{8},
|
||||
Group{
|
||||
title(Tr::tr("Chat Settings")),
|
||||
Column{Row{chatTokensThreshold, Stretch{1}}, sharingCurrentFile, stream, autosave}},
|
||||
Column{Row{chatTokensThreshold, Stretch{1}}, linkOpenFiles, stream, autosave}},
|
||||
Space{8},
|
||||
Group{
|
||||
title(Tr::tr("General Parameters")),
|
||||
@ -227,6 +227,7 @@ void ChatAssistantSettings::resetSettingsToDefaults()
|
||||
resetAspect(systemPrompt);
|
||||
resetAspect(ollamaLivetime);
|
||||
resetAspect(contextWindow);
|
||||
resetAspect(linkOpenFiles);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ public:
|
||||
|
||||
// Chat settings
|
||||
Utils::IntegerAspect chatTokensThreshold{this};
|
||||
Utils::BoolAspect sharingCurrentFile{this};
|
||||
Utils::BoolAspect linkOpenFiles{this};
|
||||
Utils::BoolAspect stream{this};
|
||||
Utils::BoolAspect autosave{this};
|
||||
|
||||
|
||||
@ -153,16 +153,16 @@ CodeCompletionSettings::CodeCompletionSettings()
|
||||
systemPrompt.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||
systemPrompt.setDefaultValue(
|
||||
"You are an expert in C++, Qt, and QML programming. Your task is to provide code "
|
||||
"suggestions that seamlessly integrate with existing code. You will receive a code context "
|
||||
"with specified insertion points. Your goal is to complete only one logic expression "
|
||||
"within these points."
|
||||
"suggestions that seamlessly integrate with existing code. Do not repeat code from position "
|
||||
"before or after <cursor>. You will receive a code context with specified insertion points. "
|
||||
"Your goal is to complete only one code block."
|
||||
"Here is the code context with insertion points:<code_context>Before: {{variable}}After: "
|
||||
"{{variable}}</code_context> Instructions: 1. Carefully analyze the provided code context. "
|
||||
"2. Consider the existing code and the specified insertion points.3. Generate a code "
|
||||
"suggestion that completes one logic expression between the 'Before' and 'After' points. "
|
||||
"4. Ensure your suggestion does not repeat any existing code. 5. Format your suggestion as "
|
||||
"a code block using triple backticks. 6. Do not include any comments or descriptions with "
|
||||
"your code suggestion. Remember to include only the new code to be inserted.");
|
||||
"your code suggestion.");
|
||||
|
||||
useUserMessageTemplateForCC.setSettingsKey(Constants::CC_USE_USER_TEMPLATE);
|
||||
useUserMessageTemplateForCC.setDefaultValue(true);
|
||||
@ -310,6 +310,7 @@ void CodeCompletionSettings::resetSettingsToDefaults()
|
||||
resetAspect(autoCompletion);
|
||||
resetAspect(multiLineCompletion);
|
||||
resetAspect(stream);
|
||||
resetAspect(smartProcessInstuctText);
|
||||
resetAspect(temperature);
|
||||
resetAspect(maxTokens);
|
||||
resetAspect(useTopP);
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
#include "SettingsDialog.hpp"
|
||||
#include "SettingsTr.hpp"
|
||||
#include "SettingsUtils.hpp"
|
||||
#include "UpdateDialog.hpp"
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
@ -60,7 +61,12 @@ GeneralSettings::GeneralSettings()
|
||||
enableLogging.setLabelText(TrConstants::ENABLE_LOG);
|
||||
enableLogging.setDefaultValue(false);
|
||||
|
||||
enableCheckUpdate.setSettingsKey(Constants::ENABLE_CHECK_UPDATE);
|
||||
enableCheckUpdate.setLabelText(TrConstants::ENABLE_CHECK_UPDATE_ON_START);
|
||||
enableCheckUpdate.setDefaultValue(true);
|
||||
|
||||
resetToDefaults.m_buttonText = TrConstants::RESET_TO_DEFAULTS;
|
||||
checkUpdate.m_buttonText = TrConstants::CHECK_UPDATE;
|
||||
|
||||
initStringAspect(ccProvider, Constants::CC_PROVIDER, TrConstants::PROVIDER, "Ollama");
|
||||
ccProvider.setReadOnly(true);
|
||||
@ -129,13 +135,15 @@ GeneralSettings::GeneralSettings()
|
||||
auto ccGroup = Group{title(TrConstants::CODE_COMPLETION), ccGrid};
|
||||
auto caGroup = Group{title(TrConstants::CHAT_ASSISTANT), caGrid};
|
||||
|
||||
auto rootLayout = Column{Row{enableQodeAssist, Stretch{1}, resetToDefaults},
|
||||
Row{enableLogging, Stretch{1}},
|
||||
Space{8},
|
||||
ccGroup,
|
||||
Space{8},
|
||||
caGroup,
|
||||
Stretch{1}};
|
||||
auto rootLayout = Column{
|
||||
Row{enableQodeAssist, Stretch{1}, Row{checkUpdate, resetToDefaults}},
|
||||
Row{enableLogging, Stretch{1}},
|
||||
Row{enableCheckUpdate, Stretch{1}},
|
||||
Space{8},
|
||||
ccGroup,
|
||||
Space{8},
|
||||
caGroup,
|
||||
Stretch{1}};
|
||||
|
||||
return rootLayout;
|
||||
});
|
||||
@ -295,6 +303,9 @@ void GeneralSettings::setupConnections()
|
||||
Logger::instance().setLoggingEnabled(enableLogging.volatileValue());
|
||||
});
|
||||
connect(&resetToDefaults, &ButtonAspect::clicked, this, &GeneralSettings::resetPageToDefaults);
|
||||
connect(&checkUpdate, &ButtonAspect::clicked, this, [this]() {
|
||||
QodeAssist::UpdateDialog::checkForUpdatesAndShow(Core::ICore::dialogParent());
|
||||
});
|
||||
}
|
||||
|
||||
void GeneralSettings::resetPageToDefaults()
|
||||
@ -316,6 +327,7 @@ void GeneralSettings::resetPageToDefaults()
|
||||
resetAspect(caModel);
|
||||
resetAspect(caTemplate);
|
||||
resetAspect(caUrl);
|
||||
resetAspect(enableCheckUpdate);
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,6 +35,8 @@ public:
|
||||
|
||||
Utils::BoolAspect enableQodeAssist{this};
|
||||
Utils::BoolAspect enableLogging{this};
|
||||
Utils::BoolAspect enableCheckUpdate{this};
|
||||
ButtonAspect checkUpdate{this};
|
||||
ButtonAspect resetToDefaults{this};
|
||||
|
||||
// code completion setttings
|
||||
|
||||
188
settings/PluginUpdater.cpp
Normal file
188
settings/PluginUpdater.cpp
Normal file
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "PluginUpdater.hpp"
|
||||
|
||||
#include <coreplugin/coreconstants.h>
|
||||
#include <coreplugin/coreplugin.h>
|
||||
#include <extensionsystem/pluginmanager.h>
|
||||
#include <extensionsystem/pluginspec.h>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QStandardPaths>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
PluginUpdater::PluginUpdater(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_networkManager(new QNetworkAccessManager(this))
|
||||
{}
|
||||
|
||||
void PluginUpdater::checkForUpdates()
|
||||
{
|
||||
if (m_isCheckingUpdate)
|
||||
return;
|
||||
|
||||
m_isCheckingUpdate = true;
|
||||
QNetworkRequest request((QUrl(getUpdateUrl())));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
handleUpdateResponse(reply);
|
||||
m_isCheckingUpdate = false;
|
||||
reply->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
void PluginUpdater::handleUpdateResponse(QNetworkReply *reply)
|
||||
{
|
||||
UpdateInfo info;
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emit downloadError(reply->errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());
|
||||
QJsonObject obj = doc.object();
|
||||
|
||||
info.version = obj["tag_name"].toString();
|
||||
if (info.version.startsWith('v'))
|
||||
info.version.remove(0, 1);
|
||||
|
||||
QString qtcVersionStr = Core::ICore::versionString().split(' ').last();
|
||||
QVersionNumber qtcVersion = QVersionNumber::fromString(qtcVersionStr);
|
||||
info.currentIdeVersion = qtcVersionStr;
|
||||
|
||||
auto assets = obj["assets"].toArray();
|
||||
for (const auto &asset : assets) {
|
||||
QString name = asset.toObject()["name"].toString();
|
||||
|
||||
if (name.startsWith("QodeAssist-")) {
|
||||
QString assetVersionStr = name.section('-', 1, 1);
|
||||
QVersionNumber assetVersion = QVersionNumber::fromString(assetVersionStr);
|
||||
info.targetIdeVersion = assetVersionStr;
|
||||
|
||||
if (assetVersion != qtcVersion) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
if (name.contains("Windows"))
|
||||
#elif defined(Q_OS_MACOS)
|
||||
if (name.contains("macOS"))
|
||||
#else
|
||||
if (name.contains("Linux") && !name.contains("experimental"))
|
||||
#endif
|
||||
{
|
||||
info.downloadUrl = asset.toObject()["browser_download_url"].toString();
|
||||
info.fileName = name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (info.downloadUrl.isEmpty()) {
|
||||
info.incompatibleIdeVersion = true;
|
||||
emit updateCheckFinished(info);
|
||||
return;
|
||||
}
|
||||
|
||||
info.changeLog = obj["body"].toString();
|
||||
info.isUpdateAvailable = QVersionNumber::fromString(info.version)
|
||||
> QVersionNumber::fromString(currentVersion());
|
||||
|
||||
m_lastUpdateInfo = info;
|
||||
emit updateCheckFinished(info);
|
||||
}
|
||||
|
||||
void PluginUpdater::downloadUpdate(const QString &url)
|
||||
{
|
||||
QNetworkRequest request(url);
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::downloadProgress, this, &PluginUpdater::handleDownloadProgress);
|
||||
connect(reply, &QNetworkReply::finished, this, &PluginUpdater::handleDownloadFinished);
|
||||
}
|
||||
|
||||
QString PluginUpdater::currentVersion() const
|
||||
{
|
||||
const auto pluginSpecs = ExtensionSystem::PluginManager::plugins();
|
||||
for (const ExtensionSystem::PluginSpec *spec : pluginSpecs) {
|
||||
if (spec->name() == QLatin1String("QodeAssist"))
|
||||
return spec->version();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool PluginUpdater::isUpdateAvailable() const
|
||||
{
|
||||
return m_lastUpdateInfo.isUpdateAvailable;
|
||||
}
|
||||
|
||||
QString PluginUpdater::getUpdateUrl() const
|
||||
{
|
||||
return "https://api.github.com/repos/Palm1r/qodeassist/releases/latest";
|
||||
}
|
||||
|
||||
void PluginUpdater::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
||||
{
|
||||
emit downloadProgress(bytesReceived, bytesTotal);
|
||||
}
|
||||
|
||||
void PluginUpdater::handleDownloadFinished()
|
||||
{
|
||||
auto reply = qobject_cast<QNetworkReply *>(sender());
|
||||
QTC_ASSERT(reply, return);
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emit downloadError(reply->errorString());
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
QString downloadPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)
|
||||
+ QDir::separator() + "QodeAssist_v" + m_lastUpdateInfo.version;
|
||||
QDir().mkpath(downloadPath);
|
||||
|
||||
QString filePath = downloadPath + QDir::separator() + m_lastUpdateInfo.fileName;
|
||||
|
||||
if (QFile::exists(filePath)) {
|
||||
emit downloadError(tr("Update file already exists: %1").arg(filePath));
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
emit downloadError(tr("Could not save the update file"));
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
file.write(reply->readAll());
|
||||
file.close();
|
||||
|
||||
emit downloadFinished(filePath);
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
72
settings/PluginUpdater.hpp
Normal file
72
settings/PluginUpdater.hpp
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/plugininstallwizard.h>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
#include <QVersionNumber>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
class PluginUpdater : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct UpdateInfo
|
||||
{
|
||||
QString version;
|
||||
QString downloadUrl;
|
||||
QString changeLog;
|
||||
QString fileName;
|
||||
bool isUpdateAvailable;
|
||||
bool incompatibleIdeVersion{false};
|
||||
QString targetIdeVersion;
|
||||
QString currentIdeVersion;
|
||||
};
|
||||
|
||||
explicit PluginUpdater(QObject *parent = nullptr);
|
||||
~PluginUpdater() = default;
|
||||
|
||||
void checkForUpdates();
|
||||
void downloadUpdate(const QString &url);
|
||||
QString currentVersion() const;
|
||||
bool isUpdateAvailable() const;
|
||||
|
||||
signals:
|
||||
void updateCheckFinished(const UpdateInfo &info);
|
||||
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
void downloadFinished(const QString &filePath);
|
||||
void downloadError(const QString &error);
|
||||
|
||||
private:
|
||||
void handleUpdateResponse(QNetworkReply *reply);
|
||||
void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
void handleDownloadFinished();
|
||||
QString getUpdateUrl() const;
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
UpdateInfo m_lastUpdateInfo;
|
||||
bool m_isCheckingUpdate{false};
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
@ -69,6 +69,14 @@ ProviderSettings::ProviderSettings()
|
||||
claudeApiKey.setDefaultValue("");
|
||||
claudeApiKey.setAutoApply(true);
|
||||
|
||||
openAiApiKey.setSettingsKey(Constants::OPEN_AI_API_KEY);
|
||||
openAiApiKey.setLabelText(Tr::tr("OpenAI API Key:"));
|
||||
openAiApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
openAiApiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
|
||||
openAiApiKey.setHistoryCompleter(Constants::OPEN_AI_API_KEY_HISTORY);
|
||||
openAiApiKey.setDefaultValue("");
|
||||
openAiApiKey.setAutoApply(true);
|
||||
|
||||
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
|
||||
|
||||
readSettings();
|
||||
@ -83,6 +91,8 @@ ProviderSettings::ProviderSettings()
|
||||
Space{8},
|
||||
Group{title(Tr::tr("OpenRouter Settings")), Column{openRouterApiKey}},
|
||||
Space{8},
|
||||
Group{title(Tr::tr("OpenAI Settings")), Column{openAiApiKey}},
|
||||
Space{8},
|
||||
Group{title(Tr::tr("OpenAI Compatible Settings")), Column{openAiCompatApiKey}},
|
||||
Space{8},
|
||||
Group{title(Tr::tr("Claude Settings")), Column{claudeApiKey}},
|
||||
@ -101,6 +111,7 @@ void ProviderSettings::setupConnections()
|
||||
openAiCompatApiKey.writeSettings();
|
||||
});
|
||||
connect(&claudeApiKey, &ButtonAspect::changed, this, [this]() { claudeApiKey.writeSettings(); });
|
||||
connect(&openAiApiKey, &ButtonAspect::changed, this, [this]() { openAiApiKey.writeSettings(); });
|
||||
}
|
||||
|
||||
void ProviderSettings::resetSettingsToDefaults()
|
||||
@ -116,6 +127,7 @@ void ProviderSettings::resetSettingsToDefaults()
|
||||
resetAspect(openRouterApiKey);
|
||||
resetAspect(openAiCompatApiKey);
|
||||
resetAspect(claudeApiKey);
|
||||
resetAspect(openAiApiKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -36,6 +36,7 @@ public:
|
||||
Utils::StringAspect openRouterApiKey{this};
|
||||
Utils::StringAspect openAiCompatApiKey{this};
|
||||
Utils::StringAspect claudeApiKey{this};
|
||||
Utils::StringAspect openAiApiKey{this};
|
||||
|
||||
private:
|
||||
void setupConnections();
|
||||
|
||||
@ -50,6 +50,8 @@ const char CA_URL_HISTORY[] = "QodeAssist.caUrlHistory";
|
||||
const char ENABLE_QODE_ASSIST[] = "QodeAssist.enableQodeAssist";
|
||||
const char CC_AUTO_COMPLETION[] = "QodeAssist.ccAutoCompletion";
|
||||
const char ENABLE_LOGGING[] = "QodeAssist.enableLogging";
|
||||
const char ENABLE_CHECK_UPDATE[] = "QodeAssist.enableCheckUpdate";
|
||||
|
||||
const char PROVIDER_PATHS[] = "QodeAssist.providerPaths";
|
||||
const char СС_START_SUGGESTION_TIMER[] = "QodeAssist.startSuggestionTimer";
|
||||
const char СС_AUTO_COMPLETION_CHAR_THRESHOLD[] = "QodeAssist.autoCompletionCharThreshold";
|
||||
@ -60,7 +62,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";
|
||||
|
||||
@ -87,6 +89,8 @@ const char OPEN_AI_COMPAT_API_KEY[] = "QodeAssist.openAiCompatApiKey";
|
||||
const char OPEN_AI_COMPAT_API_KEY_HISTORY[] = "QodeAssist.openAiCompatApiKeyHistory";
|
||||
const char CLAUDE_API_KEY[] = "QodeAssist.claudeApiKey";
|
||||
const char CLAUDE_API_KEY_HISTORY[] = "QodeAssist.claudeApiKeyHistory";
|
||||
const char OPEN_AI_API_KEY[] = "QodeAssist.openAiApiKey";
|
||||
const char OPEN_AI_API_KEY_HISTORY[] = "QodeAssist.openAiApiKeyHistory";
|
||||
|
||||
// context settings
|
||||
const char CC_READ_FULL_FILE[] = "QodeAssist.ccReadFullFile";
|
||||
|
||||
@ -28,6 +28,7 @@ inline const char *ENABLE_QODE_ASSIST = QT_TRANSLATE_NOOP("QtC::QodeAssist", "En
|
||||
inline const char *GENERAL = QT_TRANSLATE_NOOP("QtC::QodeAssist", "General");
|
||||
inline const char *RESET_TO_DEFAULTS = QT_TRANSLATE_NOOP("QtC::QodeAssist",
|
||||
"Reset Page to Defaults");
|
||||
inline const char *CHECK_UPDATE = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Check Update");
|
||||
inline const char *SELECT = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Select...");
|
||||
inline const char *PROVIDER = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Provider:");
|
||||
inline const char *MODEL = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Model:");
|
||||
@ -36,6 +37,9 @@ inline const char *URL = QT_TRANSLATE_NOOP("QtC::QodeAssist", "URL:");
|
||||
inline const char *STATUS = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Status:");
|
||||
inline const char *TEST = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Test");
|
||||
inline const char *ENABLE_LOG = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Enable Logging");
|
||||
inline const char *ENABLE_CHECK_UPDATE_ON_START
|
||||
= QT_TRANSLATE_NOOP("QtC::QodeAssist", "Check for updates when Qt Creator starts");
|
||||
|
||||
inline const char *CODE_COMPLETION = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Code Completion");
|
||||
inline const char *CHAT_ASSISTANT = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Chat Assistant");
|
||||
inline const char *RESET_SETTINGS = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Reset Settings");
|
||||
|
||||
210
settings/UpdateDialog.cpp
Normal file
210
settings/UpdateDialog.cpp
Normal file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "UpdateDialog.hpp"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
#include <extensionsystem/pluginmanager.h>
|
||||
#include <extensionsystem/pluginspec.h>
|
||||
#include <QDesktopServices>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
UpdateDialog::UpdateDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, m_updater(new PluginUpdater(this))
|
||||
{
|
||||
setWindowTitle(tr("QodeAssist Update"));
|
||||
setMinimumWidth(400);
|
||||
setMinimumHeight(500);
|
||||
|
||||
m_layout = new QVBoxLayout(this);
|
||||
m_layout->setSpacing(12);
|
||||
|
||||
auto *supportLabel = new QLabel(
|
||||
tr("QodeAssist is an open-source project that helps\n"
|
||||
"developers write better code. If you find it useful, please"),
|
||||
this);
|
||||
supportLabel->setAlignment(Qt::AlignCenter);
|
||||
m_layout->addWidget(supportLabel);
|
||||
|
||||
auto *supportLink = new QLabel(
|
||||
tr("<a href='https://ko-fi.com/qodeassist' style='color: #0066cc;'>Support on Ko-fi "
|
||||
"☕</a>"),
|
||||
this);
|
||||
supportLink->setOpenExternalLinks(true);
|
||||
supportLink->setTextFormat(Qt::RichText);
|
||||
supportLink->setAlignment(Qt::AlignCenter);
|
||||
m_layout->addWidget(supportLink);
|
||||
|
||||
m_layout->addSpacing(20);
|
||||
|
||||
m_titleLabel = new QLabel(tr("A new version of QodeAssist is available!"), this);
|
||||
m_titleLabel->setStyleSheet("font-weight: bold; font-size: 14px;");
|
||||
m_titleLabel->setAlignment(Qt::AlignCenter);
|
||||
m_layout->addWidget(m_titleLabel);
|
||||
|
||||
m_versionLabel = new QLabel(
|
||||
tr("Version %1 is now available - you have %2").arg(m_updater->currentVersion()), this);
|
||||
m_versionLabel->setAlignment(Qt::AlignCenter);
|
||||
m_layout->addWidget(m_versionLabel);
|
||||
|
||||
m_releaseLink = new QLabel(this);
|
||||
m_releaseLink->setOpenExternalLinks(true);
|
||||
m_releaseLink->setTextFormat(Qt::RichText);
|
||||
m_releaseLink->setAlignment(Qt::AlignCenter);
|
||||
m_layout->addWidget(m_releaseLink);
|
||||
|
||||
if (!m_changelogLabel) {
|
||||
m_changelogLabel = new QLabel(tr("Release Notes:"), this);
|
||||
m_layout->addWidget(m_changelogLabel);
|
||||
|
||||
m_changelogText = new QTextEdit(this);
|
||||
m_changelogText->setReadOnly(true);
|
||||
m_changelogText->setMinimumHeight(100);
|
||||
m_layout->addWidget(m_changelogText);
|
||||
}
|
||||
|
||||
m_progress = new QProgressBar(this);
|
||||
m_progress->setVisible(false);
|
||||
m_layout->addWidget(m_progress);
|
||||
|
||||
auto *buttonLayout = new QHBoxLayout;
|
||||
m_downloadButton = new QPushButton(tr("Download"), this);
|
||||
m_downloadButton->setEnabled(false);
|
||||
buttonLayout->addWidget(m_downloadButton);
|
||||
|
||||
m_closeButton = new QPushButton(tr("Close"), this);
|
||||
buttonLayout->addWidget(m_closeButton);
|
||||
|
||||
m_layout->addLayout(buttonLayout);
|
||||
|
||||
connect(m_updater, &PluginUpdater::updateCheckFinished, this, &UpdateDialog::handleUpdateInfo);
|
||||
connect(m_updater, &PluginUpdater::downloadProgress, this, &UpdateDialog::updateProgress);
|
||||
connect(m_updater, &PluginUpdater::downloadFinished, this, &UpdateDialog::handleDownloadFinished);
|
||||
connect(m_updater, &PluginUpdater::downloadError, this, &UpdateDialog::handleDownloadError);
|
||||
|
||||
connect(m_downloadButton, &QPushButton::clicked, this, &UpdateDialog::startDownload);
|
||||
connect(m_closeButton, &QPushButton::clicked, this, &QDialog::reject);
|
||||
|
||||
m_updater->checkForUpdates();
|
||||
}
|
||||
|
||||
void UpdateDialog::checkForUpdatesAndShow(QWidget *parent)
|
||||
{
|
||||
auto *dialog = new UpdateDialog(parent);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
dialog->show();
|
||||
}
|
||||
|
||||
void UpdateDialog::handleUpdateInfo(const PluginUpdater::UpdateInfo &info)
|
||||
{
|
||||
m_releaseLink->setText(
|
||||
tr("<a href='https://github.com/Palm1r/QodeAssist/releases'>You can also download "
|
||||
"from GitHub Releases</a>"));
|
||||
|
||||
if (info.incompatibleIdeVersion) {
|
||||
m_titleLabel->setText(tr("Incompatible Qt Creator Version"));
|
||||
m_versionLabel->setText(tr("This update requires Qt Creator %1, current is %2.\n"
|
||||
"Please upgrade Qt Creator to use this version of QodeAssist.")
|
||||
.arg(info.targetIdeVersion, info.currentIdeVersion));
|
||||
m_downloadButton->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!info.isUpdateAvailable) {
|
||||
m_titleLabel->setText(tr("QodeAssist is up to date"));
|
||||
m_downloadButton->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
m_titleLabel->setText(tr("A new version of QodeAssist is available!"));
|
||||
m_versionLabel->setText(tr("Version %1 is now available - you have %2")
|
||||
.arg(info.version, m_updater->currentVersion()));
|
||||
|
||||
if (!info.changeLog.isEmpty()) {
|
||||
if (!m_changelogLabel) {
|
||||
m_changelogLabel = new QLabel(tr("Release Notes:"), this);
|
||||
m_layout->insertWidget(2, m_changelogLabel);
|
||||
|
||||
m_changelogText = new QTextEdit(this);
|
||||
m_changelogText->setReadOnly(true);
|
||||
m_changelogText->setMaximumHeight(200);
|
||||
m_layout->insertWidget(3, m_changelogText);
|
||||
}
|
||||
m_changelogText->setText(info.changeLog);
|
||||
}
|
||||
|
||||
m_downloadButton->setEnabled(true);
|
||||
m_updateInfo = info;
|
||||
}
|
||||
|
||||
void UpdateDialog::startDownload()
|
||||
{
|
||||
m_downloadButton->setEnabled(false);
|
||||
m_progress->setVisible(true);
|
||||
m_updater->downloadUpdate(m_updateInfo.downloadUrl);
|
||||
}
|
||||
|
||||
void UpdateDialog::updateProgress(qint64 received, qint64 total)
|
||||
{
|
||||
m_progress->setMaximum(total);
|
||||
m_progress->setValue(received);
|
||||
}
|
||||
|
||||
void UpdateDialog::handleDownloadFinished(const QString &path)
|
||||
{
|
||||
m_progress->setVisible(false);
|
||||
|
||||
QMessageBox msgBox(this);
|
||||
msgBox.setWindowTitle(tr("Update Downloaded"));
|
||||
msgBox.setText(tr("The update has been downloaded successfully.\n"
|
||||
"Would you like to close Qt Creator now and open the plugin folder?"));
|
||||
msgBox.setInformativeText(tr("To complete the update:\n\n"
|
||||
"1. Close Qt Creator completely\n"
|
||||
"2. Navigate to the plugin folder\n"
|
||||
"3. Remove the old version of QodeAssist plugin\n"
|
||||
"4. Install plugin as usual via About plugin menu"));
|
||||
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
msgBox.setDefaultButton(QMessageBox::Yes);
|
||||
msgBox.setIcon(QMessageBox::Information);
|
||||
|
||||
if (msgBox.exec() == QMessageBox::Yes) {
|
||||
const auto pluginSpecs = ExtensionSystem::PluginManager::plugins();
|
||||
for (const ExtensionSystem::PluginSpec *spec : pluginSpecs) {
|
||||
if (spec->name() == QLatin1String("QodeAssist")) {
|
||||
const auto pluginPath = spec->filePath().path();
|
||||
QFileInfo fileInfo(pluginPath);
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(fileInfo.absolutePath()));
|
||||
Core::ICore::exit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
accept();
|
||||
}
|
||||
|
||||
void UpdateDialog::handleDownloadError(const QString &error)
|
||||
{
|
||||
m_progress->setVisible(false);
|
||||
m_downloadButton->setEnabled(true);
|
||||
QMessageBox::critical(this, tr("Update Error"), tr("Failed to update: %1").arg(error));
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
64
settings/UpdateDialog.hpp
Normal file
64
settings/UpdateDialog.hpp
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QTextEdit>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "PluginUpdater.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
class UpdateDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit UpdateDialog(QWidget *parent = nullptr);
|
||||
|
||||
static void checkForUpdatesAndShow(QWidget *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void handleUpdateInfo(const PluginUpdater::UpdateInfo &info);
|
||||
void startDownload();
|
||||
void updateProgress(qint64 received, qint64 total);
|
||||
void handleDownloadFinished(const QString &path);
|
||||
void handleDownloadError(const QString &error);
|
||||
|
||||
private:
|
||||
PluginUpdater *m_updater;
|
||||
QVBoxLayout *m_layout;
|
||||
QLabel *m_titleLabel;
|
||||
QLabel *m_versionLabel;
|
||||
QLabel *m_releaseLink;
|
||||
QLabel *m_changelogLabel{nullptr};
|
||||
QTextEdit *m_changelogText{nullptr};
|
||||
QProgressBar *m_progress;
|
||||
QPushButton *m_downloadButton;
|
||||
QPushButton *m_closeButton;
|
||||
PluginUpdater::UpdateInfo m_updateInfo;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
48
templates/CodeLlamaQMLFim.hpp
Normal file
48
templates/CodeLlamaQMLFim.hpp
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "llmcore/PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class CodeLlamaQMLFim : public LLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
|
||||
QString name() const override { return "CodeLlama QML FIM"; }
|
||||
QString promptTemplate() const override { return "<SUF>%1<PRE>%2<MID>"; }
|
||||
QStringList stopWords() const override
|
||||
{
|
||||
return QStringList() << "<SUF>" << "<PRE>" << "</PRE>" << "</SUF>" << "< EOT >" << "\\end"
|
||||
<< "<MID>" << "</MID>" << "##";
|
||||
}
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
QString formattedPrompt = promptTemplate().arg(context.suffix, context.prefix);
|
||||
request["prompt"] = formattedPrompt;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "The message will contain the following tokens: <SUF>%1<PRE>%2<MID>";
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
39
templates/OpenAI.hpp
Normal file
39
templates/OpenAI.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "llmcore/PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class OpenAI : public LLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||
QString name() const override { return "OpenAI"; }
|
||||
QString promptTemplate() const override { return {}; }
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override {}
|
||||
QString description() const override { return "OpenAI"; }
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
@ -25,11 +25,13 @@
|
||||
#include "templates/ChatML.hpp"
|
||||
#include "templates/Claude.hpp"
|
||||
#include "templates/CodeLlamaFim.hpp"
|
||||
#include "templates/CodeLlamaQMLFim.hpp"
|
||||
#include "templates/CustomFimTemplate.hpp"
|
||||
#include "templates/DeepSeekCoderFim.hpp"
|
||||
#include "templates/Llama2.hpp"
|
||||
#include "templates/Llama3.hpp"
|
||||
#include "templates/Ollama.hpp"
|
||||
#include "templates/OpenAI.hpp"
|
||||
#include "templates/Qwen.hpp"
|
||||
#include "templates/StarCoder2Fim.hpp"
|
||||
|
||||
@ -51,6 +53,8 @@ inline void registerTemplates()
|
||||
templateManager.registerTemplate<Alpaca>();
|
||||
templateManager.registerTemplate<Llama2>();
|
||||
templateManager.registerTemplate<Claude>();
|
||||
templateManager.registerTemplate<OpenAI>();
|
||||
templateManager.registerTemplate<CodeLlamaQMLFim>();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
|
||||
Reference in New Issue
Block a user