/* * 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 . */ #include "QuickRefactorHandler.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace QodeAssist { QuickRefactorHandler::QuickRefactorHandler(QObject *parent) : QObject(parent) , m_requestHandler(new LLMCore::RequestHandler(this)) , m_currentEditor(nullptr) , m_isRefactoringInProgress(false) , m_contextManager(this) { connect( m_requestHandler, &LLMCore::RequestHandler::completionReceived, this, &QuickRefactorHandler::handleLLMResponse); connect( m_requestHandler, &LLMCore::RequestHandler::requestFinished, this, [this](const QString &requestId, bool success, const QString &errorString) { if (!success && requestId == m_lastRequestId) { m_isRefactoringInProgress = false; RefactorResult result; result.success = false; result.errorMessage = errorString; emit refactoringCompleted(result); } }); } QuickRefactorHandler::~QuickRefactorHandler() {} void QuickRefactorHandler::sendRefactorRequest( TextEditor::TextEditorWidget *editor, const QString &instructions) { if (m_isRefactoringInProgress) { cancelRequest(); } m_currentEditor = editor; Utils::Text::Range range; if (editor->textCursor().hasSelection()) { QTextCursor cursor = editor->textCursor(); int startPos = cursor.selectionStart(); int endPos = cursor.selectionEnd(); QTextBlock startBlock = editor->document()->findBlock(startPos); int startLine = startBlock.blockNumber() + 1; int startColumn = startPos - startBlock.position(); QTextBlock endBlock = editor->document()->findBlock(endPos); int endLine = endBlock.blockNumber() + 1; int endColumn = endPos - endBlock.position(); Utils::Text::Position startPosition; startPosition.line = startLine; startPosition.column = startColumn; Utils::Text::Position endPosition; endPosition.line = endLine; endPosition.column = endColumn; range = Utils::Text::Range(); range.begin = startPosition; range.end = endPosition; } else { QTextCursor cursor = editor->textCursor(); int cursorPos = cursor.position(); QTextBlock block = editor->document()->findBlock(cursorPos); int line = block.blockNumber() + 1; int column = cursorPos - block.position(); Utils::Text::Position cursorPosition; cursorPosition.line = line; cursorPosition.column = column; range = Utils::Text::Range(); range.begin = cursorPosition; range.end = cursorPosition; } m_currentRange = range; prepareAndSendRequest(editor, instructions, range); } void QuickRefactorHandler::prepareAndSendRequest( TextEditor::TextEditorWidget *editor, const QString &instructions, const Utils::Text::Range &range) { auto &settings = Settings::generalSettings(); auto &providerRegistry = LLMCore::ProvidersManager::instance(); auto &promptManager = LLMCore::PromptTemplateManager::instance(); const auto providerName = settings.caProvider(); auto provider = providerRegistry.getProviderByName(providerName); if (!provider) { LOG_MESSAGE(QString("No provider found with name: %1").arg(providerName)); RefactorResult result; result.success = false; result.errorMessage = QString("No provider found with name: %1").arg(providerName); emit refactoringCompleted(result); return; } const auto templateName = settings.caTemplate(); auto promptTemplate = promptManager.getChatTemplateByName(templateName); if (!promptTemplate) { LOG_MESSAGE(QString("No template found with name: %1").arg(templateName)); RefactorResult result; result.success = false; result.errorMessage = QString("No template found with name: %1").arg(templateName); emit refactoringCompleted(result); return; } LLMCore::LLMConfig config; config.requestType = LLMCore::RequestType::Chat; config.provider = provider; config.promptTemplate = promptTemplate; config.url = QString("%1%2").arg(settings.caUrl(), provider->chatEndpoint()); config.providerRequest = {{"model", settings.caModel()}, {"stream", Settings::chatAssistantSettings().stream()}}; config.apiKey = provider->apiKey(); LLMCore::ContextData context = prepareContext(editor, range, instructions); provider ->prepareRequest(config.providerRequest, promptTemplate, context, LLMCore::RequestType::Chat); QString requestId = QUuid::createUuid().toString(); m_lastRequestId = requestId; QJsonObject request{{"id", requestId}}; m_isRefactoringInProgress = true; m_requestHandler->sendLLMRequest(config, request); } LLMCore::ContextData QuickRefactorHandler::prepareContext( TextEditor::TextEditorWidget *editor, const Utils::Text::Range &range, const QString &instructions) { LLMCore::ContextData context; auto textDocument = editor->textDocument(); Context::DocumentReaderQtCreator documentReader; auto documentInfo = documentReader.readDocument(textDocument->filePath().toUrlishString()); if (!documentInfo.document) { LOG_MESSAGE("Error: Document is not available"); return context; } 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; if (cursor.hasSelection()) { int selEnd = cursor.selectionEnd(); int selStart = cursor.selectionStart(); taggedContent .insert(selEnd, selEnd == cursorPos ? "" : ""); taggedContent.insert( selStart, selStart == cursorPos ? "" : ""); } else { taggedContent.insert(cursorPos, ""); } QString systemPrompt = Settings::codeCompletionSettings().quickRefactorSystemPrompt(); systemPrompt += "\n\nFile information:"; systemPrompt += "\nLanguage: " + documentInfo.mimeType; systemPrompt += "\nFile path: " + documentInfo.filePath; systemPrompt += "\n\nCode context with position markers:"; systemPrompt += taggedContent; systemPrompt += "\n\nOutput format:"; systemPrompt += "\n- Generate ONLY the code that should replace the current selection " "between or be " "inserted at cursor position"; systemPrompt += "\n- Do not include any explanations, comments about the code, or markdown " "code block markers"; systemPrompt += "\n- The output should be ready to insert directly into the editor"; systemPrompt += "\n- Follow the existing code style and indentation patterns"; if (Settings::codeCompletionSettings().useOpenFilesInQuickRefactor()) { systemPrompt += "\n\n" + m_contextManager.openedFilesContext({documentInfo.filePath}); } context.systemPrompt = systemPrompt; QVector messages; messages.append( {"user", instructions.isEmpty() ? "Refactor the code to improve its quality and maintainability." : instructions}); context.history = messages; return context; } void QuickRefactorHandler::handleLLMResponse( const QString &response, const QJsonObject &request, bool isComplete) { if (request["id"].toString() != m_lastRequestId) { return; } if (isComplete) { QString cleanedResponse = response.trimmed(); if (cleanedResponse.startsWith("```")) { int firstNewLine = cleanedResponse.indexOf('\n'); int lastFence = cleanedResponse.lastIndexOf("```"); if (firstNewLine != -1 && lastFence > firstNewLine) { cleanedResponse = cleanedResponse.mid(firstNewLine + 1, lastFence - firstNewLine - 1).trimmed(); } else if (lastFence != -1) { cleanedResponse = cleanedResponse.mid(3, lastFence - 3).trimmed(); } } RefactorResult result; result.newText = cleanedResponse; result.insertRange = m_currentRange; result.success = true; LOG_MESSAGE("Refactoring completed successfully. New code to insert: "); LOG_MESSAGE("---------- BEGIN REFACTORED CODE ----------"); LOG_MESSAGE(cleanedResponse); LOG_MESSAGE("----------- END REFACTORED CODE -----------"); emit refactoringCompleted(result); } } void QuickRefactorHandler::cancelRequest() { if (m_isRefactoringInProgress) { m_requestHandler->cancelRequest(m_lastRequestId); m_isRefactoringInProgress = false; RefactorResult result; result.success = false; result.errorMessage = "Refactoring request was cancelled"; emit refactoringCompleted(result); } } } // namespace QodeAssist