mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-02-10 01:00:23 -05:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aac58d6933 | |||
| 93c69df1b9 | |||
| 4b0b589d0e | |||
| 04c44f5916 | |||
| 6e56646b4c | |||
| c89fe1451b | |||
| 938636ab48 | |||
| e3a2b5a64c | |||
| 0ae12e0fc6 | |||
| 793b855819 | |||
| 8e052ff45c | |||
| 2fb876ff00 | |||
| cd1a9e16e0 | |||
| f07610df5c | |||
| 7cd35082b2 | |||
| c1dd59e65c | |||
| faaf59f163 | |||
| 539a220771 | |||
| 397dd33a96 | |||
| 9361c27d6e | |||
| 15af137728 | |||
| 24ad5fd015 | |||
| 216c28aa5e | |||
| f64ea42071 | |||
| 314ba06db1 | |||
| 384e07ba62 | |||
| 4d06541a36 | |||
| 356f28a97b | |||
| b5ca11ed38 | |||
| d49cd07dd0 | |||
| 8e61651bae | |||
| 1e0063a7bb | |||
| 44add994b9 | |||
| 99136d7b76 | |||
| da7abbd9be | |||
| 16035d6de6 | |||
| 4891cc4b1e | |||
| 7370258485 | |||
| a1df602182 | |||
| a1b813206a | |||
| ada65e9ef7 | |||
| 753365ea52 | |||
| 4d9adf75ff | |||
| a974b0aa82 | |||
| 6703a7026d | |||
| d7fc62f94b | |||
| dec8967df2 | |||
| 4f0f9338dc | |||
| ec26a31ec5 | |||
| 55d359e44e | |||
| 46258a11f6 | |||
| 4bccd8db91 | |||
| e3495e10f0 | |||
| d97a3514cc | |||
| fa79803836 | |||
| b4f969908f | |||
| 9beb48ee97 | |||
| d6320b04f7 | |||
| 7797007160 | |||
| a613ea19f4 | |||
| 1201da6af3 | |||
| 6bd6edf54d | |||
| 61aae8e3ba | |||
| 089bd0594e |
15
.github/FUNDING.yml
vendored
Normal file
15
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: petrmdev
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
polar: # Replace with a single Polar username
|
||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||
thanks_dev: # Replace with a single thanks.dev username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@ -32,18 +32,34 @@ add_qtc_plugin(QodeAssist
|
||||
LLMClientInterface.hpp LLMClientInterface.cpp
|
||||
PromptTemplateManager.hpp PromptTemplateManager.cpp
|
||||
templates/PromptTemplate.hpp
|
||||
templates/CodeLLamaTemplate.hpp
|
||||
templates/CodeLlamaFimTemplate.hpp
|
||||
templates/StarCoder2Template.hpp
|
||||
templates/DeepSeekCoderV2.hpp
|
||||
templates/CustomTemplate.hpp
|
||||
templates/DeepSeekCoderChatTemplate.hpp
|
||||
templates/CodeLlamaInstruct.hpp
|
||||
providers/LLMProvider.hpp
|
||||
providers/OllamaProvider.hpp providers/OllamaProvider.cpp
|
||||
providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp
|
||||
providers/OpenAICompatProvider.hpp providers/OpenAICompatProvider.cpp
|
||||
LLMProvidersManager.hpp LLMProvidersManager.cpp
|
||||
QodeAssistSettings.hpp QodeAssistSettings.cpp
|
||||
QodeAssist.qrc
|
||||
LSPCompletion.hpp
|
||||
LLMSuggestion.hpp LLMSuggestion.cpp
|
||||
QodeAssistHoverHandler.hpp QodeAssistHoverHandler.cpp
|
||||
QodeAssistClient.hpp QodeAssistClient.cpp
|
||||
QodeAssistUtils.hpp
|
||||
DocumentContextReader.hpp DocumentContextReader.cpp
|
||||
QodeAssistData.hpp
|
||||
utils/CounterTooltip.hpp utils/CounterTooltip.cpp
|
||||
settings/GeneralSettings.hpp settings/GeneralSettings.cpp
|
||||
settings/ContextSettings.hpp settings/ContextSettings.cpp
|
||||
settings/CustomPromptSettings.hpp settings/CustomPromptSettings.cpp
|
||||
settings/PresetPromptsSettings.hpp settings/PresetPromptsSettings.cpp
|
||||
settings/SettingsUtils.hpp
|
||||
core/ChangesManager.h core/ChangesManager.cpp
|
||||
core/LLMRequestHandler.hpp core/LLMRequestHandler.cpp
|
||||
core/LLMRequestConfig.hpp
|
||||
chat/ChatWidget.h chat/ChatWidget.cpp
|
||||
chat/ChatOutputPane.h chat/ChatOutputPane.cpp
|
||||
chat/ChatClientInterface.hpp chat/ChatClientInterface.cpp
|
||||
)
|
||||
|
||||
@ -23,10 +23,38 @@
|
||||
#include <QTextBlock>
|
||||
#include <languageserverprotocol/lsptypes.h>
|
||||
|
||||
#include "QodeAssistSettings.hpp"
|
||||
#include "core/ChangesManager.h"
|
||||
#include "settings/ContextSettings.hpp"
|
||||
|
||||
const QRegularExpression &getYearRegex()
|
||||
{
|
||||
static const QRegularExpression yearRegex("\\b(19|20)\\d{2}\\b");
|
||||
return yearRegex;
|
||||
}
|
||||
|
||||
const QRegularExpression &getNameRegex()
|
||||
{
|
||||
static const QRegularExpression nameRegex("\\b[A-Z][a-z.]+ [A-Z][a-z.]+\\b");
|
||||
return nameRegex;
|
||||
}
|
||||
|
||||
const QRegularExpression &getCommentRegex()
|
||||
{
|
||||
static const QRegularExpression
|
||||
commentRegex(R"((/\*[\s\S]*?\*/|//.*$|#.*$|//{2,}[\s\S]*?//{2,}))",
|
||||
QRegularExpression::MultilineOption);
|
||||
return commentRegex;
|
||||
}
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
DocumentContextReader::DocumentContextReader(TextEditor::TextDocument *textDocument)
|
||||
: m_textDocument(textDocument)
|
||||
, m_document(textDocument->document())
|
||||
{
|
||||
m_copyrightInfo = findCopyright();
|
||||
}
|
||||
|
||||
QString DocumentContextReader::getLineText(int lineNumber, int cursorPosition) const
|
||||
{
|
||||
if (!m_document || lineNumber < 0)
|
||||
@ -55,76 +83,41 @@ QString DocumentContextReader::getContextBefore(int lineNumber,
|
||||
int cursorPosition,
|
||||
int linesCount) const
|
||||
{
|
||||
QString context;
|
||||
for (int i = qMax(0, lineNumber - linesCount); i <= lineNumber; ++i) {
|
||||
QString line = getLineText(i, i == lineNumber ? cursorPosition : -1);
|
||||
context += line;
|
||||
if (i < lineNumber)
|
||||
context += "\n";
|
||||
int effectiveStartLine;
|
||||
if (m_copyrightInfo.found) {
|
||||
effectiveStartLine = qMax(m_copyrightInfo.endLine + 1, lineNumber - linesCount);
|
||||
} else {
|
||||
effectiveStartLine = qMax(0, lineNumber - linesCount);
|
||||
}
|
||||
return context;
|
||||
|
||||
return getContextBetween(effectiveStartLine, lineNumber, cursorPosition);
|
||||
}
|
||||
|
||||
QString DocumentContextReader::getContextAfter(int lineNumber,
|
||||
int cursorPosition,
|
||||
int linesCount) const
|
||||
{
|
||||
QString context;
|
||||
int maxLine = lineNumber + linesCount;
|
||||
for (int i = lineNumber; i <= maxLine; ++i) {
|
||||
QString line = getLineText(i);
|
||||
if (i == lineNumber && cursorPosition >= 0) {
|
||||
line = line.mid(cursorPosition);
|
||||
}
|
||||
context += line;
|
||||
if (i < maxLine && !line.isEmpty())
|
||||
context += "\n";
|
||||
}
|
||||
return context;
|
||||
int endLine = qMin(m_document->blockCount() - 1, lineNumber + linesCount);
|
||||
return getContextBetween(lineNumber + 1, endLine, cursorPosition);
|
||||
}
|
||||
|
||||
QString DocumentContextReader::readWholeFileBefore(int lineNumber, int cursorPosition) const
|
||||
{
|
||||
QString content;
|
||||
QTextBlock block = m_document->begin();
|
||||
int currentLine = 0;
|
||||
|
||||
while (block.isValid() && currentLine <= lineNumber) {
|
||||
if (currentLine == lineNumber) {
|
||||
content += block.text().left(cursorPosition);
|
||||
break;
|
||||
} else {
|
||||
content += block.text() + "\n";
|
||||
}
|
||||
block = block.next();
|
||||
currentLine++;
|
||||
int startLine = 0;
|
||||
if (m_copyrightInfo.found) {
|
||||
startLine = m_copyrightInfo.endLine + 1;
|
||||
}
|
||||
|
||||
return content;
|
||||
startLine = qMin(startLine, lineNumber);
|
||||
|
||||
QString result = getContextBetween(startLine, lineNumber, cursorPosition);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString DocumentContextReader::readWholeFileAfter(int lineNumber, int cursorPosition) const
|
||||
{
|
||||
QString content;
|
||||
QTextBlock block = m_document->begin();
|
||||
int currentLine = 0;
|
||||
|
||||
while (block.isValid() && currentLine < lineNumber) {
|
||||
block = block.next();
|
||||
currentLine++;
|
||||
}
|
||||
|
||||
while (block.isValid()) {
|
||||
if (currentLine == lineNumber) {
|
||||
content += block.text().mid(cursorPosition) + "\n";
|
||||
} else {
|
||||
content += block.text() + "\n";
|
||||
}
|
||||
block = block.next();
|
||||
currentLine++;
|
||||
}
|
||||
|
||||
return content.trimmed();
|
||||
return getContextBetween(lineNumber, m_document->blockCount() - 1, cursorPosition);
|
||||
}
|
||||
|
||||
QString DocumentContextReader::getLanguageAndFileInfo() const
|
||||
@ -138,18 +131,135 @@ QString DocumentContextReader::getLanguageAndFileInfo() const
|
||||
QString filePath = m_textDocument->filePath().toString();
|
||||
QString fileExtension = QFileInfo(filePath).suffix();
|
||||
|
||||
return QString("//Language: %1 (MIME: %2) filepath: %3(%4)\n\n")
|
||||
.arg(language)
|
||||
.arg(mimeType)
|
||||
.arg(filePath)
|
||||
.arg(fileExtension);
|
||||
return QString("Language: %1 (MIME: %2) filepath: %3(%4)\n\n")
|
||||
.arg(language, mimeType, filePath, fileExtension);
|
||||
}
|
||||
|
||||
QString DocumentContextReader::getSpecificInstructions() const
|
||||
{
|
||||
QString specificInstruction = settings().specificInstractions().arg(
|
||||
QString specificInstruction = Settings::contextSettings().specificInstractions().arg(
|
||||
LanguageServerProtocol::TextDocumentItem::mimeTypeToLanguageId(m_textDocument->mimeType()));
|
||||
return QString("//Instructions: %1").arg(specificInstruction);
|
||||
return QString("%1").arg(specificInstruction);
|
||||
}
|
||||
|
||||
CopyrightInfo DocumentContextReader::findCopyright()
|
||||
{
|
||||
CopyrightInfo result = {-1, -1, false};
|
||||
|
||||
QString text = m_document->toPlainText();
|
||||
QRegularExpressionMatchIterator matchIterator = getCommentRegex().globalMatch(text);
|
||||
|
||||
QList<CopyrightInfo> copyrightBlocks;
|
||||
|
||||
while (matchIterator.hasNext()) {
|
||||
QRegularExpressionMatch match = matchIterator.next();
|
||||
QString matchedText = match.captured().toLower();
|
||||
|
||||
if (matchedText.contains("copyright") || matchedText.contains("(C)")
|
||||
|| matchedText.contains("(c)") || matchedText.contains("©")
|
||||
|| getYearRegex().match(text).hasMatch() || getNameRegex().match(text).hasMatch()) {
|
||||
int startPos = match.capturedStart();
|
||||
int endPos = match.capturedEnd();
|
||||
|
||||
CopyrightInfo info;
|
||||
info.startLine = m_document->findBlock(startPos).blockNumber();
|
||||
info.endLine = m_document->findBlock(endPos).blockNumber();
|
||||
info.found = true;
|
||||
|
||||
copyrightBlocks.append(info);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < copyrightBlocks.size() - 1; ++i) {
|
||||
if (copyrightBlocks[i].endLine + 1 >= copyrightBlocks[i + 1].startLine) {
|
||||
copyrightBlocks[i].endLine = copyrightBlocks[i + 1].endLine;
|
||||
copyrightBlocks.removeAt(i + 1);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
|
||||
if (!copyrightBlocks.isEmpty()) { // temproary solution, need cache
|
||||
return copyrightBlocks.first();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString DocumentContextReader::getContextBetween(int startLine,
|
||||
int endLine,
|
||||
int cursorPosition) const
|
||||
{
|
||||
QString context;
|
||||
for (int i = startLine; i <= endLine; ++i) {
|
||||
QTextBlock block = m_document->findBlockByNumber(i);
|
||||
if (!block.isValid()) {
|
||||
break;
|
||||
}
|
||||
if (i == endLine) {
|
||||
context += block.text().left(cursorPosition);
|
||||
} else {
|
||||
context += block.text() + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
CopyrightInfo DocumentContextReader::copyrightInfo() const
|
||||
{
|
||||
return m_copyrightInfo;
|
||||
}
|
||||
|
||||
ContextData DocumentContextReader::prepareContext(int lineNumber, int cursorPosition) const
|
||||
{
|
||||
QString contextBefore = getContextBefore(lineNumber, cursorPosition);
|
||||
QString contextAfter = getContextAfter(lineNumber, cursorPosition);
|
||||
QString instructions = getInstructions();
|
||||
|
||||
return {contextBefore, contextAfter, instructions};
|
||||
}
|
||||
|
||||
QString DocumentContextReader::getContextBefore(int lineNumber, int cursorPosition) const
|
||||
{
|
||||
if (Settings::contextSettings().readFullFile()) {
|
||||
return readWholeFileBefore(lineNumber, cursorPosition);
|
||||
} else {
|
||||
int effectiveStartLine;
|
||||
int beforeCursor = Settings::contextSettings().readStringsBeforeCursor();
|
||||
if (m_copyrightInfo.found) {
|
||||
effectiveStartLine = qMax(m_copyrightInfo.endLine + 1, lineNumber - beforeCursor);
|
||||
} else {
|
||||
effectiveStartLine = qMax(0, lineNumber - beforeCursor);
|
||||
}
|
||||
return getContextBetween(effectiveStartLine, lineNumber, cursorPosition);
|
||||
}
|
||||
}
|
||||
|
||||
QString DocumentContextReader::getContextAfter(int lineNumber, int cursorPosition) const
|
||||
{
|
||||
if (Settings::contextSettings().readFullFile()) {
|
||||
return readWholeFileAfter(lineNumber, cursorPosition);
|
||||
} else {
|
||||
int endLine = qMin(m_document->blockCount() - 1,
|
||||
lineNumber + Settings::contextSettings().readStringsAfterCursor());
|
||||
return getContextBetween(lineNumber + 1, endLine, -1);
|
||||
}
|
||||
}
|
||||
|
||||
QString DocumentContextReader::getInstructions() const
|
||||
{
|
||||
QString instructions;
|
||||
|
||||
if (Settings::contextSettings().useSpecificInstructions())
|
||||
instructions += getSpecificInstructions();
|
||||
|
||||
if (Settings::contextSettings().useFilePathInContext())
|
||||
instructions += getLanguageAndFileInfo();
|
||||
|
||||
if (Settings::contextSettings().useProjectChangesCache())
|
||||
instructions += ChangesManager::instance().getRecentChangesContext(m_textDocument);
|
||||
|
||||
return instructions;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@ -22,15 +22,21 @@
|
||||
#include <QTextDocument>
|
||||
#include <texteditor/textdocument.h>
|
||||
|
||||
#include "QodeAssistData.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
struct CopyrightInfo
|
||||
{
|
||||
int startLine;
|
||||
int endLine;
|
||||
bool found;
|
||||
};
|
||||
|
||||
class DocumentContextReader
|
||||
{
|
||||
public:
|
||||
DocumentContextReader(TextEditor::TextDocument *textDocument)
|
||||
: m_textDocument(textDocument)
|
||||
, m_document(textDocument->document())
|
||||
{}
|
||||
DocumentContextReader(TextEditor::TextDocument *textDocument);
|
||||
|
||||
QString getLineText(int lineNumber, int cursorPosition = -1) const;
|
||||
QString getContextBefore(int lineNumber, int cursorPosition, int linesCount) const;
|
||||
@ -39,10 +45,22 @@ public:
|
||||
QString readWholeFileAfter(int lineNumber, int cursorPosition) const;
|
||||
QString getLanguageAndFileInfo() const;
|
||||
QString getSpecificInstructions() const;
|
||||
CopyrightInfo findCopyright();
|
||||
QString getContextBetween(int startLine, int endLine, int cursorPosition) const;
|
||||
|
||||
CopyrightInfo copyrightInfo() const;
|
||||
|
||||
ContextData prepareContext(int lineNumber, int cursorPosition) const;
|
||||
|
||||
private:
|
||||
QString getContextBefore(int lineNumber, int cursorPosition) const;
|
||||
QString getContextAfter(int lineNumber, int cursorPosition) const;
|
||||
QString getInstructions() const;
|
||||
|
||||
private:
|
||||
TextEditor::TextDocument *m_textDocument;
|
||||
QTextDocument *m_document;
|
||||
CopyrightInfo m_copyrightInfo;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@ -28,15 +28,19 @@
|
||||
#include "DocumentContextReader.hpp"
|
||||
#include "LLMProvidersManager.hpp"
|
||||
#include "PromptTemplateManager.hpp"
|
||||
#include "QodeAssistSettings.hpp"
|
||||
#include "QodeAssistUtils.hpp"
|
||||
#include "core/LLMRequestConfig.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
LLMClientInterface::LLMClientInterface()
|
||||
: m_manager(new QNetworkAccessManager(this))
|
||||
: m_requestHandler(this)
|
||||
{
|
||||
updateProvider();
|
||||
connect(&m_requestHandler,
|
||||
&LLMRequestHandler::completionReceived,
|
||||
this,
|
||||
&LLMClientInterface::sendCompletionToClient);
|
||||
}
|
||||
|
||||
Utils::FilePath LLMClientInterface::serverDeviceTemplate() const
|
||||
@ -51,8 +55,6 @@ void LLMClientInterface::startImpl()
|
||||
|
||||
void LLMClientInterface::sendData(const QByteArray &data)
|
||||
{
|
||||
updateProvider();
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data);
|
||||
if (!doc.isObject())
|
||||
return;
|
||||
@ -69,6 +71,8 @@ void LLMClientInterface::sendData(const QByteArray &data)
|
||||
} else if (method == "textDocument/didOpen") {
|
||||
handleTextDocumentDidOpen(request);
|
||||
} else if (method == "getCompletionsCycling") {
|
||||
QString requestId = request["id"].toString();
|
||||
startTimeMeasurement(requestId);
|
||||
handleCompletion(request);
|
||||
} else if (method == "$/cancelRequest") {
|
||||
handleCancelRequest(request);
|
||||
@ -82,61 +86,13 @@ void LLMClientInterface::sendData(const QByteArray &data)
|
||||
void LLMClientInterface::handleCancelRequest(const QJsonObject &request)
|
||||
{
|
||||
QString id = request["params"].toObject()["id"].toString();
|
||||
if (m_activeRequests.contains(id)) {
|
||||
m_activeRequests[id]->abort();
|
||||
m_activeRequests.remove(id);
|
||||
if (m_requestHandler.cancelRequest(id)) {
|
||||
logMessage(QString("Request %1 cancelled successfully").arg(id));
|
||||
} else {
|
||||
logMessage(QString("Request %1 not found").arg(id));
|
||||
}
|
||||
}
|
||||
|
||||
QString LLMClientInterface::сontextBefore(TextEditor::TextEditorWidget *widget,
|
||||
int lineNumber,
|
||||
int cursorPosition)
|
||||
{
|
||||
if (!widget)
|
||||
return QString();
|
||||
|
||||
DocumentContextReader reader(widget->textDocument());
|
||||
QString languageAndFileInfo = reader.getLanguageAndFileInfo();
|
||||
|
||||
QString contextBefore;
|
||||
if (settings().readFullFile()) {
|
||||
contextBefore = reader.readWholeFileBefore(lineNumber, cursorPosition);
|
||||
} else {
|
||||
contextBefore = reader.getContextBefore(lineNumber,
|
||||
cursorPosition,
|
||||
settings().readStringsBeforeCursor());
|
||||
}
|
||||
|
||||
return QString("%1\n%2\n%3")
|
||||
.arg(reader.getSpecificInstructions())
|
||||
.arg(reader.getLanguageAndFileInfo())
|
||||
.arg(contextBefore);
|
||||
}
|
||||
|
||||
QString LLMClientInterface::сontextAfter(TextEditor::TextEditorWidget *widget,
|
||||
int lineNumber,
|
||||
int cursorPosition)
|
||||
{
|
||||
if (!widget)
|
||||
return QString();
|
||||
|
||||
DocumentContextReader reader(widget->textDocument());
|
||||
|
||||
QString contextAfter;
|
||||
if (settings().readFullFile()) {
|
||||
contextAfter = reader.readWholeFileAfter(lineNumber, cursorPosition);
|
||||
} else {
|
||||
contextAfter = reader.getContextAfter(lineNumber,
|
||||
cursorPosition,
|
||||
settings().readStringsAfterCursor());
|
||||
}
|
||||
|
||||
return contextAfter;
|
||||
}
|
||||
|
||||
void LLMClientInterface::handleInitialize(const QJsonObject &request)
|
||||
{
|
||||
QJsonObject response;
|
||||
@ -151,7 +107,7 @@ void LLMClientInterface::handleInitialize(const QJsonObject &request)
|
||||
result["capabilities"] = capabilities;
|
||||
|
||||
QJsonObject serverInfo;
|
||||
serverInfo["name"] = "Ollama LSP Server";
|
||||
serverInfo["name"] = "QodeAssist LSP Server";
|
||||
serverInfo["version"] = "0.1";
|
||||
result["serverInfo"] = serverInfo;
|
||||
|
||||
@ -170,9 +126,7 @@ void LLMClientInterface::handleShutdown(const QJsonObject &request)
|
||||
emit messageReceived(LanguageServerProtocol::JsonRpcMessage(response));
|
||||
}
|
||||
|
||||
void LLMClientInterface::handleTextDocumentDidOpen(const QJsonObject &request)
|
||||
{
|
||||
}
|
||||
void LLMClientInterface::handleTextDocumentDidOpen(const QJsonObject &request) {}
|
||||
|
||||
void LLMClientInterface::handleInitialized(const QJsonObject &request)
|
||||
{
|
||||
@ -189,39 +143,30 @@ void LLMClientInterface::handleExit(const QJsonObject &request)
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void LLMClientInterface::handleLLMResponse(QNetworkReply *reply, const QJsonObject &request)
|
||||
void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
||||
{
|
||||
QString &accumulatedResponse = m_accumulatedResponses[reply];
|
||||
auto updatedContext = prepareContext(request);
|
||||
|
||||
auto &templateManager = PromptTemplateManager::instance();
|
||||
const Templates::PromptTemplate *currentTemplate = templateManager.getCurrentTemplate();
|
||||
LLMConfig config;
|
||||
config.requestType = RequestType::Fim;
|
||||
config.provider = LLMProvidersManager::instance().getCurrentFimProvider();
|
||||
config.promptTemplate = PromptTemplateManager::instance().getCurrentFimTemplate();
|
||||
config.url = QUrl(QString("%1%2").arg(Settings::generalSettings().url(),
|
||||
Settings::generalSettings().endPoint()));
|
||||
|
||||
auto &providerManager = LLMProvidersManager::instance();
|
||||
bool isComplete = providerManager.getCurrentProvider()->handleResponse(reply,
|
||||
accumulatedResponse);
|
||||
config.providerRequest = {{"model", Settings::generalSettings().modelName.value()},
|
||||
{"stream", true},
|
||||
{"stop",
|
||||
QJsonArray::fromStringList(config.promptTemplate->stopWords())}};
|
||||
|
||||
QJsonObject position = request["params"].toObject()["doc"].toObject()["position"].toObject();
|
||||
config.promptTemplate->prepareRequest(config.providerRequest, updatedContext);
|
||||
config.provider->prepareRequest(config.providerRequest);
|
||||
|
||||
if (isComplete || reply->isFinished()) {
|
||||
if (isComplete) {
|
||||
auto cleanedCompletion = removeStopWords(accumulatedResponse);
|
||||
sendCompletionToClient(cleanedCompletion, request, position, true);
|
||||
} else {
|
||||
handleCompletion(request, accumulatedResponse);
|
||||
}
|
||||
m_accumulatedResponses.remove(reply);
|
||||
}
|
||||
m_requestHandler.sendLLMRequest(config, request);
|
||||
}
|
||||
|
||||
void LLMClientInterface::handleCompletion(const QJsonObject &request,
|
||||
const QString &accumulatedCompletion)
|
||||
{
|
||||
auto updatedContext = prepareContext(request, accumulatedCompletion);
|
||||
sendLLMRequest(request, updatedContext);
|
||||
}
|
||||
|
||||
LLMClientInterface::ContextPair LLMClientInterface::prepareContext(
|
||||
const QJsonObject &request, const QString &accumulatedCompletion)
|
||||
ContextData LLMClientInterface::prepareContext(const QJsonObject &request,
|
||||
const QStringView &accumulatedCompletion)
|
||||
{
|
||||
QJsonObject params = request["params"].toObject();
|
||||
QJsonObject doc = params["doc"].toObject();
|
||||
@ -234,36 +179,22 @@ LLMClientInterface::ContextPair LLMClientInterface::prepareContext(
|
||||
|
||||
if (!textDocument) {
|
||||
logMessage("Error: Document is not available for" + filePath.toString());
|
||||
return {"", ""};
|
||||
return ContextData{};
|
||||
}
|
||||
|
||||
int cursorPosition = position["character"].toInt();
|
||||
int lineNumber = position["line"].toInt();
|
||||
|
||||
auto textEditor = TextEditor::BaseTextEditor::currentTextEditor();
|
||||
TextEditor::TextEditorWidget *widget = textEditor->editorWidget();
|
||||
|
||||
QString contextBefore = сontextBefore(widget, lineNumber, cursorPosition);
|
||||
QString contextAfter = сontextAfter(widget, lineNumber, cursorPosition);
|
||||
|
||||
QString updatedContextBefore = contextBefore + accumulatedCompletion;
|
||||
|
||||
return {updatedContextBefore, contextAfter};
|
||||
}
|
||||
|
||||
void LLMClientInterface::updateProvider()
|
||||
{
|
||||
m_serverUrl = QUrl(QString("%1:%2%3")
|
||||
.arg(settings().url.value())
|
||||
.arg(settings().port.value())
|
||||
.arg(settings().endPoint.value()));
|
||||
DocumentContextReader reader(textDocument);
|
||||
return reader.prepareContext(lineNumber, cursorPosition);
|
||||
}
|
||||
|
||||
void LLMClientInterface::sendCompletionToClient(const QString &completion,
|
||||
const QJsonObject &request,
|
||||
const QJsonObject &position,
|
||||
bool isComplete)
|
||||
{
|
||||
QJsonObject position = request["params"].toObject()["doc"].toObject()["position"].toObject();
|
||||
|
||||
QJsonObject response;
|
||||
response["jsonrpc"] = "2.0";
|
||||
response[LanguageServerProtocol::idKey] = request["id"];
|
||||
@ -290,66 +221,34 @@ void LLMClientInterface::sendCompletionToClient(const QString &completion,
|
||||
logMessage(QString("Full response: \n%1")
|
||||
.arg(QString::fromUtf8(QJsonDocument(response).toJson(QJsonDocument::Indented))));
|
||||
|
||||
QString requestId = request["id"].toString();
|
||||
endTimeMeasurement(requestId);
|
||||
emit messageReceived(LanguageServerProtocol::JsonRpcMessage(response));
|
||||
}
|
||||
|
||||
void LLMClientInterface::sendLLMRequest(const QJsonObject &request, const ContextPair &prompt)
|
||||
void LLMClientInterface::startTimeMeasurement(const QString &requestId)
|
||||
{
|
||||
QJsonObject ollamaRequest = {{"model", settings().modelName.value()}, {"stream", true}};
|
||||
m_requestStartTimes[requestId] = QDateTime::currentMSecsSinceEpoch();
|
||||
}
|
||||
|
||||
auto currentTemplate = PromptTemplateManager::instance().getCurrentTemplate();
|
||||
currentTemplate->prepareRequest(ollamaRequest, prompt.prefix, prompt.suffix);
|
||||
|
||||
auto &providerManager = LLMProvidersManager::instance();
|
||||
providerManager.getCurrentProvider()->prepareRequest(ollamaRequest);
|
||||
|
||||
logMessage(
|
||||
QString("Sending request to llm: \nurl: %1\nRequest body:\n%2")
|
||||
.arg(m_serverUrl.toString())
|
||||
.arg(QString::fromUtf8(QJsonDocument(ollamaRequest).toJson(QJsonDocument::Indented))));
|
||||
|
||||
QNetworkRequest networkRequest(m_serverUrl);
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
QNetworkReply *reply = m_manager->post(networkRequest, QJsonDocument(ollamaRequest).toJson());
|
||||
if (!reply) {
|
||||
logMessage("Error: Failed to create network reply");
|
||||
return;
|
||||
void LLMClientInterface::endTimeMeasurement(const QString &requestId)
|
||||
{
|
||||
if (m_requestStartTimes.contains(requestId)) {
|
||||
qint64 startTime = m_requestStartTimes[requestId];
|
||||
qint64 endTime = QDateTime::currentMSecsSinceEpoch();
|
||||
qint64 totalTime = endTime - startTime;
|
||||
logPerformance(requestId, "TotalCompletionTime", totalTime);
|
||||
m_requestStartTimes.remove(requestId);
|
||||
}
|
||||
|
||||
QString requestId = request["id"].toString();
|
||||
m_activeRequests[requestId] = reply;
|
||||
|
||||
connect(reply, &QNetworkReply::readyRead, this, [this, reply, request]() {
|
||||
handleLLMResponse(reply, request);
|
||||
});
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply, requestId]() {
|
||||
reply->deleteLater();
|
||||
m_activeRequests.remove(requestId);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
logMessage(QString("Error in Ollama request: %1").arg(reply->errorString()));
|
||||
} else {
|
||||
logMessage("Request finished successfully");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QString LLMClientInterface::removeStopWords(const QString &completion)
|
||||
void LLMClientInterface::logPerformance(const QString &requestId,
|
||||
const QString &operation,
|
||||
qint64 elapsedMs)
|
||||
{
|
||||
QString filteredCompletion = completion;
|
||||
|
||||
auto currentTemplate = PromptTemplateManager::instance().getCurrentTemplate();
|
||||
QStringList stopWords = currentTemplate->stopWords();
|
||||
|
||||
for (const QString &stopWord : stopWords) {
|
||||
filteredCompletion = filteredCompletion.replace(stopWord, "");
|
||||
}
|
||||
|
||||
return filteredCompletion;
|
||||
logMessage(QString("Performance: %1 %2 took %3 ms").arg(requestId, operation).arg(elapsedMs));
|
||||
}
|
||||
|
||||
void LLMClientInterface::parseCurrentMessage()
|
||||
{
|
||||
}
|
||||
void LLMClientInterface::parseCurrentMessage() {}
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@ -22,6 +22,9 @@
|
||||
#include <languageclient/languageclientinterface.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
|
||||
#include "QodeAssistData.hpp"
|
||||
#include "core/LLMRequestHandler.hpp"
|
||||
|
||||
class QNetworkReply;
|
||||
class QNetworkAccessManager;
|
||||
|
||||
@ -34,28 +37,13 @@ class LLMClientInterface : public LanguageClient::BaseClientInterface
|
||||
public:
|
||||
LLMClientInterface();
|
||||
|
||||
public:
|
||||
struct ContextPair
|
||||
{
|
||||
QString prefix;
|
||||
QString suffix;
|
||||
};
|
||||
|
||||
Utils::FilePath serverDeviceTemplate() const override;
|
||||
|
||||
void sendCompletionToClient(const QString &completion,
|
||||
const QJsonObject &request,
|
||||
const QJsonObject &position,
|
||||
bool isComplete);
|
||||
|
||||
void handleCompletion(const QJsonObject &request,
|
||||
const QString &accumulatedCompletion = QString());
|
||||
void sendLLMRequest(const QJsonObject &request, const ContextPair &prompt);
|
||||
void handleLLMResponse(QNetworkReply *reply, const QJsonObject &request);
|
||||
|
||||
ContextPair prepareContext(const QJsonObject &request,
|
||||
const QString &accumulatedCompletion = QString{});
|
||||
void updateProvider();
|
||||
void handleCompletion(const QJsonObject &request);
|
||||
|
||||
protected:
|
||||
void startImpl() override;
|
||||
@ -70,14 +58,16 @@ private:
|
||||
void handleExit(const QJsonObject &request);
|
||||
void handleCancelRequest(const QJsonObject &request);
|
||||
|
||||
QString сontextBefore(TextEditor::TextEditorWidget *widget, int lineNumber, int cursorPosition);
|
||||
QString сontextAfter(TextEditor::TextEditorWidget *widget, int lineNumber, int cursorPosition);
|
||||
QString removeStopWords(const QString &completion);
|
||||
ContextData prepareContext(const QJsonObject &request,
|
||||
const QStringView &accumulatedCompletion = QString{});
|
||||
|
||||
QUrl m_serverUrl;
|
||||
QNetworkAccessManager *m_manager;
|
||||
QMap<QString, QNetworkReply *> m_activeRequests;
|
||||
QMap<QNetworkReply *, QString> m_accumulatedResponses;
|
||||
LLMRequestHandler m_requestHandler;
|
||||
QElapsedTimer m_completionTimer;
|
||||
QMap<QString, qint64> m_requestStartTimes;
|
||||
|
||||
void startTimeMeasurement(const QString &requestId);
|
||||
void endTimeMeasurement(const QString &requestId);
|
||||
void logPerformance(const QString &requestId, const QString &operation, qint64 elapsedMs);
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@ -19,6 +19,8 @@
|
||||
|
||||
#include "LLMProvidersManager.hpp"
|
||||
|
||||
#include "QodeAssistUtils.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
LLMProvidersManager &LLMProvidersManager::instance()
|
||||
@ -27,25 +29,53 @@ LLMProvidersManager &LLMProvidersManager::instance()
|
||||
return instance;
|
||||
}
|
||||
|
||||
QStringList LLMProvidersManager::getProviderNames() const
|
||||
Providers::LLMProvider *LLMProvidersManager::setCurrentFimProvider(const QString &name)
|
||||
{
|
||||
return m_providers.keys();
|
||||
}
|
||||
|
||||
void LLMProvidersManager::setCurrentProvider(const QString &name)
|
||||
{
|
||||
if (m_providers.contains(name)) {
|
||||
m_currentProviderName = name;
|
||||
}
|
||||
}
|
||||
|
||||
Providers::LLMProvider *LLMProvidersManager::getCurrentProvider()
|
||||
{
|
||||
if (m_currentProviderName.isEmpty()) {
|
||||
logMessage("Setting current FIM provider to: " + name);
|
||||
if (!m_providers.contains(name)) {
|
||||
logMessage("Can't find provider with name: " + name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return m_providers[m_currentProviderName];
|
||||
m_currentFimProvider = m_providers[name];
|
||||
return m_currentFimProvider;
|
||||
}
|
||||
|
||||
Providers::LLMProvider *LLMProvidersManager::setCurrentChatProvider(const QString &name)
|
||||
{
|
||||
logMessage("Setting current chat provider to: " + name);
|
||||
if (!m_providers.contains(name)) {
|
||||
logMessage("Can't find chat provider with name: " + name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_currentChatProvider = m_providers[name];
|
||||
return m_currentChatProvider;
|
||||
}
|
||||
|
||||
Providers::LLMProvider *LLMProvidersManager::getCurrentFimProvider()
|
||||
{
|
||||
if (m_currentFimProvider == nullptr) {
|
||||
logMessage("Current fim provider is null");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return m_currentFimProvider;
|
||||
}
|
||||
|
||||
Providers::LLMProvider *LLMProvidersManager::getCurrentChatProvider()
|
||||
{
|
||||
if (m_currentChatProvider == nullptr) {
|
||||
logMessage("Current chat provider is null");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return m_currentChatProvider;
|
||||
}
|
||||
|
||||
QStringList LLMProvidersManager::providersNames() const
|
||||
{
|
||||
return m_providers.keys();
|
||||
}
|
||||
|
||||
LLMProvidersManager::~LLMProvidersManager()
|
||||
|
||||
@ -29,6 +29,7 @@ class LLMProvidersManager
|
||||
{
|
||||
public:
|
||||
static LLMProvidersManager &instance();
|
||||
~LLMProvidersManager();
|
||||
|
||||
template<typename T>
|
||||
void registerProvider()
|
||||
@ -40,11 +41,13 @@ public:
|
||||
m_providers[name] = provider;
|
||||
}
|
||||
|
||||
QStringList getProviderNames() const;
|
||||
void setCurrentProvider(const QString &name);
|
||||
Providers::LLMProvider *getCurrentProvider();
|
||||
Providers::LLMProvider *setCurrentFimProvider(const QString &name);
|
||||
Providers::LLMProvider *setCurrentChatProvider(const QString &name);
|
||||
|
||||
~LLMProvidersManager();
|
||||
Providers::LLMProvider *getCurrentFimProvider();
|
||||
Providers::LLMProvider *getCurrentChatProvider();
|
||||
|
||||
QStringList providersNames() const;
|
||||
|
||||
private:
|
||||
LLMProvidersManager() = default;
|
||||
@ -52,7 +55,8 @@ private:
|
||||
LLMProvidersManager &operator=(const LLMProvidersManager &) = delete;
|
||||
|
||||
QMap<QString, Providers::LLMProvider *> m_providers;
|
||||
QString m_currentProviderName;
|
||||
Providers::LLMProvider *m_currentFimProvider = nullptr;
|
||||
Providers::LLMProvider *m_currentChatProvider = nullptr;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@ -19,10 +19,17 @@
|
||||
|
||||
#include "LLMSuggestion.hpp"
|
||||
|
||||
#include <QTextCursor>
|
||||
#include <QtWidgets/qtoolbar.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
#include <utils/stringutils.h>
|
||||
#include <utils/tooltip/tooltip.h>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
LLMSuggestion::LLMSuggestion(const Completion &completion, QTextDocument *origin)
|
||||
: m_completion(completion)
|
||||
, m_linesCount(0)
|
||||
{
|
||||
int startPos = completion.range().start().toPositionInDocument(origin);
|
||||
int endPos = completion.range().end().toPositionInDocument(origin);
|
||||
@ -63,8 +70,35 @@ bool LLMSuggestion::apply()
|
||||
|
||||
bool LLMSuggestion::applyWord(TextEditor::TextEditorWidget *widget)
|
||||
{
|
||||
Q_UNUSED(widget)
|
||||
return apply();
|
||||
return applyNextLine(widget);
|
||||
}
|
||||
|
||||
bool LLMSuggestion::applyNextLine(TextEditor::TextEditorWidget *widget)
|
||||
{
|
||||
const QString text = m_completion.text();
|
||||
QStringList lines = text.split('\n');
|
||||
|
||||
if (m_linesCount < lines.size())
|
||||
m_linesCount++;
|
||||
|
||||
showTooltip(widget, m_linesCount);
|
||||
|
||||
return m_linesCount == lines.size() && !Utils::ToolTip::isVisible();
|
||||
}
|
||||
|
||||
void LLMSuggestion::onCounterFinished(int count)
|
||||
{
|
||||
Utils::ToolTip::hide();
|
||||
m_linesCount = 0;
|
||||
QTextCursor cursor = m_completion.range().toSelection(m_start.document());
|
||||
cursor.beginEditBlock();
|
||||
cursor.removeSelectedText();
|
||||
|
||||
QStringList lines = m_completion.text().split('\n');
|
||||
QString textToInsert = lines.mid(0, count).join('\n');
|
||||
|
||||
cursor.insertText(textToInsert);
|
||||
cursor.endEditBlock();
|
||||
}
|
||||
|
||||
void LLMSuggestion::reset()
|
||||
@ -77,4 +111,14 @@ int LLMSuggestion::position()
|
||||
return m_start.position();
|
||||
}
|
||||
|
||||
void LLMSuggestion::showTooltip(TextEditor::TextEditorWidget *widget, int count)
|
||||
{
|
||||
Utils::ToolTip::hide();
|
||||
QPoint pos = widget->mapToGlobal(widget->cursorRect().topRight());
|
||||
pos += QPoint(-10, -50);
|
||||
m_counterTooltip = new CounterTooltip(count);
|
||||
Utils::ToolTip::show(pos, m_counterTooltip, widget);
|
||||
connect(m_counterTooltip, &CounterTooltip::finished, this, &LLMSuggestion::onCounterFinished);
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@ -19,27 +19,37 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include "LSPCompletion.hpp"
|
||||
#include <texteditor/textdocumentlayout.h>
|
||||
|
||||
#include "LSPCompletion.hpp"
|
||||
#include "utils/CounterTooltip.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
class LLMSuggestion final : public TextEditor::TextSuggestion
|
||||
class LLMSuggestion final : public QObject, public TextEditor::TextSuggestion
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
LLMSuggestion(const Completion &completion, QTextDocument *origin);
|
||||
|
||||
bool apply() final;
|
||||
bool applyWord(TextEditor::TextEditorWidget *widget) final;
|
||||
bool applyNextLine(TextEditor::TextEditorWidget *widget);
|
||||
void reset() final;
|
||||
int position() final;
|
||||
|
||||
const Completion &completion() const { return m_completion; }
|
||||
|
||||
void showTooltip(TextEditor::TextEditorWidget *widget, int count);
|
||||
void onCounterFinished(int count);
|
||||
|
||||
private:
|
||||
Completion m_completion;
|
||||
QTextCursor m_start;
|
||||
int m_linesCount;
|
||||
|
||||
CounterTooltip *m_counterTooltip = nullptr;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@ -43,6 +43,10 @@ public:
|
||||
{
|
||||
return typedValue<LanguageServerProtocol::Position>(LanguageServerProtocol::positionKey);
|
||||
}
|
||||
void setRange(const LanguageServerProtocol::Range &range)
|
||||
{
|
||||
insert(LanguageServerProtocol::rangeKey, range);
|
||||
}
|
||||
LanguageServerProtocol::Range range() const
|
||||
{
|
||||
return typedValue<LanguageServerProtocol::Range>(LanguageServerProtocol::rangeKey);
|
||||
|
||||
@ -19,6 +19,8 @@
|
||||
|
||||
#include "PromptTemplateManager.hpp"
|
||||
|
||||
#include "QodeAssistUtils.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
PromptTemplateManager &PromptTemplateManager::instance()
|
||||
@ -27,27 +29,60 @@ PromptTemplateManager &PromptTemplateManager::instance()
|
||||
return instance;
|
||||
}
|
||||
|
||||
void PromptTemplateManager::setCurrentTemplate(const QString &name)
|
||||
void PromptTemplateManager::setCurrentFimTemplate(const QString &name)
|
||||
{
|
||||
if (m_templates.contains(name)) {
|
||||
m_currentTemplateName = name;
|
||||
logMessage("Setting current FIM provider to: " + name);
|
||||
if (!m_fimTemplates.contains(name) || m_fimTemplates[name] == nullptr) {
|
||||
logMessage("Error to set current FIM template" + name);
|
||||
return;
|
||||
}
|
||||
|
||||
m_currentFimTemplate = m_fimTemplates[name];
|
||||
}
|
||||
|
||||
const Templates::PromptTemplate *PromptTemplateManager::getCurrentTemplate() const
|
||||
Templates::PromptTemplate *PromptTemplateManager::getCurrentFimTemplate()
|
||||
{
|
||||
auto it = m_templates.find(m_currentTemplateName);
|
||||
return it != m_templates.end() ? it.value() : nullptr;
|
||||
if (m_currentFimTemplate == nullptr) {
|
||||
logMessage("Current fim provider is null");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return m_currentFimTemplate;
|
||||
}
|
||||
|
||||
QStringList PromptTemplateManager::getTemplateNames() const
|
||||
void PromptTemplateManager::setCurrentChatTemplate(const QString &name)
|
||||
{
|
||||
return m_templates.keys();
|
||||
logMessage("Setting current chat provider to: " + name);
|
||||
if (!m_chatTemplates.contains(name) || m_chatTemplates[name] == nullptr) {
|
||||
logMessage("Error to set current chat template" + name);
|
||||
return;
|
||||
}
|
||||
|
||||
m_currentChatTemplate = m_chatTemplates[name];
|
||||
}
|
||||
|
||||
Templates::PromptTemplate *PromptTemplateManager::getCurrentChatTemplate()
|
||||
{
|
||||
if (m_currentChatTemplate == nullptr)
|
||||
logMessage("Current chat provider is null");
|
||||
|
||||
return m_currentChatTemplate;
|
||||
}
|
||||
|
||||
QStringList PromptTemplateManager::fimTemplatesNames() const
|
||||
{
|
||||
return m_fimTemplates.keys();
|
||||
}
|
||||
|
||||
QStringList PromptTemplateManager::chatTemplatesNames() const
|
||||
{
|
||||
return m_chatTemplates.keys();
|
||||
}
|
||||
|
||||
PromptTemplateManager::~PromptTemplateManager()
|
||||
{
|
||||
qDeleteAll(m_templates);
|
||||
qDeleteAll(m_fimTemplates);
|
||||
qDeleteAll(m_chatTemplates);
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@ -30,6 +30,7 @@ class PromptTemplateManager
|
||||
{
|
||||
public:
|
||||
static PromptTemplateManager &instance();
|
||||
~PromptTemplateManager();
|
||||
|
||||
template<typename T>
|
||||
void registerTemplate()
|
||||
@ -38,22 +39,31 @@ public:
|
||||
"T must inherit from PromptTemplate");
|
||||
T *template_ptr = new T();
|
||||
QString name = template_ptr->name();
|
||||
m_templates[name] = template_ptr;
|
||||
if (template_ptr->type() == Templates::TemplateType::Fim) {
|
||||
m_fimTemplates[name] = template_ptr;
|
||||
} else if (template_ptr->type() == Templates::TemplateType::Chat) {
|
||||
m_chatTemplates[name] = template_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
void setCurrentTemplate(const QString &name);
|
||||
const Templates::PromptTemplate *getCurrentTemplate() const;
|
||||
QStringList getTemplateNames() const;
|
||||
void setCurrentFimTemplate(const QString &name);
|
||||
Templates::PromptTemplate *getCurrentFimTemplate();
|
||||
|
||||
~PromptTemplateManager();
|
||||
void setCurrentChatTemplate(const QString &name);
|
||||
Templates::PromptTemplate *getCurrentChatTemplate();
|
||||
|
||||
QStringList fimTemplatesNames() const;
|
||||
QStringList chatTemplatesNames() const;
|
||||
|
||||
private:
|
||||
PromptTemplateManager() = default;
|
||||
PromptTemplateManager(const PromptTemplateManager &) = delete;
|
||||
PromptTemplateManager &operator=(const PromptTemplateManager &) = delete;
|
||||
|
||||
QMap<QString, Templates::PromptTemplate *> m_templates;
|
||||
QString m_currentTemplateName;
|
||||
QMap<QString, Templates::PromptTemplate *> m_fimTemplates;
|
||||
QMap<QString, Templates::PromptTemplate *> m_chatTemplates;
|
||||
Templates::PromptTemplate *m_currentFimTemplate;
|
||||
Templates::PromptTemplate *m_currentChatTemplate;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"Name" : "QodeAssist",
|
||||
"Version" : "0.0.3",
|
||||
"Version" : "0.2.0",
|
||||
"CompatVersion" : "${IDE_VERSION_COMPAT}",
|
||||
"Vendor" : "Petr Mironychev",
|
||||
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",
|
||||
@ -11,6 +11,6 @@ Alternatively, this file may be used under the terms of the GNU General Public L
|
||||
"Prerequisites:",
|
||||
"- One of the supported LLM providers installed (e.g., Ollama or LM Studio)",
|
||||
"- A compatible large language model downloaded for your chosen provider (e.g., CodeLlama, StarCoder2)"],
|
||||
"Url" : "https://github.com/Palm1r",
|
||||
"Url" : "https://github.com/Palm1r/QodeAssist",
|
||||
${IDE_PLUGIN_DEPENDENCIES}
|
||||
}
|
||||
|
||||
@ -31,7 +31,9 @@
|
||||
|
||||
#include "LLMClientInterface.hpp"
|
||||
#include "LLMSuggestion.hpp"
|
||||
#include "QodeAssistSettings.hpp"
|
||||
#include "core/ChangesManager.h"
|
||||
#include "settings/ContextSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
|
||||
using namespace LanguageServerProtocol;
|
||||
using namespace TextEditor;
|
||||
@ -43,6 +45,7 @@ namespace QodeAssist {
|
||||
|
||||
QodeAssistClient::QodeAssistClient()
|
||||
: LanguageClient::Client(new LLMClientInterface())
|
||||
, m_recentCharCount(0)
|
||||
{
|
||||
setName("Qode Assist");
|
||||
LanguageClient::LanguageFilter filter;
|
||||
@ -51,6 +54,8 @@ QodeAssistClient::QodeAssistClient()
|
||||
|
||||
start();
|
||||
setupConnections();
|
||||
|
||||
m_typingTimer.start();
|
||||
}
|
||||
|
||||
QodeAssistClient::~QodeAssistClient()
|
||||
@ -70,7 +75,7 @@ void QodeAssistClient::openDocument(TextEditor::TextDocument *document)
|
||||
this,
|
||||
[this, document](int position, int charsRemoved, int charsAdded) {
|
||||
Q_UNUSED(charsRemoved)
|
||||
if (!settings().enableAutoComplete())
|
||||
if (!Settings::generalSettings().enableAutoComplete())
|
||||
return;
|
||||
|
||||
auto project = ProjectManager::projectForFile(document->filePath());
|
||||
@ -80,13 +85,31 @@ void QodeAssistClient::openDocument(TextEditor::TextDocument *document)
|
||||
auto textEditor = BaseTextEditor::currentTextEditor();
|
||||
if (!textEditor || textEditor->document() != document)
|
||||
return;
|
||||
|
||||
if (Settings::contextSettings().useProjectChangesCache())
|
||||
ChangesManager::instance().addChange(document,
|
||||
position,
|
||||
charsRemoved,
|
||||
charsAdded);
|
||||
|
||||
TextEditorWidget *widget = textEditor->editorWidget();
|
||||
if (widget->isReadOnly() || widget->multiTextCursor().hasMultipleCursors())
|
||||
return;
|
||||
const int cursorPosition = widget->textCursor().position();
|
||||
if (cursorPosition < position || cursorPosition > position + charsAdded)
|
||||
return;
|
||||
scheduleRequest(widget);
|
||||
|
||||
m_recentCharCount += charsAdded;
|
||||
|
||||
if (m_typingTimer.elapsed()
|
||||
> Settings::generalSettings().autoCompletionTypingInterval()) {
|
||||
m_recentCharCount = charsAdded;
|
||||
m_typingTimer.restart();
|
||||
}
|
||||
|
||||
if (m_recentCharCount > Settings::generalSettings().autoCompletionCharThreshold()) {
|
||||
scheduleRequest(widget);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -130,7 +153,8 @@ void QodeAssistClient::scheduleRequest(TextEditor::TextEditorWidget *editor)
|
||||
connect(timer, &QTimer::timeout, this, [this, editor]() {
|
||||
if (editor
|
||||
&& editor->textCursor().position()
|
||||
== m_scheduledRequests[editor]->property("cursorPosition").toInt())
|
||||
== m_scheduledRequests[editor]->property("cursorPosition").toInt()
|
||||
&& m_recentCharCount > Settings::generalSettings().autoCompletionCharThreshold())
|
||||
requestCompletions(editor);
|
||||
});
|
||||
connect(editor, &TextEditorWidget::destroyed, this, [this, editor]() {
|
||||
@ -144,9 +168,8 @@ void QodeAssistClient::scheduleRequest(TextEditor::TextEditorWidget *editor)
|
||||
}
|
||||
|
||||
it.value()->setProperty("cursorPosition", editor->textCursor().position());
|
||||
it.value()->start(settings().startSuggestionTimer());
|
||||
it.value()->start(Settings::generalSettings().startSuggestionTimer());
|
||||
}
|
||||
|
||||
void QodeAssistClient::handleCompletions(const GetCompletionRequest::Response &response,
|
||||
TextEditor::TextEditorWidget *editor)
|
||||
{
|
||||
@ -190,7 +213,6 @@ void QodeAssistClient::handleCompletions(const GetCompletionRequest::Response &r
|
||||
return;
|
||||
editor->insertSuggestion(
|
||||
std::make_unique<LLMSuggestion>(completions.first(), editor->document()));
|
||||
editor->addHoverHandler(&m_hoverHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,7 +227,7 @@ void QodeAssistClient::cancelRunningRequest(TextEditor::TextEditorWidget *editor
|
||||
|
||||
bool QodeAssistClient::isEnabled(ProjectExplorer::Project *project) const
|
||||
{
|
||||
return settings().enableQodeAssist();
|
||||
return Settings::generalSettings().enableQodeAssist();
|
||||
}
|
||||
|
||||
void QodeAssistClient::setupConnections()
|
||||
@ -237,11 +259,6 @@ void QodeAssistClient::cleanupConnections()
|
||||
disconnect(m_documentOpenedConnection);
|
||||
disconnect(m_documentClosedConnection);
|
||||
|
||||
for (IEditor *editor : DocumentModel::editorsForOpenedDocuments()) {
|
||||
if (auto textEditor = qobject_cast<BaseTextEditor *>(editor))
|
||||
textEditor->editorWidget()->removeHoverHandler(&m_hoverHandler);
|
||||
}
|
||||
|
||||
qDeleteAll(m_scheduledRequests);
|
||||
m_scheduledRequests.clear();
|
||||
}
|
||||
|
||||
@ -27,7 +27,6 @@
|
||||
#include <languageclient/client.h>
|
||||
|
||||
#include "LSPCompletion.hpp"
|
||||
#include "QodeAssistHoverHandler.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
@ -54,9 +53,11 @@ private:
|
||||
|
||||
QHash<TextEditor::TextEditorWidget *, GetCompletionRequest> m_runningRequests;
|
||||
QHash<TextEditor::TextEditorWidget *, QTimer *> m_scheduledRequests;
|
||||
QodeAssistHoverHandler m_hoverHandler;
|
||||
QMetaObject::Connection m_documentOpenedConnection;
|
||||
QMetaObject::Connection m_documentClosedConnection;
|
||||
|
||||
QElapsedTimer m_typingTimer;
|
||||
int m_recentCharCount;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@ -30,7 +30,6 @@ const char ENABLE_AUTO_COMPLETE[] = "QodeAssist.enableAutoComplete";
|
||||
const char ENABLE_LOGGING[] = "QodeAssist.enableLogging";
|
||||
const char LLM_PROVIDERS[] = "QodeAssist.llmProviders";
|
||||
const char URL[] = "QodeAssist.url";
|
||||
const char PORT[] = "QodeAssist.port";
|
||||
const char END_POINT[] = "QodeAssist.endPoint";
|
||||
const char MODEL_NAME[] = "QodeAssist.modelName";
|
||||
const char SELECT_MODELS[] = "QodeAssist.selectModels";
|
||||
@ -50,11 +49,32 @@ const char USE_FREQUENCY_PENALTY[] = "QodeAssist.useFrequencyPenalty";
|
||||
const char FREQUENCY_PENALTY[] = "QodeAssist.frequencyPenalty";
|
||||
const char PROVIDER_PATHS[] = "QodeAssist.providerPaths";
|
||||
const char START_SUGGESTION_TIMER[] = "QodeAssist.startSuggestionTimer";
|
||||
const char AUTO_COMPLETION_CHAR_THRESHOLD[] = "QodeAssist.autoCompletionCharThreshold";
|
||||
const char AUTO_COMPLETION_TYPING_INTERVAL[] = "QodeAssist.autoCompletionTypingInterval";
|
||||
const char MAX_FILE_THRESHOLD[] = "QodeAssist.maxFileThreshold";
|
||||
const char OLLAMA_LIVETIME[] = "QodeAssist.ollamaLivetime";
|
||||
const char SPECIFIC_INSTRUCTIONS[] = "QodeAssist.specificInstractions";
|
||||
const char MULTILINE_COMPLETION[] = "QodeAssist.multilineCompletion";
|
||||
const char API_KEY[] = "QodeAssist.apiKey";
|
||||
const char USE_SPECIFIC_INSTRUCTIONS[] = "QodeAssist.useSpecificInstructions";
|
||||
const char USE_FILE_PATH_IN_CONTEXT[] = "QodeAssist.useFilePathInContext";
|
||||
const char CUSTOM_JSON_TEMPLATE[] = "QodeAssist.customJsonTemplate";
|
||||
const char USE_PROJECT_CHANGES_CACHE[] = "QodeAssist.useProjectChangesCache";
|
||||
const char MAX_CHANGES_CACHE_SIZE[] = "QodeAssist.maxChangesCacheSize";
|
||||
const char CHAT_LLM_PROVIDERS[] = "QodeAssist.chatLlmProviders";
|
||||
const char CHAT_URL[] = "QodeAssist.chatUrl";
|
||||
const char CHAT_END_POINT[] = "QodeAssist.chatEndPoint";
|
||||
const char CHAT_MODEL_NAME[] = "QodeAssist.chatModelName";
|
||||
const char CHAT_SELECT_MODELS[] = "QodeAssist.chatSelectModels";
|
||||
const char CHAT_PROMPTS[] = "QodeAssist.chatPrompts";
|
||||
|
||||
const char QODE_ASSIST_GENERAL_OPTIONS_ID[] = "QodeAssist.GeneralOptions";
|
||||
const char QODE_ASSIST_GENERAL_SETTINGS_PAGE_ID[] = "QodeAssist.1GeneralSettingsPageId";
|
||||
const char QODE_ASSIST_CONTEXT_SETTINGS_PAGE_ID[] = "QodeAssist.2ContextSettingsPageId";
|
||||
const char QODE_ASSIST_PRESET_PROMPTS_SETTINGS_PAGE_ID[]
|
||||
= "QodeAssist.3PresetPromptsSettingsPageId";
|
||||
const char QODE_ASSIST_CUSTOM_PROMPT_SETTINGS_PAGE_ID[] = "QodeAssist.4CustomPromptSettingsPageId";
|
||||
|
||||
const char QODE_ASSIST_GENERAL_OPTIONS_CATEGORY[] = "QodeAssist.Category";
|
||||
const char QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY[] = "Qode Assist";
|
||||
|
||||
|
||||
33
QodeAssistData.hpp
Normal file
33
QodeAssistData.hpp
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
struct ContextData
|
||||
{
|
||||
QString prefix;
|
||||
QString suffix;
|
||||
QString instriuctions;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
@ -1,114 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Qt Company Ltd.
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of Qode Assist.
|
||||
*
|
||||
* The Qt Company portions:
|
||||
* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||
*
|
||||
* Petr Mironychev portions:
|
||||
* 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 "QodeAssistHoverHandler.hpp"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QScopeGuard>
|
||||
#include <QToolBar>
|
||||
#include <QToolButton>
|
||||
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <texteditor/textdocumentlayout.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
|
||||
#include <utils/tooltip/tooltip.h>
|
||||
#include <utils/utilsicons.h>
|
||||
|
||||
#include "LLMSuggestion.hpp"
|
||||
#include "LSPCompletion.hpp"
|
||||
#include "QodeAssisttr.h"
|
||||
|
||||
using namespace TextEditor;
|
||||
using namespace LanguageServerProtocol;
|
||||
using namespace Utils;
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
class QodeAssistCompletionToolTip : public QToolBar
|
||||
{
|
||||
public:
|
||||
QodeAssistCompletionToolTip(TextEditorWidget *editor)
|
||||
: m_editor(editor)
|
||||
{
|
||||
auto apply = addAction(Tr::tr("Apply (%1)").arg(QKeySequence(Qt::Key_Tab).toString()));
|
||||
connect(apply, &QAction::triggered, this, &QodeAssistCompletionToolTip::apply);
|
||||
}
|
||||
|
||||
private:
|
||||
void apply()
|
||||
{
|
||||
if (TextSuggestion *suggestion = m_editor->currentSuggestion()) {
|
||||
if (!suggestion->apply())
|
||||
return;
|
||||
}
|
||||
ToolTip::hide();
|
||||
}
|
||||
|
||||
TextEditorWidget *m_editor;
|
||||
};
|
||||
|
||||
void QodeAssistHoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget,
|
||||
int pos,
|
||||
ReportPriority report)
|
||||
{
|
||||
QScopeGuard cleanup([&] { report(Priority_None); });
|
||||
if (!editorWidget->suggestionVisible())
|
||||
return;
|
||||
|
||||
QTextCursor cursor(editorWidget->document());
|
||||
cursor.setPosition(pos);
|
||||
m_block = cursor.block();
|
||||
auto *suggestion = dynamic_cast<LLMSuggestion *>(TextDocumentLayout::suggestion(m_block));
|
||||
|
||||
if (!suggestion)
|
||||
return;
|
||||
|
||||
const Completion completion = suggestion->completion();
|
||||
if (completion.text().isEmpty())
|
||||
return;
|
||||
|
||||
cleanup.dismiss();
|
||||
report(Priority_Suggestion);
|
||||
}
|
||||
|
||||
void QodeAssistHoverHandler::operateTooltip(TextEditor::TextEditorWidget *editorWidget,
|
||||
const QPoint &point)
|
||||
{
|
||||
Q_UNUSED(point)
|
||||
auto *suggestion = dynamic_cast<LLMSuggestion *>(TextDocumentLayout::suggestion(m_block));
|
||||
|
||||
if (!suggestion)
|
||||
return;
|
||||
|
||||
auto tooltipWidget = new QodeAssistCompletionToolTip(editorWidget);
|
||||
|
||||
const QRect cursorRect = editorWidget->cursorRect(editorWidget->textCursor());
|
||||
QPoint pos = editorWidget->viewport()->mapToGlobal(cursorRect.topLeft())
|
||||
- Utils::ToolTip::offsetFromPosition();
|
||||
pos.ry() -= tooltipWidget->sizeHint().height();
|
||||
ToolTip::show(pos, tooltipWidget, editorWidget);
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
@ -1,388 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "QodeAssistSettings.hpp"
|
||||
|
||||
#include <QInputDialog>
|
||||
#include <QtWidgets/qmessagebox.h>
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include "QodeAssistConstants.hpp"
|
||||
#include "QodeAssisttr.h"
|
||||
|
||||
#include "LLMProvidersManager.hpp"
|
||||
#include "PromptTemplateManager.hpp"
|
||||
#include "QodeAssistUtils.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
QodeAssistSettings &settings()
|
||||
{
|
||||
static QodeAssistSettings settings;
|
||||
return settings;
|
||||
}
|
||||
|
||||
QodeAssistSettings::QodeAssistSettings()
|
||||
{
|
||||
setAutoApply(false);
|
||||
|
||||
enableQodeAssist.setSettingsKey(Constants::ENABLE_QODE_ASSIST);
|
||||
enableQodeAssist.setLabelText(Tr::tr("Enable Qode Assist"));
|
||||
enableQodeAssist.setDefaultValue(true);
|
||||
|
||||
enableAutoComplete.setSettingsKey(Constants::ENABLE_AUTO_COMPLETE);
|
||||
enableAutoComplete.setLabelText(Tr::tr("Enable Auto Complete"));
|
||||
enableAutoComplete.setDefaultValue(true);
|
||||
|
||||
enableLogging.setSettingsKey(Constants::ENABLE_LOGGING);
|
||||
enableLogging.setLabelText(Tr::tr("Enable Logging"));
|
||||
enableLogging.setDefaultValue(false);
|
||||
|
||||
llmProviders.setSettingsKey(Constants::LLM_PROVIDERS);
|
||||
llmProviders.setDisplayName(Tr::tr("LLM Providers:"));
|
||||
llmProviders.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
|
||||
llmProviders.setDefaultValue(1);
|
||||
|
||||
url.setSettingsKey(Constants::URL);
|
||||
url.setLabelText(Tr::tr("URL:"));
|
||||
url.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
|
||||
endPoint.setSettingsKey(Constants::END_POINT);
|
||||
endPoint.setLabelText(Tr::tr("Endpoint:"));
|
||||
endPoint.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
|
||||
port.setSettingsKey(Constants::PORT);
|
||||
port.setLabelText(Tr::tr("Port:"));
|
||||
port.setRange(1, 65535);
|
||||
|
||||
modelName.setSettingsKey(Constants::MODEL_NAME);
|
||||
modelName.setLabelText(Tr::tr("LLM Name:"));
|
||||
modelName.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
|
||||
temperature.setSettingsKey(Constants::TEMPERATURE);
|
||||
temperature.setLabelText(Tr::tr("Temperature:"));
|
||||
temperature.setDefaultValue(0.2);
|
||||
temperature.setRange(0.0, 10.0);
|
||||
|
||||
selectModels.m_buttonText = Tr::tr("Select Models");
|
||||
|
||||
ollamaLivetime.setSettingsKey(Constants::OLLAMA_LIVETIME);
|
||||
ollamaLivetime.setLabelText(
|
||||
Tr::tr("Time to suspend Ollama after completion request (in minutes), "
|
||||
"Only Ollama, -1 to disable"));
|
||||
ollamaLivetime.setDefaultValue("5m");
|
||||
ollamaLivetime.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
|
||||
fimPrompts.setDisplayName(Tr::tr("Fill-In-Middle Prompt"));
|
||||
fimPrompts.setSettingsKey(Constants::FIM_PROMPTS);
|
||||
fimPrompts.setDefaultValue(1);
|
||||
fimPrompts.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
|
||||
|
||||
readFullFile.setSettingsKey(Constants::READ_FULL_FILE);
|
||||
readFullFile.setLabelText(Tr::tr("Read Full File"));
|
||||
readFullFile.setDefaultValue(false);
|
||||
|
||||
maxFileThreshold.setSettingsKey(Constants::MAX_FILE_THRESHOLD);
|
||||
maxFileThreshold.setLabelText(Tr::tr("Max File Threshold:"));
|
||||
maxFileThreshold.setRange(10, 100000);
|
||||
maxFileThreshold.setDefaultValue(600);
|
||||
|
||||
readStringsBeforeCursor.setSettingsKey(Constants::READ_STRINGS_BEFORE_CURSOR);
|
||||
readStringsBeforeCursor.setLabelText(Tr::tr("Read Strings Before Cursor"));
|
||||
readStringsBeforeCursor.setDefaultValue(50);
|
||||
|
||||
readStringsAfterCursor.setSettingsKey(Constants::READ_STRINGS_AFTER_CURSOR);
|
||||
readStringsAfterCursor.setLabelText(Tr::tr("Read Strings After Cursor"));
|
||||
readStringsAfterCursor.setDefaultValue(30);
|
||||
|
||||
maxTokens.setSettingsKey(Constants::MAX_TOKENS);
|
||||
maxTokens.setLabelText(Tr::tr("Max Tokens"));
|
||||
maxTokens.setRange(-1, 10000);
|
||||
maxTokens.setDefaultValue(250);
|
||||
|
||||
useTopP.setSettingsKey(Constants::USE_TOP_P);
|
||||
useTopP.setDefaultValue(false);
|
||||
|
||||
topP.setSettingsKey(Constants::TOP_P);
|
||||
topP.setLabelText(Tr::tr("top_p"));
|
||||
topP.setDefaultValue(0.9);
|
||||
topP.setRange(0.0, 10.0);
|
||||
|
||||
useTopK.setSettingsKey(Constants::USE_TOP_K);
|
||||
useTopK.setDefaultValue(false);
|
||||
|
||||
topK.setSettingsKey(Constants::TOP_K);
|
||||
topK.setLabelText(Tr::tr("top_k"));
|
||||
topK.setDefaultValue(0.1);
|
||||
topK.setRange(0, 10.0);
|
||||
|
||||
usePresencePenalty.setSettingsKey(Constants::USE_PRESENCE_PENALTY);
|
||||
usePresencePenalty.setDefaultValue(false);
|
||||
|
||||
presencePenalty.setSettingsKey(Constants::PRESENCE_PENALTY);
|
||||
presencePenalty.setLabelText(Tr::tr("presence_penalty"));
|
||||
presencePenalty.setDefaultValue(0.0);
|
||||
presencePenalty.setRange(-2.0, 2.0);
|
||||
|
||||
useFrequencyPenalty.setSettingsKey(Constants::USE_FREQUENCY_PENALTY);
|
||||
useFrequencyPenalty.setDefaultValue(false);
|
||||
|
||||
frequencyPenalty.setSettingsKey(Constants::FREQUENCY_PENALTY);
|
||||
frequencyPenalty.setLabelText(Tr::tr("frequency_penalty"));
|
||||
frequencyPenalty.setDefaultValue(0.0);
|
||||
frequencyPenalty.setRange(-2.0, 2.0);
|
||||
|
||||
providerPaths.setSettingsKey(Constants::PROVIDER_PATHS);
|
||||
providerPaths.setLabelText(Tr::tr("Provider Paths:"));
|
||||
|
||||
startSuggestionTimer.setSettingsKey(Constants::START_SUGGESTION_TIMER);
|
||||
startSuggestionTimer.setLabelText(Tr::tr("Start Suggestion Timer:"));
|
||||
startSuggestionTimer.setRange(10, 10000);
|
||||
startSuggestionTimer.setDefaultValue(500);
|
||||
|
||||
specificInstractions.setSettingsKey(Constants::SPECIFIC_INSTRUCTIONS);
|
||||
specificInstractions.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||
specificInstractions.setLabelText(
|
||||
Tr::tr("Instructions: Please keep %1 for languge name, warning, it shouldn't too big"));
|
||||
specificInstractions.setDefaultValue(
|
||||
"You are an expert %1 code completion AI."
|
||||
"CRITICAL: Please provide minimal the best possible code completion suggestions.\n");
|
||||
|
||||
resetToDefaults.m_buttonText = Tr::tr("Reset to Defaults");
|
||||
|
||||
const auto &manager = LLMProvidersManager::instance();
|
||||
if (!manager.getProviderNames().isEmpty()) {
|
||||
const auto providerNames = manager.getProviderNames();
|
||||
for (const QString &name : providerNames) {
|
||||
llmProviders.addOption(name);
|
||||
}
|
||||
}
|
||||
|
||||
const auto &promptManager = PromptTemplateManager::instance();
|
||||
if (!promptManager.getTemplateNames().isEmpty()) {
|
||||
const auto promptNames = promptManager.getTemplateNames();
|
||||
for (const QString &name : promptNames) {
|
||||
fimPrompts.addOption(name);
|
||||
}
|
||||
}
|
||||
|
||||
readSettings();
|
||||
|
||||
topK.setEnabled(useTopK());
|
||||
topP.setEnabled(useTopP());
|
||||
presencePenalty.setEnabled(usePresencePenalty());
|
||||
frequencyPenalty.setEnabled(useFrequencyPenalty());
|
||||
readStringsAfterCursor.setEnabled(!readFullFile());
|
||||
readStringsBeforeCursor.setEnabled(!readFullFile());
|
||||
PromptTemplateManager::instance().setCurrentTemplate(fimPrompts.stringValue());
|
||||
LLMProvidersManager::instance().setCurrentProvider(llmProviders.stringValue());
|
||||
updateProviderSettings();
|
||||
|
||||
setLoggingEnabled(enableLogging());
|
||||
|
||||
setLayouter([this]() {
|
||||
using namespace Layouting;
|
||||
|
||||
return Column{Group{title(Tr::tr("General Settings")),
|
||||
Form{Column{enableQodeAssist,
|
||||
enableAutoComplete,
|
||||
enableLogging,
|
||||
Row{Stretch{1}, resetToDefaults}}}},
|
||||
Group{title(Tr::tr("LLM Providers")),
|
||||
Form{Column{llmProviders, Row{url, port, endPoint}, providerPaths}}},
|
||||
Group{title(Tr::tr("LLM Model Settings")),
|
||||
Form{Column{Row{selectModels, modelName}}}},
|
||||
Group{title(Tr::tr("FIM Prompt Settings")),
|
||||
Form{Column{fimPrompts,
|
||||
readFullFile,
|
||||
maxFileThreshold,
|
||||
ollamaLivetime,
|
||||
specificInstractions,
|
||||
temperature,
|
||||
maxTokens,
|
||||
readStringsBeforeCursor,
|
||||
readStringsAfterCursor,
|
||||
startSuggestionTimer,
|
||||
Row{useTopP, topP, Stretch{1}},
|
||||
Row{useTopK, topK, Stretch{1}},
|
||||
Row{usePresencePenalty, presencePenalty, Stretch{1}},
|
||||
Row{useFrequencyPenalty, frequencyPenalty, Stretch{1}}}}},
|
||||
st};
|
||||
});
|
||||
|
||||
setupConnections();
|
||||
}
|
||||
|
||||
void QodeAssistSettings::setupConnections()
|
||||
{
|
||||
connect(&llmProviders, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
|
||||
int index = llmProviders.volatileValue();
|
||||
logMessage(QString("currentProvider %1").arg(llmProviders.displayForIndex(index)));
|
||||
LLMProvidersManager::instance().setCurrentProvider(llmProviders.displayForIndex(index));
|
||||
updateProviderSettings();
|
||||
});
|
||||
|
||||
connect(&fimPrompts, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
|
||||
int index = fimPrompts.volatileValue();
|
||||
logMessage(QString("currentPrompt %1").arg(fimPrompts.displayForIndex(index)));
|
||||
PromptTemplateManager::instance().setCurrentTemplate(fimPrompts.displayForIndex(index));
|
||||
});
|
||||
|
||||
connect(&selectModels, &ButtonAspect::clicked, this, [this]() { showModelSelectionDialog(); });
|
||||
connect(&useTopP, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
topP.setEnabled(useTopP.volatileValue());
|
||||
});
|
||||
connect(&useTopK, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
topK.setEnabled(useTopK.volatileValue());
|
||||
});
|
||||
connect(&usePresencePenalty, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
presencePenalty.setEnabled(usePresencePenalty.volatileValue());
|
||||
});
|
||||
connect(&useFrequencyPenalty, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
frequencyPenalty.setEnabled(useFrequencyPenalty.volatileValue());
|
||||
});
|
||||
connect(&readFullFile, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
readStringsAfterCursor.setEnabled(!readFullFile.volatileValue());
|
||||
readStringsBeforeCursor.setEnabled(!readFullFile.volatileValue());
|
||||
});
|
||||
connect(&resetToDefaults,
|
||||
&ButtonAspect::clicked,
|
||||
this,
|
||||
&QodeAssistSettings::resetSettingsToDefaults);
|
||||
connect(&enableLogging, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
setLoggingEnabled(enableLogging.volatileValue());
|
||||
});
|
||||
}
|
||||
|
||||
void QodeAssistSettings::updateProviderSettings()
|
||||
{
|
||||
auto *provider = LLMProvidersManager::instance().getCurrentProvider();
|
||||
|
||||
if (provider) {
|
||||
logMessage(QString("currentProvider %1").arg(provider->name()));
|
||||
url.setValue(provider->url());
|
||||
port.setValue(provider->defaultPort());
|
||||
endPoint.setValue(provider->completionEndpoint());
|
||||
ollamaLivetime.setEnabled(provider->name() == "Ollama");
|
||||
}
|
||||
}
|
||||
|
||||
QStringList QodeAssistSettings::getInstalledModels()
|
||||
{
|
||||
auto *provider = LLMProvidersManager::instance().getCurrentProvider();
|
||||
if (provider) {
|
||||
auto env = getEnvironmentWithProviderPaths();
|
||||
return provider->getInstalledModels(env);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void QodeAssistSettings::showModelSelectionDialog()
|
||||
{
|
||||
QStringList models = getInstalledModels();
|
||||
|
||||
bool ok;
|
||||
QString selectedModel = QInputDialog::getItem(Core::ICore::dialogParent(),
|
||||
Tr::tr("Select LLM Model"),
|
||||
Tr::tr("Choose a model:"),
|
||||
models,
|
||||
0,
|
||||
false,
|
||||
&ok);
|
||||
|
||||
if (ok && !selectedModel.isEmpty()) {
|
||||
modelName.setValue(selectedModel);
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
|
||||
Utils::Environment QodeAssistSettings::getEnvironmentWithProviderPaths() const
|
||||
{
|
||||
Utils::Environment env = Utils::Environment::systemEnvironment();
|
||||
const QStringList additionalPaths = providerPaths.volatileValue();
|
||||
for (const QString &path : additionalPaths) {
|
||||
env.prependOrSetPath(path);
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
void QodeAssistSettings::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(enableQodeAssist);
|
||||
resetAspect(enableAutoComplete);
|
||||
resetAspect(llmProviders);
|
||||
resetAspect(url);
|
||||
resetAspect(port);
|
||||
resetAspect(endPoint);
|
||||
resetAspect(modelName);
|
||||
resetAspect(fimPrompts);
|
||||
resetAspect(temperature);
|
||||
resetAspect(maxTokens);
|
||||
resetAspect(readFullFile);
|
||||
resetAspect(maxFileThreshold);
|
||||
resetAspect(readStringsBeforeCursor);
|
||||
resetAspect(readStringsAfterCursor);
|
||||
resetAspect(useTopP);
|
||||
resetAspect(topP);
|
||||
resetAspect(useTopK);
|
||||
resetAspect(topK);
|
||||
resetAspect(usePresencePenalty);
|
||||
resetAspect(presencePenalty);
|
||||
resetAspect(useFrequencyPenalty);
|
||||
resetAspect(frequencyPenalty);
|
||||
resetAspect(startSuggestionTimer);
|
||||
resetAspect(enableLogging);
|
||||
resetAspect(ollamaLivetime);
|
||||
resetAspect(specificInstractions);
|
||||
|
||||
updateProviderSettings();
|
||||
apply();
|
||||
|
||||
QMessageBox::information(Core::ICore::dialogParent(),
|
||||
Tr::tr("Settings Reset"),
|
||||
Tr::tr("All settings have been reset to their default values."));
|
||||
}
|
||||
}
|
||||
|
||||
class QodeAssistSettingsPage : public Core::IOptionsPage
|
||||
{
|
||||
public:
|
||||
QodeAssistSettingsPage()
|
||||
{
|
||||
setId(Constants::QODE_ASSIST_GENERAL_OPTIONS_ID);
|
||||
setDisplayName("Qode Assist");
|
||||
setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY);
|
||||
setDisplayCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY);
|
||||
setCategoryIconPath(":/resources/images/qoderassist-icon.png");
|
||||
setSettingsProvider([] { return &settings(); });
|
||||
}
|
||||
};
|
||||
|
||||
const QodeAssistSettingsPage settingsPage;
|
||||
|
||||
} // namespace QodeAssist
|
||||
@ -1,113 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QPushButton>
|
||||
#include <utils/aspects.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
template<typename AspectType>
|
||||
void resetAspect(AspectType &aspect)
|
||||
{
|
||||
aspect.setValue(aspect.defaultValue());
|
||||
}
|
||||
|
||||
class ButtonAspect : public Utils::BaseAspect
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ButtonAspect(Utils::AspectContainer *container = nullptr)
|
||||
: Utils::BaseAspect(container)
|
||||
{}
|
||||
|
||||
void addToLayout(Layouting::Layout &parent) override
|
||||
{
|
||||
auto button = new QPushButton(m_buttonText);
|
||||
connect(button, &QPushButton::clicked, this, &ButtonAspect::clicked);
|
||||
parent.addItem(button);
|
||||
}
|
||||
|
||||
QString m_buttonText;
|
||||
signals:
|
||||
void clicked();
|
||||
};
|
||||
|
||||
class QodeAssistSettings : public Utils::AspectContainer
|
||||
{
|
||||
public:
|
||||
QodeAssistSettings();
|
||||
|
||||
Utils::BoolAspect enableQodeAssist{this};
|
||||
Utils::BoolAspect enableAutoComplete{this};
|
||||
Utils::BoolAspect enableLogging{this};
|
||||
|
||||
Utils::SelectionAspect llmProviders{this};
|
||||
Utils::StringAspect url{this};
|
||||
Utils::IntegerAspect port{this};
|
||||
Utils::StringAspect endPoint{this};
|
||||
|
||||
Utils::StringAspect modelName{this};
|
||||
ButtonAspect selectModels{this};
|
||||
|
||||
Utils::SelectionAspect fimPrompts{this};
|
||||
Utils::DoubleAspect temperature{this};
|
||||
Utils::IntegerAspect maxTokens{this};
|
||||
|
||||
Utils::BoolAspect readFullFile{this};
|
||||
Utils::IntegerAspect readStringsBeforeCursor{this};
|
||||
Utils::IntegerAspect readStringsAfterCursor{this};
|
||||
|
||||
Utils::BoolAspect useTopP{this};
|
||||
Utils::DoubleAspect topP{this};
|
||||
|
||||
Utils::BoolAspect useTopK{this};
|
||||
Utils::DoubleAspect topK{this};
|
||||
|
||||
Utils::BoolAspect usePresencePenalty{this};
|
||||
Utils::DoubleAspect presencePenalty{this};
|
||||
|
||||
Utils::BoolAspect useFrequencyPenalty{this};
|
||||
Utils::DoubleAspect frequencyPenalty{this};
|
||||
|
||||
Utils::StringListAspect providerPaths{this};
|
||||
|
||||
Utils::IntegerAspect startSuggestionTimer{this};
|
||||
Utils::IntegerAspect maxFileThreshold{this};
|
||||
|
||||
Utils::StringAspect ollamaLivetime{this};
|
||||
Utils::StringAspect specificInstractions{this};
|
||||
|
||||
ButtonAspect resetToDefaults{this};
|
||||
|
||||
private:
|
||||
void setupConnections();
|
||||
void updateProviderSettings();
|
||||
QStringList getInstalledModels();
|
||||
void showModelSelectionDialog();
|
||||
Utils::Environment getEnvironmentWithProviderPaths() const;
|
||||
void resetSettingsToDefaults();
|
||||
};
|
||||
|
||||
QodeAssistSettings &settings();
|
||||
|
||||
} // namespace QodeAssist
|
||||
@ -19,7 +19,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
#include <coreplugin/messagemanager.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
@ -41,7 +46,7 @@ inline void logMessage(const QString &message, bool silent = true)
|
||||
if (!loggingEnabled())
|
||||
return;
|
||||
|
||||
const QString prefixedMessage = QLatin1String("[QLLamaAssist] ") + message;
|
||||
const QString prefixedMessage = QLatin1String("[Qode Assist] ") + message;
|
||||
if (silent) {
|
||||
Core::MessageManager::writeSilently(prefixedMessage);
|
||||
} else {
|
||||
@ -55,6 +60,8 @@ inline void logMessages(const QStringList &messages, bool silent = true)
|
||||
return;
|
||||
|
||||
QStringList prefixedMessages;
|
||||
qDebug() << prefixedMessages;
|
||||
|
||||
for (const QString &message : messages) {
|
||||
prefixedMessages << (QLatin1String("[Qode Assist] ") + message);
|
||||
}
|
||||
@ -65,4 +72,36 @@ inline void logMessages(const QStringList &messages, bool silent = true)
|
||||
}
|
||||
}
|
||||
|
||||
inline bool pingUrl(const QUrl &url, int timeout = 5000)
|
||||
{
|
||||
if (!url.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QNetworkAccessManager manager;
|
||||
QNetworkRequest request(url);
|
||||
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, true);
|
||||
|
||||
QScopedPointer<QNetworkReply> reply(manager.get(request));
|
||||
|
||||
QTimer timer;
|
||||
timer.setSingleShot(true);
|
||||
|
||||
QEventLoop loop;
|
||||
QObject::connect(reply.data(), &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
|
||||
timer.start(timeout);
|
||||
loop.exec();
|
||||
|
||||
if (timer.isActive()) {
|
||||
timer.stop();
|
||||
return (reply->error() == QNetworkReply::NoError);
|
||||
} else {
|
||||
QObject::disconnect(reply.data(), &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
reply->abort();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
104
README.md
104
README.md
@ -1,34 +1,67 @@
|
||||
# QodeAssist
|
||||
[](https://github.com/Palm1r/QodeAssist/actions/workflows/build_cmake.yml)
|
||||
|
||||
QodeAssist is an AI-powered coding assistant plugin for Qt Creator. It provides intelligent code completion and suggestions for C++ and QML, leveraging large language models through local providers like Ollama. Enhance your coding productivity with context-aware AI assistance directly in your Qt development environment.
|
||||
|
||||
<img src="https://github.com/user-attachments/assets/255a52f1-5cc0-4ca3-b05c-c4cf9cdbe25a" width="600" alt="QodeAssistPreview">
|
||||
|
||||
## Supported LLM Providers
|
||||
QodeAssist currently supports the following LLM (Large Language Model) providers:
|
||||
- [Ollama](https://ollama.com)
|
||||
- [LM Studio](https://lmstudio.ai)
|
||||
- OpenAI compatible providers
|
||||
|
||||
## Supported Models
|
||||
QodeAssist has been tested with the following language models:
|
||||
|
||||
Ollama:
|
||||
QodeAssist has been thoroughly tested and optimized for use with the following language models, all of which are specifically trained for Fill-in-the-Middle (FIM) tasks:
|
||||
|
||||
- CodeLlama
|
||||
- StarCoder2
|
||||
- DeepSeek-Coder-V2-Lite-Base
|
||||
|
||||
These models have demonstrated excellent performance in code completion and assistance tasks within the QodeAssist environment.
|
||||
|
||||
### Custom Prompts
|
||||
|
||||
For advanced users or those with specific requirements, QodeAssist offers the flexibility to create, save, and load custom prompts using JSON templates. This feature allows you to tailor the AI's behavior to your exact needs.
|
||||
|
||||
To get started with custom prompts:
|
||||
|
||||
1. Navigate to the "Custom Template" option in the FIM Prompt Settings.
|
||||
2. Create your custom JSON prompt template.
|
||||
3. Use the "Save Custom Template to JSON" button to store your template for future use.
|
||||
4. To use a previously saved template, click "Load Custom Template from JSON".
|
||||
5. Make sure to select "Custom Template" from the dropdown menu in the FIM Prompt Settings on the General page to activate your custom template.
|
||||
|
||||
For inspiration and examples of effective custom prompts, please refer to the `rawPromptExamples` folder in our repository.
|
||||
|
||||
<img width="600" alt="Custom template" src="https://github.com/user-attachments/assets/4a14c552-baba-4531-ab4f-cb1f9ac6620b">
|
||||
<img width="600" alt="Select custom template" src="https://github.com/user-attachments/assets/3651dafd-83f9-4df9-943f-69c28cd3d8a3">
|
||||
|
||||
### Tested Models
|
||||
|
||||
#### Ollama:
|
||||
- [starcoder2](https://ollama.com/library/starcoder2)
|
||||
- [codellama](https://ollama.com/library/codellama)
|
||||
|
||||
LM studio:
|
||||
#### LM Studio:
|
||||
- [second-state/StarCoder2-7B-GGUF](https://huggingface.co/second-state/StarCoder2-7B-GGUF)
|
||||
- [TheBloke/CodeLlama-7B-GGUF](https://huggingface.co/TheBloke/CodeLlama-7B-GGUF)
|
||||
|
||||
Please note that while these models have been specifically tested and confirmed to work well with QodeAssist, other models compatible with the supported providers may also work. We encourage users to experiment with different models and report their experiences.
|
||||
|
||||
If you've successfully used a model that's not listed here, please let us know by opening an issue or submitting a pull request to update this list.
|
||||
|
||||
## Development Progress
|
||||
|
||||
- [x] Basic plugin with code autocomplete functionality
|
||||
- [ ] Improve and automate settings
|
||||
- [x] Improve and automate settings
|
||||
- [ ] Add chat functionality
|
||||
- [x] Sharing diff with model
|
||||
- [ ] Sharing project source with model
|
||||
- [ ] Support for more providers and models
|
||||
|
||||
## Installation Plugin
|
||||
## Plugin installation using Ollama as an example
|
||||
|
||||
1. Install QtCreator 14.0
|
||||
2. Install [Ollama](https://ollama.com). Make sure to review the system requirements before installation.
|
||||
@ -38,23 +71,64 @@ ollama run starcoder2:7b
|
||||
```
|
||||
4. Download the QodeAssist plugin.
|
||||
5. Launch Qt Creator and install the plugin:
|
||||
- Go to About -> About Plugins
|
||||
- Go to MacOS: Qt Creator -> About Plugins...
|
||||
Windows\Linux: Help -> About Plugins...
|
||||
- Click on "Install Plugin..."
|
||||
- Select the downloaded QodeAssist plugin archive file
|
||||
|
||||
## Configure Plugin
|
||||
|
||||
<img src="https://github.com/user-attachments/assets/0743d09e-1f02-44ed-9a1a-85e2a0a0c01a" width="800" alt="QodeAssist в действии">
|
||||
|
||||
1. Open Qt Creator settings
|
||||
2. Navigate to the "Qode Assist" tab
|
||||
3. Choose your LLM provider (e.g., Ollama)
|
||||
- If you haven't added the provider to your system PATH, specify the path to the provider executable in the "Provider Paths" field
|
||||
4. Select the installed model
|
||||
- If you need to enter the model name manually, it indicates that the plugin cannot locate the provider's executable file. However, this doesn't affect the plugin's functionality – it will still work correctly. This autoselector input option is provided for your convenience, allowing you to easily select and use different models
|
||||
5. Choose the prompt template that corresponds to your model
|
||||
6. Apply the settings
|
||||
3. Select "General" page
|
||||
4. Choose your LLM provider (e.g., Ollama)
|
||||
5. Select the installed model by the "Select Model" button
|
||||
- For LM Studio you will see current load model
|
||||
6. Choose the prompt template that corresponds to your model
|
||||
7. Apply the settings
|
||||
|
||||
You're all set! QodeAssist is now ready to use in Qt Creator.
|
||||
|
||||
## Hotkeys
|
||||
|
||||
- To call manual request to suggestion, you can use or change it in settings
|
||||
- on Mac: Option + Command + Q
|
||||
- on Windows: Ctrl + Alt + Q
|
||||
- To insert the full suggestion, you can use the TAB key
|
||||
- To insert line by line, you can use the "Move cursor word right" shortcut:
|
||||
- On Mac: Option + Right Arrow
|
||||
- On Windows: Alt + Right Arrow
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If QodeAssist is having problems connecting to the LLM provider, please check the following:
|
||||
|
||||
1. Verify the IP address and port:
|
||||
|
||||
- For Ollama, the default is usually http://localhost:11434
|
||||
- For LM Studio, the default is usually http://localhost:1234
|
||||
|
||||
2. Check the endpoint:
|
||||
|
||||
Make sure the endpoint in the settings matches the one required by your provider
|
||||
- For Ollama, it should be /api/generate
|
||||
- For LM Studio and OpenAI compatible providers, it's usually /v1/chat/completions
|
||||
|
||||
3. Confirm that the selected model and template are compatible:
|
||||
|
||||
Ensure you've chosen the correct model in the "Select Models" option
|
||||
Verify that the selected prompt template matches the model you're using
|
||||
|
||||
If you're still experiencing issues with QodeAssist, you can try resetting the settings to their default values:
|
||||
1. Open Qt Creator settings
|
||||
2. Navigate to the "Qode Assist" tab
|
||||
3. Pick settings page for reset
|
||||
4. Click on the "Reset Page to Defaults" button
|
||||
- The API key will not reset
|
||||
- Select model after reset
|
||||
|
||||
## Support the development of QodeAssist
|
||||
If you find QodeAssist helpful, there are several ways you can support the project:
|
||||
|
||||
@ -64,8 +138,8 @@ If you find QodeAssist helpful, there are several ways you can support the proje
|
||||
|
||||
3. **Spread the Word**: Star our GitHub repository and share QodeAssist with your fellow developers.
|
||||
|
||||
4. **Financial Support**: If you'd like to support the development financially, you can make a donation using one of the following cryptocurrency addresses:
|
||||
|
||||
4. **Financial Support**: If you'd like to support the development financially, you can make a donation using one of the following:
|
||||
- [](https://ko-fi.com/P5P412V96G)
|
||||
- Bitcoin (BTC): `bc1qndq7f0mpnlya48vk7kugvyqj5w89xrg4wzg68t`
|
||||
- Ethereum (ETH): `0xA5e8c37c94b24e25F9f1f292a01AF55F03099D8D`
|
||||
- Litecoin (LTC): `ltc1qlrxnk30s2pcjchzx4qrxvdjt5gzuervy5mv0vy`
|
||||
@ -83,4 +157,4 @@ Create a build directory and run
|
||||
where `<path_to_qtcreator>` is the relative or absolute path to a Qt Creator build directory, or to a
|
||||
combined binary and development package (Windows / Linux), or to the `Qt Creator.app/Contents/Resources/`
|
||||
directory of a combined binary and development package (macOS), and `<path_to_plugin_source>` is the
|
||||
relative or absolute path to this plugin directory.
|
||||
relative or absolute path to this plugin directory.
|
||||
|
||||
153
chat/ChatClientInterface.cpp
Normal file
153
chat/ChatClientInterface.cpp
Normal file
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ChatClientInterface.hpp"
|
||||
#include "LLMProvidersManager.hpp"
|
||||
#include "PromptTemplateManager.hpp"
|
||||
#include "QodeAssistUtils.hpp"
|
||||
#include "settings/ContextSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/PresetPromptsSettings.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QUuid>
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
ChatClientInterface::ChatClientInterface(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_requestHandler(new LLMRequestHandler(this))
|
||||
{
|
||||
connect(m_requestHandler,
|
||||
&LLMRequestHandler::completionReceived,
|
||||
this,
|
||||
[this](const QString &completion, const QJsonObject &, bool isComplete) {
|
||||
handleLLMResponse(completion, isComplete);
|
||||
});
|
||||
|
||||
connect(m_requestHandler,
|
||||
&LLMRequestHandler::requestFinished,
|
||||
this,
|
||||
[this](const QString &, bool success, const QString &errorString) {
|
||||
if (!success) {
|
||||
emit errorOccurred(errorString);
|
||||
}
|
||||
});
|
||||
|
||||
// QJsonObject systemMessage;
|
||||
// systemMessage["role"] = "system";
|
||||
// systemMessage["content"] = "You are a helpful C++ and QML programming assistant.";
|
||||
// m_chatHistory.append(systemMessage);
|
||||
}
|
||||
|
||||
ChatClientInterface::~ChatClientInterface()
|
||||
{
|
||||
}
|
||||
|
||||
void ChatClientInterface::sendMessage(const QString &message)
|
||||
{
|
||||
logMessage("Sending message: " + message);
|
||||
logMessage("chatProvider " + Settings::generalSettings().chatLlmProviders.stringValue());
|
||||
logMessage("chatTemplate " + Settings::generalSettings().chatPrompts.stringValue());
|
||||
|
||||
auto chatTemplate = PromptTemplateManager::instance().getCurrentChatTemplate();
|
||||
auto chatProvider = LLMProvidersManager::instance().getCurrentChatProvider();
|
||||
|
||||
ContextData context;
|
||||
context.prefix = message;
|
||||
context.suffix = "";
|
||||
if (Settings::contextSettings().useSpecificInstructions())
|
||||
context.instriuctions = Settings::contextSettings().specificInstractions();
|
||||
|
||||
QJsonObject providerRequest;
|
||||
providerRequest["model"] = Settings::generalSettings().chatModelName();
|
||||
providerRequest["stream"] = true;
|
||||
|
||||
providerRequest["messages"] = m_chatHistory;
|
||||
|
||||
chatTemplate->prepareRequest(providerRequest, context);
|
||||
chatProvider->prepareRequest(providerRequest);
|
||||
|
||||
m_chatHistory = providerRequest["messages"].toArray();
|
||||
|
||||
LLMConfig config;
|
||||
config.requestType = RequestType::Chat;
|
||||
config.provider = chatProvider;
|
||||
config.promptTemplate = chatTemplate;
|
||||
config.url = QString("%1%2").arg(Settings::generalSettings().chatUrl(),
|
||||
Settings::generalSettings().chatEndPoint());
|
||||
config.providerRequest = providerRequest;
|
||||
|
||||
QJsonObject request;
|
||||
request["id"] = QUuid::createUuid().toString();
|
||||
|
||||
m_accumulatedResponse.clear();
|
||||
m_pendingMessage = message;
|
||||
m_requestHandler->sendLLMRequest(config, request);
|
||||
}
|
||||
|
||||
void ChatClientInterface::handleLLMResponse(const QString &response, bool isComplete)
|
||||
{
|
||||
m_accumulatedResponse += response;
|
||||
logMessage("Accumulated response: " + m_accumulatedResponse);
|
||||
|
||||
if (isComplete) {
|
||||
logMessage("Message completed. Final response: " + m_accumulatedResponse);
|
||||
emit messageReceived(m_accumulatedResponse.trimmed());
|
||||
|
||||
QJsonObject assistantMessage;
|
||||
assistantMessage["role"] = "assistant";
|
||||
assistantMessage["content"] = m_accumulatedResponse.trimmed();
|
||||
m_chatHistory.append(assistantMessage);
|
||||
|
||||
m_pendingMessage.clear();
|
||||
m_accumulatedResponse.clear();
|
||||
|
||||
trimChatHistory();
|
||||
}
|
||||
}
|
||||
|
||||
void ChatClientInterface::trimChatHistory()
|
||||
{
|
||||
int maxTokens = 4000;
|
||||
int totalTokens = 0;
|
||||
QJsonArray newHistory;
|
||||
|
||||
if (!m_chatHistory.isEmpty()
|
||||
&& m_chatHistory.first().toObject()["role"].toString() == "system") {
|
||||
newHistory.append(m_chatHistory.first());
|
||||
}
|
||||
|
||||
for (int i = m_chatHistory.size() - 1; i >= 0; --i) {
|
||||
QJsonObject message = m_chatHistory[i].toObject();
|
||||
int messageTokens = message["content"].toString().length() / 4;
|
||||
|
||||
if (totalTokens + messageTokens > maxTokens) {
|
||||
break;
|
||||
}
|
||||
|
||||
newHistory.prepend(message);
|
||||
totalTokens += messageTokens;
|
||||
}
|
||||
|
||||
m_chatHistory = newHistory;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
53
chat/ChatClientInterface.hpp
Normal file
53
chat/ChatClientInterface.hpp
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QtCore/qjsonarray.h>
|
||||
#include "QodeAssistData.hpp"
|
||||
#include "core/LLMRequestHandler.hpp"
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
class ChatClientInterface : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ChatClientInterface(QObject *parent = nullptr);
|
||||
~ChatClientInterface();
|
||||
|
||||
void sendMessage(const QString &message);
|
||||
|
||||
signals:
|
||||
void messageReceived(const QString &message);
|
||||
void errorOccurred(const QString &error);
|
||||
|
||||
private:
|
||||
void handleLLMResponse(const QString &response, bool isComplete);
|
||||
void trimChatHistory();
|
||||
|
||||
LLMRequestHandler *m_requestHandler;
|
||||
QString m_accumulatedResponse;
|
||||
QString m_pendingMessage;
|
||||
QJsonArray m_chatHistory;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
95
chat/ChatOutputPane.cpp
Normal file
95
chat/ChatOutputPane.cpp
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ChatOutputPane.h"
|
||||
|
||||
#include "QodeAssisttr.h"
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
ChatOutputPane::ChatOutputPane(QObject *parent)
|
||||
: Core::IOutputPane(parent)
|
||||
, m_chatWidget(new ChatWidget)
|
||||
{
|
||||
setId("QodeAssistChat");
|
||||
setDisplayName(Tr::tr("QodeAssist Chat"));
|
||||
setPriorityInStatusBar(-40);
|
||||
}
|
||||
|
||||
ChatOutputPane::~ChatOutputPane()
|
||||
{
|
||||
delete m_chatWidget;
|
||||
}
|
||||
|
||||
QWidget *ChatOutputPane::outputWidget(QWidget *)
|
||||
{
|
||||
return m_chatWidget;
|
||||
}
|
||||
|
||||
QList<QWidget *> ChatOutputPane::toolBarWidgets() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void ChatOutputPane::clearContents()
|
||||
{
|
||||
m_chatWidget->clear();
|
||||
}
|
||||
|
||||
void ChatOutputPane::visibilityChanged(bool visible)
|
||||
{
|
||||
if (visible)
|
||||
m_chatWidget->scrollToBottom();
|
||||
}
|
||||
|
||||
void ChatOutputPane::setFocus()
|
||||
{
|
||||
m_chatWidget->setFocus();
|
||||
}
|
||||
|
||||
bool ChatOutputPane::hasFocus() const
|
||||
{
|
||||
return m_chatWidget->hasFocus();
|
||||
}
|
||||
|
||||
bool ChatOutputPane::canFocus() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChatOutputPane::canNavigate() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChatOutputPane::canNext() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChatOutputPane::canPrevious() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void ChatOutputPane::goToNext() {}
|
||||
|
||||
void ChatOutputPane::goToPrev() {}
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
52
chat/ChatOutputPane.h
Normal file
52
chat/ChatOutputPane.h
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ChatWidget.h"
|
||||
#include <coreplugin/ioutputpane.h>
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
class ChatOutputPane : public Core::IOutputPane
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ChatOutputPane(QObject *parent = nullptr);
|
||||
~ChatOutputPane() override;
|
||||
|
||||
QWidget *outputWidget(QWidget *parent) override;
|
||||
QList<QWidget *> toolBarWidgets() const override;
|
||||
void clearContents() override;
|
||||
void visibilityChanged(bool visible) override;
|
||||
void setFocus() override;
|
||||
bool hasFocus() const override;
|
||||
bool canFocus() const override;
|
||||
bool canNavigate() const override;
|
||||
bool canNext() const override;
|
||||
bool canPrevious() const override;
|
||||
void goToNext() override;
|
||||
void goToPrev() override;
|
||||
|
||||
private:
|
||||
ChatWidget *m_chatWidget;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
150
chat/ChatWidget.cpp
Normal file
150
chat/ChatWidget.cpp
Normal file
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ChatWidget.h"
|
||||
#include "QodeAssistUtils.hpp"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QScrollBar>
|
||||
#include <QVBoxLayout>
|
||||
#include <QtCore/qtimer.h>
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
ChatWidget::ChatWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_showTimestamp(false)
|
||||
, m_chatClient(new ChatClientInterface(this))
|
||||
{
|
||||
setupUi();
|
||||
|
||||
connect(m_sendButton, &QPushButton::clicked, this, &ChatWidget::sendMessage);
|
||||
connect(m_messageInput, &QLineEdit::returnPressed, this, &ChatWidget::sendMessage);
|
||||
|
||||
connect(m_chatClient, &ChatClientInterface::messageReceived, this, &ChatWidget::receiveMessage);
|
||||
connect(m_chatClient, &ChatClientInterface::errorOccurred, this, &ChatWidget::handleError);
|
||||
|
||||
logMessage("ChatWidget initialized");
|
||||
}
|
||||
|
||||
void ChatWidget::setupUi()
|
||||
{
|
||||
m_chatDisplay = new QTextEdit(this);
|
||||
m_chatDisplay->setReadOnly(true);
|
||||
|
||||
m_messageInput = new QLineEdit(this);
|
||||
m_sendButton = new QPushButton("Send", this);
|
||||
|
||||
QHBoxLayout *inputLayout = new QHBoxLayout;
|
||||
inputLayout->addWidget(m_messageInput);
|
||||
inputLayout->addWidget(m_sendButton);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->addWidget(m_chatDisplay);
|
||||
mainLayout->addLayout(inputLayout);
|
||||
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
void ChatWidget::sendMessage()
|
||||
{
|
||||
QString message = m_messageInput->text().trimmed();
|
||||
if (!message.isEmpty()) {
|
||||
logMessage("Sending message: " + message);
|
||||
addMessage(message, true);
|
||||
m_chatClient->sendMessage(message);
|
||||
m_messageInput->clear();
|
||||
addMessage("AI is typing...", false);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatWidget::receiveMessage(const QString &message)
|
||||
{
|
||||
logMessage("Received message: " + message);
|
||||
updateLastAIMessage(message);
|
||||
}
|
||||
|
||||
void ChatWidget::receivePartialMessage(const QString &partialMessage)
|
||||
{
|
||||
logMessage("Received partial message: " + partialMessage);
|
||||
m_currentAIResponse += partialMessage;
|
||||
updateLastAIMessage(m_currentAIResponse);
|
||||
}
|
||||
|
||||
void ChatWidget::onMessageCompleted()
|
||||
{
|
||||
logMessage("Message completed. Final response: " + m_currentAIResponse);
|
||||
updateLastAIMessage(m_currentAIResponse);
|
||||
m_currentAIResponse.clear();
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
void ChatWidget::handleError(const QString &error)
|
||||
{
|
||||
logMessage("Error occurred: " + error);
|
||||
addMessage("Error: " + error, false);
|
||||
}
|
||||
|
||||
void ChatWidget::addMessage(const QString &message, bool fromUser)
|
||||
{
|
||||
auto prefix = fromUser ? "You: " : "AI: ";
|
||||
QString timestamp = m_showTimestamp ? QDateTime::currentDateTime().toString("[hh:mm:ss] ") : "";
|
||||
QString fullMessage = timestamp + prefix + message;
|
||||
logMessage("Adding message to display: " + fullMessage);
|
||||
m_chatDisplay->append(fullMessage);
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
void ChatWidget::updateLastAIMessage(const QString &message)
|
||||
{
|
||||
logMessage("Updating last AI message: " + message);
|
||||
QTextCursor cursor = m_chatDisplay->textCursor();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
|
||||
cursor.removeSelectedText();
|
||||
|
||||
QString timestamp = m_showTimestamp ? QDateTime::currentDateTime().toString("[hh:mm:ss] ") : "";
|
||||
cursor.insertText(timestamp + "AI: " + message);
|
||||
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
m_chatDisplay->setTextCursor(cursor);
|
||||
|
||||
scrollToBottom();
|
||||
m_chatDisplay->repaint();
|
||||
}
|
||||
|
||||
void ChatWidget::clear()
|
||||
{
|
||||
m_chatDisplay->clear();
|
||||
m_currentAIResponse.clear();
|
||||
}
|
||||
|
||||
void ChatWidget::scrollToBottom()
|
||||
{
|
||||
QScrollBar *scrollBar = m_chatDisplay->verticalScrollBar();
|
||||
scrollBar->setValue(scrollBar->maximum());
|
||||
}
|
||||
|
||||
void ChatWidget::setShowTimestamp(bool show)
|
||||
{
|
||||
m_showTimestamp = show;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
62
chat/ChatWidget.h
Normal file
62
chat/ChatWidget.h
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QTextEdit>
|
||||
#include <QWidget>
|
||||
|
||||
#include "ChatClientInterface.hpp"
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
class ChatWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ChatWidget(QWidget *parent = nullptr);
|
||||
|
||||
void clear();
|
||||
void scrollToBottom();
|
||||
void setShowTimestamp(bool show);
|
||||
|
||||
void receiveMessage(const QString &message);
|
||||
private slots:
|
||||
void sendMessage();
|
||||
void receivePartialMessage(const QString &partialMessage);
|
||||
void onMessageCompleted();
|
||||
void handleError(const QString &error);
|
||||
|
||||
private:
|
||||
QTextEdit *m_chatDisplay;
|
||||
QLineEdit *m_messageInput;
|
||||
QPushButton *m_sendButton;
|
||||
bool m_showTimestamp;
|
||||
ChatClientInterface *m_chatClient;
|
||||
QString m_currentAIResponse;
|
||||
|
||||
void setupUi();
|
||||
void addMessage(const QString &message, bool fromUser = true);
|
||||
void updateLastAIMessage(const QString &message);
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
83
core/ChangesManager.cpp
Normal file
83
core/ChangesManager.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ChangesManager.h"
|
||||
#include "QodeAssistUtils.hpp"
|
||||
#include "settings/ContextSettings.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
ChangesManager &ChangesManager::instance()
|
||||
{
|
||||
static ChangesManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
ChangesManager::ChangesManager()
|
||||
: QObject(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
ChangesManager::~ChangesManager()
|
||||
{
|
||||
}
|
||||
|
||||
void ChangesManager::addChange(TextEditor::TextDocument *document,
|
||||
int position,
|
||||
int charsRemoved,
|
||||
int charsAdded)
|
||||
{
|
||||
auto &documentQueue = m_documentChanges[document];
|
||||
|
||||
QTextBlock block = document->document()->findBlock(position);
|
||||
int lineNumber = block.blockNumber();
|
||||
QString lineContent = block.text();
|
||||
QString fileName = document->filePath().fileName();
|
||||
|
||||
ChangeInfo change{fileName, lineNumber, lineContent};
|
||||
|
||||
auto it = std::find_if(documentQueue.begin(),
|
||||
documentQueue.end(),
|
||||
[lineNumber](const ChangeInfo &c) { return c.lineNumber == lineNumber; });
|
||||
|
||||
if (it != documentQueue.end()) {
|
||||
it->lineContent = lineContent;
|
||||
} else {
|
||||
documentQueue.enqueue(change);
|
||||
|
||||
if (documentQueue.size() > Settings::contextSettings().maxChangesCacheSize()) {
|
||||
documentQueue.dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString ChangesManager::getRecentChangesContext(const TextEditor::TextDocument *currentDocument) const
|
||||
{
|
||||
QString context;
|
||||
for (auto it = m_documentChanges.constBegin(); it != m_documentChanges.constEnd(); ++it) {
|
||||
if (it.key() != currentDocument) {
|
||||
for (const auto &change : it.value()) {
|
||||
context += change.lineContent + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
61
core/ChangesManager.h
Normal file
61
core/ChangesManager.h
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QHash>
|
||||
#include <QQueue>
|
||||
#include <QTimer>
|
||||
#include <texteditor/textdocument.h>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
class ChangesManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct ChangeInfo
|
||||
{
|
||||
QString fileName;
|
||||
int lineNumber;
|
||||
QString lineContent;
|
||||
};
|
||||
|
||||
static ChangesManager &instance();
|
||||
|
||||
void addChange(TextEditor::TextDocument *document,
|
||||
int position,
|
||||
int charsRemoved,
|
||||
int charsAdded);
|
||||
QString getRecentChangesContext(const TextEditor::TextDocument *currentDocument) const;
|
||||
|
||||
private:
|
||||
ChangesManager();
|
||||
~ChangesManager();
|
||||
ChangesManager(const ChangesManager &) = delete;
|
||||
ChangesManager &operator=(const ChangesManager &) = delete;
|
||||
|
||||
void cleanupOldChanges();
|
||||
|
||||
QHash<TextEditor::TextDocument *, QQueue<ChangeInfo>> m_documentChanges;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
40
core/LLMRequestConfig.hpp
Normal file
40
core/LLMRequestConfig.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QUrl>
|
||||
#include "providers/LLMProvider.hpp"
|
||||
#include "templates/PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
enum class RequestType { Fim, Chat };
|
||||
|
||||
struct LLMConfig
|
||||
{
|
||||
QUrl url;
|
||||
Providers::LLMProvider *provider;
|
||||
Templates::PromptTemplate *promptTemplate;
|
||||
QJsonObject providerRequest;
|
||||
RequestType requestType;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
160
core/LLMRequestHandler.cpp
Normal file
160
core/LLMRequestHandler.cpp
Normal file
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "LLMRequestHandler.hpp"
|
||||
#include "LLMProvidersManager.hpp"
|
||||
#include "QodeAssistUtils.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QNetworkReply>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
LLMRequestHandler::LLMRequestHandler(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_manager(new QNetworkAccessManager(this))
|
||||
{}
|
||||
|
||||
void LLMRequestHandler::sendLLMRequest(const LLMConfig &config, const QJsonObject &request)
|
||||
{
|
||||
logMessage(QString("Sending request to llm: \nurl: %1\nRequest body:\n%2")
|
||||
.arg(config.url.toString(),
|
||||
QString::fromUtf8(
|
||||
QJsonDocument(config.providerRequest).toJson(QJsonDocument::Indented))));
|
||||
|
||||
QNetworkRequest networkRequest(config.url);
|
||||
prepareNetworkRequest(networkRequest, config.providerRequest);
|
||||
|
||||
QNetworkReply *reply = m_manager->post(networkRequest,
|
||||
QJsonDocument(config.providerRequest).toJson());
|
||||
if (!reply) {
|
||||
logMessage("Error: Failed to create network reply");
|
||||
return;
|
||||
}
|
||||
|
||||
QString requestId = request["id"].toString();
|
||||
m_activeRequests[requestId] = reply;
|
||||
|
||||
connect(reply, &QNetworkReply::readyRead, this, [this, reply, request, config]() {
|
||||
handleLLMResponse(reply, request, config);
|
||||
});
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply, requestId]() {
|
||||
reply->deleteLater();
|
||||
m_activeRequests.remove(requestId);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
logMessage(QString("Error in QodeAssist request: %1").arg(reply->errorString()));
|
||||
emit requestFinished(requestId, false, reply->errorString());
|
||||
} else {
|
||||
logMessage("Request finished successfully");
|
||||
emit requestFinished(requestId, true, QString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void LLMRequestHandler::handleLLMResponse(QNetworkReply *reply,
|
||||
const QJsonObject &request,
|
||||
const LLMConfig &config)
|
||||
{
|
||||
QString &accumulatedResponse = m_accumulatedResponses[reply];
|
||||
|
||||
bool isComplete = config.provider->handleResponse(reply, accumulatedResponse);
|
||||
|
||||
if (config.requestType == RequestType::Fim) {
|
||||
if (!Settings::generalSettings().multiLineCompletion()
|
||||
&& processSingleLineCompletion(reply, request, accumulatedResponse, config)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (isComplete || reply->isFinished()) {
|
||||
if (isComplete) {
|
||||
if (config.requestType == RequestType::Fim) {
|
||||
auto cleanedCompletion = removeStopWords(accumulatedResponse,
|
||||
config.promptTemplate->stopWords());
|
||||
emit completionReceived(cleanedCompletion, request, true);
|
||||
} else {
|
||||
emit completionReceived(accumulatedResponse, request, true);
|
||||
}
|
||||
} else {
|
||||
emit completionReceived(accumulatedResponse, request, false);
|
||||
}
|
||||
m_accumulatedResponses.remove(reply);
|
||||
}
|
||||
}
|
||||
|
||||
bool LLMRequestHandler::cancelRequest(const QString &id)
|
||||
{
|
||||
if (m_activeRequests.contains(id)) {
|
||||
QNetworkReply *reply = m_activeRequests[id];
|
||||
reply->abort();
|
||||
m_activeRequests.remove(id);
|
||||
m_accumulatedResponses.remove(reply);
|
||||
emit requestCancelled(id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LLMRequestHandler::prepareNetworkRequest(QNetworkRequest &networkRequest,
|
||||
const QJsonObject &providerRequest)
|
||||
{
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
if (providerRequest.contains("api_key")) {
|
||||
QString apiKey = providerRequest["api_key"].toString();
|
||||
networkRequest.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey).toUtf8());
|
||||
}
|
||||
}
|
||||
|
||||
bool LLMRequestHandler::processSingleLineCompletion(QNetworkReply *reply,
|
||||
const QJsonObject &request,
|
||||
const QString &accumulatedResponse,
|
||||
const LLMConfig &config)
|
||||
{
|
||||
int newlinePos = accumulatedResponse.indexOf('\n');
|
||||
|
||||
if (newlinePos != -1) {
|
||||
QString singleLineCompletion = accumulatedResponse.left(newlinePos).trimmed();
|
||||
singleLineCompletion = removeStopWords(singleLineCompletion,
|
||||
config.promptTemplate->stopWords());
|
||||
|
||||
emit completionReceived(singleLineCompletion, request, true);
|
||||
m_accumulatedResponses.remove(reply);
|
||||
reply->abort();
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString LLMRequestHandler::removeStopWords(const QStringView &completion,
|
||||
const QStringList &stopWords)
|
||||
{
|
||||
QString filteredCompletion = completion.toString();
|
||||
|
||||
for (const QString &stopWord : stopWords) {
|
||||
filteredCompletion = filteredCompletion.replace(stopWord, "");
|
||||
}
|
||||
|
||||
return filteredCompletion;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
64
core/LLMRequestHandler.hpp
Normal file
64
core/LLMRequestHandler.hpp
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QObject>
|
||||
|
||||
#include "QodeAssistData.hpp"
|
||||
#include "core/LLMRequestConfig.hpp"
|
||||
|
||||
class QNetworkReply;
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
class LLMRequestHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LLMRequestHandler(QObject *parent = nullptr);
|
||||
|
||||
void sendLLMRequest(const LLMConfig &config, const QJsonObject &request);
|
||||
void handleLLMResponse(QNetworkReply *reply,
|
||||
const QJsonObject &request,
|
||||
const LLMConfig &config);
|
||||
bool cancelRequest(const QString &id);
|
||||
|
||||
signals:
|
||||
void completionReceived(const QString &completion, const QJsonObject &request, bool isComplete);
|
||||
void requestFinished(const QString &requestId, bool success, const QString &errorString);
|
||||
void requestCancelled(const QString &id);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager *m_manager;
|
||||
QMap<QString, QNetworkReply *> m_activeRequests;
|
||||
QMap<QNetworkReply *, QString> m_accumulatedResponses;
|
||||
|
||||
void prepareNetworkRequest(QNetworkRequest &networkRequest, const QJsonObject &providerRequest);
|
||||
bool processSingleLineCompletion(QNetworkReply *reply,
|
||||
const QJsonObject &request,
|
||||
const QString &accumulatedResponse,
|
||||
const LLMConfig &config);
|
||||
QString removeStopWords(const QStringView &completion, const QStringList &stopWords);
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
@ -34,8 +34,8 @@ public:
|
||||
|
||||
virtual QString name() const = 0;
|
||||
virtual QString url() const = 0;
|
||||
virtual int defaultPort() const = 0;
|
||||
virtual QString completionEndpoint() const = 0;
|
||||
virtual QString chatEndpoint() const = 0;
|
||||
|
||||
virtual void prepareRequest(QJsonObject &request) = 0;
|
||||
virtual bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) = 0;
|
||||
|
||||
@ -19,14 +19,15 @@
|
||||
|
||||
#include "LMStudioProvider.hpp"
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QProcess>
|
||||
|
||||
#include "PromptTemplateManager.hpp"
|
||||
#include "QodeAssistSettings.hpp"
|
||||
#include "QodeAssistUtils.hpp"
|
||||
#include "settings/PresetPromptsSettings.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
@ -39,12 +40,7 @@ QString LMStudioProvider::name() const
|
||||
|
||||
QString LMStudioProvider::url() const
|
||||
{
|
||||
return "http://localhost";
|
||||
}
|
||||
|
||||
int LMStudioProvider::defaultPort() const
|
||||
{
|
||||
return 1234;
|
||||
return "http://localhost:1234";
|
||||
}
|
||||
|
||||
QString LMStudioProvider::completionEndpoint() const
|
||||
@ -52,27 +48,30 @@ QString LMStudioProvider::completionEndpoint() const
|
||||
return "/v1/chat/completions";
|
||||
}
|
||||
|
||||
QString LMStudioProvider::chatEndpoint() const
|
||||
{
|
||||
return "/v1/chat/completions";
|
||||
}
|
||||
|
||||
void LMStudioProvider::prepareRequest(QJsonObject &request)
|
||||
{
|
||||
const auto ¤tTemplate = PromptTemplateManager::instance().getCurrentTemplate();
|
||||
|
||||
auto &settings = Settings::presetPromptsSettings();
|
||||
if (request.contains("prompt")) {
|
||||
QJsonArray messages{
|
||||
{QJsonObject{{"role", "user"}, {"content", request.take("prompt").toString()}}}};
|
||||
request["messages"] = std::move(messages);
|
||||
}
|
||||
|
||||
request["max_tokens"] = settings().maxTokens();
|
||||
request["temperature"] = settings().temperature();
|
||||
request["stop"] = QJsonArray::fromStringList(currentTemplate->stopWords());
|
||||
if (settings().useTopP())
|
||||
request["top_p"] = settings().topP();
|
||||
if (settings().useTopK())
|
||||
request["top_k"] = settings().topK();
|
||||
if (settings().useFrequencyPenalty())
|
||||
request["frequency_penalty"] = settings().frequencyPenalty();
|
||||
if (settings().usePresencePenalty())
|
||||
request["presence_penalty"] = settings().presencePenalty();
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
request["temperature"] = settings.temperature();
|
||||
if (settings.useTopP())
|
||||
request["top_p"] = settings.topP();
|
||||
if (settings.useTopK())
|
||||
request["top_k"] = settings.topK();
|
||||
if (settings.useFrequencyPenalty())
|
||||
request["frequency_penalty"] = settings.frequencyPenalty();
|
||||
if (settings.usePresencePenalty())
|
||||
request["presence_penalty"] = settings.presencePenalty();
|
||||
}
|
||||
|
||||
bool LMStudioProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
|
||||
@ -118,53 +117,32 @@ bool LMStudioProvider::handleResponse(QNetworkReply *reply, QString &accumulated
|
||||
|
||||
QList<QString> LMStudioProvider::getInstalledModels(const Utils::Environment &env)
|
||||
{
|
||||
QProcess process;
|
||||
process.setEnvironment(env.toStringList());
|
||||
QString lmsConsoleName;
|
||||
#ifdef Q_OS_WIN
|
||||
lmsConsoleName = "lms.exe";
|
||||
#else
|
||||
lmsConsoleName = "lms";
|
||||
#endif
|
||||
auto lmsPath = env.searchInPath(lmsConsoleName).toString();
|
||||
QList<QString> models;
|
||||
QNetworkAccessManager manager;
|
||||
QNetworkRequest request(QUrl(url() + "/v1/models"));
|
||||
|
||||
if (!QFileInfo::exists(lmsPath)) {
|
||||
qWarning() << "LMS executable not found at" << lmsPath;
|
||||
return {};
|
||||
}
|
||||
QNetworkReply *reply = manager.get(request);
|
||||
|
||||
process.start(lmsPath, QStringList() << "ls");
|
||||
if (!process.waitForStarted()) {
|
||||
qWarning() << "Failed to start LMS process:" << process.errorString();
|
||||
return {};
|
||||
}
|
||||
QEventLoop loop;
|
||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
loop.exec();
|
||||
|
||||
if (!process.waitForFinished()) {
|
||||
qWarning() << "LMS process did not finish:" << process.errorString();
|
||||
return {};
|
||||
}
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QByteArray responseData = reply->readAll();
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
|
||||
QJsonObject jsonObject = jsonResponse.object();
|
||||
QJsonArray modelArray = jsonObject["data"].toArray();
|
||||
|
||||
QStringList models;
|
||||
if (process.exitCode() == 0) {
|
||||
QString output = QString::fromUtf8(process.readAllStandardOutput());
|
||||
QStringList lines = output.split('\n', Qt::SkipEmptyParts);
|
||||
|
||||
// Skip the header lines
|
||||
for (int i = 2; i < lines.size(); ++i) {
|
||||
QString line = lines[i].trimmed();
|
||||
if (!line.isEmpty()) {
|
||||
// The model name is the first column
|
||||
QString modelName = line.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts)
|
||||
.first();
|
||||
models.append(modelName);
|
||||
}
|
||||
for (const QJsonValue &value : modelArray) {
|
||||
QJsonObject modelObject = value.toObject();
|
||||
QString modelId = modelObject["id"].toString();
|
||||
models.append(modelId);
|
||||
}
|
||||
qDebug() << "Models:" << models;
|
||||
} else {
|
||||
// Handle error
|
||||
qWarning() << "Error running 'lms list':" << process.errorString();
|
||||
logMessage(QString("Error fetching models: %1").arg(reply->errorString()));
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
return models;
|
||||
}
|
||||
|
||||
|
||||
@ -30,8 +30,8 @@ public:
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
int defaultPort() const override;
|
||||
QString completionEndpoint() const override;
|
||||
QString chatEndpoint() const override;
|
||||
void prepareRequest(QJsonObject &request) override;
|
||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||
QList<QString> getInstalledModels(const Utils::Environment &env) override;
|
||||
|
||||
@ -23,10 +23,11 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QProcess>
|
||||
#include <QtCore/qeventloop.h>
|
||||
|
||||
#include "PromptTemplateManager.hpp"
|
||||
#include "QodeAssistSettings.hpp"
|
||||
#include "QodeAssistUtils.hpp"
|
||||
#include "settings/PresetPromptsSettings.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
@ -39,12 +40,7 @@ QString OllamaProvider::name() const
|
||||
|
||||
QString OllamaProvider::url() const
|
||||
{
|
||||
return "http://localhost";
|
||||
}
|
||||
|
||||
int OllamaProvider::defaultPort() const
|
||||
{
|
||||
return 11434;
|
||||
return "http://localhost:11434";
|
||||
}
|
||||
|
||||
QString OllamaProvider::completionEndpoint() const
|
||||
@ -52,99 +48,108 @@ QString OllamaProvider::completionEndpoint() const
|
||||
return "/api/generate";
|
||||
}
|
||||
|
||||
QString OllamaProvider::chatEndpoint() const
|
||||
{
|
||||
return "/api/chat";
|
||||
}
|
||||
|
||||
void OllamaProvider::prepareRequest(QJsonObject &request)
|
||||
{
|
||||
auto currentTemplate = PromptTemplateManager::instance().getCurrentTemplate();
|
||||
auto &settings = Settings::presetPromptsSettings();
|
||||
|
||||
QJsonObject options;
|
||||
options["num_predict"] = settings().maxTokens();
|
||||
options["keep_alive"] = settings().ollamaLivetime();
|
||||
options["temperature"] = settings().temperature();
|
||||
options["stop"] = QJsonArray::fromStringList(currentTemplate->stopWords());
|
||||
if (settings().useTopP())
|
||||
options["top_p"] = settings().topP();
|
||||
if (settings().useTopK())
|
||||
options["top_k"] = settings().topK();
|
||||
if (settings().useFrequencyPenalty())
|
||||
options["frequency_penalty"] = settings().frequencyPenalty();
|
||||
if (settings().usePresencePenalty())
|
||||
options["presence_penalty"] = settings().presencePenalty();
|
||||
options["num_predict"] = settings.maxTokens();
|
||||
options["keep_alive"] = settings.ollamaLivetime();
|
||||
options["temperature"] = settings.temperature();
|
||||
if (settings.useTopP())
|
||||
options["top_p"] = settings.topP();
|
||||
if (settings.useTopK())
|
||||
options["top_k"] = settings.topK();
|
||||
if (settings.useFrequencyPenalty())
|
||||
options["frequency_penalty"] = settings.frequencyPenalty();
|
||||
if (settings.usePresencePenalty())
|
||||
options["presence_penalty"] = settings.presencePenalty();
|
||||
request["options"] = options;
|
||||
}
|
||||
|
||||
bool OllamaProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
|
||||
{
|
||||
QString endpoint = reply->url().path();
|
||||
|
||||
bool isComplete = false;
|
||||
while (reply->canReadLine()) {
|
||||
QByteArray line = reply->readLine().trimmed();
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(line);
|
||||
if (jsonResponse.isNull()) {
|
||||
qWarning() << "Invalid JSON response from Ollama:" << line;
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(line);
|
||||
if (doc.isNull()) {
|
||||
logMessage("Invalid JSON response from Ollama: " + QString::fromUtf8(line));
|
||||
continue;
|
||||
}
|
||||
QJsonObject responseObj = jsonResponse.object();
|
||||
if (responseObj.contains("response")) {
|
||||
QString completion = responseObj["response"].toString();
|
||||
|
||||
accumulatedResponse += completion;
|
||||
QJsonObject responseObj = doc.object();
|
||||
|
||||
if (responseObj.contains("error")) {
|
||||
QString errorMessage = responseObj["error"].toString();
|
||||
logMessage("Error in Ollama response: " + errorMessage);
|
||||
return false;
|
||||
}
|
||||
if (responseObj["done"].toBool()) {
|
||||
|
||||
if (endpoint == completionEndpoint()) {
|
||||
if (responseObj.contains("response")) {
|
||||
QString completion = responseObj["response"].toString();
|
||||
accumulatedResponse += completion;
|
||||
}
|
||||
} else if (endpoint == chatEndpoint()) {
|
||||
if (responseObj.contains("message")) {
|
||||
QJsonObject message = responseObj["message"].toObject();
|
||||
if (message.contains("content")) {
|
||||
QString content = message["content"].toString();
|
||||
accumulatedResponse += content;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logMessage("Unknown endpoint: " + endpoint);
|
||||
}
|
||||
|
||||
if (responseObj.contains("done") && responseObj["done"].toBool()) {
|
||||
isComplete = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return isComplete;
|
||||
}
|
||||
|
||||
QList<QString> OllamaProvider::getInstalledModels(const Utils::Environment &env)
|
||||
{
|
||||
QProcess process;
|
||||
process.setEnvironment(env.toStringList());
|
||||
QString ollamaConsoleName;
|
||||
#ifdef Q_OS_WIN
|
||||
ollamaConsoleName = "ollama.exe";
|
||||
#else
|
||||
ollamaConsoleName = "ollama";
|
||||
#endif
|
||||
QList<QString> models;
|
||||
QNetworkAccessManager manager;
|
||||
QNetworkRequest request(QUrl(url() + "/api/tags"));
|
||||
QNetworkReply *reply = manager.get(request);
|
||||
|
||||
auto ollamaPath = env.searchInPath(ollamaConsoleName).toString();
|
||||
QEventLoop loop;
|
||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
loop.exec();
|
||||
|
||||
if (!QFileInfo::exists(ollamaPath)) {
|
||||
qWarning() << "Ollama executable not found at" << ollamaPath;
|
||||
return {};
|
||||
}
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QByteArray responseData = reply->readAll();
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
|
||||
QJsonObject jsonObject = jsonResponse.object();
|
||||
QJsonArray modelArray = jsonObject["models"].toArray();
|
||||
|
||||
process.start(ollamaPath, QStringList() << "list");
|
||||
if (!process.waitForStarted()) {
|
||||
qWarning() << "Failed to start Ollama process:" << process.errorString();
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!process.waitForFinished()) {
|
||||
qWarning() << "Ollama process did not finish:" << process.errorString();
|
||||
return {};
|
||||
}
|
||||
|
||||
QStringList models;
|
||||
if (process.exitCode() == 0) {
|
||||
QString output = QString::fromUtf8(process.readAllStandardOutput());
|
||||
QStringList lines = output.split('\n', Qt::SkipEmptyParts);
|
||||
|
||||
for (int i = 1; i < lines.size(); ++i) {
|
||||
QString line = lines[i].trimmed();
|
||||
if (!line.isEmpty()) {
|
||||
QString modelName = line.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts)
|
||||
.first();
|
||||
models.append(modelName);
|
||||
}
|
||||
for (const QJsonValue &value : modelArray) {
|
||||
QJsonObject modelObject = value.toObject();
|
||||
QString modelName = modelObject["name"].toString();
|
||||
models.append(modelName);
|
||||
}
|
||||
} else {
|
||||
qWarning() << "Error running 'ollama list':" << process.errorString();
|
||||
logMessage(QString("Error fetching models: %1").arg(reply->errorString()));
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
return models;
|
||||
}
|
||||
|
||||
|
||||
@ -30,8 +30,8 @@ public:
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
int defaultPort() const override;
|
||||
QString completionEndpoint() const override;
|
||||
QString chatEndpoint() const override;
|
||||
void prepareRequest(QJsonObject &request) override;
|
||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||
QList<QString> getInstalledModels(const Utils::Environment &env) override;
|
||||
|
||||
126
providers/OpenAICompatProvider.cpp
Normal file
126
providers/OpenAICompatProvider.cpp
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "OpenAICompatProvider.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "PromptTemplateManager.hpp"
|
||||
#include "settings/PresetPromptsSettings.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
OpenAICompatProvider::OpenAICompatProvider() {}
|
||||
|
||||
QString OpenAICompatProvider::name() const
|
||||
{
|
||||
return "OpenAI Compatible (experimental)";
|
||||
}
|
||||
|
||||
QString OpenAICompatProvider::url() const
|
||||
{
|
||||
return "http://localhost:1234";
|
||||
}
|
||||
|
||||
QString OpenAICompatProvider::completionEndpoint() const
|
||||
{
|
||||
return "/v1/chat/completions";
|
||||
}
|
||||
|
||||
QString OpenAICompatProvider::chatEndpoint() const
|
||||
{
|
||||
return "/v1/chat/completions";
|
||||
}
|
||||
|
||||
void OpenAICompatProvider::prepareRequest(QJsonObject &request)
|
||||
{
|
||||
auto &settings = Settings::presetPromptsSettings();
|
||||
if (request.contains("prompt")) {
|
||||
QJsonArray messages{
|
||||
{QJsonObject{{"role", "user"}, {"content", request.take("prompt").toString()}}}};
|
||||
request["messages"] = std::move(messages);
|
||||
}
|
||||
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
request["temperature"] = settings.temperature();
|
||||
if (settings.useTopP())
|
||||
request["top_p"] = settings.topP();
|
||||
if (settings.useTopK())
|
||||
request["top_k"] = settings.topK();
|
||||
if (settings.useFrequencyPenalty())
|
||||
request["frequency_penalty"] = settings.frequencyPenalty();
|
||||
if (settings.usePresencePenalty())
|
||||
request["presence_penalty"] = settings.presencePenalty();
|
||||
|
||||
const QString &apiKey = settings.apiKey.value();
|
||||
if (!apiKey.isEmpty()) {
|
||||
request["api_key"] = apiKey;
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenAICompatProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
|
||||
{
|
||||
bool isComplete = false;
|
||||
while (reply->canReadLine()) {
|
||||
QByteArray line = reply->readLine().trimmed();
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (line == "data: [DONE]") {
|
||||
isComplete = true;
|
||||
break;
|
||||
}
|
||||
if (line.startsWith("data: ")) {
|
||||
line = line.mid(6); // Remove "data: " prefix
|
||||
}
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(line);
|
||||
if (jsonResponse.isNull()) {
|
||||
qWarning() << "Invalid JSON response from LM Studio:" << line;
|
||||
continue;
|
||||
}
|
||||
QJsonObject responseObj = jsonResponse.object();
|
||||
if (responseObj.contains("choices")) {
|
||||
QJsonArray choices = responseObj["choices"].toArray();
|
||||
if (!choices.isEmpty()) {
|
||||
QJsonObject choice = choices.first().toObject();
|
||||
QJsonObject delta = choice["delta"].toObject();
|
||||
if (delta.contains("content")) {
|
||||
QString completion = delta["content"].toString();
|
||||
|
||||
accumulatedResponse += completion;
|
||||
}
|
||||
if (choice["finish_reason"].toString() == "stop") {
|
||||
isComplete = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return isComplete;
|
||||
}
|
||||
|
||||
QList<QString> OpenAICompatProvider::getInstalledModels(const Utils::Environment &env)
|
||||
{
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
40
providers/OpenAICompatProvider.hpp
Normal file
40
providers/OpenAICompatProvider.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "LLMProvider.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class OpenAICompatProvider : public LLMProvider
|
||||
{
|
||||
public:
|
||||
OpenAICompatProvider();
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
QString completionEndpoint() const override;
|
||||
QString chatEndpoint() const override;
|
||||
void prepareRequest(QJsonObject &request) override;
|
||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||
QList<QString> getInstalledModels(const Utils::Environment &env) override;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@ -26,8 +26,9 @@
|
||||
#include <coreplugin/coreconstants.h>
|
||||
#include <coreplugin/icontext.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/messagemanager.h>
|
||||
#include <coreplugin/modemanager.h>
|
||||
#include <coreplugin/statusbarmanager.h>
|
||||
|
||||
#include <extensionsystem/iplugin.h>
|
||||
#include <languageclient/languageclientmanager.h>
|
||||
|
||||
@ -41,9 +42,17 @@
|
||||
#include "LLMProvidersManager.hpp"
|
||||
#include "PromptTemplateManager.hpp"
|
||||
#include "QodeAssistClient.hpp"
|
||||
#include "chat/ChatOutputPane.h"
|
||||
#include "providers/LMStudioProvider.hpp"
|
||||
#include "providers/OllamaProvider.hpp"
|
||||
#include "templates/CodeLLamaTemplate.hpp"
|
||||
#include "providers/OpenAICompatProvider.hpp"
|
||||
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "templates/CodeLlamaFimTemplate.hpp"
|
||||
#include "templates/CodeLlamaInstruct.hpp"
|
||||
#include "templates/CustomTemplate.hpp"
|
||||
#include "templates/DeepSeekCoderChatTemplate.hpp"
|
||||
#include "templates/DeepSeekCoderV2.hpp"
|
||||
#include "templates/StarCoder2Template.hpp"
|
||||
|
||||
using namespace Utils;
|
||||
@ -72,19 +81,26 @@ public:
|
||||
auto &providerManager = LLMProvidersManager::instance();
|
||||
providerManager.registerProvider<Providers::OllamaProvider>();
|
||||
providerManager.registerProvider<Providers::LMStudioProvider>();
|
||||
providerManager.registerProvider<Providers::OpenAICompatProvider>();
|
||||
|
||||
auto &templateManager = PromptTemplateManager::instance();
|
||||
templateManager.registerTemplate<Templates::CodeLLamaTemplate>();
|
||||
templateManager.registerTemplate<Templates::CodeLlamaFimTemplate>();
|
||||
templateManager.registerTemplate<Templates::StarCoder2Template>();
|
||||
templateManager.registerTemplate<Templates::DeepSeekCoderV2Template>();
|
||||
templateManager.registerTemplate<Templates::CustomTemplate>();
|
||||
templateManager.registerTemplate<Templates::DeepSeekCoderChatTemplate>();
|
||||
templateManager.registerTemplate<Templates::CodeLlamaInstructTemplate>();
|
||||
|
||||
Utils::Icon QCODEASSIST_ICON(
|
||||
{{":/resources/images/qoderassist-icon.png", Utils::Theme::IconsBaseColor}});
|
||||
|
||||
ActionBuilder requestAction(this, Constants::QODE_ASSIST_REQUEST_SUGGESTION);
|
||||
requestAction.setToolTip(
|
||||
Tr::tr("Request Ollama suggestion at the current editor's cursor position."));
|
||||
requestAction.setText(Tr::tr("Request Ollama Suggestion"));
|
||||
Tr::tr("Generate Qode Assist suggestion at the current cursor position."));
|
||||
requestAction.setText(Tr::tr("Request QodeAssist Suggestion"));
|
||||
requestAction.setIcon(QCODEASSIST_ICON.icon());
|
||||
const QKeySequence defaultShortcut = QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Q);
|
||||
requestAction.setDefaultKeySequence(defaultShortcut);
|
||||
requestAction.addOnTriggered(this, [this] {
|
||||
if (auto editor = TextEditor::TextEditorWidget::currentTextEditorWidget()) {
|
||||
if (m_qodeAssistClient && m_qodeAssistClient->reachable()) {
|
||||
@ -98,6 +114,8 @@ public:
|
||||
auto toggleButton = new QToolButton;
|
||||
toggleButton->setDefaultAction(requestAction.contextAction());
|
||||
StatusBarManager::addStatusBarWidget(toggleButton, StatusBarManager::RightCorner);
|
||||
|
||||
m_chatOutputPane = new Chat::ChatOutputPane(this);
|
||||
}
|
||||
|
||||
void extensionsInitialized() final
|
||||
@ -131,6 +149,7 @@ public:
|
||||
|
||||
private:
|
||||
QPointer<QodeAssistClient> m_qodeAssistClient;
|
||||
QPointer<Chat::ChatOutputPane> m_chatOutputPane;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Internal
|
||||
|
||||
19
rawPromptExamples/OllamaStarCoder2FIM.json
Normal file
19
rawPromptExamples/OllamaStarCoder2FIM.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"prompt": "{{QODE_INSTRUCTIONS}}<fim_prefix>{{QODE_PREFIX}}<fim_suffix>{{QODE_SUFFIX}}<fim_middle>",
|
||||
"options": {
|
||||
"temperature": 0.7,
|
||||
"top_p": 0.95,
|
||||
"top_k": 40,
|
||||
"num_predict": 175,
|
||||
"stop": [
|
||||
"<|endoftext|>",
|
||||
"<file_sep>",
|
||||
"<fim_prefix>",
|
||||
"<fim_suffix>",
|
||||
"<fim_middle>"
|
||||
],
|
||||
"frequency_penalty": 0,
|
||||
"presence_penalty": 0
|
||||
},
|
||||
"stream": true
|
||||
}
|
||||
16
rawPromptExamples/OpenAICodeqwenChat.json
Normal file
16
rawPromptExamples/OpenAICodeqwenChat.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"max_tokens": 150,
|
||||
"messages": [
|
||||
{
|
||||
"content": "{{QODE_INSTRUCTIONS}}\n### Instruction:{{QODE_PREFIX}}{{QODE_SUFFIX}} ### Response:\n",
|
||||
"role": "user"
|
||||
}
|
||||
],
|
||||
"stop": [
|
||||
"### Instruction:",
|
||||
"### Response:",
|
||||
"\n\n### "
|
||||
],
|
||||
"stream": true,
|
||||
"temperature": 0.2
|
||||
}
|
||||
155
settings/ContextSettings.cpp
Normal file
155
settings/ContextSettings.cpp
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ContextSettings.hpp"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
|
||||
#include "QodeAssistConstants.hpp"
|
||||
#include "QodeAssisttr.h"
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
ContextSettings &contextSettings()
|
||||
{
|
||||
static ContextSettings settings;
|
||||
return settings;
|
||||
}
|
||||
|
||||
ContextSettings::ContextSettings()
|
||||
{
|
||||
setAutoApply(false);
|
||||
|
||||
setDisplayName(Tr::tr("Context"));
|
||||
|
||||
readFullFile.setSettingsKey(Constants::READ_FULL_FILE);
|
||||
readFullFile.setLabelText(Tr::tr("Read Full File"));
|
||||
readFullFile.setDefaultValue(false);
|
||||
|
||||
readStringsBeforeCursor.setSettingsKey(Constants::READ_STRINGS_BEFORE_CURSOR);
|
||||
readStringsBeforeCursor.setLabelText(Tr::tr("Read Strings Before Cursor"));
|
||||
readStringsBeforeCursor.setRange(0, 10000);
|
||||
readStringsBeforeCursor.setDefaultValue(50);
|
||||
|
||||
readStringsAfterCursor.setSettingsKey(Constants::READ_STRINGS_AFTER_CURSOR);
|
||||
readStringsAfterCursor.setLabelText(Tr::tr("Read Strings After Cursor"));
|
||||
readStringsAfterCursor.setRange(0, 10000);
|
||||
readStringsAfterCursor.setDefaultValue(30);
|
||||
|
||||
useFilePathInContext.setSettingsKey(Constants::USE_FILE_PATH_IN_CONTEXT);
|
||||
useFilePathInContext.setDefaultValue(false);
|
||||
useFilePathInContext.setLabelText(Tr::tr("Use File Path in Context"));
|
||||
|
||||
useSpecificInstructions.setSettingsKey(Constants::USE_SPECIFIC_INSTRUCTIONS);
|
||||
useSpecificInstructions.setDefaultValue(true);
|
||||
useSpecificInstructions.setLabelText(Tr::tr("Use Specific Instructions"));
|
||||
|
||||
specificInstractions.setSettingsKey(Constants::SPECIFIC_INSTRUCTIONS);
|
||||
specificInstractions.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||
specificInstractions.setLabelText(
|
||||
Tr::tr("Instructions: Please keep %1 for languge name, warning, it shouldn't too big"));
|
||||
specificInstractions.setDefaultValue(
|
||||
"You are an expert %1 code completion AI."
|
||||
"CRITICAL: Please provide minimal the best possible code completion suggestions.\n");
|
||||
|
||||
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
|
||||
|
||||
useProjectChangesCache.setSettingsKey(Constants::USE_PROJECT_CHANGES_CACHE);
|
||||
useProjectChangesCache.setDefaultValue(true);
|
||||
useProjectChangesCache.setLabelText(Tr::tr("Use Project Changes Cache"));
|
||||
|
||||
maxChangesCacheSize.setSettingsKey(Constants::MAX_CHANGES_CACHE_SIZE);
|
||||
maxChangesCacheSize.setLabelText(Tr::tr("Max Changes Cache Size"));
|
||||
maxChangesCacheSize.setRange(2, 1000);
|
||||
maxChangesCacheSize.setDefaultValue(20);
|
||||
|
||||
readSettings();
|
||||
|
||||
readStringsAfterCursor.setEnabled(!readFullFile());
|
||||
readStringsBeforeCursor.setEnabled(!readFullFile());
|
||||
specificInstractions.setEnabled(useSpecificInstructions());
|
||||
|
||||
setupConnection();
|
||||
|
||||
setLayouter([this]() {
|
||||
using namespace Layouting;
|
||||
return Column{Row{readFullFile, Stretch{1}, resetToDefaults},
|
||||
Row{readStringsBeforeCursor, Stretch{1}},
|
||||
Row{readStringsAfterCursor, Stretch{1}},
|
||||
useFilePathInContext,
|
||||
useSpecificInstructions,
|
||||
specificInstractions,
|
||||
useProjectChangesCache,
|
||||
Row{maxChangesCacheSize, Stretch{1}},
|
||||
Stretch{1}};
|
||||
});
|
||||
}
|
||||
|
||||
void ContextSettings::setupConnection()
|
||||
{
|
||||
connect(&readFullFile, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
readStringsAfterCursor.setEnabled(!readFullFile.volatileValue());
|
||||
readStringsBeforeCursor.setEnabled(!readFullFile.volatileValue());
|
||||
});
|
||||
connect(&useSpecificInstructions, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
specificInstractions.setEnabled(useSpecificInstructions.volatileValue());
|
||||
});
|
||||
connect(&resetToDefaults, &ButtonAspect::clicked, this, &ContextSettings::resetPageToDefaults);
|
||||
}
|
||||
|
||||
void ContextSettings::resetPageToDefaults()
|
||||
{
|
||||
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(readFullFile);
|
||||
resetAspect(readStringsBeforeCursor);
|
||||
resetAspect(readStringsAfterCursor);
|
||||
resetAspect(useFilePathInContext);
|
||||
resetAspect(useSpecificInstructions);
|
||||
resetAspect(specificInstractions);
|
||||
}
|
||||
}
|
||||
|
||||
class ContextSettingsPage : public Core::IOptionsPage
|
||||
{
|
||||
public:
|
||||
ContextSettingsPage()
|
||||
{
|
||||
setId(Constants::QODE_ASSIST_CONTEXT_SETTINGS_PAGE_ID);
|
||||
setDisplayName(Tr::tr("Context"));
|
||||
setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY);
|
||||
setDisplayCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY);
|
||||
setCategoryIconPath(":/resources/images/qoderassist-icon.png");
|
||||
setSettingsProvider([] { return &contextSettings(); });
|
||||
}
|
||||
};
|
||||
|
||||
const ContextSettingsPage contextSettingsPage;
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
52
settings/ContextSettings.hpp
Normal file
52
settings/ContextSettings.hpp
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utils/aspects.h>
|
||||
|
||||
#include "SettingsUtils.hpp"
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
class ContextSettings : public Utils::AspectContainer
|
||||
{
|
||||
public:
|
||||
ContextSettings();
|
||||
|
||||
Utils::BoolAspect readFullFile{this};
|
||||
Utils::IntegerAspect readStringsBeforeCursor{this};
|
||||
Utils::IntegerAspect readStringsAfterCursor{this};
|
||||
|
||||
Utils::StringAspect specificInstractions{this};
|
||||
Utils::BoolAspect useSpecificInstructions{this};
|
||||
Utils::BoolAspect useFilePathInContext{this};
|
||||
Utils::BoolAspect useProjectChangesCache{this};
|
||||
Utils::IntegerAspect maxChangesCacheSize{this};
|
||||
|
||||
ButtonAspect resetToDefaults{this};
|
||||
|
||||
private:
|
||||
void setupConnection();
|
||||
void resetPageToDefaults();
|
||||
};
|
||||
|
||||
ContextSettings &contextSettings();
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
199
settings/CustomPromptSettings.cpp
Normal file
199
settings/CustomPromptSettings.cpp
Normal file
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "CustomPromptSettings.hpp"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QJsonParseError>
|
||||
#include <QMessageBox>
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
|
||||
#include "QodeAssistConstants.hpp"
|
||||
#include "QodeAssisttr.h"
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
CustomPromptSettings &customPromptSettings()
|
||||
{
|
||||
static CustomPromptSettings settings;
|
||||
return settings;
|
||||
}
|
||||
|
||||
CustomPromptSettings::CustomPromptSettings()
|
||||
{
|
||||
setAutoApply(false);
|
||||
|
||||
setDisplayName(Tr::tr("Custom Prompt"));
|
||||
|
||||
customJsonLabel.setLabelText("Custom JSON Template:");
|
||||
customJsonLabel.setDisplayStyle(Utils::StringAspect::LabelDisplay);
|
||||
|
||||
customJsonLegend.setLabelText(Tr::tr(R"(Prompt components:
|
||||
- model is set on General Page
|
||||
- {{QODE_INSTRUCTIONS}}: Placeholder for specific instructions or context.
|
||||
- {{QODE_PREFIX}}: Will be replaced with the actual code before the cursor.
|
||||
- {{QODE_SUFFIX}}: Will be replaced with the actual code after the cursor.
|
||||
)"));
|
||||
|
||||
customJsonTemplate.setSettingsKey(Constants::CUSTOM_JSON_TEMPLATE);
|
||||
customJsonTemplate.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||
|
||||
customJsonTemplate.setDefaultValue(R"({
|
||||
"prompt": "{{QODE_INSTRUCTIONS}}<fim_prefix>{{QODE_PREFIX}}<fim_suffix>{{QODE_SUFFIX}}<fim_middle>",
|
||||
"options": {
|
||||
"temperature": 0.7,
|
||||
"top_p": 0.95,
|
||||
"top_k": 40,
|
||||
"num_predict": 100,
|
||||
"stop": [
|
||||
"<|endoftext|>",
|
||||
"<file_sep>",
|
||||
"<fim_prefix>",
|
||||
"<fim_suffix>",
|
||||
"<fim_middle>"
|
||||
],
|
||||
"frequency_penalty": 0,
|
||||
"presence_penalty": 0
|
||||
},
|
||||
"stream": true
|
||||
})");
|
||||
saveCustomTemplateButton.m_buttonText = (Tr::tr("Save Custom Template to JSON"));
|
||||
loadCustomTemplateButton.m_buttonText = (Tr::tr("Load Custom Template from JSON"));
|
||||
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
|
||||
|
||||
readSettings();
|
||||
|
||||
setupConnection();
|
||||
|
||||
setLayouter([this]() {
|
||||
using namespace Layouting;
|
||||
return Column{Row{customJsonLabel, Stretch{1}, resetToDefaults},
|
||||
Row{customJsonTemplate,
|
||||
Column{saveCustomTemplateButton,
|
||||
loadCustomTemplateButton,
|
||||
customJsonLegend,
|
||||
Stretch{1}}}};
|
||||
});
|
||||
}
|
||||
|
||||
void CustomPromptSettings::setupConnection()
|
||||
{
|
||||
connect(&resetToDefaults,
|
||||
&ButtonAspect::clicked,
|
||||
this,
|
||||
&CustomPromptSettings::resetSettingsToDefaults);
|
||||
connect(&saveCustomTemplateButton,
|
||||
&ButtonAspect::clicked,
|
||||
this,
|
||||
&CustomPromptSettings::saveCustomTemplate);
|
||||
connect(&loadCustomTemplateButton,
|
||||
&ButtonAspect::clicked,
|
||||
this,
|
||||
&CustomPromptSettings::loadCustomTemplate);
|
||||
}
|
||||
|
||||
void CustomPromptSettings::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(customJsonTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
void CustomPromptSettings::saveCustomTemplate()
|
||||
{
|
||||
QString fileName = QFileDialog::getSaveFileName(nullptr,
|
||||
Tr::tr("Save JSON Template"),
|
||||
QString(),
|
||||
Tr::tr("JSON Files (*.json)"));
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
QFile file(fileName);
|
||||
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QTextStream out(&file);
|
||||
out << customJsonTemplate.value();
|
||||
file.close();
|
||||
QMessageBox::information(nullptr,
|
||||
Tr::tr("Save Successful"),
|
||||
Tr::tr("JSON template has been saved successfully."));
|
||||
} else {
|
||||
QMessageBox::critical(nullptr,
|
||||
Tr::tr("Save Failed"),
|
||||
Tr::tr("Failed to save JSON template."));
|
||||
}
|
||||
}
|
||||
|
||||
void CustomPromptSettings::loadCustomTemplate()
|
||||
{
|
||||
QString fileName = QFileDialog::getOpenFileName(nullptr,
|
||||
Tr::tr("Load JSON Template"),
|
||||
QString(),
|
||||
Tr::tr("JSON Files (*.json)"));
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
QFile file(fileName);
|
||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QTextStream in(&file);
|
||||
QString jsonContent = in.readAll();
|
||||
file.close();
|
||||
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonContent.toUtf8(), &parseError);
|
||||
if (parseError.error == QJsonParseError::NoError) {
|
||||
customJsonTemplate.setVolatileValue(jsonContent);
|
||||
QMessageBox::information(nullptr,
|
||||
Tr::tr("Load Successful"),
|
||||
Tr::tr("JSON template has been loaded successfully."));
|
||||
} else {
|
||||
QMessageBox::critical(nullptr,
|
||||
Tr::tr("Invalid JSON"),
|
||||
Tr::tr("The selected file contains invalid JSON."));
|
||||
}
|
||||
} else {
|
||||
QMessageBox::critical(nullptr,
|
||||
Tr::tr("Load Failed"),
|
||||
Tr::tr("Failed to load JSON template."));
|
||||
}
|
||||
}
|
||||
|
||||
class CustomPromptSettingsPage : public Core::IOptionsPage
|
||||
{
|
||||
public:
|
||||
CustomPromptSettingsPage()
|
||||
{
|
||||
setId(Constants::QODE_ASSIST_CUSTOM_PROMPT_SETTINGS_PAGE_ID);
|
||||
setDisplayName(Tr::tr("Custom Prompt"));
|
||||
setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY);
|
||||
setSettingsProvider([] { return &customPromptSettings(); });
|
||||
}
|
||||
};
|
||||
|
||||
const CustomPromptSettingsPage customPromptSettingsPage;
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
48
settings/CustomPromptSettings.hpp
Normal file
48
settings/CustomPromptSettings.hpp
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "settings/SettingsUtils.hpp"
|
||||
#include <utils/aspects.h>
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
class CustomPromptSettings : public Utils::AspectContainer
|
||||
{
|
||||
public:
|
||||
CustomPromptSettings();
|
||||
|
||||
Utils::StringAspect customJsonLabel{this};
|
||||
Utils::StringAspect customJsonTemplate{this};
|
||||
Utils::StringAspect customJsonLegend{this};
|
||||
ButtonAspect saveCustomTemplateButton{this};
|
||||
ButtonAspect loadCustomTemplateButton{this};
|
||||
ButtonAspect resetToDefaults{this};
|
||||
|
||||
private:
|
||||
void setupConnection();
|
||||
void resetSettingsToDefaults();
|
||||
void saveCustomTemplate();
|
||||
void loadCustomTemplate();
|
||||
};
|
||||
|
||||
CustomPromptSettings &customPromptSettings();
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
386
settings/GeneralSettings.cpp
Normal file
386
settings/GeneralSettings.cpp
Normal file
@ -0,0 +1,386 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "GeneralSettings.hpp"
|
||||
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/utilsicons.h>
|
||||
|
||||
#include "LLMProvidersManager.hpp"
|
||||
#include "PromptTemplateManager.hpp"
|
||||
#include "QodeAssistConstants.hpp"
|
||||
#include "QodeAssistUtils.hpp"
|
||||
#include "QodeAssisttr.h"
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
GeneralSettings &generalSettings()
|
||||
{
|
||||
static GeneralSettings settings;
|
||||
return settings;
|
||||
}
|
||||
|
||||
GeneralSettings::GeneralSettings()
|
||||
{
|
||||
setAutoApply(false);
|
||||
|
||||
setDisplayName(Tr::tr("General"));
|
||||
|
||||
enableQodeAssist.setSettingsKey(Constants::ENABLE_QODE_ASSIST);
|
||||
enableQodeAssist.setLabelText(Tr::tr("Enable Qode Assist"));
|
||||
enableQodeAssist.setDefaultValue(true);
|
||||
|
||||
enableAutoComplete.setSettingsKey(Constants::ENABLE_AUTO_COMPLETE);
|
||||
enableAutoComplete.setLabelText(Tr::tr("Enable Auto Complete"));
|
||||
enableAutoComplete.setDefaultValue(true);
|
||||
|
||||
enableLogging.setSettingsKey(Constants::ENABLE_LOGGING);
|
||||
enableLogging.setLabelText(Tr::tr("Enable Logging"));
|
||||
enableLogging.setDefaultValue(false);
|
||||
|
||||
multiLineCompletion.setSettingsKey(Constants::MULTILINE_COMPLETION);
|
||||
multiLineCompletion.setDefaultValue(false);
|
||||
multiLineCompletion.setLabelText(Tr::tr("Enable Multiline Completion(experimental)"));
|
||||
|
||||
startSuggestionTimer.setSettingsKey(Constants::START_SUGGESTION_TIMER);
|
||||
startSuggestionTimer.setLabelText(Tr::tr("with delay(ms)"));
|
||||
startSuggestionTimer.setRange(10, 10000);
|
||||
startSuggestionTimer.setDefaultValue(500);
|
||||
|
||||
autoCompletionCharThreshold.setSettingsKey(Constants::AUTO_COMPLETION_CHAR_THRESHOLD);
|
||||
autoCompletionCharThreshold.setLabelText(Tr::tr("AI suggestion triggers after typing"));
|
||||
autoCompletionCharThreshold.setToolTip(
|
||||
Tr::tr("The number of characters that need to be typed within the typing interval "
|
||||
"before an AI suggestion request is sent."));
|
||||
autoCompletionCharThreshold.setRange(0, 10);
|
||||
autoCompletionCharThreshold.setDefaultValue(0);
|
||||
|
||||
autoCompletionTypingInterval.setSettingsKey(Constants::AUTO_COMPLETION_TYPING_INTERVAL);
|
||||
autoCompletionTypingInterval.setLabelText(Tr::tr("character(s) within(ms)"));
|
||||
autoCompletionTypingInterval.setToolTip(
|
||||
Tr::tr("The time window (in milliseconds) during which the character threshold "
|
||||
"must be met to trigger an AI suggestion request."));
|
||||
autoCompletionTypingInterval.setRange(500, 5000);
|
||||
autoCompletionTypingInterval.setDefaultValue(2000);
|
||||
|
||||
llmProviders.setSettingsKey(Constants::LLM_PROVIDERS);
|
||||
llmProviders.setDisplayName(Tr::tr("AI Suggest Provider:"));
|
||||
llmProviders.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
|
||||
|
||||
url.setSettingsKey(Constants::URL);
|
||||
url.setLabelText(Tr::tr("URL:"));
|
||||
url.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
|
||||
endPoint.setSettingsKey(Constants::END_POINT);
|
||||
endPoint.setLabelText(Tr::tr("FIM Endpoint:"));
|
||||
endPoint.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
|
||||
modelName.setSettingsKey(Constants::MODEL_NAME);
|
||||
modelName.setLabelText(Tr::tr("Model name:"));
|
||||
modelName.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
|
||||
selectModels.m_buttonText = Tr::tr("Select Fill-In-the-Middle Model");
|
||||
|
||||
fimPrompts.setDisplayName(Tr::tr("Fill-In-the-Middle Prompt"));
|
||||
fimPrompts.setSettingsKey(Constants::FIM_PROMPTS);
|
||||
fimPrompts.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
|
||||
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
|
||||
|
||||
chatLlmProviders.setSettingsKey(Constants::CHAT_LLM_PROVIDERS);
|
||||
chatLlmProviders.setDisplayName(Tr::tr("AI Chat Provider:"));
|
||||
chatLlmProviders.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
|
||||
|
||||
chatUrl.setSettingsKey(Constants::CHAT_URL);
|
||||
chatUrl.setLabelText(Tr::tr("URL:"));
|
||||
chatUrl.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
|
||||
chatEndPoint.setSettingsKey(Constants::CHAT_END_POINT);
|
||||
chatEndPoint.setLabelText(Tr::tr("Chat Endpoint:"));
|
||||
chatEndPoint.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
|
||||
chatModelName.setSettingsKey(Constants::CHAT_MODEL_NAME);
|
||||
chatModelName.setLabelText(Tr::tr("Model name:"));
|
||||
chatModelName.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
|
||||
chatSelectModels.m_buttonText = Tr::tr("Select Chat Model");
|
||||
|
||||
chatPrompts.setDisplayName(Tr::tr("Chat Prompt"));
|
||||
chatPrompts.setSettingsKey(Constants::CHAT_PROMPTS);
|
||||
chatPrompts.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
|
||||
|
||||
loadProviders();
|
||||
loadPrompts();
|
||||
|
||||
readSettings();
|
||||
|
||||
auto fimProviderName = llmProviders.displayForIndex(llmProviders.value());
|
||||
setCurrentFimProvider(fimProviderName);
|
||||
auto chatProviderName = chatLlmProviders.displayForIndex(chatLlmProviders.value());
|
||||
setCurrentChatProvider(chatProviderName);
|
||||
|
||||
auto nameFimPromts = fimPrompts.displayForIndex(fimPrompts.value());
|
||||
PromptTemplateManager::instance().setCurrentFimTemplate(nameFimPromts);
|
||||
auto nameChatPromts = chatPrompts.displayForIndex(chatPrompts.value());
|
||||
PromptTemplateManager::instance().setCurrentChatTemplate(nameChatPromts);
|
||||
|
||||
setLoggingEnabled(enableLogging());
|
||||
|
||||
setupConnections();
|
||||
|
||||
setLayouter([this]() {
|
||||
using namespace Layouting;
|
||||
|
||||
auto rootLayout
|
||||
= Column{Row{enableQodeAssist, Stretch{1}, resetToDefaults},
|
||||
enableAutoComplete,
|
||||
multiLineCompletion,
|
||||
Row{autoCompletionCharThreshold,
|
||||
autoCompletionTypingInterval,
|
||||
startSuggestionTimer,
|
||||
Stretch{1}},
|
||||
Space{8},
|
||||
enableLogging,
|
||||
Space{8},
|
||||
Group{title(Tr::tr("AI Suggestions")),
|
||||
Column{Row{llmProviders, Stretch{1}},
|
||||
Row{url, endPoint, fimUrlIndicator},
|
||||
Row{selectModels, modelName, fimModelIndicator},
|
||||
Row{fimPrompts, Stretch{1}}}},
|
||||
Space{16},
|
||||
Group{title(Tr::tr("AI Chat(experimental)")),
|
||||
Column{Row{chatLlmProviders, Stretch{1}},
|
||||
Row{chatUrl, chatEndPoint, chatUrlIndicator},
|
||||
Row{chatSelectModels, chatModelName, chatModelIndicator},
|
||||
Row{chatPrompts, Stretch{1}}}},
|
||||
Stretch{1}};
|
||||
return rootLayout;
|
||||
});
|
||||
|
||||
updateStatusIndicators();
|
||||
}
|
||||
|
||||
void GeneralSettings::setupConnections()
|
||||
{
|
||||
connect(&llmProviders, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
|
||||
auto providerName = llmProviders.displayForIndex(llmProviders.volatileValue());
|
||||
setCurrentFimProvider(providerName);
|
||||
modelName.setVolatileValue("");
|
||||
});
|
||||
connect(&chatLlmProviders, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
|
||||
auto providerName = chatLlmProviders.displayForIndex(chatLlmProviders.volatileValue());
|
||||
setCurrentChatProvider(providerName);
|
||||
chatModelName.setVolatileValue("");
|
||||
});
|
||||
|
||||
connect(&fimPrompts, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
|
||||
int index = fimPrompts.volatileValue();
|
||||
PromptTemplateManager::instance().setCurrentFimTemplate(fimPrompts.displayForIndex(index));
|
||||
});
|
||||
connect(&chatPrompts, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
|
||||
int index = chatPrompts.volatileValue();
|
||||
PromptTemplateManager::instance().setCurrentChatTemplate(chatPrompts.displayForIndex(index));
|
||||
});
|
||||
|
||||
connect(&selectModels, &ButtonAspect::clicked, this, [this]() {
|
||||
auto *provider = LLMProvidersManager::instance().getCurrentFimProvider();
|
||||
showModelSelectionDialog(&modelName, provider);
|
||||
});
|
||||
connect(&chatSelectModels, &ButtonAspect::clicked, this, [this]() {
|
||||
auto *provider = LLMProvidersManager::instance().getCurrentChatProvider();
|
||||
showModelSelectionDialog(&chatModelName, provider);
|
||||
});
|
||||
|
||||
connect(&enableLogging, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
setLoggingEnabled(enableLogging.volatileValue());
|
||||
});
|
||||
connect(&resetToDefaults, &ButtonAspect::clicked, this, &GeneralSettings::resetPageToDefaults);
|
||||
|
||||
connect(&url,
|
||||
&Utils::StringAspect::volatileValueChanged,
|
||||
this,
|
||||
&GeneralSettings::updateStatusIndicators);
|
||||
connect(&modelName,
|
||||
&Utils::StringAspect::volatileValueChanged,
|
||||
this,
|
||||
&GeneralSettings::updateStatusIndicators);
|
||||
connect(&chatUrl,
|
||||
&Utils::StringAspect::volatileValueChanged,
|
||||
this,
|
||||
&GeneralSettings::updateStatusIndicators);
|
||||
connect(&chatModelName,
|
||||
&Utils::StringAspect::volatileValueChanged,
|
||||
this,
|
||||
&GeneralSettings::updateStatusIndicators);
|
||||
}
|
||||
|
||||
void GeneralSettings::showModelSelectionDialog(Utils::StringAspect *modelNameObj,
|
||||
Providers::LLMProvider *provider)
|
||||
{
|
||||
Utils::Environment env = Utils::Environment::systemEnvironment();
|
||||
|
||||
if (provider) {
|
||||
QStringList models = provider->getInstalledModels(env);
|
||||
bool ok;
|
||||
QString selectedModel = QInputDialog::getItem(Core::ICore::dialogParent(),
|
||||
Tr::tr("Select LLM Model"),
|
||||
Tr::tr("Choose a model:"),
|
||||
models,
|
||||
0,
|
||||
false,
|
||||
&ok);
|
||||
|
||||
if (ok && !selectedModel.isEmpty()) {
|
||||
modelNameObj->setVolatileValue(selectedModel);
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GeneralSettings::resetPageToDefaults()
|
||||
{
|
||||
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(enableQodeAssist);
|
||||
resetAspect(enableAutoComplete);
|
||||
resetAspect(enableLogging);
|
||||
resetAspect(startSuggestionTimer);
|
||||
resetAspect(autoCompletionTypingInterval);
|
||||
resetAspect(autoCompletionCharThreshold);
|
||||
}
|
||||
|
||||
int fimIndex = llmProviders.indexForDisplay("Ollama");
|
||||
llmProviders.setVolatileValue(fimIndex);
|
||||
int chatIndex = chatLlmProviders.indexForDisplay("Ollama");
|
||||
chatLlmProviders.setVolatileValue(chatIndex);
|
||||
modelName.setVolatileValue("");
|
||||
chatModelName.setVolatileValue("");
|
||||
|
||||
updateStatusIndicators();
|
||||
}
|
||||
|
||||
void GeneralSettings::updateStatusIndicators()
|
||||
{
|
||||
bool fimUrlValid = !url.volatileValue().isEmpty() && !endPoint.volatileValue().isEmpty();
|
||||
bool fimModelValid = !modelName.volatileValue().isEmpty();
|
||||
bool chatUrlValid = !chatUrl.volatileValue().isEmpty()
|
||||
&& !chatEndPoint.volatileValue().isEmpty();
|
||||
bool chatModelValid = !chatModelName.volatileValue().isEmpty();
|
||||
|
||||
bool fimPingSuccessful = false;
|
||||
if (fimUrlValid) {
|
||||
QUrl pingUrl(url.volatileValue());
|
||||
fimPingSuccessful = QodeAssist::pingUrl(pingUrl);
|
||||
}
|
||||
bool chatPingSuccessful = false;
|
||||
if (chatUrlValid) {
|
||||
QUrl pingUrl(chatUrl.volatileValue());
|
||||
chatPingSuccessful = QodeAssist::pingUrl(pingUrl);
|
||||
}
|
||||
|
||||
setIndicatorStatus(fimModelIndicator,
|
||||
fimModelValid ? tr("Model is properly configured")
|
||||
: tr("No model selected or model name is invalid"),
|
||||
fimModelValid);
|
||||
setIndicatorStatus(fimUrlIndicator,
|
||||
fimPingSuccessful ? tr("Server is reachable")
|
||||
: tr("Server is not reachable or URL is invalid"),
|
||||
fimPingSuccessful);
|
||||
|
||||
setIndicatorStatus(chatModelIndicator,
|
||||
chatModelValid ? tr("Model is properly configured")
|
||||
: tr("No model selected or model name is invalid"),
|
||||
chatModelValid);
|
||||
setIndicatorStatus(chatUrlIndicator,
|
||||
chatPingSuccessful ? tr("Server is reachable")
|
||||
: tr("Server is not reachable or URL is invalid"),
|
||||
chatPingSuccessful);
|
||||
}
|
||||
|
||||
void GeneralSettings::setIndicatorStatus(Utils::StringAspect &indicator,
|
||||
const QString &tooltip,
|
||||
bool isValid)
|
||||
{
|
||||
const Utils::Icon &icon = isValid ? Utils::Icons::OK : Utils::Icons::WARNING;
|
||||
indicator.setLabelPixmap(icon.pixmap());
|
||||
indicator.setToolTip(tooltip);
|
||||
}
|
||||
|
||||
void GeneralSettings::setCurrentFimProvider(const QString &name)
|
||||
{
|
||||
const auto provider = LLMProvidersManager::instance().setCurrentFimProvider(name);
|
||||
if (!provider)
|
||||
return;
|
||||
|
||||
url.setValue(provider->url());
|
||||
endPoint.setValue(provider->completionEndpoint());
|
||||
}
|
||||
|
||||
void GeneralSettings::setCurrentChatProvider(const QString &name)
|
||||
{
|
||||
const auto provider = LLMProvidersManager::instance().setCurrentChatProvider(name);
|
||||
if (!provider)
|
||||
return;
|
||||
|
||||
chatUrl.setValue(provider->url());
|
||||
chatEndPoint.setValue(provider->chatEndpoint());
|
||||
}
|
||||
|
||||
void GeneralSettings::loadProviders()
|
||||
{
|
||||
for (const auto &name : LLMProvidersManager::instance().providersNames()) {
|
||||
llmProviders.addOption(name);
|
||||
chatLlmProviders.addOption(name);
|
||||
}
|
||||
}
|
||||
|
||||
void GeneralSettings::loadPrompts()
|
||||
{
|
||||
for (const auto &name : PromptTemplateManager::instance().fimTemplatesNames()) {
|
||||
fimPrompts.addOption(name);
|
||||
}
|
||||
for (const auto &name : PromptTemplateManager::instance().chatTemplatesNames()) {
|
||||
chatPrompts.addOption(name);
|
||||
}
|
||||
}
|
||||
|
||||
class GeneralSettingsPage : public Core::IOptionsPage
|
||||
{
|
||||
public:
|
||||
GeneralSettingsPage()
|
||||
{
|
||||
setId(Constants::QODE_ASSIST_GENERAL_SETTINGS_PAGE_ID);
|
||||
setDisplayName(Tr::tr("General"));
|
||||
setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY);
|
||||
setSettingsProvider([] { return &generalSettings(); });
|
||||
}
|
||||
};
|
||||
|
||||
const GeneralSettingsPage generalSettingsPage;
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
82
settings/GeneralSettings.hpp
Normal file
82
settings/GeneralSettings.hpp
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utils/aspects.h>
|
||||
|
||||
#include "providers/LLMProvider.hpp"
|
||||
#include "settings/SettingsUtils.hpp"
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
class GeneralSettings : public Utils::AspectContainer
|
||||
{
|
||||
public:
|
||||
GeneralSettings();
|
||||
|
||||
Utils::BoolAspect enableQodeAssist{this};
|
||||
Utils::BoolAspect enableAutoComplete{this};
|
||||
Utils::BoolAspect multiLineCompletion{this};
|
||||
Utils::BoolAspect enableLogging{this};
|
||||
Utils::IntegerAspect startSuggestionTimer{this};
|
||||
Utils::IntegerAspect autoCompletionCharThreshold{this};
|
||||
Utils::IntegerAspect autoCompletionTypingInterval{this};
|
||||
|
||||
Utils::SelectionAspect llmProviders{this};
|
||||
Utils::StringAspect url{this};
|
||||
Utils::StringAspect endPoint{this};
|
||||
|
||||
Utils::StringAspect modelName{this};
|
||||
ButtonAspect selectModels{this};
|
||||
Utils::SelectionAspect fimPrompts{this};
|
||||
ButtonAspect resetToDefaults{this};
|
||||
|
||||
Utils::SelectionAspect chatLlmProviders{this};
|
||||
Utils::StringAspect chatUrl{this};
|
||||
Utils::StringAspect chatEndPoint{this};
|
||||
|
||||
Utils::StringAspect chatModelName{this};
|
||||
ButtonAspect chatSelectModels{this};
|
||||
Utils::SelectionAspect chatPrompts{this};
|
||||
|
||||
Utils::StringAspect fimModelIndicator{this};
|
||||
Utils::StringAspect fimUrlIndicator{this};
|
||||
Utils::StringAspect chatModelIndicator{this};
|
||||
Utils::StringAspect chatUrlIndicator{this};
|
||||
|
||||
private:
|
||||
void setupConnections();
|
||||
void showModelSelectionDialog(Utils::StringAspect *modelNameObj,
|
||||
Providers::LLMProvider *provider);
|
||||
void resetPageToDefaults();
|
||||
|
||||
void updateStatusIndicators();
|
||||
void setIndicatorStatus(Utils::StringAspect &indicator, const QString &tooltip, bool isValid);
|
||||
|
||||
void setCurrentFimProvider(const QString &name);
|
||||
void setCurrentChatProvider(const QString &name);
|
||||
|
||||
void loadProviders();
|
||||
void loadPrompts();
|
||||
};
|
||||
|
||||
GeneralSettings &generalSettings();
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
167
settings/PresetPromptsSettings.cpp
Normal file
167
settings/PresetPromptsSettings.cpp
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "PresetPromptsSettings.hpp"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
|
||||
#include "QodeAssistConstants.hpp"
|
||||
#include "QodeAssisttr.h"
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
PresetPromptsSettings &presetPromptsSettings()
|
||||
{
|
||||
static PresetPromptsSettings settings;
|
||||
return settings;
|
||||
}
|
||||
|
||||
PresetPromptsSettings::PresetPromptsSettings()
|
||||
{
|
||||
setAutoApply(false);
|
||||
|
||||
setDisplayName(Tr::tr("Preset Prompts Params"));
|
||||
|
||||
temperature.setSettingsKey(Constants::TEMPERATURE);
|
||||
temperature.setLabelText(Tr::tr("Temperature:"));
|
||||
temperature.setDefaultValue(0.2);
|
||||
temperature.setRange(0.0, 10.0);
|
||||
temperature.setSingleStep(0.1);
|
||||
|
||||
ollamaLivetime.setSettingsKey(Constants::OLLAMA_LIVETIME);
|
||||
ollamaLivetime.setLabelText(
|
||||
Tr::tr("Time to suspend Ollama after completion request (in minutes), "
|
||||
"Only Ollama, -1 to disable"));
|
||||
ollamaLivetime.setDefaultValue("5m");
|
||||
ollamaLivetime.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
|
||||
maxTokens.setSettingsKey(Constants::MAX_TOKENS);
|
||||
maxTokens.setLabelText(Tr::tr("Max Tokens"));
|
||||
maxTokens.setRange(-1, 10000);
|
||||
maxTokens.setDefaultValue(150);
|
||||
|
||||
useTopP.setSettingsKey(Constants::USE_TOP_P);
|
||||
useTopP.setDefaultValue(false);
|
||||
|
||||
topP.setSettingsKey(Constants::TOP_P);
|
||||
topP.setLabelText(Tr::tr("use top_p"));
|
||||
topP.setDefaultValue(0.9);
|
||||
topP.setRange(0.0, 1.0);
|
||||
topP.setSingleStep(0.1);
|
||||
|
||||
useTopK.setSettingsKey(Constants::USE_TOP_K);
|
||||
useTopK.setDefaultValue(false);
|
||||
|
||||
topK.setSettingsKey(Constants::TOP_K);
|
||||
topK.setLabelText(Tr::tr("use top_k"));
|
||||
topK.setDefaultValue(50);
|
||||
topK.setRange(1, 1000);
|
||||
|
||||
usePresencePenalty.setSettingsKey(Constants::USE_PRESENCE_PENALTY);
|
||||
usePresencePenalty.setDefaultValue(false);
|
||||
|
||||
presencePenalty.setSettingsKey(Constants::PRESENCE_PENALTY);
|
||||
presencePenalty.setLabelText(Tr::tr("use presence_penalty"));
|
||||
presencePenalty.setDefaultValue(0.0);
|
||||
presencePenalty.setRange(-2.0, 2.0);
|
||||
presencePenalty.setSingleStep(0.1);
|
||||
|
||||
useFrequencyPenalty.setSettingsKey(Constants::USE_FREQUENCY_PENALTY);
|
||||
useFrequencyPenalty.setDefaultValue(false);
|
||||
|
||||
frequencyPenalty.setSettingsKey(Constants::FREQUENCY_PENALTY);
|
||||
frequencyPenalty.setLabelText(Tr::tr("use frequency_penalty"));
|
||||
frequencyPenalty.setDefaultValue(0.0);
|
||||
frequencyPenalty.setRange(-2.0, 2.0);
|
||||
frequencyPenalty.setSingleStep(0.1);
|
||||
|
||||
apiKey.setSettingsKey(Constants::API_KEY);
|
||||
apiKey.setLabelText(Tr::tr("API Key:"));
|
||||
apiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
apiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
|
||||
|
||||
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
|
||||
|
||||
readSettings();
|
||||
|
||||
setupConnections();
|
||||
|
||||
setLayouter([this]() {
|
||||
using namespace Layouting;
|
||||
return Column{Row{temperature, Stretch{1}, resetToDefaults},
|
||||
Row{maxTokens, Stretch{1}},
|
||||
Row{useTopP, topP, Stretch{1}},
|
||||
Row{useTopK, topK, Stretch{1}},
|
||||
Row{usePresencePenalty, presencePenalty, Stretch{1}},
|
||||
Row{useFrequencyPenalty, frequencyPenalty, Stretch{1}},
|
||||
apiKey,
|
||||
Stretch{1}};
|
||||
});
|
||||
}
|
||||
|
||||
void PresetPromptsSettings::setupConnections()
|
||||
{
|
||||
connect(&resetToDefaults,
|
||||
&ButtonAspect::clicked,
|
||||
this,
|
||||
&PresetPromptsSettings::resetSettingsToDefaults);
|
||||
}
|
||||
|
||||
void PresetPromptsSettings::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(ollamaLivetime);
|
||||
resetAspect(useTopP);
|
||||
resetAspect(topP);
|
||||
resetAspect(useTopK);
|
||||
resetAspect(topK);
|
||||
resetAspect(usePresencePenalty);
|
||||
resetAspect(presencePenalty);
|
||||
resetAspect(useFrequencyPenalty);
|
||||
resetAspect(frequencyPenalty);
|
||||
}
|
||||
}
|
||||
|
||||
class PresetPromptsSettingsPage : public Core::IOptionsPage
|
||||
{
|
||||
public:
|
||||
PresetPromptsSettingsPage()
|
||||
{
|
||||
setId(Constants::QODE_ASSIST_PRESET_PROMPTS_SETTINGS_PAGE_ID);
|
||||
setDisplayName(Tr::tr("Preset Prompts Params"));
|
||||
setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY);
|
||||
setSettingsProvider([] { return &presetPromptsSettings(); });
|
||||
}
|
||||
};
|
||||
|
||||
const PresetPromptsSettingsPage presetPromptsSettingsPage;
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
59
settings/PresetPromptsSettings.hpp
Normal file
59
settings/PresetPromptsSettings.hpp
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "settings/SettingsUtils.hpp"
|
||||
#include <utils/aspects.h>
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
class PresetPromptsSettings : public Utils::AspectContainer
|
||||
{
|
||||
public:
|
||||
PresetPromptsSettings();
|
||||
|
||||
Utils::DoubleAspect temperature{this};
|
||||
Utils::IntegerAspect maxTokens{this};
|
||||
|
||||
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};
|
||||
|
||||
Utils::StringAspect ollamaLivetime{this};
|
||||
Utils::StringAspect apiKey{this};
|
||||
|
||||
ButtonAspect resetToDefaults{this};
|
||||
|
||||
private:
|
||||
void setupConnections();
|
||||
void resetSettingsToDefaults();
|
||||
};
|
||||
|
||||
PresetPromptsSettings &presetPromptsSettings();
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
55
settings/SettingsUtils.hpp
Normal file
55
settings/SettingsUtils.hpp
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QPushButton>
|
||||
#include <utils/aspects.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
template<typename AspectType>
|
||||
void resetAspect(AspectType &aspect)
|
||||
{
|
||||
aspect.setVolatileValue(aspect.defaultValue());
|
||||
}
|
||||
|
||||
class ButtonAspect : public Utils::BaseAspect
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ButtonAspect(Utils::AspectContainer *container = nullptr)
|
||||
: Utils::BaseAspect(container)
|
||||
{}
|
||||
|
||||
void addToLayout(Layouting::Layout &parent) override
|
||||
{
|
||||
auto button = new QPushButton(m_buttonText);
|
||||
connect(button, &QPushButton::clicked, this, &ButtonAspect::clicked);
|
||||
parent.addItem(button);
|
||||
}
|
||||
|
||||
QString m_buttonText;
|
||||
signals:
|
||||
void clicked();
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
@ -23,21 +23,22 @@
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class CodeLLamaTemplate : public PromptTemplate
|
||||
class CodeLlamaFimTemplate : public PromptTemplate
|
||||
{
|
||||
public:
|
||||
QString name() const override { return "CodeLlama"; }
|
||||
QString promptTemplate() const override { return "<PRE> %1 <SUF>%2 <MID>"; }
|
||||
TemplateType type() const override { return TemplateType::Fim; }
|
||||
QString name() const override { return "CodeLlama FIM"; }
|
||||
QString promptTemplate() const override { return "%1<PRE> %2 <SUF>%3 <MID>"; }
|
||||
QStringList stopWords() const override
|
||||
{
|
||||
return QStringList() << "<EOT>" << "<PRE>" << "<SUF" << "<MID>";
|
||||
}
|
||||
|
||||
void prepareRequest(QJsonObject &request,
|
||||
const QString &prefix,
|
||||
const QString &suffix) const override
|
||||
void prepareRequest(QJsonObject &request, const ContextData &context) const override
|
||||
{
|
||||
QString formattedPrompt = promptTemplate().arg(prefix, suffix);
|
||||
QString formattedPrompt = promptTemplate().arg(context.instriuctions,
|
||||
context.prefix,
|
||||
context.suffix);
|
||||
request["prompt"] = formattedPrompt;
|
||||
}
|
||||
};
|
||||
49
templates/CodeLlamaInstruct.hpp
Normal file
49
templates/CodeLlamaInstruct.hpp
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/qjsonarray.h>
|
||||
#include "PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class CodeLlamaInstructTemplate : public PromptTemplate
|
||||
{
|
||||
public:
|
||||
TemplateType type() const override { return TemplateType::Chat; }
|
||||
QString name() const override { return "CodeLlama Chat"; }
|
||||
QString promptTemplate() const override { return "[INST] %1 [/INST]"; }
|
||||
QStringList stopWords() const override { return QStringList() << "[INST]" << "[/INST]"; }
|
||||
|
||||
void prepareRequest(QJsonObject &request, const ContextData &context) const override
|
||||
{
|
||||
QString formattedPrompt = promptTemplate().arg(context.prefix);
|
||||
QJsonArray messages = request["messages"].toArray();
|
||||
|
||||
QJsonObject newMessage;
|
||||
newMessage["role"] = "user";
|
||||
newMessage["content"] = formattedPrompt;
|
||||
messages.append(newMessage);
|
||||
|
||||
request["messages"] = messages;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
90
templates/CustomTemplate.hpp
Normal file
90
templates/CustomTemplate.hpp
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "PromptTemplate.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "QodeAssistUtils.hpp"
|
||||
#include "settings/CustomPromptSettings.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class CustomTemplate : public PromptTemplate
|
||||
{
|
||||
public:
|
||||
TemplateType type() const override { return TemplateType::Fim; }
|
||||
QString name() const override { return "Custom FIM Template"; }
|
||||
QString promptTemplate() const override
|
||||
{
|
||||
return Settings::customPromptSettings().customJsonTemplate();
|
||||
}
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
|
||||
void prepareRequest(QJsonObject &request, const ContextData &context) const override
|
||||
{
|
||||
QJsonDocument doc = QJsonDocument::fromJson(promptTemplate().toUtf8());
|
||||
if (doc.isNull() || !doc.isObject()) {
|
||||
logMessage(QString("Invalid JSON template in settings"));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject templateObj = doc.object();
|
||||
QJsonObject processedObj = processJsonTemplate(templateObj, context);
|
||||
|
||||
for (auto it = processedObj.begin(); it != processedObj.end(); ++it) {
|
||||
request[it.key()] = it.value();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QJsonValue processJsonValue(const QJsonValue &value, const ContextData &context) const
|
||||
{
|
||||
if (value.isString()) {
|
||||
QString str = value.toString();
|
||||
str.replace("{{QODE_INSTRUCTIONS}}", context.instriuctions);
|
||||
str.replace("{{QODE_PREFIX}}", context.prefix);
|
||||
str.replace("{{QODE_SUFFIX}}", context.suffix);
|
||||
return str;
|
||||
} else if (value.isObject()) {
|
||||
return processJsonTemplate(value.toObject(), context);
|
||||
} else if (value.isArray()) {
|
||||
QJsonArray newArray;
|
||||
for (const QJsonValue &arrayValue : value.toArray()) {
|
||||
newArray.append(processJsonValue(arrayValue, context));
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
QJsonObject processJsonTemplate(const QJsonObject &templateObj, const ContextData &context) const
|
||||
{
|
||||
QJsonObject result;
|
||||
for (auto it = templateObj.begin(); it != templateObj.end(); ++it) {
|
||||
result[it.key()] = processJsonValue(it.value(), context);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
} // namespace QodeAssist::Templates
|
||||
54
templates/DeepSeekCoderChatTemplate.hpp
Normal file
54
templates/DeepSeekCoderChatTemplate.hpp
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonArray>
|
||||
#include "PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class DeepSeekCoderChatTemplate : public PromptTemplate
|
||||
{
|
||||
public:
|
||||
QString name() const override { return "DeepSeek Coder Chat"; }
|
||||
TemplateType type() const override { return TemplateType::Chat; }
|
||||
|
||||
QString promptTemplate() const override { return "### Instruction:\n%1\n### Response:\n"; }
|
||||
|
||||
QStringList stopWords() const override
|
||||
{
|
||||
return QStringList() << "### Instruction:" << "### Response:" << "\n\n### " << "<|EOT|>";
|
||||
}
|
||||
|
||||
void prepareRequest(QJsonObject &request, const ContextData &context) const override
|
||||
{
|
||||
QString formattedPrompt = promptTemplate().arg(context.prefix);
|
||||
QJsonArray messages = request["messages"].toArray();
|
||||
|
||||
QJsonObject newMessage;
|
||||
newMessage["role"] = "user";
|
||||
newMessage["content"] = formattedPrompt;
|
||||
messages.append(newMessage);
|
||||
|
||||
request["messages"] = messages;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
45
templates/DeepSeekCoderV2.hpp
Normal file
45
templates/DeepSeekCoderV2.hpp
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class DeepSeekCoderV2Template : public PromptTemplate
|
||||
{
|
||||
public:
|
||||
TemplateType type() const override { return TemplateType::Fim; }
|
||||
QString name() const override { return "DeepSeekCoder FIM"; }
|
||||
QString promptTemplate() const override
|
||||
{
|
||||
return "%1<|fim▁begin|>%2<|fim▁hole|>%3<|fim▁end|>";
|
||||
}
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
void prepareRequest(QJsonObject &request, const ContextData &context) const override
|
||||
{
|
||||
QString formattedPrompt = promptTemplate().arg(context.instriuctions,
|
||||
context.prefix,
|
||||
context.suffix);
|
||||
request["prompt"] = formattedPrompt;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
@ -23,18 +23,20 @@
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
#include "QodeAssistData.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
enum class TemplateType { Chat, Fim };
|
||||
|
||||
class PromptTemplate
|
||||
{
|
||||
public:
|
||||
virtual ~PromptTemplate() = default;
|
||||
virtual TemplateType type() const = 0;
|
||||
virtual QString name() const = 0;
|
||||
virtual QString promptTemplate() const = 0;
|
||||
virtual QStringList stopWords() const = 0;
|
||||
virtual void prepareRequest(QJsonObject &request,
|
||||
const QString &prefix,
|
||||
const QString &suffix) const
|
||||
= 0;
|
||||
virtual void prepareRequest(QJsonObject &request, const ContextData &context) const = 0;
|
||||
};
|
||||
} // namespace QodeAssist::Templates
|
||||
|
||||
@ -26,18 +26,19 @@ namespace QodeAssist::Templates {
|
||||
class StarCoder2Template : public PromptTemplate
|
||||
{
|
||||
public:
|
||||
QString name() const override { return "StarCoder2"; }
|
||||
QString promptTemplate() const override { return "<fim_prefix>%1<fim_suffix>%2<fim_middle>"; }
|
||||
TemplateType type() const override { return TemplateType::Fim; }
|
||||
QString name() const override { return "StarCoder2 FIM"; }
|
||||
QString promptTemplate() const override { return "%1<fim_prefix>%2<fim_suffix>%3<fim_middle>"; }
|
||||
QStringList stopWords() const override
|
||||
{
|
||||
return QStringList() << "<|endoftext|>" << "<file_sep>" << "<fim_prefix>" << "<fim_suffix>"
|
||||
<< "<fim_middle>";
|
||||
}
|
||||
void prepareRequest(QJsonObject &request,
|
||||
const QString &prefix,
|
||||
const QString &suffix) const override
|
||||
void prepareRequest(QJsonObject &request, const ContextData &context) const override
|
||||
{
|
||||
QString formattedPrompt = promptTemplate().arg(prefix, suffix);
|
||||
QString formattedPrompt = promptTemplate().arg(context.instriuctions,
|
||||
context.prefix,
|
||||
context.suffix);
|
||||
request["prompt"] = formattedPrompt;
|
||||
}
|
||||
};
|
||||
|
||||
48
utils/CounterTooltip.cpp
Normal file
48
utils/CounterTooltip.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "CounterTooltip.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
CounterTooltip::CounterTooltip(int count)
|
||||
: m_count(count)
|
||||
{
|
||||
m_label = new QLabel(this);
|
||||
addWidget(m_label);
|
||||
updateLabel();
|
||||
|
||||
m_timer = new QTimer(this);
|
||||
m_timer->setSingleShot(true);
|
||||
m_timer->setInterval(2000);
|
||||
|
||||
connect(m_timer, &QTimer::timeout, this, [this] { emit finished(m_count); });
|
||||
|
||||
m_timer->start();
|
||||
}
|
||||
|
||||
CounterTooltip::~CounterTooltip() {}
|
||||
|
||||
void CounterTooltip::updateLabel()
|
||||
{
|
||||
const auto hotkey = QKeySequence(QKeySequence::MoveToNextWord).toString();
|
||||
m_label->setText(QString("Insert Next %1 line(s) (%2)").arg(m_count).arg(hotkey));
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
@ -1,13 +1,8 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Qt Company Ltd.
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of Qode Assist.
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* The Qt Company portions:
|
||||
* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||
*
|
||||
* Petr Mironychev portions:
|
||||
* 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
|
||||
@ -24,24 +19,30 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QTextBlock>
|
||||
#include <texteditor/basehoverhandler.h>
|
||||
#include <QLabel>
|
||||
#include <QTimer>
|
||||
#include <QToolBar>
|
||||
#include <QWidget>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
class QodeAssistHoverHandler : public TextEditor::BaseHoverHandler
|
||||
class CounterTooltip : public QToolBar
|
||||
{
|
||||
public:
|
||||
QodeAssistHoverHandler() = default;
|
||||
Q_OBJECT
|
||||
|
||||
protected:
|
||||
void identifyMatch(TextEditor::TextEditorWidget *editorWidget,
|
||||
int pos,
|
||||
ReportPriority report) final;
|
||||
void operateTooltip(TextEditor::TextEditorWidget *editorWidget, const QPoint &point) final;
|
||||
public:
|
||||
CounterTooltip(int count);
|
||||
~CounterTooltip();
|
||||
|
||||
signals:
|
||||
void finished(int count);
|
||||
|
||||
private:
|
||||
QTextBlock m_block;
|
||||
void updateLabel();
|
||||
|
||||
QLabel *m_label;
|
||||
QTimer *m_timer;
|
||||
int m_count;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
Reference in New Issue
Block a user