Skip to content

Commit

Permalink
feat: Add language-specific LLM preset configuration
Browse files Browse the repository at this point in the history
- Add ability to configure separate provider/model/template for specific programming language
- Add UI controls for language preset configuration
- Support custom provider selection per language
- Support custom model selection per language
- Support custom template selection per language
  • Loading branch information
Palm1r committed Feb 2, 2025
1 parent e836b86 commit 2a0beb6
Show file tree
Hide file tree
Showing 11 changed files with 310 additions and 21 deletions.
29 changes: 23 additions & 6 deletions ConfigurationManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ void ConfigurationManager::setupConnections()
connect(&m_generalSettings.caSelectTemplate, &Button::clicked, this, &Config::selectTemplate);
connect(&m_generalSettings.ccSetUrl, &Button::clicked, this, &Config::selectUrl);
connect(&m_generalSettings.caSetUrl, &Button::clicked, this, &Config::selectUrl);

connect(
&m_generalSettings.ccPreset1SelectProvider, &Button::clicked, this, &Config::selectProvider);
connect(&m_generalSettings.ccPreset1SetUrl, &Button::clicked, this, &Config::selectUrl);
connect(&m_generalSettings.ccPreset1SelectModel, &Button::clicked, this, &Config::selectModel);
connect(
&m_generalSettings.ccPreset1SelectTemplate, &Button::clicked, this, &Config::selectTemplate);
}

void ConfigurationManager::selectProvider()
Expand All @@ -69,6 +76,8 @@ void ConfigurationManager::selectProvider()

auto &targetSettings = (settingsButton == &m_generalSettings.ccSelectProvider)
? m_generalSettings.ccProvider
: settingsButton == &m_generalSettings.ccPreset1SelectProvider
? m_generalSettings.ccPreset1Provider
: m_generalSettings.caProvider;

QTimer::singleShot(0, this, [this, providersList, &targetSettings] {
Expand All @@ -86,14 +95,19 @@ void ConfigurationManager::selectModel()
return;

const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectModel);
const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectModel);

const QString providerName = isCodeCompletion ? m_generalSettings.ccProvider.volatileValue()
: m_generalSettings.caProvider.volatileValue();
: isPreset1 ? m_generalSettings.ccPreset1Provider.volatileValue()
: m_generalSettings.caProvider.volatileValue();

const auto providerUrl = isCodeCompletion ? m_generalSettings.ccUrl.volatileValue()
: isPreset1 ? m_generalSettings.ccPreset1Url.volatileValue()
: m_generalSettings.caUrl.volatileValue();

auto &targetSettings = isCodeCompletion ? m_generalSettings.ccModel : m_generalSettings.caModel;
auto &targetSettings = isCodeCompletion ? m_generalSettings.ccModel
: isPreset1 ? m_generalSettings.ccPreset1Model
: m_generalSettings.caModel;

if (auto provider = m_providersManager.getProviderByName(providerName)) {
if (!provider->supportsModelListing()) {
Expand Down Expand Up @@ -122,11 +136,13 @@ void ConfigurationManager::selectTemplate()
return;

const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectTemplate);
const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectTemplate);

const auto templateList = isCodeCompletion ? m_templateManger.fimTemplatesNames()
: m_templateManger.chatTemplatesNames();
const auto templateList = isCodeCompletion || isPreset1 ? m_templateManger.fimTemplatesNames()
: m_templateManger.chatTemplatesNames();

auto &targetSettings = isCodeCompletion ? m_generalSettings.ccTemplate
: isPreset1 ? m_generalSettings.ccPreset1Template
: m_generalSettings.caTemplate;

QTimer::singleShot(0, &m_generalSettings, [this, templateList, &targetSettings]() {
Expand All @@ -150,8 +166,9 @@ void ConfigurationManager::selectUrl()
urls.append(url);
}

auto &targetSettings = (settingsButton == &m_generalSettings.ccSetUrl)
? m_generalSettings.ccUrl
auto &targetSettings = (settingsButton == &m_generalSettings.ccSetUrl) ? m_generalSettings.ccUrl
: settingsButton == &m_generalSettings.ccPreset1SetUrl
? m_generalSettings.ccPreset1Url
: m_generalSettings.caUrl;

QTimer::singleShot(0, &m_generalSettings, [this, urls, &targetSettings]() {
Expand Down
60 changes: 51 additions & 9 deletions LLMClientInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,20 +146,41 @@ void LLMClientInterface::handleExit(const QJsonObject &request)
emit finished();
}

bool QodeAssist::LLMClientInterface::isSpecifyCompletion(const QJsonObject &request)
{
auto &generalSettings = Settings::generalSettings();

Context::ProgrammingLanguage documentLanguage = getDocumentLanguage(request);
Context::ProgrammingLanguage preset1Language = Context::ProgrammingLanguageUtils::fromString(
generalSettings.preset1Language.displayForIndex(generalSettings.preset1Language()));

return generalSettings.specifyPreset1() && documentLanguage == preset1Language;
}

void LLMClientInterface::handleCompletion(const QJsonObject &request)
{
auto updatedContext = prepareContext(request);
const auto updatedContext = prepareContext(request);
auto &completeSettings = Settings::codeCompletionSettings();
auto &generalSettings = Settings::generalSettings();

bool isPreset1Active = isSpecifyCompletion(request);

const auto providerName = !isPreset1Active ? generalSettings.ccProvider()
: generalSettings.ccPreset1Provider();
const auto modelName = !isPreset1Active ? generalSettings.ccModel()
: generalSettings.ccPreset1Model();
const auto url = !isPreset1Active ? generalSettings.ccUrl() : generalSettings.ccPreset1Url();

auto providerName = Settings::generalSettings().ccProvider();
auto provider = LLMCore::ProvidersManager::instance().getProviderByName(providerName);
const auto provider = LLMCore::ProvidersManager::instance().getProviderByName(providerName);

if (!provider) {
LOG_MESSAGE(QString("No provider found with name: %1").arg(providerName));
return;
}

auto templateName = Settings::generalSettings().ccTemplate();
auto templateName = !isPreset1Active ? generalSettings.ccTemplate()
: generalSettings.ccPreset1Template();

auto promptTemplate = LLMCore::PromptTemplateManager::instance().getFimTemplateByName(
templateName);

Expand All @@ -168,19 +189,18 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
return;
}

// TODO refactor to dynamic presets system
LLMCore::LLMConfig config;
config.requestType = LLMCore::RequestType::CodeCompletion;
config.provider = provider;
config.promptTemplate = promptTemplate;
config.url = QUrl(QString("%1%2").arg(
Settings::generalSettings().ccUrl(),
url,
promptTemplate->type() == LLMCore::TemplateType::Fim ? provider->completionEndpoint()
: provider->chatEndpoint()));
config.apiKey = provider->apiKey();

config.providerRequest
= {{"model", Settings::generalSettings().ccModel()},
{"stream", Settings::codeCompletionSettings().stream()}};
config.providerRequest = {{"model", modelName}, {"stream", completeSettings.stream()}};

config.multiLineCompletion = completeSettings.multiLineCompletion();

Expand Down Expand Up @@ -246,11 +266,33 @@ LLMCore::ContextData LLMClientInterface::prepareContext(const QJsonObject &reque
return reader.prepareContext(lineNumber, cursorPosition);
}

Context::ProgrammingLanguage LLMClientInterface::getDocumentLanguage(const QJsonObject &request) const
{
QJsonObject params = request["params"].toObject();
QJsonObject doc = params["doc"].toObject();
QString uri = doc["uri"].toString();

Utils::FilePath filePath = Utils::FilePath::fromString(QUrl(uri).toLocalFile());
TextEditor::TextDocument *textDocument = TextEditor::TextDocument::textDocumentForFilePath(
filePath);

if (!textDocument) {
LOG_MESSAGE("Error: Document is not available for" + filePath.toString());
return Context::ProgrammingLanguage::Unknown;
}

return Context::ProgrammingLanguageUtils::fromMimeType(textDocument->mimeType());
}

void LLMClientInterface::sendCompletionToClient(const QString &completion,
const QJsonObject &request,
bool isComplete)
{
auto templateName = Settings::generalSettings().ccTemplate();
bool isPreset1Active = isSpecifyCompletion(request);

auto templateName = !isPreset1Active ? Settings::generalSettings().ccTemplate()
: Settings::generalSettings().ccPreset1Template();

auto promptTemplate = LLMCore::PromptTemplateManager::instance().getFimTemplateByName(
templateName);

Expand Down
7 changes: 5 additions & 2 deletions LLMClientInterface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <languageclient/languageclientinterface.h>
#include <texteditor/texteditor.h>

#include <context/ProgrammingLanguage.hpp>
#include <llmcore/ContextData.hpp>
#include <llmcore/RequestHandler.hpp>

Expand Down Expand Up @@ -58,8 +59,10 @@ class LLMClientInterface : public LanguageClient::BaseClientInterface
void handleExit(const QJsonObject &request);
void handleCancelRequest(const QJsonObject &request);

LLMCore::ContextData prepareContext(const QJsonObject &request,
const QStringView &accumulatedCompletion = QString{});
LLMCore::ContextData prepareContext(
const QJsonObject &request, const QStringView &accumulatedCompletion = QString{});
Context::ProgrammingLanguage getDocumentLanguage(const QJsonObject &request) const;
bool isSpecifyCompletion(const QJsonObject &request);

LLMCore::RequestHandler m_requestHandler;
QElapsedTimer m_completionTimer;
Expand Down
1 change: 1 addition & 0 deletions context/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ add_library(Context STATIC
ContextManager.hpp ContextManager.cpp
ContentFile.hpp
TokenUtils.hpp TokenUtils.cpp
ProgrammingLanguage.hpp ProgrammingLanguage.cpp
)

target_link_libraries(Context
Expand Down
70 changes: 70 additions & 0 deletions context/ProgrammingLanguage.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/

#include "ProgrammingLanguage.hpp"

namespace QodeAssist::Context {

ProgrammingLanguage ProgrammingLanguageUtils::fromMimeType(const QString &mimeType)
{
if (mimeType == "text/x-qml" || mimeType == "application/javascript"
|| mimeType == "text/javascript" || mimeType == "text/x-javascript") {
return ProgrammingLanguage::QML;
}
if (mimeType == "text/x-c++src" || mimeType == "text/x-c++hdr" || mimeType == "text/x-csrc"
|| mimeType == "text/x-chdr") {
return ProgrammingLanguage::Cpp;
}
if (mimeType == "text/x-python") {
return ProgrammingLanguage::Python;
}
return ProgrammingLanguage::Unknown;
}

QString ProgrammingLanguageUtils::toString(ProgrammingLanguage language)
{
switch (language) {
case ProgrammingLanguage::Cpp:
return "c/c++";
case ProgrammingLanguage::QML:
return "qml";
case ProgrammingLanguage::Python:
return "python";
case ProgrammingLanguage::Unknown:
default:
return QString();
}
}

ProgrammingLanguage ProgrammingLanguageUtils::fromString(const QString &str)
{
QString lower = str.toLower();
if (lower == "c/c++") {
return ProgrammingLanguage::Cpp;
}
if (lower == "qml") {
return ProgrammingLanguage::QML;
}
if (lower == "python") {
return ProgrammingLanguage::Python;
}
return ProgrammingLanguage::Unknown;
}

} // namespace QodeAssist::Context
43 changes: 43 additions & 0 deletions context/ProgrammingLanguage.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/

#pragma once

#include <QString>

namespace QodeAssist::Context {

enum class ProgrammingLanguage {
QML, // QML/JavaScript
Cpp, // C/C++
Python,
Unknown,
};

namespace ProgrammingLanguageUtils {

ProgrammingLanguage fromMimeType(const QString &mimeType);

QString toString(ProgrammingLanguage language);

ProgrammingLanguage fromString(const QString &str);

} // namespace ProgrammingLanguageUtils

} // namespace QodeAssist::Context
15 changes: 15 additions & 0 deletions settings/ButtonAspect.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,26 @@ class ButtonAspect : public Utils::BaseAspect
void addToLayoutImpl(Layouting::Layout &parent) override
{
auto button = new QPushButton(m_buttonText);
button->setVisible(m_visible);
connect(button, &QPushButton::clicked, this, &ButtonAspect::clicked);
connect(this, &ButtonAspect::visibleChanged, button, &QPushButton::setVisible);
parent.addItem(button);
}

void updateVisibility(bool visible)
{
if (m_visible == visible)
return;
m_visible = visible;
emit visibleChanged(visible);
}

QString m_buttonText;

signals:
void clicked();
void visibleChanged(bool state);

private:
bool m_visible = true;
};
Loading

0 comments on commit 2a0beb6

Please sign in to comment.