mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-05-30 02:49:12 -04:00
refactor: Full rework quick refactor (#257)
This commit is contained in:
@@ -102,6 +102,8 @@ add_qtc_plugin(QodeAssist
|
||||
QodeAssist.qrc
|
||||
LSPCompletion.hpp
|
||||
LLMSuggestion.hpp LLMSuggestion.cpp
|
||||
RefactorSuggestion.hpp RefactorSuggestion.cpp
|
||||
RefactorSuggestionHoverHandler.hpp RefactorSuggestionHoverHandler.cpp
|
||||
QodeAssistClient.hpp QodeAssistClient.cpp
|
||||
chat/ChatOutputPane.h chat/ChatOutputPane.cpp
|
||||
chat/NavigationPanel.hpp chat/NavigationPanel.cpp
|
||||
@@ -109,10 +111,13 @@ add_qtc_plugin(QodeAssist
|
||||
CodeHandler.hpp CodeHandler.cpp
|
||||
UpdateStatusWidget.hpp UpdateStatusWidget.cpp
|
||||
widgets/CompletionProgressHandler.hpp widgets/CompletionProgressHandler.cpp
|
||||
widgets/CompletionErrorHandler.hpp widgets/CompletionErrorHandler.cpp
|
||||
widgets/ProgressWidget.hpp widgets/ProgressWidget.cpp
|
||||
widgets/ErrorWidget.hpp widgets/ErrorWidget.cpp
|
||||
widgets/EditorChatButton.hpp widgets/EditorChatButton.cpp
|
||||
widgets/EditorChatButtonHandler.hpp widgets/EditorChatButtonHandler.cpp
|
||||
widgets/QuickRefactorDialog.hpp widgets/QuickRefactorDialog.cpp
|
||||
|
||||
QuickRefactorHandler.hpp QuickRefactorHandler.cpp
|
||||
tools/ToolsFactory.hpp tools/ToolsFactory.cpp
|
||||
tools/ReadVisibleFilesTool.hpp tools/ReadVisibleFilesTool.cpp
|
||||
|
||||
@@ -158,7 +158,12 @@ void ClientInterface::sendMessage(
|
||||
config.apiKey = provider->apiKey();
|
||||
|
||||
config.provider->prepareRequest(
|
||||
config.providerRequest, promptTemplate, context, LLMCore::RequestType::Chat, isToolsEnabled);
|
||||
config.providerRequest,
|
||||
promptTemplate,
|
||||
context,
|
||||
LLMCore::RequestType::Chat,
|
||||
isToolsEnabled,
|
||||
Settings::chatAssistantSettings().enableThinkingMode());
|
||||
|
||||
QString requestId = QUuid::createUuid().toString();
|
||||
QJsonObject request{{"id", requestId}};
|
||||
|
||||
@@ -51,6 +51,8 @@ void ConfigurationManager::updateTemplateDescription(const Utils::StringAspect &
|
||||
m_generalSettings.ccTemplateDescription.setValue(templ->description());
|
||||
} else if (&templateAspect == &m_generalSettings.caTemplate) {
|
||||
m_generalSettings.caTemplateDescription.setValue(templ->description());
|
||||
} else if (&templateAspect == &m_generalSettings.qrTemplate) {
|
||||
m_generalSettings.qrTemplateDescription.setValue(templ->description());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +60,7 @@ void ConfigurationManager::updateAllTemplateDescriptions()
|
||||
{
|
||||
updateTemplateDescription(m_generalSettings.ccTemplate);
|
||||
updateTemplateDescription(m_generalSettings.caTemplate);
|
||||
updateTemplateDescription(m_generalSettings.qrTemplate);
|
||||
}
|
||||
|
||||
void ConfigurationManager::checkTemplate(const Utils::StringAspect &templateAspect)
|
||||
@@ -94,12 +97,16 @@ void ConfigurationManager::setupConnections()
|
||||
|
||||
connect(&m_generalSettings.ccSelectProvider, &Button::clicked, this, &Config::selectProvider);
|
||||
connect(&m_generalSettings.caSelectProvider, &Button::clicked, this, &Config::selectProvider);
|
||||
connect(&m_generalSettings.qrSelectProvider, &Button::clicked, this, &Config::selectProvider);
|
||||
connect(&m_generalSettings.ccSelectModel, &Button::clicked, this, &Config::selectModel);
|
||||
connect(&m_generalSettings.caSelectModel, &Button::clicked, this, &Config::selectModel);
|
||||
connect(&m_generalSettings.qrSelectModel, &Button::clicked, this, &Config::selectModel);
|
||||
connect(&m_generalSettings.ccSelectTemplate, &Button::clicked, this, &Config::selectTemplate);
|
||||
connect(&m_generalSettings.caSelectTemplate, &Button::clicked, this, &Config::selectTemplate);
|
||||
connect(&m_generalSettings.qrSelectTemplate, &Button::clicked, this, &Config::selectTemplate);
|
||||
connect(&m_generalSettings.ccSetUrl, &Button::clicked, this, &Config::selectUrl);
|
||||
connect(&m_generalSettings.caSetUrl, &Button::clicked, this, &Config::selectUrl);
|
||||
connect(&m_generalSettings.qrSetUrl, &Button::clicked, this, &Config::selectUrl);
|
||||
|
||||
connect(
|
||||
&m_generalSettings.ccPreset1SelectProvider, &Button::clicked, this, &Config::selectProvider);
|
||||
@@ -115,6 +122,10 @@ void ConfigurationManager::setupConnections()
|
||||
connect(&m_generalSettings.caTemplate, &Utils::StringAspect::changed, this, [this]() {
|
||||
updateTemplateDescription(m_generalSettings.caTemplate);
|
||||
});
|
||||
|
||||
connect(&m_generalSettings.qrTemplate, &Utils::StringAspect::changed, this, [this]() {
|
||||
updateTemplateDescription(m_generalSettings.qrTemplate);
|
||||
});
|
||||
}
|
||||
|
||||
void ConfigurationManager::selectProvider()
|
||||
@@ -129,6 +140,8 @@ void ConfigurationManager::selectProvider()
|
||||
? m_generalSettings.ccProvider
|
||||
: settingsButton == &m_generalSettings.ccPreset1SelectProvider
|
||||
? m_generalSettings.ccPreset1Provider
|
||||
: settingsButton == &m_generalSettings.qrSelectProvider
|
||||
? m_generalSettings.qrProvider
|
||||
: m_generalSettings.caProvider;
|
||||
|
||||
QTimer::singleShot(0, this, [this, providersList, &targetSettings] {
|
||||
@@ -145,17 +158,21 @@ void ConfigurationManager::selectModel()
|
||||
|
||||
const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectModel);
|
||||
const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectModel);
|
||||
const bool isQuickRefactor = (settingsButton == &m_generalSettings.qrSelectModel);
|
||||
|
||||
const QString providerName = isCodeCompletion ? m_generalSettings.ccProvider.volatileValue()
|
||||
: isPreset1 ? m_generalSettings.ccPreset1Provider.volatileValue()
|
||||
: isQuickRefactor ? m_generalSettings.qrProvider.volatileValue()
|
||||
: m_generalSettings.caProvider.volatileValue();
|
||||
|
||||
const auto providerUrl = isCodeCompletion ? m_generalSettings.ccUrl.volatileValue()
|
||||
: isPreset1 ? m_generalSettings.ccPreset1Url.volatileValue()
|
||||
: isQuickRefactor ? m_generalSettings.qrUrl.volatileValue()
|
||||
: m_generalSettings.caUrl.volatileValue();
|
||||
|
||||
auto &targetSettings = isCodeCompletion ? m_generalSettings.ccModel
|
||||
: isPreset1 ? m_generalSettings.ccPreset1Model
|
||||
: isQuickRefactor ? m_generalSettings.qrModel
|
||||
: m_generalSettings.caModel;
|
||||
|
||||
if (auto provider = m_providersManager.getProviderByName(providerName)) {
|
||||
@@ -186,8 +203,10 @@ void ConfigurationManager::selectTemplate()
|
||||
|
||||
const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectTemplate);
|
||||
const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectTemplate);
|
||||
const bool isQuickRefactor = (settingsButton == &m_generalSettings.qrSelectTemplate);
|
||||
const QString providerName = isCodeCompletion ? m_generalSettings.ccProvider.volatileValue()
|
||||
: isPreset1 ? m_generalSettings.ccPreset1Provider.volatileValue()
|
||||
: isQuickRefactor ? m_generalSettings.qrProvider.volatileValue()
|
||||
: m_generalSettings.caProvider.volatileValue();
|
||||
auto providerID = m_providersManager.getProviderByName(providerName)->providerID();
|
||||
|
||||
@@ -197,6 +216,7 @@ void ConfigurationManager::selectTemplate()
|
||||
|
||||
auto &targetSettings = isCodeCompletion ? m_generalSettings.ccTemplate
|
||||
: isPreset1 ? m_generalSettings.ccPreset1Template
|
||||
: isQuickRefactor ? m_generalSettings.qrTemplate
|
||||
: m_generalSettings.caTemplate;
|
||||
|
||||
QTimer::singleShot(0, &m_generalSettings, [this, templateList, &targetSettings]() {
|
||||
@@ -221,6 +241,8 @@ void ConfigurationManager::selectUrl()
|
||||
auto &targetSettings = (settingsButton == &m_generalSettings.ccSetUrl) ? m_generalSettings.ccUrl
|
||||
: settingsButton == &m_generalSettings.ccPreset1SetUrl
|
||||
? m_generalSettings.ccPreset1Url
|
||||
: settingsButton == &m_generalSettings.qrSetUrl
|
||||
? m_generalSettings.qrUrl
|
||||
: m_generalSettings.caUrl;
|
||||
|
||||
QTimer::singleShot(0, &m_generalSettings, [this, urls, &targetSettings]() {
|
||||
|
||||
@@ -86,7 +86,22 @@ void LLMClientInterface::handleRequestFailed(const QString &requestId, const QSt
|
||||
return;
|
||||
|
||||
LOG_MESSAGE(QString("Request %1 failed: %2").arg(requestId, error));
|
||||
|
||||
// Send LSP error response to client
|
||||
const RequestContext &ctx = it.value();
|
||||
QJsonObject response;
|
||||
response["jsonrpc"] = "2.0";
|
||||
response[LanguageServerProtocol::idKey] = ctx.originalRequest["id"];
|
||||
|
||||
QJsonObject errorObject;
|
||||
errorObject["code"] = -32603; // Internal error code
|
||||
errorObject["message"] = error;
|
||||
response["error"] = errorObject;
|
||||
|
||||
emit messageReceived(LanguageServerProtocol::JsonRpcMessage(response));
|
||||
|
||||
m_activeRequests.erase(it);
|
||||
m_performanceLogger.endTimeMeasurement(requestId);
|
||||
}
|
||||
|
||||
void LLMClientInterface::sendData(const QByteArray &data)
|
||||
@@ -110,7 +125,8 @@ void LLMClientInterface::sendData(const QByteArray &data)
|
||||
QString requestId = request["id"].toString();
|
||||
m_performanceLogger.startTimeMeasurement(requestId);
|
||||
handleCompletion(request);
|
||||
} else if (method == "$/cancelRequest") {
|
||||
} else if (method == "cancelRequest") {
|
||||
qDebug() << "Cancelling request";
|
||||
handleCancelRequest();
|
||||
} else if (method == "exit") {
|
||||
// TODO make exit handler
|
||||
@@ -194,12 +210,32 @@ void LLMClientInterface::handleExit(const QJsonObject &request)
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void LLMClientInterface::sendErrorResponse(const QJsonObject &request, const QString &errorMessage)
|
||||
{
|
||||
QJsonObject response;
|
||||
response["jsonrpc"] = "2.0";
|
||||
response[LanguageServerProtocol::idKey] = request["id"];
|
||||
|
||||
QJsonObject errorObject;
|
||||
errorObject["code"] = -32603; // Internal error code
|
||||
errorObject["message"] = errorMessage;
|
||||
response["error"] = errorObject;
|
||||
|
||||
emit messageReceived(LanguageServerProtocol::JsonRpcMessage(response));
|
||||
|
||||
// End performance measurement if it was started
|
||||
QString requestId = request["id"].toString();
|
||||
m_performanceLogger.endTimeMeasurement(requestId);
|
||||
}
|
||||
|
||||
void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
||||
{
|
||||
auto filePath = Context::extractFilePathFromRequest(request);
|
||||
auto documentInfo = m_documentReader.readDocument(filePath);
|
||||
if (!documentInfo.document) {
|
||||
LOG_MESSAGE("Error: Document is not available for" + filePath);
|
||||
QString error = QString("Document is not available: %1").arg(filePath);
|
||||
LOG_MESSAGE("Error: " + error);
|
||||
sendErrorResponse(request, error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -217,7 +253,9 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
||||
const auto provider = m_providerRegistry.getProviderByName(providerName);
|
||||
|
||||
if (!provider) {
|
||||
LOG_MESSAGE(QString("No provider found with name: %1").arg(providerName));
|
||||
QString error = QString("No provider found with name: %1").arg(providerName);
|
||||
LOG_MESSAGE(error);
|
||||
sendErrorResponse(request, error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -227,7 +265,9 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
||||
auto promptTemplate = m_promptProvider->getTemplateByName(templateName);
|
||||
|
||||
if (!promptTemplate) {
|
||||
LOG_MESSAGE(QString("No template found with name: %1").arg(templateName));
|
||||
QString error = QString("No template found with name: %1").arg(templateName);
|
||||
LOG_MESSAGE(error);
|
||||
sendErrorResponse(request, error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -309,12 +349,15 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
||||
promptTemplate,
|
||||
updatedContext,
|
||||
LLMCore::RequestType::CodeCompletion,
|
||||
false,
|
||||
false);
|
||||
|
||||
auto errors = config.provider->validateRequest(config.providerRequest, promptTemplate->type());
|
||||
if (!errors.isEmpty()) {
|
||||
LOG_MESSAGE("Validate errors for fim request:");
|
||||
QString error = QString("Request validation failed: %1").arg(errors.join("; "));
|
||||
LOG_MESSAGE("Validate errors for request:");
|
||||
LOG_MESSAGES(errors);
|
||||
sendErrorResponse(request, error);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ private:
|
||||
void handleInitialized(const QJsonObject &request);
|
||||
void handleExit(const QJsonObject &request);
|
||||
void handleCancelRequest();
|
||||
void sendErrorResponse(const QJsonObject &request, const QString &errorMessage);
|
||||
|
||||
struct RequestContext
|
||||
{
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
|
||||
#include "QodeAssistClient.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QInputDialog>
|
||||
#include <QKeyEvent>
|
||||
#include <QTimer>
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
@@ -33,6 +35,8 @@
|
||||
|
||||
#include "LLMClientInterface.hpp"
|
||||
#include "LLMSuggestion.hpp"
|
||||
#include "RefactorSuggestion.hpp"
|
||||
#include "RefactorSuggestionHoverHandler.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProjectSettings.hpp"
|
||||
@@ -61,11 +65,15 @@ QodeAssistClient::QodeAssistClient(LLMClientInterface *clientInterface)
|
||||
setupConnections();
|
||||
|
||||
m_typingTimer.start();
|
||||
|
||||
// Create hover handler for refactoring suggestions
|
||||
m_refactorHoverHandler = new RefactorSuggestionHoverHandler();
|
||||
}
|
||||
|
||||
QodeAssistClient::~QodeAssistClient()
|
||||
{
|
||||
cleanupConnections();
|
||||
delete m_refactorHoverHandler;
|
||||
}
|
||||
|
||||
void QodeAssistClient::openDocument(TextEditor::TextDocument *document)
|
||||
@@ -75,6 +83,14 @@ void QodeAssistClient::openDocument(TextEditor::TextDocument *document)
|
||||
return;
|
||||
|
||||
Client::openDocument(document);
|
||||
|
||||
// Register hover handler for this document
|
||||
auto editors = TextEditor::BaseTextEditor::textEditorsForDocument(document);
|
||||
for (auto *editor : editors) {
|
||||
if (auto *widget = editor->editorWidget()) {
|
||||
widget->addHoverHandler(m_refactorHoverHandler);
|
||||
}
|
||||
}
|
||||
connect(
|
||||
document,
|
||||
&TextDocument::contentsChangedWithPosition,
|
||||
@@ -175,6 +191,7 @@ void QodeAssistClient::requestCompletions(TextEditor::TextEditorWidget *editor)
|
||||
}
|
||||
request.setResponseCallback([this, editor = QPointer<TextEditorWidget>(editor)](
|
||||
const GetCompletionRequest::Response &response) {
|
||||
qDebug() << "setResponseCallback";
|
||||
QTC_ASSERT(editor, return);
|
||||
handleCompletions(response, editor);
|
||||
});
|
||||
@@ -243,9 +260,18 @@ void QodeAssistClient::scheduleRequest(TextEditor::TextEditorWidget *editor)
|
||||
void QodeAssistClient::handleCompletions(
|
||||
const GetCompletionRequest::Response &response, TextEditor::TextEditorWidget *editor)
|
||||
{
|
||||
if (response.error())
|
||||
qDebug() << "hideProgress";
|
||||
m_progressHandler.hideProgress();
|
||||
|
||||
if (response.error()) {
|
||||
log(*response.error());
|
||||
|
||||
QString errorMessage = tr("Code completion failed: %1")
|
||||
.arg(response.error()->message());
|
||||
m_errorHandler.showError(editor, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
int requestPosition = -1;
|
||||
if (const auto requestParams = m_runningRequests.take(editor).params())
|
||||
requestPosition = requestParams->position().toPositionInDocument(editor->document());
|
||||
@@ -288,9 +314,11 @@ void QodeAssistClient::handleCompletions(
|
||||
Text::Position pos{toTextPos(c.position())};
|
||||
return TextSuggestion::Data{range, pos, c.text()};
|
||||
});
|
||||
m_progressHandler.hideProgress();
|
||||
if (completions.isEmpty())
|
||||
|
||||
if (completions.isEmpty()) {
|
||||
LOG_MESSAGE("No valid completions received");
|
||||
return;
|
||||
}
|
||||
editor->insertSuggestion(std::make_unique<LLMSuggestion>(suggestions, editor->document()));
|
||||
}
|
||||
}
|
||||
@@ -344,30 +372,85 @@ void QodeAssistClient::cleanupConnections()
|
||||
|
||||
void QodeAssistClient::handleRefactoringResult(const RefactorResult &result)
|
||||
{
|
||||
m_progressHandler.hideProgress();
|
||||
|
||||
if (!result.success) {
|
||||
// Show error to user
|
||||
QString errorMessage = result.errorMessage.isEmpty()
|
||||
? tr("Quick refactor failed")
|
||||
: tr("Quick refactor failed: %1").arg(result.errorMessage);
|
||||
|
||||
if (result.editor) {
|
||||
m_errorHandler.showError(result.editor, errorMessage);
|
||||
}
|
||||
|
||||
LOG_MESSAGE(QString("Refactoring failed: %1").arg(result.errorMessage));
|
||||
return;
|
||||
}
|
||||
|
||||
auto editor = BaseTextEditor::currentTextEditor();
|
||||
if (!editor) {
|
||||
LOG_MESSAGE("Refactoring failed: No active editor found");
|
||||
if (!result.editor) {
|
||||
LOG_MESSAGE("Refactoring result has no editor");
|
||||
return;
|
||||
}
|
||||
|
||||
auto editorWidget = editor->editorWidget();
|
||||
TextEditorWidget *editorWidget = result.editor;
|
||||
|
||||
QTextCursor cursor = editorWidget->textCursor();
|
||||
cursor.beginEditBlock();
|
||||
auto toTextPos = [](const Utils::Text::Position &pos) {
|
||||
return Utils::Text::Position{pos.line, pos.column};
|
||||
};
|
||||
|
||||
int startPos = result.insertRange.begin.toPositionInDocument(editorWidget->document());
|
||||
int endPos = result.insertRange.end.toPositionInDocument(editorWidget->document());
|
||||
Utils::Text::Range range{toTextPos(result.insertRange.begin), toTextPos(result.insertRange.end)};
|
||||
Utils::Text::Position pos = toTextPos(result.insertRange.begin);
|
||||
|
||||
cursor.setPosition(startPos);
|
||||
cursor.setPosition(endPos, QTextCursor::KeepAnchor);
|
||||
int startPos = range.begin.toPositionInDocument(editorWidget->document());
|
||||
int endPos = range.end.toPositionInDocument(editorWidget->document());
|
||||
|
||||
cursor.insertText(result.newText);
|
||||
cursor.endEditBlock();
|
||||
m_progressHandler.hideProgress();
|
||||
if (startPos != endPos) {
|
||||
QTextCursor startCursor(editorWidget->document());
|
||||
startCursor.setPosition(startPos);
|
||||
if (startCursor.positionInBlock() > 0) {
|
||||
startCursor.movePosition(QTextCursor::StartOfBlock);
|
||||
}
|
||||
|
||||
QTextCursor endCursor(editorWidget->document());
|
||||
endCursor.setPosition(endPos);
|
||||
if (endCursor.positionInBlock() > 0) {
|
||||
endCursor.movePosition(QTextCursor::EndOfBlock);
|
||||
if (!endCursor.atEnd()) {
|
||||
endCursor.movePosition(QTextCursor::NextCharacter);
|
||||
}
|
||||
}
|
||||
|
||||
Utils::Text::Position expandedBegin = Utils::Text::Position::fromPositionInDocument(
|
||||
editorWidget->document(), startCursor.position());
|
||||
Utils::Text::Position expandedEnd = Utils::Text::Position::fromPositionInDocument(
|
||||
editorWidget->document(), endCursor.position());
|
||||
|
||||
range = Utils::Text::Range(expandedBegin, expandedEnd);
|
||||
}
|
||||
|
||||
TextEditor::TextSuggestion::Data suggestionData{
|
||||
Utils::Text::Range{toTextPos(result.insertRange.begin), toTextPos(result.insertRange.end)},
|
||||
pos,
|
||||
result.newText
|
||||
};
|
||||
editorWidget->insertSuggestion(
|
||||
std::make_unique<RefactorSuggestion>(suggestionData, editorWidget->document()));
|
||||
|
||||
m_refactorHoverHandler->setSuggestionRange(range);
|
||||
|
||||
m_refactorHoverHandler->setApplyCallback([this, editorWidget]() {
|
||||
QKeyEvent tabEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier);
|
||||
QApplication::sendEvent(editorWidget, &tabEvent);
|
||||
m_refactorHoverHandler->clearSuggestionRange();
|
||||
});
|
||||
|
||||
m_refactorHoverHandler->setDismissCallback([this, editorWidget]() {
|
||||
editorWidget->clearSuggestion();
|
||||
m_refactorHoverHandler->clearSuggestionRange();
|
||||
});
|
||||
|
||||
LOG_MESSAGE("Displaying refactoring suggestion with hover handler");
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
#include "LLMClientInterface.hpp"
|
||||
#include "LSPCompletion.hpp"
|
||||
#include "QuickRefactorHandler.hpp"
|
||||
#include "RefactorSuggestionHoverHandler.hpp"
|
||||
#include "widgets/CompletionProgressHandler.hpp"
|
||||
#include "widgets/CompletionErrorHandler.hpp"
|
||||
#include "widgets/EditorChatButtonHandler.hpp"
|
||||
#include <languageclient/client.h>
|
||||
#include <llmcore/IPromptProvider.hpp>
|
||||
@@ -70,8 +72,10 @@ private:
|
||||
QElapsedTimer m_typingTimer;
|
||||
int m_recentCharCount;
|
||||
CompletionProgressHandler m_progressHandler;
|
||||
CompletionErrorHandler m_errorHandler;
|
||||
EditorChatButtonHandler m_chatButtonHandler;
|
||||
QuickRefactorHandler *m_refactorHandler{nullptr};
|
||||
RefactorSuggestionHoverHandler *m_refactorHoverHandler{nullptr};
|
||||
LLMClientInterface *m_llmClient;
|
||||
};
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <logger/Logger.hpp>
|
||||
#include <settings/ChatAssistantSettings.hpp>
|
||||
#include <settings/GeneralSettings.hpp>
|
||||
#include <settings/QuickRefactorSettings.hpp>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
@@ -110,26 +111,30 @@ void QuickRefactorHandler::prepareAndSendRequest(
|
||||
auto &providerRegistry = LLMCore::ProvidersManager::instance();
|
||||
auto &promptManager = LLMCore::PromptTemplateManager::instance();
|
||||
|
||||
const auto providerName = settings.caProvider();
|
||||
const auto providerName = settings.qrProvider();
|
||||
auto provider = providerRegistry.getProviderByName(providerName);
|
||||
|
||||
if (!provider) {
|
||||
LOG_MESSAGE(QString("No provider found with name: %1").arg(providerName));
|
||||
QString error = QString("No provider found with name: %1").arg(providerName);
|
||||
LOG_MESSAGE(error);
|
||||
RefactorResult result;
|
||||
result.success = false;
|
||||
result.errorMessage = QString("No provider found with name: %1").arg(providerName);
|
||||
result.errorMessage = error;
|
||||
result.editor = editor;
|
||||
emit refactoringCompleted(result);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto templateName = settings.caTemplate();
|
||||
const auto templateName = settings.qrTemplate();
|
||||
auto promptTemplate = promptManager.getChatTemplateByName(templateName);
|
||||
|
||||
if (!promptTemplate) {
|
||||
LOG_MESSAGE(QString("No template found with name: %1").arg(templateName));
|
||||
QString error = QString("No template found with name: %1").arg(templateName);
|
||||
LOG_MESSAGE(error);
|
||||
RefactorResult result;
|
||||
result.success = false;
|
||||
result.errorMessage = QString("No template found with name: %1").arg(templateName);
|
||||
result.errorMessage = error;
|
||||
result.editor = editor;
|
||||
emit refactoringCompleted(result);
|
||||
return;
|
||||
}
|
||||
@@ -138,18 +143,34 @@ void QuickRefactorHandler::prepareAndSendRequest(
|
||||
config.requestType = LLMCore::RequestType::QuickRefactoring;
|
||||
config.provider = provider;
|
||||
config.promptTemplate = promptTemplate;
|
||||
config.url = QString("%1%2").arg(settings.caUrl(), provider->chatEndpoint());
|
||||
config.providerRequest = {{"model", settings.caModel()}, {"stream", true}};
|
||||
config.url = QString("%1%2").arg(settings.qrUrl(), provider->chatEndpoint());
|
||||
config.apiKey = provider->apiKey();
|
||||
|
||||
if (provider->providerID() == LLMCore::ProviderID::GoogleAI) {
|
||||
QString stream = QString{"streamGenerateContent?alt=sse"};
|
||||
config.url = QUrl(QString("%1/models/%2:%3")
|
||||
.arg(
|
||||
Settings::generalSettings().qrUrl(),
|
||||
Settings::generalSettings().qrModel(),
|
||||
stream));
|
||||
} else {
|
||||
config.url
|
||||
= QString("%1%2").arg(Settings::generalSettings().qrUrl(), provider->chatEndpoint());
|
||||
config.providerRequest
|
||||
= {{"model", Settings::generalSettings().qrModel()}, {"stream", true}};
|
||||
}
|
||||
|
||||
LLMCore::ContextData context = prepareContext(editor, range, instructions);
|
||||
|
||||
bool enableTools = Settings::quickRefactorSettings().useTools();
|
||||
bool enableThinking = Settings::quickRefactorSettings().useThinking();
|
||||
provider->prepareRequest(
|
||||
config.providerRequest,
|
||||
promptTemplate,
|
||||
context,
|
||||
LLMCore::RequestType::QuickRefactoring,
|
||||
false);
|
||||
enableTools,
|
||||
enableThinking);
|
||||
|
||||
QString requestId = QUuid::createUuid().toString();
|
||||
m_lastRequestId = requestId;
|
||||
@@ -195,22 +216,75 @@ LLMCore::ContextData QuickRefactorHandler::prepareContext(
|
||||
QTextCursor cursor = editor->textCursor();
|
||||
int cursorPos = cursor.position();
|
||||
|
||||
// TODO add selecting content before and after cursor/selection
|
||||
QString fullContent = documentInfo.document->toPlainText();
|
||||
QString taggedContent = fullContent;
|
||||
Context::DocumentContextReader
|
||||
reader(documentInfo.document, documentInfo.mimeType, documentInfo.filePath);
|
||||
|
||||
QString taggedContent;
|
||||
bool readFullFile = Settings::quickRefactorSettings().readFullFile();
|
||||
|
||||
if (cursor.hasSelection()) {
|
||||
int selEnd = cursor.selectionEnd();
|
||||
int selStart = cursor.selectionStart();
|
||||
taggedContent
|
||||
.insert(selEnd, selEnd == cursorPos ? "<selection_end><cursor>" : "<selection_end>");
|
||||
taggedContent.insert(
|
||||
selStart, selStart == cursorPos ? "<cursor><selection_start>" : "<selection_start>");
|
||||
int selEnd = cursor.selectionEnd();
|
||||
|
||||
QTextBlock startBlock = documentInfo.document->findBlock(selStart);
|
||||
int startLine = startBlock.blockNumber();
|
||||
int startColumn = selStart - startBlock.position();
|
||||
|
||||
QTextBlock endBlock = documentInfo.document->findBlock(selEnd);
|
||||
int endLine = endBlock.blockNumber();
|
||||
int endColumn = selEnd - endBlock.position();
|
||||
|
||||
QString contextBefore;
|
||||
if (readFullFile) {
|
||||
contextBefore = reader.readWholeFileBefore(startLine, startColumn);
|
||||
} else {
|
||||
taggedContent.insert(cursorPos, "<cursor>");
|
||||
contextBefore = reader.getContextBefore(
|
||||
startLine, startColumn, Settings::quickRefactorSettings().readStringsBeforeCursor() + 1);
|
||||
}
|
||||
|
||||
QString systemPrompt = Settings::codeCompletionSettings().quickRefactorSystemPrompt();
|
||||
QString selectedText = cursor.selectedText();
|
||||
selectedText.replace(QChar(0x2029), "\n");
|
||||
|
||||
QString contextAfter;
|
||||
if (readFullFile) {
|
||||
contextAfter = reader.readWholeFileAfter(endLine, endColumn);
|
||||
} else {
|
||||
contextAfter = reader.getContextAfter(
|
||||
endLine, endColumn, Settings::quickRefactorSettings().readStringsAfterCursor() + 1);
|
||||
}
|
||||
|
||||
taggedContent = contextBefore;
|
||||
if (selStart == cursorPos) {
|
||||
taggedContent += "<cursor><selection_start>" + selectedText + "<selection_end>";
|
||||
} else {
|
||||
taggedContent += "<selection_start>" + selectedText + "<selection_end><cursor>";
|
||||
}
|
||||
taggedContent += contextAfter;
|
||||
} else {
|
||||
QTextBlock block = documentInfo.document->findBlock(cursorPos);
|
||||
int line = block.blockNumber();
|
||||
int column = cursorPos - block.position();
|
||||
|
||||
QString contextBefore;
|
||||
if (readFullFile) {
|
||||
contextBefore = reader.readWholeFileBefore(line, column);
|
||||
} else {
|
||||
contextBefore = reader.getContextBefore(
|
||||
line, column, Settings::quickRefactorSettings().readStringsBeforeCursor() + 1);
|
||||
}
|
||||
|
||||
QString contextAfter;
|
||||
if (readFullFile) {
|
||||
contextAfter = reader.readWholeFileAfter(line, column);
|
||||
} else {
|
||||
contextAfter = reader.getContextAfter(
|
||||
line, column, Settings::quickRefactorSettings().readStringsAfterCursor() + 1);
|
||||
}
|
||||
|
||||
taggedContent = contextBefore + "<cursor>" + contextAfter;
|
||||
}
|
||||
|
||||
QString systemPrompt = Settings::quickRefactorSettings().systemPrompt();
|
||||
|
||||
auto project = LLMCore::RulesLoader::getActiveProject();
|
||||
if (project) {
|
||||
@@ -263,6 +337,8 @@ void QuickRefactorHandler::handleLLMResponse(
|
||||
}
|
||||
|
||||
if (isComplete) {
|
||||
m_isRefactoringInProgress = false;
|
||||
|
||||
QString cleanedResponse = response.trimmed();
|
||||
if (cleanedResponse.startsWith("```")) {
|
||||
int firstNewLine = cleanedResponse.indexOf('\n');
|
||||
@@ -280,6 +356,7 @@ void QuickRefactorHandler::handleLLMResponse(
|
||||
result.newText = cleanedResponse;
|
||||
result.insertRange = m_currentRange;
|
||||
result.success = true;
|
||||
result.editor = m_currentEditor;
|
||||
|
||||
LOG_MESSAGE("Refactoring completed successfully. New code to insert: ");
|
||||
LOG_MESSAGE("---------- BEGIN REFACTORED CODE ----------");
|
||||
@@ -316,6 +393,7 @@ void QuickRefactorHandler::cancelRequest()
|
||||
void QuickRefactorHandler::handleFullResponse(const QString &requestId, const QString &fullText)
|
||||
{
|
||||
if (requestId == m_lastRequestId) {
|
||||
m_activeRequests.remove(requestId);
|
||||
QJsonObject request{{"id", requestId}};
|
||||
handleLLMResponse(fullText, request, true);
|
||||
}
|
||||
@@ -324,10 +402,12 @@ void QuickRefactorHandler::handleFullResponse(const QString &requestId, const QS
|
||||
void QuickRefactorHandler::handleRequestFailed(const QString &requestId, const QString &error)
|
||||
{
|
||||
if (requestId == m_lastRequestId) {
|
||||
m_activeRequests.remove(requestId);
|
||||
m_isRefactoringInProgress = false;
|
||||
RefactorResult result;
|
||||
result.success = false;
|
||||
result.errorMessage = error;
|
||||
result.editor = m_currentEditor;
|
||||
emit refactoringCompleted(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ struct RefactorResult
|
||||
Utils::Text::Range insertRange;
|
||||
bool success;
|
||||
QString errorMessage;
|
||||
TextEditor::TextEditorWidget *editor{nullptr};
|
||||
};
|
||||
|
||||
class QuickRefactorHandler : public QObject
|
||||
|
||||
202
RefactorSuggestion.cpp
Normal file
202
RefactorSuggestion.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright (C) 2024-2025 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 "RefactorSuggestion.hpp"
|
||||
#include "LLMSuggestion.hpp"
|
||||
|
||||
#include <QTextBlock>
|
||||
#include <QTextCursor>
|
||||
#include <QTextDocument>
|
||||
|
||||
#include <texteditor/texteditor.h>
|
||||
#include <logger/Logger.hpp>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
namespace {
|
||||
QString extractLeadingWhitespace(const QString &text)
|
||||
{
|
||||
QString indent;
|
||||
int firstLineEnd = text.indexOf('\n');
|
||||
QString firstLine = (firstLineEnd != -1) ? text.left(firstLineEnd) : text;
|
||||
for (int i = 0; i < firstLine.length(); ++i) {
|
||||
if (firstLine[i].isSpace()) {
|
||||
indent += firstLine[i];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return indent;
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
RefactorSuggestion::RefactorSuggestion(const Data &suggestion, QTextDocument *sourceDocument)
|
||||
: TextEditor::TextSuggestion([&suggestion, sourceDocument]() {
|
||||
Data expandedData = suggestion;
|
||||
|
||||
int startPos = suggestion.range.begin.toPositionInDocument(sourceDocument);
|
||||
int endPos = suggestion.range.end.toPositionInDocument(sourceDocument);
|
||||
startPos = qBound(0, startPos, sourceDocument->characterCount());
|
||||
endPos = qBound(0, endPos, sourceDocument->characterCount());
|
||||
|
||||
if (startPos != endPos) {
|
||||
QTextCursor startCursor(sourceDocument);
|
||||
startCursor.setPosition(startPos);
|
||||
int startPosInBlock = startCursor.positionInBlock();
|
||||
|
||||
if (startPosInBlock > 0) {
|
||||
startCursor.movePosition(QTextCursor::StartOfBlock);
|
||||
}
|
||||
|
||||
QTextCursor endCursor(sourceDocument);
|
||||
endCursor.setPosition(endPos);
|
||||
int endPosInBlock = endCursor.positionInBlock();
|
||||
|
||||
if (endPosInBlock > 0) {
|
||||
endCursor.movePosition(QTextCursor::EndOfBlock);
|
||||
if (!endCursor.atEnd()) {
|
||||
endCursor.movePosition(QTextCursor::NextCharacter);
|
||||
}
|
||||
}
|
||||
|
||||
Utils::Text::Position expandedBegin = Utils::Text::Position::fromPositionInDocument(
|
||||
sourceDocument, startCursor.position());
|
||||
Utils::Text::Position expandedEnd = Utils::Text::Position::fromPositionInDocument(
|
||||
sourceDocument, endCursor.position());
|
||||
|
||||
expandedData.range = Utils::Text::Range(expandedBegin, expandedEnd);
|
||||
}
|
||||
|
||||
return expandedData;
|
||||
}(), sourceDocument)
|
||||
, m_suggestionData(suggestion)
|
||||
{
|
||||
const QString refactoredText = suggestion.text;
|
||||
|
||||
int startPos = suggestion.range.begin.toPositionInDocument(sourceDocument);
|
||||
int endPos = suggestion.range.end.toPositionInDocument(sourceDocument);
|
||||
startPos = qBound(0, startPos, sourceDocument->characterCount());
|
||||
endPos = qBound(0, endPos, sourceDocument->characterCount());
|
||||
|
||||
QTextCursor startCursor(sourceDocument);
|
||||
startCursor.setPosition(startPos);
|
||||
|
||||
if (startPos == endPos) {
|
||||
QTextBlock block = startCursor.block();
|
||||
QString blockText = block.text();
|
||||
int startPosInBlock = startCursor.positionInBlock();
|
||||
|
||||
QString leftText = blockText.left(startPosInBlock);
|
||||
QString rightText = blockText.mid(startPosInBlock);
|
||||
|
||||
QString displayText = leftText + refactoredText + rightText;
|
||||
replacementDocument()->setPlainText(displayText);
|
||||
|
||||
} else {
|
||||
QTextCursor fullLinesCursor(sourceDocument);
|
||||
fullLinesCursor.setPosition(startPos);
|
||||
fullLinesCursor.movePosition(QTextCursor::StartOfBlock);
|
||||
int fullLinesStart = fullLinesCursor.position();
|
||||
|
||||
fullLinesCursor.setPosition(endPos);
|
||||
fullLinesCursor.movePosition(QTextCursor::EndOfBlock);
|
||||
int fullLinesEnd = fullLinesCursor.position();
|
||||
|
||||
fullLinesCursor.setPosition(fullLinesStart);
|
||||
fullLinesCursor.setPosition(fullLinesEnd, QTextCursor::KeepAnchor);
|
||||
QString fullLinesText = fullLinesCursor.selectedText();
|
||||
fullLinesText.replace(QChar(0x2029), "\n");
|
||||
|
||||
QString oldIndent = extractLeadingWhitespace(fullLinesText);
|
||||
QString newIndent = extractLeadingWhitespace(refactoredText);
|
||||
|
||||
QString displayText = refactoredText;
|
||||
if (newIndent.length() < oldIndent.length()) {
|
||||
QString indentDiff = oldIndent.left(oldIndent.length() - newIndent.length());
|
||||
QStringList lines = refactoredText.split('\n');
|
||||
if (!lines.isEmpty() && !lines[0].trimmed().isEmpty()) {
|
||||
lines[0] = indentDiff + lines[0];
|
||||
displayText = lines.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
replacementDocument()->setPlainText(displayText);
|
||||
}
|
||||
}
|
||||
|
||||
bool RefactorSuggestion::apply()
|
||||
{
|
||||
const QString text = m_suggestionData.text;
|
||||
const Utils::Text::Range range = m_suggestionData.range;
|
||||
|
||||
const QTextCursor startCursor = range.begin.toTextCursor(sourceDocument());
|
||||
const QTextCursor endCursor = range.end.toTextCursor(sourceDocument());
|
||||
|
||||
const int startPos = startCursor.position();
|
||||
const int endPos = endCursor.position();
|
||||
|
||||
QTextCursor editCursor(sourceDocument());
|
||||
editCursor.beginEditBlock();
|
||||
|
||||
if (startPos == endPos) {
|
||||
editCursor.setPosition(startPos);
|
||||
editCursor.insertText(text);
|
||||
} else {
|
||||
editCursor.setPosition(startPos);
|
||||
editCursor.setPosition(endPos, QTextCursor::KeepAnchor);
|
||||
QString selectedText = editCursor.selectedText();
|
||||
selectedText.replace(QChar(0x2029), "\n");
|
||||
|
||||
QString oldIndent = extractLeadingWhitespace(selectedText);
|
||||
QString newIndent = extractLeadingWhitespace(text);
|
||||
|
||||
QString textToInsert = text;
|
||||
if (newIndent.length() < oldIndent.length()) {
|
||||
QString indentDiff = oldIndent.left(oldIndent.length() - newIndent.length());
|
||||
QStringList lines = text.split('\n');
|
||||
if (!lines.isEmpty() && !lines[0].trimmed().isEmpty()) {
|
||||
lines[0] = indentDiff + lines[0];
|
||||
textToInsert = lines.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
editCursor.setPosition(startPos);
|
||||
editCursor.setPosition(endPos, QTextCursor::KeepAnchor);
|
||||
editCursor.removeSelectedText();
|
||||
editCursor.insertText(textToInsert);
|
||||
}
|
||||
|
||||
editCursor.endEditBlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RefactorSuggestion::applyWord(TextEditor::TextEditorWidget *widget)
|
||||
{
|
||||
Q_UNUSED(widget)
|
||||
return apply();
|
||||
}
|
||||
|
||||
bool RefactorSuggestion::applyLine(TextEditor::TextEditorWidget *widget)
|
||||
{
|
||||
Q_UNUSED(widget)
|
||||
return apply();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
65
RefactorSuggestion.hpp
Normal file
65
RefactorSuggestion.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2024-2025 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 <texteditor/texteditor.h>
|
||||
#include <texteditor/textsuggestion.h>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
/**
|
||||
* @brief Persistent refactoring suggestion that displays code changes inline
|
||||
*
|
||||
* Unlike LLMSuggestion which supports partial acceptance (word/line),
|
||||
* RefactorSuggestion is designed to show complete refactoring results
|
||||
* that must be either fully accepted or rejected by the user.
|
||||
*/
|
||||
class RefactorSuggestion : public TextEditor::TextSuggestion
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a refactoring suggestion
|
||||
* @param suggestion Suggestion data (range, position, text)
|
||||
* @param sourceDocument The document where suggestion will be displayed
|
||||
*/
|
||||
RefactorSuggestion(const Data &suggestion, QTextDocument *sourceDocument);
|
||||
|
||||
/**
|
||||
* @brief Applies the full refactoring suggestion with smart overlapping
|
||||
* @return true if suggestion was applied successfully
|
||||
*/
|
||||
bool apply() override;
|
||||
|
||||
/**
|
||||
* @brief Disabled: Word-by-word acceptance not supported for refactoring
|
||||
*/
|
||||
bool applyWord(TextEditor::TextEditorWidget *widget) override;
|
||||
|
||||
/**
|
||||
* @brief Disabled: Line-by-line acceptance not supported for refactoring
|
||||
*/
|
||||
bool applyLine(TextEditor::TextEditorWidget *widget) override;
|
||||
|
||||
private:
|
||||
Data m_suggestionData;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
227
RefactorSuggestionHoverHandler.cpp
Normal file
227
RefactorSuggestionHoverHandler.cpp
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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 "RefactorSuggestionHoverHandler.hpp"
|
||||
#include "RefactorSuggestion.hpp"
|
||||
|
||||
#include <QColor>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QScopeGuard>
|
||||
#include <QTextBlock>
|
||||
#include <QTextCursor>
|
||||
#include <QWidget>
|
||||
|
||||
#include <texteditor/textdocumentlayout.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
#include <utils/theme/theme.h>
|
||||
#include <utils/tooltip/tooltip.h>
|
||||
|
||||
#include <logger/Logger.hpp>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
RefactorSuggestionHoverHandler::RefactorSuggestionHoverHandler()
|
||||
{
|
||||
setPriority(Priority_Suggestion);
|
||||
}
|
||||
|
||||
void RefactorSuggestionHoverHandler::setSuggestionRange(const Utils::Text::Range &range)
|
||||
{
|
||||
m_suggestionRange = range;
|
||||
m_hasSuggestion = true;
|
||||
}
|
||||
|
||||
void RefactorSuggestionHoverHandler::clearSuggestionRange()
|
||||
{
|
||||
m_hasSuggestion = false;
|
||||
}
|
||||
|
||||
void RefactorSuggestionHoverHandler::identifyMatch(
|
||||
TextEditor::TextEditorWidget *editorWidget,
|
||||
int pos,
|
||||
ReportPriority report)
|
||||
{
|
||||
LOG_MESSAGE(QString("RefactorSuggestionHoverHandler::identifyMatch pos=%1, hasSuggestion=%2, suggestionVisible=%3")
|
||||
.arg(pos)
|
||||
.arg(m_hasSuggestion)
|
||||
.arg(editorWidget->suggestionVisible()));
|
||||
|
||||
QScopeGuard cleanup([&] { report(Priority_None); });
|
||||
|
||||
if (!editorWidget->suggestionVisible()) {
|
||||
LOG_MESSAGE("RefactorSuggestionHoverHandler: No suggestion visible");
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCursor cursor(editorWidget->document());
|
||||
cursor.setPosition(pos);
|
||||
m_block = cursor.block();
|
||||
|
||||
// Check if this block has a suggestion
|
||||
#if QODEASSIST_QT_CREATOR_VERSION_MAJOR >= 17
|
||||
auto *suggestion = dynamic_cast<RefactorSuggestion *>(
|
||||
TextEditor::TextBlockUserData::suggestion(m_block));
|
||||
#else
|
||||
auto *userData = TextEditor::TextDocumentLayout::textUserData(m_block);
|
||||
if (!userData) {
|
||||
LOG_MESSAGE("RefactorSuggestionHoverHandler: No user data in block");
|
||||
return;
|
||||
}
|
||||
|
||||
auto *suggestion = dynamic_cast<RefactorSuggestion *>(userData->suggestion());
|
||||
#endif
|
||||
|
||||
if (!suggestion) {
|
||||
LOG_MESSAGE("RefactorSuggestionHoverHandler: No RefactorSuggestion in block");
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_MESSAGE("RefactorSuggestionHoverHandler: Found RefactorSuggestion, reporting Priority_Suggestion");
|
||||
cleanup.dismiss();
|
||||
report(Priority_Suggestion);
|
||||
}
|
||||
|
||||
void RefactorSuggestionHoverHandler::operateTooltip(
|
||||
TextEditor::TextEditorWidget *editorWidget,
|
||||
const QPoint &point)
|
||||
{
|
||||
Q_UNUSED(point)
|
||||
|
||||
#if QODEASSIST_QT_CREATOR_VERSION_MAJOR >= 17
|
||||
auto *suggestion = dynamic_cast<RefactorSuggestion *>(
|
||||
TextEditor::TextBlockUserData::suggestion(m_block));
|
||||
#else
|
||||
auto *userData = TextEditor::TextDocumentLayout::textUserData(m_block);
|
||||
if (!userData) {
|
||||
LOG_MESSAGE("RefactorSuggestionHoverHandler::operateTooltip: No user data in block");
|
||||
return;
|
||||
}
|
||||
|
||||
auto *suggestion = dynamic_cast<RefactorSuggestion *>(userData->suggestion());
|
||||
#endif
|
||||
|
||||
if (!suggestion) {
|
||||
LOG_MESSAGE("RefactorSuggestionHoverHandler::operateTooltip: No suggestion in block");
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_MESSAGE("RefactorSuggestionHoverHandler::operateTooltip: Creating tooltip widget");
|
||||
|
||||
// Create compact widget with buttons
|
||||
auto *widget = new QWidget();
|
||||
auto *layout = new QHBoxLayout(widget);
|
||||
layout->setContentsMargins(4, 3, 4, 3);
|
||||
layout->setSpacing(6);
|
||||
|
||||
// Get theme colors
|
||||
const QColor normalBg = Utils::creatorColor(Utils::Theme::BackgroundColorNormal);
|
||||
const QColor hoverBg = Utils::creatorColor(Utils::Theme::BackgroundColorHover);
|
||||
const QColor selectedBg = Utils::creatorColor(Utils::Theme::BackgroundColorSelected);
|
||||
const QColor textColor = Utils::creatorColor(Utils::Theme::TextColorNormal);
|
||||
const QColor borderColor = Utils::creatorColor(Utils::Theme::SplitterColor);
|
||||
const QColor successColor = Utils::creatorColor(Utils::Theme::TextColorNormal);
|
||||
const QColor errorColor = Utils::creatorColor(Utils::Theme::TextColorError);
|
||||
|
||||
auto *applyButton = new QPushButton("✓ Apply", widget);
|
||||
applyButton->setFocusPolicy(Qt::NoFocus);
|
||||
applyButton->setToolTip("Apply refactoring (Tab)");
|
||||
applyButton->setCursor(Qt::PointingHandCursor);
|
||||
applyButton->setStyleSheet(QString(
|
||||
"QPushButton {"
|
||||
" background-color: %1;"
|
||||
" color: %2;"
|
||||
" border: 1px solid %3;"
|
||||
" border-radius: 3px;"
|
||||
" padding: 4px 12px;"
|
||||
" font-weight: bold;"
|
||||
" font-size: 11px;"
|
||||
" min-width: 60px;"
|
||||
"}"
|
||||
"QPushButton:hover {"
|
||||
" background-color: %4;"
|
||||
" border-color: %2;"
|
||||
"}"
|
||||
"QPushButton:pressed {"
|
||||
" background-color: %5;"
|
||||
"}")
|
||||
.arg(selectedBg.name())
|
||||
.arg(successColor.name())
|
||||
.arg(borderColor.name())
|
||||
.arg(selectedBg.lighter(110).name())
|
||||
.arg(selectedBg.darker(110).name()));
|
||||
QObject::connect(applyButton, &QPushButton::clicked, widget, [this]() {
|
||||
Utils::ToolTip::hide();
|
||||
if (m_applyCallback) {
|
||||
m_applyCallback();
|
||||
}
|
||||
});
|
||||
|
||||
auto *dismissButton = new QPushButton("✕ Dismiss", widget);
|
||||
dismissButton->setFocusPolicy(Qt::NoFocus);
|
||||
dismissButton->setToolTip("Dismiss refactoring (Esc)");
|
||||
dismissButton->setCursor(Qt::PointingHandCursor);
|
||||
dismissButton->setStyleSheet(QString(
|
||||
"QPushButton {"
|
||||
" background-color: %1;"
|
||||
" color: %2;"
|
||||
" border: 1px solid %3;"
|
||||
" border-radius: 3px;"
|
||||
" padding: 4px 12px;"
|
||||
" font-size: 11px;"
|
||||
" min-width: 60px;"
|
||||
"}"
|
||||
"QPushButton:hover {"
|
||||
" background-color: %4;"
|
||||
" color: %5;"
|
||||
" border-color: %5;"
|
||||
"}"
|
||||
"QPushButton:pressed {"
|
||||
" background-color: %6;"
|
||||
"}")
|
||||
.arg(normalBg.name())
|
||||
.arg(textColor.name())
|
||||
.arg(borderColor.name())
|
||||
.arg(hoverBg.name())
|
||||
.arg(errorColor.name())
|
||||
.arg(hoverBg.darker(110).name()));
|
||||
QObject::connect(dismissButton, &QPushButton::clicked, widget, [this]() {
|
||||
Utils::ToolTip::hide();
|
||||
if (m_dismissCallback) {
|
||||
m_dismissCallback();
|
||||
}
|
||||
});
|
||||
|
||||
layout->addWidget(applyButton);
|
||||
layout->addWidget(dismissButton);
|
||||
|
||||
// Position tooltip above cursor, like standard Qt Creator suggestions
|
||||
const QRect cursorRect = editorWidget->cursorRect(editorWidget->textCursor());
|
||||
QPoint pos = editorWidget->viewport()->mapToGlobal(cursorRect.topLeft())
|
||||
- Utils::ToolTip::offsetFromPosition();
|
||||
pos.ry() -= widget->sizeHint().height();
|
||||
|
||||
LOG_MESSAGE(QString("RefactorSuggestionHoverHandler::operateTooltip: Showing tooltip at (%1, %2)")
|
||||
.arg(pos.x()).arg(pos.y()));
|
||||
|
||||
Utils::ToolTip::show(pos, widget, editorWidget);
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
72
RefactorSuggestionHoverHandler.hpp
Normal file
72
RefactorSuggestionHoverHandler.hpp
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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 <functional>
|
||||
#include <QTextBlock>
|
||||
|
||||
#include <texteditor/basehoverhandler.h>
|
||||
#include <utils/textutils.h>
|
||||
|
||||
namespace TextEditor {
|
||||
class TextEditorWidget;
|
||||
}
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
/**
|
||||
* @brief Hover handler for refactoring suggestions
|
||||
*
|
||||
* Shows interactive tooltip with Apply/Dismiss buttons when hovering over
|
||||
* a refactoring suggestion in the editor.
|
||||
*/
|
||||
class RefactorSuggestionHoverHandler : public TextEditor::BaseHoverHandler
|
||||
{
|
||||
public:
|
||||
using ApplyCallback = std::function<void()>;
|
||||
using DismissCallback = std::function<void()>;
|
||||
|
||||
RefactorSuggestionHoverHandler();
|
||||
|
||||
void setSuggestionRange(const Utils::Text::Range &range);
|
||||
void clearSuggestionRange();
|
||||
bool hasSuggestion() const { return m_hasSuggestion; }
|
||||
|
||||
void setApplyCallback(ApplyCallback callback) { m_applyCallback = std::move(callback); }
|
||||
void setDismissCallback(DismissCallback callback) { m_dismissCallback = std::move(callback); }
|
||||
|
||||
protected:
|
||||
void identifyMatch(TextEditor::TextEditorWidget *editorWidget,
|
||||
int pos,
|
||||
ReportPriority report) override;
|
||||
|
||||
void operateTooltip(TextEditor::TextEditorWidget *editorWidget,
|
||||
const QPoint &point) override;
|
||||
|
||||
private:
|
||||
Utils::Text::Range m_suggestionRange;
|
||||
bool m_hasSuggestion = false;
|
||||
ApplyCallback m_applyCallback;
|
||||
DismissCallback m_dismissCallback;
|
||||
QTextBlock m_block;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@@ -53,7 +53,8 @@ public:
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type,
|
||||
bool isToolsEnabled)
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
= 0;
|
||||
virtual QList<QString> getInstalledModels(const QString &url) = 0;
|
||||
virtual QList<QString> validateRequest(const QJsonObject &request, TemplateType type) = 0;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
@@ -76,7 +77,8 @@ void ClaudeProvider::prepareRequest(
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type,
|
||||
bool isToolsEnabled)
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
@@ -93,20 +95,33 @@ void ClaudeProvider::prepareRequest(
|
||||
request["stream"] = true;
|
||||
};
|
||||
|
||||
auto applyThinkingMode = [&request](const auto &settings) {
|
||||
QJsonObject thinkingObj;
|
||||
thinkingObj["type"] = "enabled";
|
||||
thinkingObj["budget_tokens"] = settings.thinkingBudgetTokens();
|
||||
request["thinking"] = thinkingObj;
|
||||
request["max_tokens"] = settings.thinkingMaxTokens();
|
||||
request["temperature"] = 1.0;
|
||||
};
|
||||
|
||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||
applyModelParams(Settings::codeCompletionSettings());
|
||||
request["temperature"] = Settings::codeCompletionSettings().temperature();
|
||||
} else if (type == LLMCore::RequestType::QuickRefactoring) {
|
||||
const auto &qrSettings = Settings::quickRefactorSettings();
|
||||
applyModelParams(qrSettings);
|
||||
|
||||
if (isThinkingEnabled) {
|
||||
applyThinkingMode(qrSettings);
|
||||
} else {
|
||||
request["temperature"] = qrSettings.temperature();
|
||||
}
|
||||
} else {
|
||||
const auto &chatSettings = Settings::chatAssistantSettings();
|
||||
applyModelParams(chatSettings);
|
||||
|
||||
if (chatSettings.enableThinkingMode()) {
|
||||
QJsonObject thinkingObj;
|
||||
thinkingObj["type"] = "enabled";
|
||||
thinkingObj["budget_tokens"] = chatSettings.thinkingBudgetTokens();
|
||||
request["thinking"] = thinkingObj;
|
||||
request["max_tokens"] = chatSettings.thinkingMaxTokens();
|
||||
request["temperature"] = 1.0;
|
||||
if (isThinkingEnabled) {
|
||||
applyThinkingMode(chatSettings);
|
||||
} else {
|
||||
request["temperature"] = chatSettings.temperature();
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ public:
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type,
|
||||
bool isToolsEnabled) override;
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QList<QString> getInstalledModels(const QString &url) override;
|
||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||
QString apiKey() const override;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
@@ -76,7 +77,8 @@ void GoogleAIProvider::prepareRequest(
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type,
|
||||
bool isToolsEnabled)
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
@@ -97,49 +99,43 @@ void GoogleAIProvider::prepareRequest(
|
||||
request["generationConfig"] = generationConfig;
|
||||
};
|
||||
|
||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||
applyModelParams(Settings::codeCompletionSettings());
|
||||
} else {
|
||||
const auto &chatSettings = Settings::chatAssistantSettings();
|
||||
|
||||
if (chatSettings.enableThinkingMode()) {
|
||||
auto applyThinkingMode = [&request](const auto &settings) {
|
||||
QJsonObject generationConfig;
|
||||
generationConfig["maxOutputTokens"] = chatSettings.thinkingMaxTokens();
|
||||
generationConfig["maxOutputTokens"] = settings.thinkingMaxTokens();
|
||||
|
||||
if (chatSettings.useTopP())
|
||||
generationConfig["topP"] = chatSettings.topP();
|
||||
if (chatSettings.useTopK())
|
||||
generationConfig["topK"] = chatSettings.topK();
|
||||
if (settings.useTopP())
|
||||
generationConfig["topP"] = settings.topP();
|
||||
if (settings.useTopK())
|
||||
generationConfig["topK"] = settings.topK();
|
||||
|
||||
// Set temperature to 1.0 for thinking mode
|
||||
generationConfig["temperature"] = 1.0;
|
||||
|
||||
// Add thinkingConfig
|
||||
QJsonObject thinkingConfig;
|
||||
int budgetTokens = chatSettings.thinkingBudgetTokens();
|
||||
|
||||
// Dynamic thinking: -1 (let model decide)
|
||||
// Disabled: 0 (no thinking)
|
||||
// Custom budget: positive integer
|
||||
if (budgetTokens == -1) {
|
||||
// Dynamic thinking - omit budget to let model decide
|
||||
thinkingConfig["includeThoughts"] = true;
|
||||
} else if (budgetTokens == 0) {
|
||||
// Disabled thinking
|
||||
thinkingConfig["thinkingBudget"] = 0;
|
||||
thinkingConfig["includeThoughts"] = false;
|
||||
} else {
|
||||
// Custom budget
|
||||
int budgetTokens = settings.thinkingBudgetTokens();
|
||||
if (budgetTokens != -1) {
|
||||
thinkingConfig["thinkingBudget"] = budgetTokens;
|
||||
thinkingConfig["includeThoughts"] = true;
|
||||
}
|
||||
|
||||
generationConfig["thinkingConfig"] = thinkingConfig;
|
||||
request["generationConfig"] = generationConfig;
|
||||
};
|
||||
|
||||
LOG_MESSAGE(QString("Google AI thinking mode enabled: budget=%1 tokens, maxTokens=%2")
|
||||
.arg(budgetTokens)
|
||||
.arg(chatSettings.thinkingMaxTokens()));
|
||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||
applyModelParams(Settings::codeCompletionSettings());
|
||||
} else if (type == LLMCore::RequestType::QuickRefactoring) {
|
||||
const auto &qrSettings = Settings::quickRefactorSettings();
|
||||
|
||||
if (isThinkingEnabled) {
|
||||
applyThinkingMode(qrSettings);
|
||||
} else {
|
||||
applyModelParams(qrSettings);
|
||||
}
|
||||
} else {
|
||||
const auto &chatSettings = Settings::chatAssistantSettings();
|
||||
|
||||
if (isThinkingEnabled) {
|
||||
applyThinkingMode(chatSettings);
|
||||
} else {
|
||||
applyModelParams(chatSettings);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,8 @@ public:
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type,
|
||||
bool isToolsEnabled) override;
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QList<QString> getInstalledModels(const QString &url) override;
|
||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||
QString apiKey() const override;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
@@ -223,7 +224,8 @@ void LMStudioProvider::prepareRequest(
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type,
|
||||
bool isToolsEnabled)
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
@@ -247,6 +249,8 @@ void LMStudioProvider::prepareRequest(
|
||||
|
||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||
applyModelParams(Settings::codeCompletionSettings());
|
||||
} else if (type == LLMCore::RequestType::QuickRefactoring) {
|
||||
applyModelParams(Settings::quickRefactorSettings());
|
||||
} else {
|
||||
applyModelParams(Settings::chatAssistantSettings());
|
||||
}
|
||||
|
||||
@@ -41,7 +41,8 @@ public:
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type,
|
||||
bool isToolsEnabled) override;
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QList<QString> getInstalledModels(const QString &url) override;
|
||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||
QString apiKey() const override;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
|
||||
#include <QEventLoop>
|
||||
@@ -74,7 +75,8 @@ void LlamaCppProvider::prepareRequest(
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type,
|
||||
bool isToolsEnabled)
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
@@ -98,6 +100,8 @@ void LlamaCppProvider::prepareRequest(
|
||||
|
||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||
applyModelParams(Settings::codeCompletionSettings());
|
||||
} else if (type == LLMCore::RequestType::QuickRefactoring) {
|
||||
applyModelParams(Settings::quickRefactorSettings());
|
||||
} else {
|
||||
applyModelParams(Settings::chatAssistantSettings());
|
||||
}
|
||||
|
||||
@@ -41,7 +41,8 @@ public:
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type,
|
||||
bool isToolsEnabled) override;
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QList<QString> getInstalledModels(const QString &url) override;
|
||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||
QString apiKey() const override;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
@@ -244,7 +245,8 @@ void MistralAIProvider::prepareRequest(
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type,
|
||||
bool isToolsEnabled)
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
@@ -268,6 +270,8 @@ void MistralAIProvider::prepareRequest(
|
||||
|
||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||
applyModelParams(Settings::codeCompletionSettings());
|
||||
} else if (type == LLMCore::RequestType::QuickRefactoring) {
|
||||
applyModelParams(Settings::quickRefactorSettings());
|
||||
} else {
|
||||
applyModelParams(Settings::chatAssistantSettings());
|
||||
}
|
||||
|
||||
@@ -41,7 +41,8 @@ public:
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type,
|
||||
bool isToolsEnabled) override;
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QList<QString> getInstalledModels(const QString &url) override;
|
||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||
QString apiKey() const override;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
@@ -75,7 +76,8 @@ void OllamaProvider::prepareRequest(
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type,
|
||||
bool isToolsEnabled)
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
@@ -104,6 +106,8 @@ void OllamaProvider::prepareRequest(
|
||||
|
||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||
applySettings(Settings::codeCompletionSettings());
|
||||
} else if (type == LLMCore::RequestType::QuickRefactoring) {
|
||||
applySettings(Settings::quickRefactorSettings());
|
||||
} else {
|
||||
applySettings(Settings::chatAssistantSettings());
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ public:
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type,
|
||||
bool isToolsEnabled) override;
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QList<QString> getInstalledModels(const QString &url) override;
|
||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||
QString apiKey() const override;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
@@ -74,7 +75,8 @@ void OpenAICompatProvider::prepareRequest(
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type,
|
||||
bool isToolsEnabled)
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
@@ -98,6 +100,8 @@ void OpenAICompatProvider::prepareRequest(
|
||||
|
||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||
applyModelParams(Settings::codeCompletionSettings());
|
||||
} else if (type == LLMCore::RequestType::QuickRefactoring) {
|
||||
applyModelParams(Settings::quickRefactorSettings());
|
||||
} else {
|
||||
applyModelParams(Settings::chatAssistantSettings());
|
||||
}
|
||||
|
||||
@@ -41,7 +41,8 @@ public:
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type,
|
||||
bool isToolsEnabled) override;
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QList<QString> getInstalledModels(const QString &url) override;
|
||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||
QString apiKey() const override;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
@@ -75,7 +76,8 @@ void OpenAIProvider::prepareRequest(
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type,
|
||||
bool isToolsEnabled)
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
@@ -118,6 +120,8 @@ void OpenAIProvider::prepareRequest(
|
||||
|
||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||
applyModelParams(Settings::codeCompletionSettings());
|
||||
} else if (type == LLMCore::RequestType::QuickRefactoring) {
|
||||
applyModelParams(Settings::quickRefactorSettings());
|
||||
} else {
|
||||
applyModelParams(Settings::chatAssistantSettings());
|
||||
}
|
||||
|
||||
@@ -41,7 +41,8 @@ public:
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type,
|
||||
bool isToolsEnabled) override;
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QList<QString> getInstalledModels(const QString &url) override;
|
||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||
QString apiKey() const override;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <utils/aspects.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <QPushButton>
|
||||
#include <QIcon>
|
||||
|
||||
class ButtonAspect : public Utils::BaseAspect
|
||||
{
|
||||
@@ -36,6 +37,17 @@ public:
|
||||
{
|
||||
auto button = new QPushButton(m_buttonText);
|
||||
button->setVisible(m_visible);
|
||||
|
||||
if (!m_icon.isNull()) {
|
||||
button->setIcon(m_icon);
|
||||
button->setText(""); // Clear text if icon is set
|
||||
}
|
||||
|
||||
if (m_isCompact) {
|
||||
button->setMaximumWidth(30);
|
||||
button->setToolTip(m_tooltip.isEmpty() ? m_buttonText : m_tooltip);
|
||||
}
|
||||
|
||||
connect(button, &QPushButton::clicked, this, &ButtonAspect::clicked);
|
||||
connect(this, &ButtonAspect::visibleChanged, button, &QPushButton::setVisible);
|
||||
parent.addItem(button);
|
||||
@@ -50,6 +62,9 @@ public:
|
||||
}
|
||||
|
||||
QString m_buttonText;
|
||||
QIcon m_icon;
|
||||
QString m_tooltip;
|
||||
bool m_isCompact = false;
|
||||
|
||||
signals:
|
||||
void clicked();
|
||||
|
||||
@@ -7,6 +7,7 @@ add_library(QodeAssistSettings STATIC
|
||||
SettingsTr.hpp
|
||||
CodeCompletionSettings.hpp CodeCompletionSettings.cpp
|
||||
ChatAssistantSettings.hpp ChatAssistantSettings.cpp
|
||||
QuickRefactorSettings.hpp QuickRefactorSettings.cpp
|
||||
ToolsSettings.hpp ToolsSettings.cpp
|
||||
SettingsDialog.hpp SettingsDialog.cpp
|
||||
ProjectSettings.hpp ProjectSettings.cpp
|
||||
|
||||
@@ -21,11 +21,12 @@
|
||||
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/detailswidget.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/utilsicons.h>
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QTextEdit>
|
||||
#include <QTimer>
|
||||
#include <QtWidgets/qboxlayout.h>
|
||||
#include <QtWidgets/qcompleter.h>
|
||||
@@ -117,7 +118,6 @@ GeneralSettings::GeneralSettings()
|
||||
ccTemplateDescription.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||
ccTemplateDescription.setReadOnly(true);
|
||||
ccTemplateDescription.setDefaultValue("");
|
||||
ccTemplateDescription.setLabelText(TrConstants::CURRENT_TEMPLATE_DESCRIPTION);
|
||||
|
||||
// preset1
|
||||
specifyPreset1.setSettingsKey(Constants::CC_SPECIFY_PRESET1);
|
||||
@@ -203,7 +203,56 @@ GeneralSettings::GeneralSettings()
|
||||
caTemplateDescription.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||
caTemplateDescription.setReadOnly(true);
|
||||
caTemplateDescription.setDefaultValue("");
|
||||
caTemplateDescription.setLabelText(TrConstants::CURRENT_TEMPLATE_DESCRIPTION);
|
||||
|
||||
// quick refactor settings
|
||||
initStringAspect(qrProvider, Constants::QR_PROVIDER, TrConstants::PROVIDER, "Ollama");
|
||||
qrProvider.setReadOnly(true);
|
||||
qrSelectProvider.m_buttonText = TrConstants::SELECT;
|
||||
|
||||
initStringAspect(qrModel, Constants::QR_MODEL, TrConstants::MODEL, "qwen2.5-coder:7b");
|
||||
qrModel.setHistoryCompleter(Constants::QR_MODEL_HISTORY);
|
||||
qrSelectModel.m_buttonText = TrConstants::SELECT;
|
||||
|
||||
initStringAspect(qrTemplate, Constants::QR_TEMPLATE, TrConstants::TEMPLATE, "Ollama Chat");
|
||||
qrTemplate.setReadOnly(true);
|
||||
|
||||
qrSelectTemplate.m_buttonText = TrConstants::SELECT;
|
||||
|
||||
initStringAspect(qrUrl, Constants::QR_URL, TrConstants::URL, "http://localhost:11434");
|
||||
qrUrl.setHistoryCompleter(Constants::QR_URL_HISTORY);
|
||||
qrSetUrl.m_buttonText = TrConstants::SELECT;
|
||||
|
||||
qrEndpointMode.setSettingsKey(Constants::QR_ENDPOINT_MODE);
|
||||
qrEndpointMode.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
|
||||
qrEndpointMode.addOption("Auto");
|
||||
qrEndpointMode.addOption("Custom");
|
||||
qrEndpointMode.addOption("FIM");
|
||||
qrEndpointMode.addOption("Chat");
|
||||
qrEndpointMode.setDefaultValue("Auto");
|
||||
|
||||
initStringAspect(qrCustomEndpoint, Constants::QR_CUSTOM_ENDPOINT, TrConstants::ENDPOINT_MODE, "");
|
||||
qrCustomEndpoint.setHistoryCompleter(Constants::QR_CUSTOM_ENDPOINT_HISTORY);
|
||||
|
||||
qrStatus.setDisplayStyle(Utils::StringAspect::LabelDisplay);
|
||||
qrStatus.setLabelText(TrConstants::STATUS);
|
||||
qrStatus.setDefaultValue("");
|
||||
qrTest.m_buttonText = TrConstants::TEST;
|
||||
|
||||
qrTemplateDescription.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||
qrTemplateDescription.setReadOnly(true);
|
||||
qrTemplateDescription.setDefaultValue("");
|
||||
|
||||
ccShowTemplateInfo.m_icon = Utils::Icons::INFO.icon();
|
||||
ccShowTemplateInfo.m_tooltip = Tr::tr("Show template information");
|
||||
ccShowTemplateInfo.m_isCompact = true;
|
||||
|
||||
caShowTemplateInfo.m_icon = Utils::Icons::INFO.icon();
|
||||
caShowTemplateInfo.m_tooltip = Tr::tr("Show template information");
|
||||
caShowTemplateInfo.m_isCompact = true;
|
||||
|
||||
qrShowTemplateInfo.m_icon = Utils::Icons::INFO.icon();
|
||||
qrShowTemplateInfo.m_tooltip = Tr::tr("Show template information");
|
||||
qrShowTemplateInfo.m_isCompact = true;
|
||||
|
||||
readSettings();
|
||||
|
||||
@@ -215,6 +264,7 @@ GeneralSettings::GeneralSettings()
|
||||
ccCustomEndpoint.setEnabled(ccEndpointMode.stringValue() == "Custom");
|
||||
ccPreset1CustomEndpoint.setEnabled(ccPreset1EndpointMode.stringValue() == "Custom");
|
||||
caCustomEndpoint.setEnabled(caEndpointMode.stringValue() == "Custom");
|
||||
qrCustomEndpoint.setEnabled(qrEndpointMode.stringValue() == "Custom");
|
||||
|
||||
setLayouter([this]() {
|
||||
using namespace Layouting;
|
||||
@@ -224,7 +274,7 @@ GeneralSettings::GeneralSettings()
|
||||
ccGrid.addRow({ccUrl, ccSetUrl});
|
||||
ccGrid.addRow({ccCustomEndpoint, ccEndpointMode});
|
||||
ccGrid.addRow({ccModel, ccSelectModel});
|
||||
ccGrid.addRow({ccTemplate, ccSelectTemplate});
|
||||
ccGrid.addRow({ccTemplate, ccSelectTemplate, ccShowTemplateInfo});
|
||||
|
||||
auto ccPreset1Grid = Grid{};
|
||||
ccPreset1Grid.addRow({ccPreset1Provider, ccPreset1SelectProvider});
|
||||
@@ -238,21 +288,27 @@ GeneralSettings::GeneralSettings()
|
||||
caGrid.addRow({caUrl, caSetUrl});
|
||||
caGrid.addRow({caCustomEndpoint, caEndpointMode});
|
||||
caGrid.addRow({caModel, caSelectModel});
|
||||
caGrid.addRow({caTemplate, caSelectTemplate});
|
||||
caGrid.addRow({caTemplate, caSelectTemplate, caShowTemplateInfo});
|
||||
|
||||
auto qrGrid = Grid{};
|
||||
qrGrid.addRow({qrProvider, qrSelectProvider});
|
||||
qrGrid.addRow({qrUrl, qrSetUrl});
|
||||
qrGrid.addRow({qrCustomEndpoint, qrEndpointMode});
|
||||
qrGrid.addRow({qrModel, qrSelectModel});
|
||||
qrGrid.addRow({qrTemplate, qrSelectTemplate, qrShowTemplateInfo});
|
||||
|
||||
auto ccGroup = Group{
|
||||
title(TrConstants::CODE_COMPLETION),
|
||||
Column{
|
||||
ccGrid,
|
||||
ccTemplateDescription,
|
||||
Row{specifyPreset1, preset1Language, Stretch{1}},
|
||||
ccPreset1Grid}};
|
||||
|
||||
auto caGroup = Group{
|
||||
title(TrConstants::CHAT_ASSISTANT),
|
||||
Column{
|
||||
caGrid,
|
||||
caTemplateDescription}};
|
||||
title(TrConstants::CHAT_ASSISTANT), Column{caGrid}};
|
||||
|
||||
auto qrGroup = Group{
|
||||
title(TrConstants::QUICK_REFACTOR), Column{qrGrid}};
|
||||
|
||||
auto rootLayout = Column{
|
||||
Row{enableQodeAssist, Stretch{1}, Row{checkUpdate, resetToDefaults}},
|
||||
@@ -262,6 +318,8 @@ GeneralSettings::GeneralSettings()
|
||||
ccGroup,
|
||||
Space{8},
|
||||
caGroup,
|
||||
Space{8},
|
||||
qrGroup,
|
||||
Stretch{1}};
|
||||
|
||||
return rootLayout;
|
||||
@@ -307,6 +365,9 @@ void GeneralSettings::showModelsNotFoundDialog(Utils::StringAspect &aspect)
|
||||
} else if (&aspect == &caModel) {
|
||||
providerButton = &caSelectProvider;
|
||||
urlButton = &caSetUrl;
|
||||
} else if (&aspect == &qrModel) {
|
||||
providerButton = &qrSelectProvider;
|
||||
urlButton = &qrSetUrl;
|
||||
}
|
||||
|
||||
if (providerButton && urlButton) {
|
||||
@@ -357,7 +418,8 @@ void GeneralSettings::showModelsNotSupportedDialog(Utils::StringAspect &aspect)
|
||||
QString key = QString("CompleterHistory/")
|
||||
.append(
|
||||
(&aspect == &ccModel) ? Constants::CC_MODEL_HISTORY
|
||||
: Constants::CA_MODEL_HISTORY);
|
||||
: (&aspect == &caModel) ? Constants::CA_MODEL_HISTORY
|
||||
: Constants::QR_MODEL_HISTORY);
|
||||
#if QODEASSIST_QT_CREATOR_VERSION >= QT_VERSION_CHECK(18, 0, 0)
|
||||
QStringList historyList
|
||||
= Utils::QtcSettings().value(Utils::Key(key.toLocal8Bit())).toStringList();
|
||||
@@ -399,7 +461,8 @@ void GeneralSettings::showUrlSelectionDialog(
|
||||
.append(
|
||||
(&aspect == &ccUrl) ? Constants::CC_URL_HISTORY
|
||||
: (&aspect == &ccPreset1Url) ? Constants::CC_PRESET1_URL_HISTORY
|
||||
: Constants::CA_URL_HISTORY);
|
||||
: (&aspect == &caUrl) ? Constants::CA_URL_HISTORY
|
||||
: Constants::QR_URL_HISTORY);
|
||||
#if QODEASSIST_QT_CREATOR_VERSION >= QT_VERSION_CHECK(18, 0, 0)
|
||||
QStringList historyList
|
||||
= Utils::QtcSettings().value(Utils::Key(key.toLocal8Bit())).toStringList();
|
||||
@@ -431,6 +494,31 @@ void GeneralSettings::showUrlSelectionDialog(
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
void GeneralSettings::showTemplateInfoDialog(const Utils::StringAspect &descriptionAspect, const QString &templateName)
|
||||
{
|
||||
SettingsDialog dialog(Tr::tr("Template Information"));
|
||||
dialog.addLabel(QString("<b>%1:</b> %2").arg(Tr::tr("Template"), templateName));
|
||||
dialog.addSpacing();
|
||||
|
||||
auto *descriptionLabel = new QLabel(Tr::tr("Description:"));
|
||||
dialog.layout()->addWidget(descriptionLabel);
|
||||
|
||||
auto *textEdit = new QTextEdit();
|
||||
textEdit->setReadOnly(true);
|
||||
textEdit->setMinimumHeight(200);
|
||||
textEdit->setMinimumWidth(500);
|
||||
textEdit->setText(descriptionAspect.value());
|
||||
dialog.layout()->addWidget(textEdit);
|
||||
|
||||
dialog.addSpacing();
|
||||
|
||||
auto *closeButton = new QPushButton(TrConstants::CLOSE);
|
||||
connect(closeButton, &QPushButton::clicked, &dialog, &QDialog::accept);
|
||||
dialog.buttonLayout()->addWidget(closeButton);
|
||||
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
void GeneralSettings::updatePreset1Visiblity(bool state)
|
||||
{
|
||||
ccPreset1Provider.setVisible(specifyPreset1.volatileValue());
|
||||
@@ -471,6 +559,22 @@ void GeneralSettings::setupConnections()
|
||||
caCustomEndpoint.setEnabled(
|
||||
caEndpointMode.volatileValue() == caEndpointMode.indexForDisplay("Custom"));
|
||||
});
|
||||
connect(&qrEndpointMode, &Utils::BaseAspect::volatileValueChanged, this, [this]() {
|
||||
qrCustomEndpoint.setEnabled(
|
||||
qrEndpointMode.volatileValue() == qrEndpointMode.indexForDisplay("Custom"));
|
||||
});
|
||||
|
||||
connect(&ccShowTemplateInfo, &ButtonAspect::clicked, this, [this]() {
|
||||
showTemplateInfoDialog(ccTemplateDescription, ccTemplate.value());
|
||||
});
|
||||
|
||||
connect(&caShowTemplateInfo, &ButtonAspect::clicked, this, [this]() {
|
||||
showTemplateInfoDialog(caTemplateDescription, caTemplate.value());
|
||||
});
|
||||
|
||||
connect(&qrShowTemplateInfo, &ButtonAspect::clicked, this, [this]() {
|
||||
showTemplateInfoDialog(qrTemplateDescription, qrTemplate.value());
|
||||
});
|
||||
}
|
||||
|
||||
void GeneralSettings::resetPageToDefaults()
|
||||
@@ -506,6 +610,12 @@ void GeneralSettings::resetPageToDefaults()
|
||||
resetAspect(ccPreset1CustomEndpoint);
|
||||
resetAspect(caEndpointMode);
|
||||
resetAspect(caCustomEndpoint);
|
||||
resetAspect(qrProvider);
|
||||
resetAspect(qrModel);
|
||||
resetAspect(qrTemplate);
|
||||
resetAspect(qrUrl);
|
||||
resetAspect(qrEndpointMode);
|
||||
resetAspect(qrCustomEndpoint);
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <utils/aspects.h>
|
||||
#include <QPointer>
|
||||
|
||||
#include "ButtonAspect.hpp"
|
||||
|
||||
namespace Utils {
|
||||
class DetailsWidget;
|
||||
}
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
class Provider;
|
||||
}
|
||||
@@ -102,6 +107,31 @@ public:
|
||||
|
||||
Utils::StringAspect caTemplateDescription{this};
|
||||
|
||||
// quick refactor settings
|
||||
Utils::StringAspect qrProvider{this};
|
||||
ButtonAspect qrSelectProvider{this};
|
||||
|
||||
Utils::StringAspect qrModel{this};
|
||||
ButtonAspect qrSelectModel{this};
|
||||
|
||||
Utils::StringAspect qrTemplate{this};
|
||||
ButtonAspect qrSelectTemplate{this};
|
||||
|
||||
Utils::StringAspect qrUrl{this};
|
||||
ButtonAspect qrSetUrl{this};
|
||||
|
||||
Utils::SelectionAspect qrEndpointMode{this};
|
||||
Utils::StringAspect qrCustomEndpoint{this};
|
||||
|
||||
Utils::StringAspect qrStatus{this};
|
||||
ButtonAspect qrTest{this};
|
||||
|
||||
Utils::StringAspect qrTemplateDescription{this};
|
||||
|
||||
ButtonAspect ccShowTemplateInfo{this};
|
||||
ButtonAspect caShowTemplateInfo{this};
|
||||
ButtonAspect qrShowTemplateInfo{this};
|
||||
|
||||
void showSelectionDialog(
|
||||
const QStringList &data,
|
||||
Utils::StringAspect &aspect,
|
||||
@@ -114,6 +144,8 @@ public:
|
||||
|
||||
void showUrlSelectionDialog(Utils::StringAspect &aspect, const QStringList &predefinedUrls);
|
||||
|
||||
void showTemplateInfoDialog(const Utils::StringAspect &descriptionAspect, const QString &templateName);
|
||||
|
||||
void updatePreset1Visiblity(bool state);
|
||||
|
||||
private:
|
||||
|
||||
295
settings/QuickRefactorSettings.cpp
Normal file
295
settings/QuickRefactorSettings.cpp
Normal file
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright (C) 2024-2025 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 "QuickRefactorSettings.hpp"
|
||||
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "SettingsConstants.hpp"
|
||||
#include "SettingsTr.hpp"
|
||||
#include "SettingsUtils.hpp"
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
QuickRefactorSettings &quickRefactorSettings()
|
||||
{
|
||||
static QuickRefactorSettings settings;
|
||||
return settings;
|
||||
}
|
||||
|
||||
QuickRefactorSettings::QuickRefactorSettings()
|
||||
{
|
||||
setAutoApply(false);
|
||||
|
||||
setDisplayName(Tr::tr("Quick Refactor"));
|
||||
|
||||
// General Parameters Settings
|
||||
temperature.setSettingsKey(Constants::QR_TEMPERATURE);
|
||||
temperature.setLabelText(Tr::tr("Temperature:"));
|
||||
temperature.setDefaultValue(0.5);
|
||||
temperature.setRange(0.0, 2.0);
|
||||
temperature.setSingleStep(0.1);
|
||||
|
||||
maxTokens.setSettingsKey(Constants::QR_MAX_TOKENS);
|
||||
maxTokens.setLabelText(Tr::tr("Max Tokens:"));
|
||||
maxTokens.setRange(-1, 200000);
|
||||
maxTokens.setDefaultValue(2000);
|
||||
|
||||
// Advanced Parameters
|
||||
useTopP.setSettingsKey(Constants::QR_USE_TOP_P);
|
||||
useTopP.setDefaultValue(false);
|
||||
useTopP.setLabelText(Tr::tr("Top P:"));
|
||||
|
||||
topP.setSettingsKey(Constants::QR_TOP_P);
|
||||
topP.setDefaultValue(0.9);
|
||||
topP.setRange(0.0, 1.0);
|
||||
topP.setSingleStep(0.1);
|
||||
|
||||
useTopK.setSettingsKey(Constants::QR_USE_TOP_K);
|
||||
useTopK.setDefaultValue(false);
|
||||
useTopK.setLabelText(Tr::tr("Top K:"));
|
||||
|
||||
topK.setSettingsKey(Constants::QR_TOP_K);
|
||||
topK.setDefaultValue(50);
|
||||
topK.setRange(1, 1000);
|
||||
|
||||
usePresencePenalty.setSettingsKey(Constants::QR_USE_PRESENCE_PENALTY);
|
||||
usePresencePenalty.setDefaultValue(false);
|
||||
usePresencePenalty.setLabelText(Tr::tr("Presence Penalty:"));
|
||||
|
||||
presencePenalty.setSettingsKey(Constants::QR_PRESENCE_PENALTY);
|
||||
presencePenalty.setDefaultValue(0.0);
|
||||
presencePenalty.setRange(-2.0, 2.0);
|
||||
presencePenalty.setSingleStep(0.1);
|
||||
|
||||
useFrequencyPenalty.setSettingsKey(Constants::QR_USE_FREQUENCY_PENALTY);
|
||||
useFrequencyPenalty.setDefaultValue(false);
|
||||
useFrequencyPenalty.setLabelText(Tr::tr("Frequency Penalty:"));
|
||||
|
||||
frequencyPenalty.setSettingsKey(Constants::QR_FREQUENCY_PENALTY);
|
||||
frequencyPenalty.setDefaultValue(0.0);
|
||||
frequencyPenalty.setRange(-2.0, 2.0);
|
||||
frequencyPenalty.setSingleStep(0.1);
|
||||
|
||||
// Ollama Settings
|
||||
ollamaLivetime.setSettingsKey(Constants::QR_OLLAMA_LIVETIME);
|
||||
ollamaLivetime.setToolTip(
|
||||
Tr::tr("Time to suspend Ollama after completion request (in minutes), "
|
||||
"Only Ollama, -1 to disable"));
|
||||
ollamaLivetime.setLabelText("Livetime:");
|
||||
ollamaLivetime.setDefaultValue("5m");
|
||||
ollamaLivetime.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
|
||||
contextWindow.setSettingsKey(Constants::QR_OLLAMA_CONTEXT_WINDOW);
|
||||
contextWindow.setLabelText(Tr::tr("Context Window:"));
|
||||
contextWindow.setRange(-1, 10000);
|
||||
contextWindow.setDefaultValue(2048);
|
||||
|
||||
useTools.setSettingsKey(Constants::QR_USE_TOOLS);
|
||||
useTools.setLabelText(Tr::tr("Enable Tools"));
|
||||
useTools.setToolTip(
|
||||
Tr::tr("Enable AI tools/functions for quick refactoring (allows reading project files, "
|
||||
"searching code, etc.)"));
|
||||
useTools.setDefaultValue(false);
|
||||
|
||||
useThinking.setSettingsKey(Constants::QR_USE_THINKING);
|
||||
useThinking.setLabelText(Tr::tr("Enable Thinking Mode"));
|
||||
useThinking.setToolTip(
|
||||
Tr::tr("Enable extended thinking mode for complex refactoring tasks (supported by "
|
||||
"compatible models like Claude and Google AI)"));
|
||||
useThinking.setDefaultValue(false);
|
||||
|
||||
thinkingBudgetTokens.setSettingsKey(Constants::QR_THINKING_BUDGET_TOKENS);
|
||||
thinkingBudgetTokens.setLabelText(Tr::tr("Thinking Budget Tokens:"));
|
||||
thinkingBudgetTokens.setToolTip(
|
||||
Tr::tr("Number of tokens allocated for thinking process. Use -1 for dynamic thinking "
|
||||
"(model decides), 0 to disable, or positive value for custom budget"));
|
||||
thinkingBudgetTokens.setRange(-1, 100000);
|
||||
thinkingBudgetTokens.setDefaultValue(10000);
|
||||
|
||||
thinkingMaxTokens.setSettingsKey(Constants::QR_THINKING_MAX_TOKENS);
|
||||
thinkingMaxTokens.setLabelText(Tr::tr("Thinking Max Output Tokens:"));
|
||||
thinkingMaxTokens.setToolTip(
|
||||
Tr::tr("Maximum output tokens when thinking mode is enabled (includes thinking + response)"));
|
||||
thinkingMaxTokens.setRange(1000, 200000);
|
||||
thinkingMaxTokens.setDefaultValue(16000);
|
||||
|
||||
// Context Settings
|
||||
readFullFile.setSettingsKey(Constants::QR_READ_FULL_FILE);
|
||||
readFullFile.setLabelText(Tr::tr("Read Full File"));
|
||||
readFullFile.setDefaultValue(false);
|
||||
|
||||
readFileParts.setLabelText(Tr::tr("Read Strings Before Cursor:"));
|
||||
readFileParts.setDefaultValue(true);
|
||||
|
||||
readStringsBeforeCursor.setSettingsKey(Constants::QR_READ_STRINGS_BEFORE_CURSOR);
|
||||
readStringsBeforeCursor.setLabelText(Tr::tr("Lines Before Cursor/Selection:"));
|
||||
readStringsBeforeCursor.setToolTip(
|
||||
Tr::tr("Number of lines to include before cursor or selection for context"));
|
||||
readStringsBeforeCursor.setRange(0, 10000);
|
||||
readStringsBeforeCursor.setDefaultValue(50);
|
||||
|
||||
readStringsAfterCursor.setSettingsKey(Constants::QR_READ_STRINGS_AFTER_CURSOR);
|
||||
readStringsAfterCursor.setLabelText(Tr::tr("Lines After Cursor/Selection:"));
|
||||
readStringsAfterCursor.setToolTip(
|
||||
Tr::tr("Number of lines to include after cursor or selection for context"));
|
||||
readStringsAfterCursor.setRange(0, 10000);
|
||||
readStringsAfterCursor.setDefaultValue(30);
|
||||
|
||||
systemPrompt.setSettingsKey(Constants::QR_SYSTEM_PROMPT);
|
||||
systemPrompt.setLabelText(Tr::tr("System Prompt:"));
|
||||
systemPrompt.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||
systemPrompt.setDefaultValue(
|
||||
"You are an expert C++, Qt, and QML code completion assistant. Your task is to provide "
|
||||
"precise and contextually appropriate code completions to insert depending on user "
|
||||
"instructions.\n\n");
|
||||
|
||||
resetToDefaults.m_buttonText = TrConstants::RESET_TO_DEFAULTS;
|
||||
|
||||
readSettings();
|
||||
|
||||
readFileParts.setValue(!readFullFile.value());
|
||||
|
||||
setupConnections();
|
||||
|
||||
setLayouter([this]() {
|
||||
using namespace Layouting;
|
||||
|
||||
auto genGrid = Grid{};
|
||||
genGrid.addRow({Row{temperature}});
|
||||
genGrid.addRow({Row{maxTokens}});
|
||||
|
||||
auto advancedGrid = Grid{};
|
||||
advancedGrid.addRow({useTopP, topP});
|
||||
advancedGrid.addRow({useTopK, topK});
|
||||
advancedGrid.addRow({usePresencePenalty, presencePenalty});
|
||||
advancedGrid.addRow({useFrequencyPenalty, frequencyPenalty});
|
||||
|
||||
auto ollamaGrid = Grid{};
|
||||
ollamaGrid.addRow({ollamaLivetime});
|
||||
ollamaGrid.addRow({contextWindow});
|
||||
|
||||
auto toolsGrid = Grid{};
|
||||
toolsGrid.addRow({useTools});
|
||||
toolsGrid.addRow({useThinking});
|
||||
toolsGrid.addRow({thinkingBudgetTokens});
|
||||
toolsGrid.addRow({thinkingMaxTokens});
|
||||
|
||||
auto contextGrid = Grid{};
|
||||
contextGrid.addRow({Row{readFullFile}});
|
||||
contextGrid.addRow({Row{readFileParts, readStringsBeforeCursor, readStringsAfterCursor}});
|
||||
|
||||
return Column{
|
||||
Row{Stretch{1}, resetToDefaults},
|
||||
Space{8},
|
||||
Group{
|
||||
title(Tr::tr("General Parameters")),
|
||||
Row{genGrid, Stretch{1}},
|
||||
},
|
||||
Space{8},
|
||||
Group{title(Tr::tr("Advanced Parameters")), Column{Row{advancedGrid, Stretch{1}}}},
|
||||
Space{8},
|
||||
Group{title(Tr::tr("Tools Settings")), Column{Row{toolsGrid, Stretch{1}}}},
|
||||
Space{8},
|
||||
Group{title(Tr::tr("Context Settings")), Column{Row{contextGrid, Stretch{1}}}},
|
||||
Space{8},
|
||||
Group{title(Tr::tr("Prompt Settings")), Column{Row{systemPrompt}}},
|
||||
Space{8},
|
||||
Group{title(Tr::tr("Ollama Settings")), Column{Row{ollamaGrid, Stretch{1}}}},
|
||||
Stretch{1}};
|
||||
});
|
||||
}
|
||||
|
||||
void QuickRefactorSettings::setupConnections()
|
||||
{
|
||||
connect(
|
||||
&resetToDefaults,
|
||||
&ButtonAspect::clicked,
|
||||
this,
|
||||
&QuickRefactorSettings::resetSettingsToDefaults);
|
||||
|
||||
connect(&readFullFile, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
if (readFullFile.volatileValue()) {
|
||||
readFileParts.setValue(false);
|
||||
writeSettings();
|
||||
}
|
||||
});
|
||||
|
||||
connect(&readFileParts, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
if (readFileParts.volatileValue()) {
|
||||
readFullFile.setValue(false);
|
||||
writeSettings();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void QuickRefactorSettings::resetSettingsToDefaults()
|
||||
{
|
||||
QMessageBox::StandardButton reply;
|
||||
reply = QMessageBox::question(
|
||||
Core::ICore::dialogParent(),
|
||||
Tr::tr("Reset Settings"),
|
||||
Tr::tr("Are you sure you want to reset all settings to default values?"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (reply == QMessageBox::Yes) {
|
||||
resetAspect(temperature);
|
||||
resetAspect(maxTokens);
|
||||
resetAspect(useTopP);
|
||||
resetAspect(topP);
|
||||
resetAspect(useTopK);
|
||||
resetAspect(topK);
|
||||
resetAspect(usePresencePenalty);
|
||||
resetAspect(presencePenalty);
|
||||
resetAspect(useFrequencyPenalty);
|
||||
resetAspect(frequencyPenalty);
|
||||
resetAspect(ollamaLivetime);
|
||||
resetAspect(contextWindow);
|
||||
resetAspect(useTools);
|
||||
resetAspect(useThinking);
|
||||
resetAspect(thinkingBudgetTokens);
|
||||
resetAspect(thinkingMaxTokens);
|
||||
resetAspect(readFullFile);
|
||||
resetAspect(readFileParts);
|
||||
resetAspect(readStringsBeforeCursor);
|
||||
resetAspect(readStringsAfterCursor);
|
||||
resetAspect(systemPrompt);
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
|
||||
class QuickRefactorSettingsPage : public Core::IOptionsPage
|
||||
{
|
||||
public:
|
||||
QuickRefactorSettingsPage()
|
||||
{
|
||||
setId(Constants::QODE_ASSIST_QUICK_REFACTOR_SETTINGS_PAGE_ID);
|
||||
setDisplayName(Tr::tr("Quick Refactor"));
|
||||
setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY);
|
||||
setSettingsProvider([] { return &quickRefactorSettings(); });
|
||||
}
|
||||
};
|
||||
|
||||
const QuickRefactorSettingsPage quickRefactorSettingsPage;
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
|
||||
81
settings/QuickRefactorSettings.hpp
Normal file
81
settings/QuickRefactorSettings.hpp
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2024-2025 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utils/aspects.h>
|
||||
|
||||
#include "ButtonAspect.hpp"
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
class QuickRefactorSettings : public Utils::AspectContainer
|
||||
{
|
||||
public:
|
||||
QuickRefactorSettings();
|
||||
|
||||
ButtonAspect resetToDefaults{this};
|
||||
|
||||
// General Parameters Settings
|
||||
Utils::DoubleAspect temperature{this};
|
||||
Utils::IntegerAspect maxTokens{this};
|
||||
|
||||
// Advanced Parameters
|
||||
Utils::BoolAspect useTopP{this};
|
||||
Utils::DoubleAspect topP{this};
|
||||
|
||||
Utils::BoolAspect useTopK{this};
|
||||
Utils::IntegerAspect topK{this};
|
||||
|
||||
Utils::BoolAspect usePresencePenalty{this};
|
||||
Utils::DoubleAspect presencePenalty{this};
|
||||
|
||||
Utils::BoolAspect useFrequencyPenalty{this};
|
||||
Utils::DoubleAspect frequencyPenalty{this};
|
||||
|
||||
// Ollama Settings
|
||||
Utils::StringAspect ollamaLivetime{this};
|
||||
Utils::IntegerAspect contextWindow{this};
|
||||
|
||||
// Tools Settings
|
||||
Utils::BoolAspect useTools{this};
|
||||
|
||||
// Thinking Settings
|
||||
Utils::BoolAspect useThinking{this};
|
||||
Utils::IntegerAspect thinkingBudgetTokens{this};
|
||||
Utils::IntegerAspect thinkingMaxTokens{this};
|
||||
|
||||
// Context Settings
|
||||
Utils::BoolAspect readFullFile{this};
|
||||
Utils::BoolAspect readFileParts{this};
|
||||
Utils::IntegerAspect readStringsBeforeCursor{this};
|
||||
Utils::IntegerAspect readStringsAfterCursor{this};
|
||||
|
||||
// Prompt Settings
|
||||
Utils::StringAspect systemPrompt{this};
|
||||
|
||||
private:
|
||||
void setupConnections();
|
||||
void resetSettingsToDefaults();
|
||||
};
|
||||
|
||||
QuickRefactorSettings &quickRefactorSettings();
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
|
||||
@@ -49,6 +49,17 @@ const char CA_ENDPOINT_MODE[] = "QodeAssist.caEndpointMode";
|
||||
const char CA_CUSTOM_ENDPOINT[] = "QodeAssist.caCustomEndpoint";
|
||||
const char CA_CUSTOM_ENDPOINT_HISTORY[] = "QodeAssist.caCustomEndpointHistory";
|
||||
|
||||
// quick refactor settings
|
||||
const char QR_PROVIDER[] = "QodeAssist.qrProvider";
|
||||
const char QR_MODEL[] = "QodeAssist.qrModel";
|
||||
const char QR_MODEL_HISTORY[] = "QodeAssist.qrModelHistory";
|
||||
const char QR_TEMPLATE[] = "QodeAssist.qrTemplate";
|
||||
const char QR_URL[] = "QodeAssist.qrUrl";
|
||||
const char QR_URL_HISTORY[] = "QodeAssist.qrUrlHistory";
|
||||
const char QR_ENDPOINT_MODE[] = "QodeAssist.qrEndpointMode";
|
||||
const char QR_CUSTOM_ENDPOINT[] = "QodeAssist.qrCustomEndpoint";
|
||||
const char QR_CUSTOM_ENDPOINT_HISTORY[] = "QodeAssist.qrCustomEndpointHistory";
|
||||
|
||||
const char CC_SPECIFY_PRESET1[] = "QodeAssist.ccSpecifyPreset1";
|
||||
const char CC_PRESET1_LANGUAGE[] = "QodeAssist.ccPreset1Language";
|
||||
const char CC_PRESET1_PROVIDER[] = "QodeAssist.ccPreset1Provider";
|
||||
@@ -98,14 +109,16 @@ const char QODE_ASSIST_CODE_COMPLETION_SETTINGS_PAGE_ID[]
|
||||
= "QodeAssist.2CodeCompletionSettingsPageId";
|
||||
const char QODE_ASSIST_CHAT_ASSISTANT_SETTINGS_PAGE_ID[]
|
||||
= "QodeAssist.3ChatAssistantSettingsPageId";
|
||||
const char QODE_ASSIST_TOOLS_SETTINGS_PAGE_ID[] = "QodeAssist.4ToolsSettingsPageId";
|
||||
const char QODE_ASSIST_CUSTOM_PROMPT_SETTINGS_PAGE_ID[] = "QodeAssist.5CustomPromptSettingsPageId";
|
||||
const char QODE_ASSIST_QUICK_REFACTOR_SETTINGS_PAGE_ID[]
|
||||
= "QodeAssist.4QuickRefactorSettingsPageId";
|
||||
const char QODE_ASSIST_TOOLS_SETTINGS_PAGE_ID[] = "QodeAssist.5ToolsSettingsPageId";
|
||||
const char QODE_ASSIST_CUSTOM_PROMPT_SETTINGS_PAGE_ID[] = "QodeAssist.6CustomPromptSettingsPageId";
|
||||
|
||||
const char QODE_ASSIST_GENERAL_OPTIONS_CATEGORY[] = "QodeAssist.Category";
|
||||
const char QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY[] = "QodeAssist";
|
||||
|
||||
// Provider Settings Page ID
|
||||
const char QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID[] = "QodeAssist.6ProviderSettingsPageId";
|
||||
const char QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID[] = "QodeAssist.7ProviderSettingsPageId";
|
||||
|
||||
// Provider API Keys
|
||||
const char OPEN_ROUTER_API_KEY[] = "QodeAssist.openRouterApiKey";
|
||||
@@ -178,4 +191,26 @@ const char CA_CODE_FONT_SIZE[] = "QodeAssist.caCodeFontSize";
|
||||
const char CA_TEXT_FORMAT[] = "QodeAssist.caTextFormat";
|
||||
const char CA_CHAT_RENDERER[] = "QodeAssist.caChatRenderer";
|
||||
|
||||
// quick refactor preset prompt settings
|
||||
const char QR_TEMPERATURE[] = "QodeAssist.qrTemperature";
|
||||
const char QR_MAX_TOKENS[] = "QodeAssist.qrMaxTokens";
|
||||
const char QR_USE_TOP_P[] = "QodeAssist.qrUseTopP";
|
||||
const char QR_TOP_P[] = "QodeAssist.qrTopP";
|
||||
const char QR_USE_TOP_K[] = "QodeAssist.qrUseTopK";
|
||||
const char QR_TOP_K[] = "QodeAssist.qrTopK";
|
||||
const char QR_USE_PRESENCE_PENALTY[] = "QodeAssist.qrUsePresencePenalty";
|
||||
const char QR_PRESENCE_PENALTY[] = "QodeAssist.qrPresencePenalty";
|
||||
const char QR_USE_FREQUENCY_PENALTY[] = "QodeAssist.qrUseFrequencyPenalty";
|
||||
const char QR_FREQUENCY_PENALTY[] = "QodeAssist.qrFrequencyPenalty";
|
||||
const char QR_OLLAMA_LIVETIME[] = "QodeAssist.qrOllamaLivetime";
|
||||
const char QR_OLLAMA_CONTEXT_WINDOW[] = "QodeAssist.qrOllamaContextWindow";
|
||||
const char QR_USE_TOOLS[] = "QodeAssist.qrUseTools";
|
||||
const char QR_USE_THINKING[] = "QodeAssist.qrUseThinking";
|
||||
const char QR_THINKING_BUDGET_TOKENS[] = "QodeAssist.qrThinkingBudgetTokens";
|
||||
const char QR_THINKING_MAX_TOKENS[] = "QodeAssist.qrThinkingMaxTokens";
|
||||
const char QR_READ_FULL_FILE[] = "QodeAssist.qrReadFullFile";
|
||||
const char QR_READ_STRINGS_BEFORE_CURSOR[] = "QodeAssist.qrReadStringsBeforeCursor";
|
||||
const char QR_READ_STRINGS_AFTER_CURSOR[] = "QodeAssist.qrReadStringsAfterCursor";
|
||||
const char QR_SYSTEM_PROMPT[] = "QodeAssist.qrSystemPrompt";
|
||||
|
||||
} // namespace QodeAssist::Constants
|
||||
|
||||
@@ -48,6 +48,7 @@ inline const char *ENDPOINT_MODE = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Endpoin
|
||||
|
||||
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 *QUICK_REFACTOR = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Quick Refactor");
|
||||
inline const char *RESET_SETTINGS = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Reset Settings");
|
||||
inline const char *CONFIRMATION = QT_TRANSLATE_NOOP(
|
||||
"QtC::QodeAssist", "Are you sure you want to reset all settings to default values?");
|
||||
|
||||
@@ -68,7 +68,9 @@ public:
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *promptTemplate,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType requestType) override
|
||||
LLMCore::RequestType requestType,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override
|
||||
{
|
||||
promptTemplate->prepareRequest(request, context);
|
||||
}
|
||||
|
||||
100
widgets/CompletionErrorHandler.cpp
Normal file
100
widgets/CompletionErrorHandler.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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 "CompletionErrorHandler.hpp"
|
||||
|
||||
#include <texteditor/texteditor.h>
|
||||
#include <utils/tooltip/tooltip.h>
|
||||
|
||||
#include "ErrorWidget.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
void CompletionErrorHandler::showError(
|
||||
TextEditor::TextEditorWidget *widget,
|
||||
const QString &errorMessage,
|
||||
int autoHideMs)
|
||||
{
|
||||
m_widget = widget;
|
||||
m_errorMessage = errorMessage;
|
||||
|
||||
if (m_widget) {
|
||||
const QRect cursorRect = m_widget->cursorRect(m_widget->textCursor());
|
||||
m_errorPosition = m_widget->viewport()->mapToGlobal(cursorRect.topLeft())
|
||||
- Utils::ToolTip::offsetFromPosition();
|
||||
|
||||
identifyMatch(m_widget, m_widget->textCursor().position(), [this, autoHideMs](auto priority) {
|
||||
if (priority != Priority_None) {
|
||||
if (m_errorWidget) {
|
||||
m_errorWidget->deleteLater();
|
||||
}
|
||||
|
||||
m_errorWidget = new ErrorWidget(m_errorMessage, m_widget);
|
||||
|
||||
const QRect cursorRect = m_widget->cursorRect(m_widget->textCursor());
|
||||
QPoint globalPos = m_widget->viewport()->mapToGlobal(cursorRect.topLeft());
|
||||
QPoint localPos = m_widget->mapFromGlobal(globalPos);
|
||||
localPos.ry() -= m_errorWidget->height() + 5;
|
||||
|
||||
if (localPos.y() < 0) {
|
||||
localPos.ry() = cursorRect.bottom() + 5;
|
||||
}
|
||||
|
||||
m_errorWidget->move(localPos);
|
||||
m_errorWidget->show();
|
||||
m_errorWidget->raise();
|
||||
|
||||
QObject::connect(m_errorWidget, &ErrorWidget::dismissed, m_errorWidget, [this]() {
|
||||
hideError();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void CompletionErrorHandler::hideError()
|
||||
{
|
||||
if (m_errorWidget) {
|
||||
m_errorWidget->deleteLater();
|
||||
m_errorWidget = nullptr;
|
||||
}
|
||||
Utils::ToolTip::hideImmediately();
|
||||
m_errorMessage.clear();
|
||||
}
|
||||
|
||||
void CompletionErrorHandler::identifyMatch(
|
||||
TextEditor::TextEditorWidget *editorWidget, int pos, ReportPriority report)
|
||||
{
|
||||
if (!editorWidget || m_errorMessage.isEmpty()) {
|
||||
report(Priority_None);
|
||||
return;
|
||||
}
|
||||
|
||||
report(Priority_Tooltip);
|
||||
}
|
||||
|
||||
void CompletionErrorHandler::operateTooltip(
|
||||
TextEditor::TextEditorWidget *editorWidget, const QPoint &point)
|
||||
{
|
||||
Q_UNUSED(editorWidget)
|
||||
Q_UNUSED(point)
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
54
widgets/CompletionErrorHandler.hpp
Normal file
54
widgets/CompletionErrorHandler.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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 <texteditor/basehoverhandler.h>
|
||||
#include <QPointer>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
class ErrorWidget;
|
||||
|
||||
class CompletionErrorHandler : public TextEditor::BaseHoverHandler
|
||||
{
|
||||
public:
|
||||
void showError(
|
||||
TextEditor::TextEditorWidget *widget,
|
||||
const QString &errorMessage,
|
||||
int autoHideMs = 5000);
|
||||
|
||||
void hideError();
|
||||
|
||||
bool isErrorVisible() const { return !m_errorWidget.isNull(); }
|
||||
|
||||
protected:
|
||||
void identifyMatch(
|
||||
TextEditor::TextEditorWidget *editorWidget, int pos, ReportPriority report) override;
|
||||
void operateTooltip(TextEditor::TextEditorWidget *editorWidget, const QPoint &point) override;
|
||||
|
||||
private:
|
||||
QPointer<TextEditor::TextEditorWidget> m_widget;
|
||||
QPointer<ErrorWidget> m_errorWidget;
|
||||
QString m_errorMessage;
|
||||
QPoint m_errorPosition;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@@ -53,6 +53,10 @@ void CompletionProgressHandler::showProgress(TextEditor::TextEditorWidget *widge
|
||||
|
||||
void CompletionProgressHandler::hideProgress()
|
||||
{
|
||||
if (m_progressWidget) {
|
||||
m_progressWidget->deleteLater();
|
||||
m_progressWidget = nullptr;
|
||||
}
|
||||
Utils::ToolTip::hideImmediately();
|
||||
}
|
||||
|
||||
@@ -73,12 +77,24 @@ void CompletionProgressHandler::operateTooltip(
|
||||
if (!editorWidget)
|
||||
return;
|
||||
|
||||
auto progressWidget = new ProgressWidget(editorWidget);
|
||||
if (m_progressWidget) {
|
||||
delete m_progressWidget;
|
||||
}
|
||||
|
||||
QPoint showPoint = point;
|
||||
showPoint.ry() -= progressWidget->height();
|
||||
m_progressWidget = new ProgressWidget(editorWidget);
|
||||
|
||||
Utils::ToolTip::show(showPoint, progressWidget, editorWidget);
|
||||
const QRect cursorRect = editorWidget->cursorRect(editorWidget->textCursor());
|
||||
QPoint globalPos = editorWidget->viewport()->mapToGlobal(cursorRect.topLeft());
|
||||
QPoint localPos = editorWidget->mapFromGlobal(globalPos);
|
||||
localPos.ry() -= m_progressWidget->height() + 5;
|
||||
|
||||
if (localPos.y() < 0) {
|
||||
localPos.ry() = cursorRect.bottom() + 5;
|
||||
}
|
||||
|
||||
m_progressWidget->move(localPos);
|
||||
m_progressWidget->show();
|
||||
m_progressWidget->raise();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
class ProgressWidget;
|
||||
|
||||
class CompletionProgressHandler : public TextEditor::BaseHoverHandler
|
||||
{
|
||||
public:
|
||||
@@ -37,6 +39,7 @@ protected:
|
||||
|
||||
private:
|
||||
QPointer<TextEditor::TextEditorWidget> m_widget;
|
||||
QPointer<ProgressWidget> m_progressWidget;
|
||||
QPoint m_iconPosition;
|
||||
};
|
||||
|
||||
|
||||
213
widgets/ErrorWidget.cpp
Normal file
213
widgets/ErrorWidget.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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 "ErrorWidget.hpp"
|
||||
|
||||
#include <QFontMetrics>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainterPath>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
ErrorWidget::ErrorWidget(const QString &errorMessage, QWidget *parent, int autoHideMs)
|
||||
: QWidget(parent)
|
||||
, m_errorMessage(errorMessage)
|
||||
, m_autoHideTimer(nullptr)
|
||||
, m_isHovered(false)
|
||||
{
|
||||
setupColors();
|
||||
setupIcon();
|
||||
|
||||
QFont errorFont = font();
|
||||
errorFont.setPointSize(qMax(8, errorFont.pointSize() - 2));
|
||||
setFont(errorFont);
|
||||
|
||||
setFixedSize(calculateSize());
|
||||
setAttribute(Qt::WA_TranslucentBackground);
|
||||
setMouseTracking(true);
|
||||
|
||||
if (autoHideMs > 0) {
|
||||
m_autoHideTimer = new QTimer(this);
|
||||
m_autoHideTimer->setSingleShot(true);
|
||||
connect(m_autoHideTimer, &QTimer::timeout, this, [this]() {
|
||||
if (!m_isHovered) {
|
||||
emit dismissed();
|
||||
deleteLater();
|
||||
}
|
||||
});
|
||||
m_autoHideTimer->start(autoHideMs);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorWidget::~ErrorWidget()
|
||||
{
|
||||
if (m_autoHideTimer) {
|
||||
m_autoHideTimer->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorWidget::setErrorMessage(const QString &message)
|
||||
{
|
||||
m_errorMessage = message;
|
||||
QFont smallFont = font();
|
||||
smallFont.setPointSize(qMax(8, smallFont.pointSize() - 2));
|
||||
setFont(smallFont);
|
||||
setFixedSize(calculateSize());
|
||||
update();
|
||||
}
|
||||
|
||||
void ErrorWidget::setupColors()
|
||||
{
|
||||
m_textColor = Utils::creatorTheme()->color(Utils::Theme::TextColorNormal);
|
||||
m_backgroundColor = Utils::creatorTheme()->color(Utils::Theme::BackgroundColorNormal);
|
||||
m_errorColor = Utils::creatorTheme()->color(Utils::Theme::TextColorError);
|
||||
}
|
||||
|
||||
void ErrorWidget::setupIcon()
|
||||
{
|
||||
// Create a smaller error icon (exclamation mark in a circle)
|
||||
QPixmap pixmap(18, 18);
|
||||
pixmap.fill(Qt::transparent);
|
||||
|
||||
QPainter painter(&pixmap);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
// Draw circle
|
||||
painter.setPen(QPen(m_errorColor, 1.5));
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
painter.drawEllipse(1, 1, 16, 16);
|
||||
|
||||
// Draw exclamation mark
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(m_errorColor);
|
||||
|
||||
// Vertical bar of exclamation
|
||||
painter.drawRect(8, 4, 2, 8);
|
||||
|
||||
// Dot of exclamation
|
||||
painter.drawRect(8, 13, 2, 2);
|
||||
|
||||
m_errorIcon = pixmap;
|
||||
}
|
||||
|
||||
QSize ErrorWidget::calculateSize() const
|
||||
{
|
||||
QFontMetrics fm(font());
|
||||
|
||||
// Maximum width for the text area
|
||||
const int maxTextWidth = 350;
|
||||
const int iconWidth = 18;
|
||||
const int padding = 8;
|
||||
const int margin = 12;
|
||||
|
||||
// Calculate text area with word wrapping
|
||||
QRect textRect = fm.boundingRect(
|
||||
0, 0, maxTextWidth, 1000,
|
||||
Qt::AlignLeft | Qt::TextWordWrap,
|
||||
m_errorMessage
|
||||
);
|
||||
|
||||
// Total width: margin + icon + padding + text + margin
|
||||
int totalWidth = margin + iconWidth + padding + textRect.width() + margin;
|
||||
|
||||
// Total height: larger of icon or text, plus vertical margins
|
||||
int contentHeight = qMax(iconWidth, textRect.height());
|
||||
int totalHeight = contentHeight + margin * 2;
|
||||
|
||||
return QSize(totalWidth, totalHeight);
|
||||
}
|
||||
|
||||
void ErrorWidget::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setRenderHint(QPainter::TextAntialiasing);
|
||||
|
||||
// Draw background with border
|
||||
QColor bgColor = m_backgroundColor;
|
||||
if (m_isHovered) {
|
||||
bgColor = bgColor.lighter(110);
|
||||
}
|
||||
|
||||
QPainterPath path;
|
||||
path.addRoundedRect(rect().adjusted(1, 1, -1, -1), 4, 4);
|
||||
|
||||
painter.fillPath(path, bgColor);
|
||||
painter.setPen(QPen(m_errorColor.lighter(150), 1));
|
||||
painter.drawPath(path);
|
||||
|
||||
const int iconSize = 18;
|
||||
const int padding = 8;
|
||||
const int margin = 12;
|
||||
|
||||
// Draw error icon
|
||||
if (!m_errorIcon.isNull()) {
|
||||
QRect iconRect(margin, margin, iconSize, iconSize);
|
||||
painter.drawPixmap(iconRect, m_errorIcon);
|
||||
}
|
||||
|
||||
// Draw error message with word wrap
|
||||
painter.setPen(m_textColor);
|
||||
QRect textRect = rect().adjusted(
|
||||
margin + iconSize + padding, // left
|
||||
margin, // top
|
||||
-margin, // right
|
||||
-margin // bottom
|
||||
);
|
||||
|
||||
painter.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, m_errorMessage);
|
||||
}
|
||||
|
||||
void ErrorWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
emit dismissed();
|
||||
deleteLater();
|
||||
}
|
||||
QWidget::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void ErrorWidget::enterEvent(QEnterEvent *event)
|
||||
{
|
||||
m_isHovered = true;
|
||||
update();
|
||||
|
||||
// Stop auto-hide timer while hovering
|
||||
if (m_autoHideTimer && m_autoHideTimer->isActive()) {
|
||||
m_autoHideTimer->stop();
|
||||
}
|
||||
|
||||
QWidget::enterEvent(event);
|
||||
}
|
||||
|
||||
void ErrorWidget::leaveEvent(QEvent *event)
|
||||
{
|
||||
m_isHovered = false;
|
||||
update();
|
||||
|
||||
// Restart auto-hide timer when leaving
|
||||
if (m_autoHideTimer) {
|
||||
m_autoHideTimer->start(2000); // Give 2 more seconds after leaving
|
||||
}
|
||||
|
||||
QWidget::leaveEvent(event);
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
65
widgets/ErrorWidget.hpp
Normal file
65
widgets/ErrorWidget.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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 <QPainter>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
#include <utils/theme/theme.h>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
class ErrorWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ErrorWidget(const QString &errorMessage, QWidget *parent = nullptr, int autoHideMs = 5000);
|
||||
~ErrorWidget();
|
||||
|
||||
void setErrorMessage(const QString &message);
|
||||
|
||||
QString errorMessage() const { return m_errorMessage; }
|
||||
|
||||
signals:
|
||||
void dismissed();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
void leaveEvent(QEvent *event) override;
|
||||
|
||||
private:
|
||||
QString m_errorMessage;
|
||||
QTimer *m_autoHideTimer;
|
||||
QColor m_textColor;
|
||||
QColor m_backgroundColor;
|
||||
QColor m_errorColor;
|
||||
QPixmap m_errorIcon;
|
||||
bool m_isHovered;
|
||||
|
||||
void setupColors();
|
||||
void setupIcon();
|
||||
QSize calculateSize() const;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
Reference in New Issue
Block a user